Improve links and checkboxes
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Use the markdown parser to handle links and checkboxes instead of internal javascript. Also add catch clauses to the promises in the editor.
This commit is contained in:
parent
a2c83d87b6
commit
70f1b62646
|
@ -59,7 +59,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/mocha -r esm",
|
||||
"watch": "webpack --watch",
|
||||
"watch": "webpack --watch --mode=development",
|
||||
"start": "webpack-dev-server --open --hot",
|
||||
"build": "webpack --progress --mode=production",
|
||||
"docker": "webpack --no-color --mode=production"
|
||||
|
|
|
@ -152,7 +152,7 @@ function showSearchResultsExtended(element, template, searchTool, query, input,
|
|||
}
|
||||
|
||||
return results
|
||||
})
|
||||
}).catch(e => console.log('searchtool', e))
|
||||
}
|
||||
|
||||
function formatLineResult(hits, key) {
|
||||
|
@ -284,7 +284,7 @@ function Editor(holder, input) {
|
|||
return td
|
||||
})
|
||||
])
|
||||
})
|
||||
}).catch(e => console.log('while fetching metakv', e))
|
||||
})
|
||||
})
|
||||
.then(mflatten)
|
||||
|
@ -313,8 +313,9 @@ function Editor(holder, input) {
|
|||
div.classList.add('table-wrapper')
|
||||
|
||||
return element.html(div);
|
||||
})
|
||||
}).catch(e => console.log('while creating table', e))
|
||||
})
|
||||
.catch(e => console.log('transformTable', e))
|
||||
}
|
||||
|
||||
async function transformMathExpression(converted, scope) {
|
||||
|
@ -350,7 +351,6 @@ function Editor(holder, input) {
|
|||
}
|
||||
|
||||
let converted = text
|
||||
let todo;
|
||||
|
||||
if (converted === '{{table}}') {
|
||||
transformTable.call(this, editor, id, element);
|
||||
|
@ -368,30 +368,37 @@ function Editor(holder, input) {
|
|||
})
|
||||
.catch(e => console.warn(e))
|
||||
} else {
|
||||
let re = /^([A-Z0-9 ]+)::\s*(.*)$/i;
|
||||
let res = text.match(re)
|
||||
if (res) {
|
||||
converted = '<span class="metadata-key">[[' + res[1] + ']]</span>'
|
||||
if (res[2]) {
|
||||
converted += ': ' + res[2]
|
||||
}
|
||||
} else if (text.match(/#\[\[TODO]]/)) {
|
||||
converted = converted.replace('#[[TODO]]', '<input class="checkbox" type="checkbox" />')
|
||||
todo = true;
|
||||
} else if (text.match(/#\[\[DONE]]/)) {
|
||||
converted = converted.replace('#[[DONE]]', '<input class="checkbox" type="checkbox" checked />')
|
||||
todo = false;
|
||||
}
|
||||
// let re = /^([A-Z0-9 ]+)::\s*(.*)$/i;
|
||||
// let res = text.match(re)
|
||||
// if (res) {
|
||||
// converted = '<span class="metadata-key">[[' + res[1] + ']]</span>'
|
||||
// if (res[2]) {
|
||||
// converted += ': ' + res[2]
|
||||
// }
|
||||
// } else if (text.match(/#\[\[TODO]]/)) {
|
||||
// converted = converted.replace('#[[TODO]]', '<input class="checkbox" type="checkbox" />')
|
||||
// todo = true;
|
||||
// } else if (text.match(/#\[\[DONE]]/)) {
|
||||
// converted = converted.replace('#[[DONE]]', '<input class="checkbox" type="checkbox" checked />')
|
||||
// todo = false;
|
||||
// }
|
||||
MD.options.html = true
|
||||
converted = MD.renderInline(converted)
|
||||
MD.options.html = false
|
||||
}
|
||||
|
||||
if (todo !== undefined) {
|
||||
element.toggleClass('todo--done', todo === false)
|
||||
element.toggleClass('todo--todo', todo === true)
|
||||
} else {
|
||||
element.removeClass(['todo--todo', 'todo--done'])
|
||||
try {
|
||||
const todoItem = $(converted).find(':checkbox')
|
||||
if (todoItem.length) {
|
||||
const todo = !todoItem.is(':checked')
|
||||
element.toggleClass('todo--done', todo === false)
|
||||
element.toggleClass('todo--todo', todo === true)
|
||||
} else {
|
||||
element.removeClass(['todo--todo', 'todo--done'])
|
||||
}
|
||||
} catch (e) {
|
||||
// problem with $(converted) is that it could be treated as a jQuery selector expression instead of normal text
|
||||
// the wiki text is not quite like selectors
|
||||
}
|
||||
|
||||
element.html(converted)
|
||||
|
@ -436,7 +443,9 @@ function Editor(holder, input) {
|
|||
addIndicator(
|
||||
addSaver(editor, saveUrl, page, beforeSave),
|
||||
indicator
|
||||
).save().then(() => indicator.done())
|
||||
).save()
|
||||
.then(() => indicator.done())
|
||||
.catch(e => console.log('editor.change', e))
|
||||
})
|
||||
|
||||
// editor.on('rendered', function () {
|
||||
|
@ -612,11 +621,12 @@ function Editor(holder, input) {
|
|||
|
||||
if (insideSearch) {
|
||||
let query = value.substring(start + 1, end);
|
||||
showSearchResults(commandSearch, query, input, value, 'command').then(results => {
|
||||
if (query.length > 0 && (!results || results.length === 0)) {
|
||||
searchEnabled = false
|
||||
}
|
||||
})
|
||||
showSearchResults(commandSearch, query, input, value, 'command')
|
||||
.then(results => {
|
||||
if (query.length > 0 && (!results || results.length === 0)) {
|
||||
searchEnabled = false
|
||||
}
|
||||
}).catch(e => console.log('showSearchResults', e))
|
||||
return true
|
||||
} else if (insideLink) {
|
||||
let query = value.substring(start, end);
|
||||
|
@ -645,15 +655,17 @@ function Editor(holder, input) {
|
|||
return search.startQuery(res[2])
|
||||
.then(hits => _.uniqBy(_.flatMap(hits, formatTitleResult), _.property('text')))
|
||||
.then(results => editor.replaceChildren(id, results))
|
||||
.catch(e => console.log('search query', e))
|
||||
.finally(() => editor.render())
|
||||
} else {
|
||||
return search.startQuery(res[2])
|
||||
.then(hits => _.groupBy(hits, (it) => it.title))
|
||||
.then(hits => _.flatMap(hits, formatLineResult))
|
||||
.then(results => editor.replaceChildren(id, results))
|
||||
.catch(e => console.log('search query', e))
|
||||
.finally(() => editor.render())
|
||||
}
|
||||
})
|
||||
}).catch(e => {})
|
||||
});
|
||||
return editor
|
||||
})
|
||||
|
@ -663,7 +675,7 @@ function match(s, re) {
|
|||
return new Promise((resolve, reject) => {
|
||||
let res = s.match(re)
|
||||
if (res) resolve(res)
|
||||
else reject()
|
||||
else reject(s + ' did not match ' + re)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import MarkdownIt from "markdown-it";
|
||||
import MarkdownItWikilinks from "./wikilinks";
|
||||
//import MarkdownItWikilinks from "./wikilinks";
|
||||
import MarkdownItWikilinks2 from "./wikilinks2";
|
||||
import MarkdownItMark from "markdown-it-mark";
|
||||
import MarkdownItKatex from "markdown-it-katex";
|
||||
|
||||
const MD = new MarkdownIt({
|
||||
linkify: true,
|
||||
linkify: false,
|
||||
highlight: function (str, lang) {
|
||||
if (lang === 'mermaid') {
|
||||
return '<div class="mermaid">' + str + '</div>';
|
||||
|
@ -12,14 +13,22 @@ const MD = new MarkdownIt({
|
|||
return '';
|
||||
}
|
||||
})
|
||||
|
||||
MD.use(MarkdownItWikilinks({
|
||||
MD.use(MarkdownItWikilinks2({
|
||||
baseURL: document.querySelector('body').dataset.baseUrl,
|
||||
uriSuffix: '',
|
||||
relativeBaseURL: '/edit/',
|
||||
htmlAttributes: {
|
||||
class: 'wiki-link'
|
||||
},
|
||||
})).use(MarkdownItMark).use(MarkdownItKatex)
|
||||
}))
|
||||
// MD.use(MarkdownItWikilinks({
|
||||
// baseURL: document.querySelector('body').dataset.baseUrl,
|
||||
// uriSuffix: '',
|
||||
// relativeBaseURL: '/edit/',
|
||||
// htmlAttributes: {
|
||||
// class: 'wiki-link'
|
||||
// },
|
||||
// }))
|
||||
// MD.use(MarkdownItMark).use(MarkdownItKatex)
|
||||
|
||||
export default MD;
|
||||
|
|
|
@ -2,7 +2,26 @@
|
|||
|
||||
import Plugin from "markdown-it-regexp";
|
||||
import extend from "extend";
|
||||
import sanitize from "sanitize-filename";
|
||||
|
||||
var illegalRe = /[\/\?<>\\\*\|"]/g;
|
||||
var controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
var reservedRe = /^\.+$/;
|
||||
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
var windowsTrailingRe = /[\. ]+$/;
|
||||
|
||||
function sanitize(input) {
|
||||
const replacement = '';
|
||||
if (typeof input !== 'string') {
|
||||
throw new Error('Input must be string');
|
||||
}
|
||||
var sanitized = input
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement)
|
||||
.replace(reservedRe, replacement)
|
||||
.replace(windowsReservedRe, replacement)
|
||||
.replace(windowsTrailingRe, replacement);
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
export default (options) => {
|
||||
const defaults = {
|
||||
|
@ -17,7 +36,7 @@ export default (options) => {
|
|||
},
|
||||
postProcessPageName: (pageName) => {
|
||||
pageName = pageName.trim()
|
||||
pageName = pageName.split('/').map(sanitize).join('/')
|
||||
pageName = pageName.split('/').map(sanitize).map(sanitize).join('/')
|
||||
pageName = pageName.replace(/\s+/g, '_')
|
||||
return pageName
|
||||
},
|
||||
|
|
75
editor/src/wikilinks2.js
Normal file
75
editor/src/wikilinks2.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
'use strict'
|
||||
|
||||
var util = require('util')
|
||||
|
||||
function Plugin(options) {
|
||||
var self = function (md) {
|
||||
self.options = options
|
||||
self.init(md)
|
||||
}
|
||||
|
||||
self.__proto__ = Plugin.prototype
|
||||
self.id = 'wikilinks'
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
util.inherits(Plugin, Function)
|
||||
|
||||
Plugin.prototype.init = function (md) {
|
||||
md.inline.ruler.push(this.id, this.parse.bind(this))
|
||||
md.renderer.rules[this.id] = this.render.bind(this)
|
||||
}
|
||||
|
||||
export function linkParser(id, state) {
|
||||
let input = state.src.slice(state.pos);
|
||||
const match = /^#?\[\[/.exec(input)
|
||||
if (!match) {
|
||||
return false
|
||||
}
|
||||
|
||||
let prefixLength = match[0].length
|
||||
let p = state.pos + prefixLength
|
||||
let open = 2
|
||||
while (p < state.src.length - 1 && open > 0) {
|
||||
if (state.src[p] === '[' && state.src[p + 1] === '[') {
|
||||
open += 2
|
||||
p += 2
|
||||
} else if (state.src[p] === ']' && state.src[p + 1] === ']') {
|
||||
open -= 2
|
||||
p += 2
|
||||
} else {
|
||||
p++
|
||||
}
|
||||
}
|
||||
|
||||
if (open === 0) {
|
||||
let link = state.src.slice(state.pos + prefixLength, p - 2)
|
||||
let token = state.push(id, '', 0)
|
||||
token.meta = {match: link, tag: prefixLength === 3}
|
||||
state.pos = p
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Plugin.prototype.parse = function (state, silent) {
|
||||
return linkParser(this.id, state)
|
||||
}
|
||||
|
||||
Plugin.prototype.render = function (tokens, id, options, env) {
|
||||
let {match: link, tag} = tokens[id].meta
|
||||
if (tag) {
|
||||
if (link === 'TODO') {
|
||||
return '<input type="checkbox" class="checkbox">';
|
||||
} else if (link === 'DONE') {
|
||||
return '<input type="checkbox" class="checkbox" checked>';
|
||||
}
|
||||
}
|
||||
return '<a href="/' + link.replace(' ', '_') + '" class="wiki-link">' + link + '</a>';
|
||||
}
|
||||
|
||||
export default (options) => {
|
||||
return Plugin(options);
|
||||
}
|
62
editor/test/markdown.test.js
Normal file
62
editor/test/markdown.test.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Wiki - A wiki with editor
|
||||
* Copyright (c) 2021 Peter Stuifzand
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import assert from 'assert'
|
||||
import MarkdownIt from "markdown-it";
|
||||
import MarkdownItWikilinks2 from "../src/wikilinks2";
|
||||
|
||||
const MD = new MarkdownIt({
|
||||
linkify: false,
|
||||
highlight: function (str, lang) {
|
||||
if (lang === 'mermaid') {
|
||||
return '<div class="mermaid">' + str + '</div>';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
})
|
||||
|
||||
MD.use(MarkdownItWikilinks2({
|
||||
baseURL: 'http://localhost/',
|
||||
uriSuffix: '',
|
||||
relativeBaseURL: '/edit/',
|
||||
htmlAttributes: {
|
||||
class: 'wiki-link'
|
||||
},
|
||||
}))
|
||||
|
||||
describe('MD', function () {
|
||||
it('parseLinks', function () {
|
||||
assert.deepStrictEqual(MD.renderInline("#[[TODO]]"), '<input type="checkbox" class="checkbox">')
|
||||
assert.deepStrictEqual(MD.renderInline("#[[TODO]] #[[DONE]]"), '<input type="checkbox" class="checkbox"> <input type="checkbox" class="checkbox" checked>')
|
||||
})
|
||||
it('parseLinks 2', function () {
|
||||
assert.deepStrictEqual(MD.renderInline("#[[TODO]] #[[DONE]]"), '<input type="checkbox" class="checkbox"> <input type="checkbox" class="checkbox" checked>')
|
||||
})
|
||||
it('parseLinks 3', function () {
|
||||
assert.deepStrictEqual(MD.renderInline("test #[[TODO]] test2"), 'test <input type="checkbox" class="checkbox"> test2')
|
||||
})
|
||||
it('parseLinks 4', function () {
|
||||
assert.deepStrictEqual(MD.renderInline("test [[test]] [[test2]] [[test3]]"), 'test <a href="/test" class="wiki-link">test</a> <a href="/test2" class="wiki-link">test2</a> <a href="/test3" class="wiki-link">test3</a>')
|
||||
})
|
||||
it('parseLinks 5', function () {
|
||||
assert.deepStrictEqual(MD.renderInline("test [[test]]"), 'test <a href="/test" class="wiki-link">test</a>')
|
||||
})
|
||||
it('parseLinks 6', function () {
|
||||
assert.deepStrictEqual(MD.renderInline("test [[test]] [[test2]]"), 'test <a href="/test" class="wiki-link">test</a> <a href="/test2" class="wiki-link">test2</a>')
|
||||
})
|
||||
})
|
117
editor/test/wikilinks2.test.js
Normal file
117
editor/test/wikilinks2.test.js
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Wiki - A wiki with editor
|
||||
* Copyright (c) 2021 Peter Stuifzand
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import assert from 'assert'
|
||||
import {linkParser} from '../src/wikilinks2'
|
||||
|
||||
describe('linkParser', function () {
|
||||
it('parse', function () {
|
||||
let state = {src: '', pos: 0, tokens: []};
|
||||
state.__proto__.push = function (id, s, x) {
|
||||
let token = {id, s, x};
|
||||
this.tokens.push(token)
|
||||
return token
|
||||
}
|
||||
assert.deepStrictEqual(linkParser('test', state), false);
|
||||
assert.deepStrictEqual(state, {
|
||||
src: '',
|
||||
pos: 0,
|
||||
tokens: []
|
||||
})
|
||||
})
|
||||
|
||||
it('parse 2', function () {
|
||||
let state = {src: '[[Link]]', pos: 0, tokens: []};
|
||||
state.__proto__.push = function (id, s, x) {
|
||||
let token = {id, s, x};
|
||||
this.tokens.push(token)
|
||||
return token
|
||||
}
|
||||
assert.deepStrictEqual(linkParser('test', state), true);
|
||||
assert.deepStrictEqual(state, {
|
||||
src: '[[Link]]',
|
||||
pos: 8,
|
||||
tokens: [{
|
||||
id: 'test',
|
||||
s: '',
|
||||
x: 0,
|
||||
meta: {match:'Link', tag: false}
|
||||
}]
|
||||
})
|
||||
})
|
||||
|
||||
it('parse 3', function () {
|
||||
let state = {src: '1234[[Link]]', pos: 4, tokens: []};
|
||||
state.__proto__.push = function (id, s, x) {
|
||||
let token = {id, s, x};
|
||||
this.tokens.push(token)
|
||||
return token
|
||||
}
|
||||
assert.deepStrictEqual(linkParser('test', state), true);
|
||||
assert.deepStrictEqual(state, {
|
||||
src: '1234[[Link]]',
|
||||
pos: 12,
|
||||
tokens: [{
|
||||
id: 'test',
|
||||
s: '',
|
||||
x: 0,
|
||||
meta: {match:'Link', tag: false}
|
||||
}]
|
||||
})
|
||||
})
|
||||
|
||||
it('parse 4', function () {
|
||||
let state = {src: '1234#[[TODO]]', pos: 4, tokens: []};
|
||||
state.__proto__.push = function (id, s, x) {
|
||||
let token = {id, s, x};
|
||||
this.tokens.push(token)
|
||||
return token
|
||||
}
|
||||
assert.deepStrictEqual(linkParser('test', state), true);
|
||||
assert.deepStrictEqual(state, {
|
||||
src: '1234#[[TODO]]',
|
||||
pos: 13,
|
||||
tokens: [{
|
||||
id: 'test',
|
||||
s: '',
|
||||
x: 0,
|
||||
meta: {match:'TODO', tag: true}
|
||||
}]
|
||||
})
|
||||
})
|
||||
|
||||
it('parse text and two links', function () {
|
||||
let state = {src: '1234 [[Link]] [[Link2]]', pos: 5, tokens: []};
|
||||
state.__proto__.push = function (id, s, x) {
|
||||
let token = {id, s, x};
|
||||
this.tokens.push(token)
|
||||
return token
|
||||
}
|
||||
assert.deepStrictEqual(linkParser('test', state), true);
|
||||
assert.deepStrictEqual(state, {
|
||||
src: '1234 [[Link]] [[Link2]]',
|
||||
pos: 13,
|
||||
tokens: [{
|
||||
id: 'test',
|
||||
s: '',
|
||||
x: 0,
|
||||
meta: {match:'Link', tag: false}
|
||||
}]
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user