From 1bcaff892bc4f3e421494cc9168c5732cc09dadf Mon Sep 17 00:00:00 2001 From: evilchili Date: Sat, 17 Jan 2026 11:19:38 -0800 Subject: [PATCH] fix markdown<=>html --- src/ttfrog/bootstrap.py | 1 + src/ttfrog/schema.py | 41 ++--- src/ttfrog/themes/default/page.html | 4 +- .../themes/default/static/froghat-editor.js | 29 +-- src/ttfrog/themes/default/static/froghat.js | 172 +++++++++--------- 5 files changed, 128 insertions(+), 119 deletions(-) diff --git a/src/ttfrog/bootstrap.py b/src/ttfrog/bootstrap.py index 335930e..60b2e0d 100644 --- a/src/ttfrog/bootstrap.py +++ b/src/ttfrog/bootstrap.py @@ -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="")) diff --git a/src/ttfrog/schema.py b/src/ttfrog/schema.py index bef8732..5987ad6 100644 --- a/src/ttfrog/schema.py +++ b/src/ttfrog/schema.py @@ -312,42 +312,41 @@ class Widget(Page): default = dedent( """ -# {name} +# hello -Insert the current user's name. +Insert the word "HELLO." + +## Usage +{{{ +
\\{\\{widget hello [name="NAME"] \\}\\}
+}}} ## Example -*** +This example uses the current page's widget definition: {{widget hello world}} Nice, huh? -Hello, - -*** ## 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 diff --git a/src/ttfrog/themes/default/page.html b/src/ttfrog/themes/default/page.html index 08e0f73..236e8b2 100644 --- a/src/ttfrog/themes/default/page.html +++ b/src/ttfrog/themes/default/page.html @@ -20,7 +20,7 @@ {% endif %} {% endblock %} diff --git a/src/ttfrog/themes/default/static/froghat-editor.js b/src/ttfrog/themes/default/static/froghat-editor.js index 840d078..20d7c40 100644 --- a/src/ttfrog/themes/default/static/froghat-editor.js +++ b/src/ttfrog/themes/default/static/froghat-editor.js @@ -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('
', ""); + 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; } diff --git a/src/ttfrog/themes/default/static/froghat.js b/src/ttfrog/themes/default/static/froghat.js index 9487020..37d9c98 100644 --- a/src/ttfrog/themes/default/static/froghat.js +++ b/src/ttfrog/themes/default/static/froghat.js @@ -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 { +WIDGETS = {}; - setEditable() { - }; - - toMarkdown(html) { - return html; - }; - - toHTML(md) { - return md; - }; - - parseWidgetSource(html) { - - 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 - }; - } - - 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); +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'); + }; + 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; + } } else { - contents = `Invalid widget: ${widgetName}`; + eval(`proc = ${proc}`); } - var rep = `${contents}`; - html = html.replaceAll(widgetTag, rep); - if (parts) { - html = `${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 + ""; + } + }, + style: { inline: false, toHTML: (token, node) => { @@ -437,14 +434,24 @@ class MacroPlugin extends FroghatPlugin { constructor(settings) { super(settings); - this.pattern = /(?<[^>]+?>){{(?\w+)(?(?:\s*[\w-]+)*?)?(?(?:\s+[\w-]+=\S+?)*)?\s*(?}})?(?<\/[^>]+?>)/mg; + + this.pattern = new RegExp( + '(?<[^>]+?>)?' + // capture the enclosing HTML tag, if any + '{{' + // start of the macro + '(?\\w+)' + // the macro name + '(?(?:\\s(?:\\s*(?:[\\w-](?![\\w-]+=))+))+)?' + // zero or more keywords separated by spaces + '(?[^}<]+)?' + // anything else before the closing + '\\s*(?}})?' + // is the tag closed? + '(?(?:>!\\<)*?<\\/[^>]+?>)?', // capture the enclosing HTML tag, if any + 'mg' + ); this.endPattern = /

}}\s*<\/p>/mg; this.paramPattern = /\s*(?[^=]+)="(?[^"]*)"/g; this.multilinePattern = /(?(.(?!\}{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 += "}}";