fix markdown<=>html
This commit is contained in:
parent
ff78d55b8e
commit
1bcaff892b
|
|
@ -250,6 +250,7 @@ def bootstrap():
|
||||||
groups = root.add_member(schema.Page(name="Group", body="# Groups\ngroups go here."))
|
groups = root.add_member(schema.Page(name="Group", body="# Groups\ngroups go here."))
|
||||||
npcs = root.add_member(schema.Page(name="NPC", body="# NPCS!"))
|
npcs = root.add_member(schema.Page(name="NPC", body="# NPCS!"))
|
||||||
widgets = root.add_member(schema.Page(name="Widget", body="Widgets go here."))
|
widgets = root.add_member(schema.Page(name="Widget", body="Widgets go here."))
|
||||||
|
widgets.add_member(schema.Widget(name="hello", body=schema.Widget.default))
|
||||||
|
|
||||||
# create the NPCs
|
# create the NPCs
|
||||||
npcs.add_member(schema.NPC(name="Sabetha", body=""))
|
npcs.add_member(schema.NPC(name="Sabetha", body=""))
|
||||||
|
|
|
||||||
|
|
@ -312,40 +312,39 @@ class Widget(Page):
|
||||||
|
|
||||||
default = dedent(
|
default = dedent(
|
||||||
"""
|
"""
|
||||||
# {name}
|
# hello
|
||||||
|
|
||||||
Insert the current user's name.
|
Insert the word "HELLO."
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
{{{
|
||||||
|
<pre>\\{\\{widget hello [name="NAME"] \\}\\}</pre>
|
||||||
|
}}}
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
***
|
This example uses the current page's widget definition: {{widget hello world}} Nice, huh?
|
||||||
|
|
||||||
Hello, <macro name='user' />
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
## Template
|
## Template
|
||||||
|
|
||||||
```html
|
```
|
||||||
|
HELLO${name ? ", " + name : ""}.
|
||||||
```
|
```
|
||||||
|
|
||||||
## CSS
|
## CSS
|
||||||
```css
|
|
||||||
.widget-user {{
|
```
|
||||||
display: inline- block;
|
display: inline;
|
||||||
border: 1px solid green;
|
background: green;
|
||||||
|
padding: 3px;
|
||||||
|
color: white;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 2px;
|
|
||||||
}}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Processor
|
## Processor
|
||||||
```javascript
|
|
||||||
function(tag, template, css) {{
|
```
|
||||||
// Return the HTML that should be inserted into the template div.
|
|
||||||
return document.querySelector("nav li.user a:first-child").outerHTML;
|
|
||||||
}};
|
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
<script src="{{ url_for('static', filename='froghat-editor.js' ) }}"></script>
|
<script src="{{ url_for('static', filename='froghat-editor.js' ) }}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script>
|
<script>
|
||||||
const wiki = new Froghat{% if user.can_write(page) %}Editor{% endif %}({plugins: [MacroPlugin, WidgetPlugin]});
|
const wiki = new Froghat{% if user.can_write(page) %}Editor{% endif %}({plugins: [MacroPlugin]});
|
||||||
wiki.view();
|
wiki.run();
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
class FroghatEditor extends Froghat {
|
class FroghatEditor extends Froghat {
|
||||||
|
|
||||||
constructor(settings) {
|
run() {
|
||||||
/*
|
|
||||||
* Create a new Editor instance.
|
|
||||||
*/
|
|
||||||
super(settings);
|
|
||||||
this.states = {
|
this.states = {
|
||||||
VIEW: 'view',
|
VIEW: 'view',
|
||||||
EDIT: 'edit',
|
EDIT: 'edit',
|
||||||
|
|
@ -17,9 +13,11 @@ class FroghatEditor extends Froghat {
|
||||||
});
|
});
|
||||||
this.turndown.use([turndownPluginGfm.gfm, turndownPluginGfm.tables]);
|
this.turndown.use([turndownPluginGfm.gfm, turndownPluginGfm.tables]);
|
||||||
this.turndown.keep(['pre']);
|
this.turndown.keep(['pre']);
|
||||||
this.#bindEvents();
|
//this.#bindEvents();
|
||||||
|
|
||||||
this.plugins().forEach(plugin => { plugin.setEditable() });
|
this.plugins().forEach(plugin => { plugin.setEditable() });
|
||||||
|
this.element.classList.add("loaded");
|
||||||
|
this.view();
|
||||||
}
|
}
|
||||||
|
|
||||||
#bindEvents() {
|
#bindEvents() {
|
||||||
|
|
@ -30,8 +28,8 @@ class FroghatEditor extends Froghat {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
console.log(this.#state, this.#states.EDIT);
|
console.log(this.state, this.states.EDIT);
|
||||||
if (this.#state === this.#states.EDIT) {
|
if (this.state === this.states.WYSIWYG) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
this.insertAtCursor(document.createTextNode("\n"));
|
this.insertAtCursor(document.createTextNode("\n"));
|
||||||
}
|
}
|
||||||
|
|
@ -45,8 +43,8 @@ class FroghatEditor extends Froghat {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
HtmlToMarkdown(html) {
|
htmlToMarkdown(html) {
|
||||||
return this.turndown.turndown(html);
|
return this.turndown.turndown(html || this.element.innerHTML);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMarkdown() {
|
getMarkdown() {
|
||||||
|
|
@ -54,11 +52,14 @@ class FroghatEditor extends Froghat {
|
||||||
* Return the current markdown.
|
* Return the current markdown.
|
||||||
*/
|
*/
|
||||||
if (this.getState() === this.states.EDIT) {
|
if (this.getState() === this.states.EDIT) {
|
||||||
this.cachedMarkdown = this.element.innerHTML.replaceAll(/<\/?div>/g, "\n").replaceAll('<br>', "");
|
var html = this.element.innerHTML;
|
||||||
|
html = html.replaceAll(/<(?:div|br)>/ig, '');
|
||||||
|
html = html.replaceAll(/<\/div>/ig, "\n");
|
||||||
|
this.cachedMarkdown = decodeHtmlEntities(html);
|
||||||
} else if (this.getState() === this.states.WYSIWYG) {
|
} else if (this.getState() === this.states.WYSIWYG) {
|
||||||
this.cachedMarkdown = this.HtmlToMarkdown(this.element.innerHTML);
|
this.cachedMarkdown = this.htmlToMarkdown(this.element.innerHTML);
|
||||||
} else if (!this.cachedMarkdown) {
|
} if (!this.cachedMarkdown) {
|
||||||
this.cachedMarkdown = this.source;
|
this.cachedMarkdown = this.element.textContent;
|
||||||
}
|
}
|
||||||
return this.cachedMarkdown;
|
return this.cachedMarkdown;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ class Froghat {
|
||||||
this.api = settings.api || FroghatAPIv1;
|
this.api = settings.api || FroghatAPIv1;
|
||||||
|
|
||||||
this.element = document.getElementById(settings.editorId || 'froghat');
|
this.element = document.getElementById(settings.editorId || 'froghat');
|
||||||
this.source = this.element.textContent;
|
|
||||||
|
|
||||||
this.marked = marked;
|
this.marked = marked;
|
||||||
this.marked.use({
|
this.marked.use({
|
||||||
|
|
@ -82,10 +81,14 @@ class Froghat {
|
||||||
this.enabledPlugins = {};
|
this.enabledPlugins = {};
|
||||||
|
|
||||||
settings.plugins.forEach(plugin => {
|
settings.plugins.forEach(plugin => {
|
||||||
this.enabledPlugins[plugin.name] = new plugin({name: plugin.name, editor: this});
|
this.enabledPlugins[plugin.name] = new plugin({name: plugin.name, wiki: this});
|
||||||
});
|
});
|
||||||
this.getHTML();
|
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
this.element.classList.add("loaded");
|
this.element.classList.add("loaded");
|
||||||
|
this.view();
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins() {
|
plugins() {
|
||||||
|
|
@ -116,35 +119,29 @@ class Froghat {
|
||||||
/*
|
/*
|
||||||
* Convert the markdown source to HTML.
|
* Convert the markdown source to HTML.
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
if (this.changed || !this.cachedHTML) {
|
if (this.changed || !this.cachedHTML) {
|
||||||
this.cachedHTML = this.markdownToHTML(this.getMarkdown());
|
this.cachedHTML = this.markdownToHTML(this.getMarkdown());
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
var md = this.getMarkdown();
|
||||||
|
this.cachedHTML = this.markdownToHTML(md);
|
||||||
return this.cachedHTML;
|
return this.cachedHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMarkdown() {
|
getMarkdown() {
|
||||||
if (!this.cachedMarkdown) {
|
|
||||||
this.cachedMarkdown = this.source;
|
|
||||||
}
|
|
||||||
return this.cachedMarkdown;
|
return this.cachedMarkdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
|
||||||
/*
|
|
||||||
* Discard any unsaved edits and reset the editor to its initial state.
|
|
||||||
*/
|
|
||||||
this.cachedHTML = null;
|
|
||||||
this.cachedMarkdown = null;
|
|
||||||
this.view();
|
|
||||||
}
|
|
||||||
|
|
||||||
view() {
|
view() {
|
||||||
/*
|
/*
|
||||||
* Convert the editor read-only mode and display the current HTML.
|
* Convert the wiki read-only mode and display the current HTML.
|
||||||
*/
|
*/
|
||||||
|
/*
|
||||||
if (this.getState() === this.states.VIEW) {
|
if (this.getState() === this.states.VIEW) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
this.element.innerHTML = this.getHTML();
|
this.element.innerHTML = this.getHTML();
|
||||||
this.setState(this.states.VIEW);
|
this.setState(this.states.VIEW);
|
||||||
this.contentEditable = false;
|
this.contentEditable = false;
|
||||||
|
|
@ -156,7 +153,7 @@ class FroghatPlugin {
|
||||||
|
|
||||||
constructor(settings) {
|
constructor(settings) {
|
||||||
this.name = settings.name;
|
this.name = settings.name;
|
||||||
this.editor = settings.editor;
|
this.wiki = settings.wiki;
|
||||||
this.precedence = 50;
|
this.precedence = 50;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -173,76 +170,76 @@ class FroghatPlugin {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class WidgetPlugin extends FroghatPlugin {
|
WIDGETS = {};
|
||||||
|
|
||||||
setEditable() {
|
|
||||||
};
|
|
||||||
|
|
||||||
toMarkdown(html) {
|
|
||||||
return html;
|
|
||||||
};
|
|
||||||
|
|
||||||
toHTML(md) {
|
|
||||||
return md;
|
|
||||||
};
|
|
||||||
|
|
||||||
parseWidgetSource(html) {
|
|
||||||
|
|
||||||
|
function loadWidget(name, callback) {
|
||||||
|
var widget = null;
|
||||||
|
if (Object.values(WIDGETS).indexOf(name) == -1) {
|
||||||
|
(async () => {
|
||||||
|
await FroghatAPIv1.search("Widget", name, (res) => {
|
||||||
|
if (res.code == 200) {
|
||||||
function block(prefix) {
|
function block(prefix) {
|
||||||
return RegExp('##\\s*' + prefix + '.*?```\\w*(.+?)```', 'gims');
|
return RegExp('##\\s*' + prefix + '.*?```\\w*(.+?)```', 'gims');
|
||||||
};
|
};
|
||||||
|
var html = res.response[0].body;
|
||||||
const template = block("Template").exec(html)[1];
|
var proc = block("Processor").exec(html)[1].trim();
|
||||||
const css = block("CSS").exec(html)[1];
|
if (!proc) {
|
||||||
const processor = block("Processor").exec(html)[1];
|
proc = function(token, widget) {
|
||||||
|
var name = token.keywords.split(" ").slice(1).join(" ");
|
||||||
var func;
|
var ret = '';
|
||||||
eval("func = " + processor);
|
eval("ret = `" + widget.template + "`");
|
||||||
|
return ret;
|
||||||
return {
|
|
||||||
template: template,
|
|
||||||
css: css,
|
|
||||||
processor: func
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async processWidgets(html, callback) {
|
|
||||||
|
|
||||||
var widgetPattern = /({{(.+)}})/gm;
|
|
||||||
|
|
||||||
if (!html.match(widgetPattern)) {
|
|
||||||
callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.matchAll(widgetPattern).forEach(match => {
|
|
||||||
var widgetTag = match[1];
|
|
||||||
var widgetName = match[2];
|
|
||||||
if (Object.values(WIDGETS).indexOf(widgetName) == -1) {
|
|
||||||
APIv1.search("Widget", widgetName, (res) => {
|
|
||||||
if (res.code == 200) {
|
|
||||||
var parts = parseWidgetSource(res.response[0].body);
|
|
||||||
WIDGETS[widgetName] = parts.processor;
|
|
||||||
contents = WIDGETS[widgetName](widgetTag, parts.template, parts.css);
|
|
||||||
} else {
|
} else {
|
||||||
contents = `Invalid widget: ${widgetName}`;
|
eval(`proc = ${proc}`);
|
||||||
}
|
}
|
||||||
var rep = `<span class="widget-${widgetName}" data-source="${widgetTag}">${contents}</span>`;
|
WIDGETS[name] = {
|
||||||
html = html.replaceAll(widgetTag, rep);
|
template: block("Template").exec(html)[1],
|
||||||
if (parts) {
|
css: block("CSS").exec(html)[1],
|
||||||
html = `<style type='text/css'>${parts.css}</style>${html}`;
|
processor: proc
|
||||||
}
|
};
|
||||||
callback(html);
|
} else {
|
||||||
});
|
WIDGETS[name] = {
|
||||||
}
|
template: "",
|
||||||
});
|
css: "",
|
||||||
|
processor: function() { return `Invalid Widget: "${name}"` },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback(WIDGETS[name]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
} else {
|
||||||
|
if (callback) {
|
||||||
|
callback(WIDGETS[name]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class MacroPlugin extends FroghatPlugin {
|
class MacroPlugin extends FroghatPlugin {
|
||||||
|
|
||||||
macros = {
|
macros = {
|
||||||
// image: {}
|
// image: {}
|
||||||
|
|
||||||
|
widget: {
|
||||||
|
inline: true,
|
||||||
|
toHTML: (token, node) => {
|
||||||
|
var widgetName = token.keywords.split(" ")[0];
|
||||||
|
var contents = '';
|
||||||
|
loadWidget(widgetName, (widget) => {
|
||||||
|
contents = widget.processor(token, widget);
|
||||||
|
var targets = wiki.element.querySelectorAll(`[data-macro-name="widget"][data-keywords="${token.keywords}"]`);
|
||||||
|
targets.forEach(widgetElement => {
|
||||||
|
widgetElement.style = widget.css;
|
||||||
|
widgetElement.innerHTML = contents;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return node + "</span>";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
style: {
|
style: {
|
||||||
inline: false,
|
inline: false,
|
||||||
toHTML: (token, node) => {
|
toHTML: (token, node) => {
|
||||||
|
|
@ -437,14 +434,24 @@ class MacroPlugin extends FroghatPlugin {
|
||||||
|
|
||||||
constructor(settings) {
|
constructor(settings) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.pattern = /(?<!`)(?<wrap><[^>]+?>){{(?<name>\w+)(?<keywords>(?:\s*[\w-]+)*?)?(?<parameters>(?:\s+[\w-]+=\S+?)*)?\s*(?<closed>}})?(?<endwrap><\/[^>]+?>)/mg;
|
|
||||||
|
this.pattern = new RegExp(
|
||||||
|
'(?<!`)(?<wrap><[^>]+?>)?' + // capture the enclosing HTML tag, if any
|
||||||
|
'{{' + // start of the macro
|
||||||
|
'(?<name>\\w+)' + // the macro name
|
||||||
|
'(?<keywords>(?:\\s(?:\\s*(?:[\\w-](?![\\w-]+=))+))+)?' + // zero or more keywords separated by spaces
|
||||||
|
'(?<parameters>[^}<]+)?' + // anything else before the closing
|
||||||
|
'\\s*(?<closed>}})?' + // is the tag closed?
|
||||||
|
'(?<endwrap>(?:>!\\<)*?<\\/[^>]+?>)?', // capture the enclosing HTML tag, if any
|
||||||
|
'mg'
|
||||||
|
);
|
||||||
this.endPattern = /<p>}}\s*<\/p>/mg;
|
this.endPattern = /<p>}}\s*<\/p>/mg;
|
||||||
this.paramPattern = /\s*(?<name>[^=]+)="(?<value>[^"]*)"/g;
|
this.paramPattern = /\s*(?<name>[^=]+)="(?<value>[^"]*)"/g;
|
||||||
this.multilinePattern = /(?<!\{{3}[\s\n]*)\{{3}[\s\n]*(?<content>(.(?!\}{3})*)+?)[\s\n]*\}{3}/smg;
|
this.multilinePattern = /(?<!\{{3}[\s\n]*)\{{3}[\s\n]*(?<content>(.(?!\}{3})*)+?)[\s\n]*\}{3}/smg;
|
||||||
|
|
||||||
const plugin = this;
|
const plugin = this;
|
||||||
|
|
||||||
this.editor.marked.use({
|
this.wiki.marked.use({
|
||||||
extensions: [
|
extensions: [
|
||||||
{
|
{
|
||||||
name: 'heading',
|
name: 'heading',
|
||||||
|
|
@ -493,7 +500,8 @@ class MacroPlugin extends FroghatPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
setEditable() {
|
setEditable() {
|
||||||
this.editor.turndown.addRule('macros', {
|
const plugin = this;
|
||||||
|
this.wiki.turndown.addRule('macros', {
|
||||||
filter: function (node, options) {
|
filter: function (node, options) {
|
||||||
return ((node.nodeName === 'DIV' || node.nodeName === 'SPAN') && node.dataset.pluginName == 'macro')
|
return ((node.nodeName === 'DIV' || node.nodeName === 'SPAN') && node.dataset.pluginName == 'macro')
|
||||||
},
|
},
|
||||||
|
|
@ -518,7 +526,7 @@ class MacroPlugin extends FroghatPlugin {
|
||||||
|
|
||||||
if (node.dataset.inline == "false") {
|
if (node.dataset.inline == "false") {
|
||||||
md = `\n\n${md}\n\n`;
|
md = `\n\n${md}\n\n`;
|
||||||
md += plugin.editor.HtmlToMarkdown(node.innerHTML);
|
md += plugin.wiki.htmlToMarkdown(node.innerHTML);
|
||||||
md += "\n\n}}\n\n";
|
md += "\n\n}}\n\n";
|
||||||
} else {
|
} else {
|
||||||
md += "}}";
|
md += "}}";
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user