fix markdown<=>html

This commit is contained in:
evilchili 2026-01-17 11:19:38 -08:00
parent ff78d55b8e
commit 1bcaff892b
5 changed files with 128 additions and 119 deletions

View File

@ -250,6 +250,7 @@ def bootstrap():
groups = root.add_member(schema.Page(name="Group", body="# Groups\ngroups go here."))
npcs = root.add_member(schema.Page(name="NPC", body="# NPCS!"))
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
npcs.add_member(schema.NPC(name="Sabetha", body=""))

View File

@ -312,42 +312,41 @@ class Widget(Page):
default = dedent(
"""
# {name}
# hello
Insert the current user's name.
Insert the word "HELLO."
## Usage
{{{
<pre>\\{\\{widget hello [name="NAME"] \\}\\}</pre>
}}}
## Example
***
This example uses the current page's widget definition: {{widget hello world}} Nice, huh?
Hello, <macro name='user' />
***
## Template
```html
```
HELLO${name ? ", " + name : ""}.
```
## CSS
```css
.widget-user {{
display: inline- block;
border: 1px solid green;
border-radius: 5px;
padding: 2px;
}}
```
display: inline;
background: green;
padding: 3px;
color: white;
border-radius: 5px;
```
## 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;
}};
```
"""
```
"""
)
@classmethod

View File

@ -20,7 +20,7 @@
<script src="{{ url_for('static', filename='froghat-editor.js' ) }}"></script>
{% endif %}
<script>
const wiki = new Froghat{% if user.can_write(page) %}Editor{% endif %}({plugins: [MacroPlugin, WidgetPlugin]});
wiki.view();
const wiki = new Froghat{% if user.can_write(page) %}Editor{% endif %}({plugins: [MacroPlugin]});
wiki.run();
</script>
{% endblock %}

View File

@ -1,10 +1,6 @@
class FroghatEditor extends Froghat {
constructor(settings) {
/*
* Create a new Editor instance.
*/
super(settings);
run() {
this.states = {
VIEW: 'view',
EDIT: 'edit',
@ -17,9 +13,11 @@ class FroghatEditor extends Froghat {
});
this.turndown.use([turndownPluginGfm.gfm, turndownPluginGfm.tables]);
this.turndown.keep(['pre']);
this.#bindEvents();
//this.#bindEvents();
this.plugins().forEach(plugin => { plugin.setEditable() });
this.element.classList.add("loaded");
this.view();
}
#bindEvents() {
@ -30,8 +28,8 @@ class FroghatEditor extends Froghat {
/*
if (event.key === 'Enter') {
console.log(this.#state, this.#states.EDIT);
if (this.#state === this.#states.EDIT) {
console.log(this.state, this.states.EDIT);
if (this.state === this.states.WYSIWYG) {
evt.preventDefault();
this.insertAtCursor(document.createTextNode("\n"));
}
@ -45,8 +43,8 @@ class FroghatEditor extends Froghat {
});
};
HtmlToMarkdown(html) {
return this.turndown.turndown(html);
htmlToMarkdown(html) {
return this.turndown.turndown(html || this.element.innerHTML);
}
getMarkdown() {
@ -54,11 +52,14 @@ class FroghatEditor extends Froghat {
* Return the current markdown.
*/
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) {
this.cachedMarkdown = this.HtmlToMarkdown(this.element.innerHTML);
} else if (!this.cachedMarkdown) {
this.cachedMarkdown = this.source;
this.cachedMarkdown = this.htmlToMarkdown(this.element.innerHTML);
} if (!this.cachedMarkdown) {
this.cachedMarkdown = this.element.textContent;
}
return this.cachedMarkdown;
}

View File

@ -65,7 +65,6 @@ class Froghat {
this.api = settings.api || FroghatAPIv1;
this.element = document.getElementById(settings.editorId || 'froghat');
this.source = this.element.textContent;
this.marked = marked;
this.marked.use({
@ -82,10 +81,14 @@ class Froghat {
this.enabledPlugins = {};
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.view();
}
plugins() {
@ -116,35 +119,29 @@ class Froghat {
/*
* Convert the markdown source to HTML.
*/
/*
if (this.changed || !this.cachedHTML) {
this.cachedHTML = this.markdownToHTML(this.getMarkdown());
}
*/
var md = this.getMarkdown();
this.cachedHTML = this.markdownToHTML(md);
return this.cachedHTML;
}
getMarkdown() {
if (!this.cachedMarkdown) {
this.cachedMarkdown = this.source;
}
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() {
/*
* 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) {
return;
}
*/
this.element.innerHTML = this.getHTML();
this.setState(this.states.VIEW);
this.contentEditable = false;
@ -156,7 +153,7 @@ class FroghatPlugin {
constructor(settings) {
this.name = settings.name;
this.editor = settings.editor;
this.wiki = settings.wiki;
this.precedence = 50;
};
@ -173,76 +170,76 @@ class FroghatPlugin {
};
class WidgetPlugin extends FroghatPlugin {
setEditable() {
};
toMarkdown(html) {
return html;
};
toHTML(md) {
return md;
};
parseWidgetSource(html) {
WIDGETS = {};
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) {
return RegExp('##\\s*' + prefix + '.*?```\\w*(.+?)```', 'gims');
};
const template = block("Template").exec(html)[1];
const css = block("CSS").exec(html)[1];
const processor = block("Processor").exec(html)[1];
var func;
eval("func = " + processor);
return {
template: template,
css: css,
processor: func
};
var html = res.response[0].body;
var proc = block("Processor").exec(html)[1].trim();
if (!proc) {
proc = function(token, widget) {
var name = token.keywords.split(" ").slice(1).join(" ");
var ret = '';
eval("ret = `" + widget.template + "`");
return ret;
}
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 {
contents = `Invalid widget: ${widgetName}`;
eval(`proc = ${proc}`);
}
var rep = `<span class="widget-${widgetName}" data-source="${widgetTag}">${contents}</span>`;
html = html.replaceAll(widgetTag, rep);
if (parts) {
html = `<style type='text/css'>${parts.css}</style>${html}`;
}
callback(html);
});
}
});
WIDGETS[name] = {
template: block("Template").exec(html)[1],
css: block("CSS").exec(html)[1],
processor: proc
};
} 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 {
macros = {
// 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: {
inline: false,
toHTML: (token, node) => {
@ -437,14 +434,24 @@ class MacroPlugin extends FroghatPlugin {
constructor(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.paramPattern = /\s*(?<name>[^=]+)="(?<value>[^"]*)"/g;
this.multilinePattern = /(?<!\{{3}[\s\n]*)\{{3}[\s\n]*(?<content>(.(?!\}{3})*)+?)[\s\n]*\}{3}/smg;
const plugin = this;
this.editor.marked.use({
this.wiki.marked.use({
extensions: [
{
name: 'heading',
@ -493,7 +500,8 @@ class MacroPlugin extends FroghatPlugin {
}
setEditable() {
this.editor.turndown.addRule('macros', {
const plugin = this;
this.wiki.turndown.addRule('macros', {
filter: function (node, options) {
return ((node.nodeName === 'DIV' || node.nodeName === 'SPAN') && node.dataset.pluginName == 'macro')
},
@ -518,7 +526,7 @@ class MacroPlugin extends FroghatPlugin {
if (node.dataset.inline == "false") {
md = `\n\n${md}\n\n`;
md += plugin.editor.HtmlToMarkdown(node.innerHTML);
md += plugin.wiki.htmlToMarkdown(node.innerHTML);
md += "\n\n}}\n\n";
} else {
md += "}}";