From dee7579b2956fda6ca64819fa7d08b2832a235a9 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sun, 15 Sep 2024 15:28:31 -0700 Subject: [PATCH] Move bergamot widget into blog theme Signed-off-by: Danila Fedorin --- assets/js/bergamot-helpers.js | 98 ++++++++++++++++++++++ assets/js/katex-component.js | 31 +++++++ config.toml | 3 + layouts/partials/bergamotrenderpreset.html | 6 ++ layouts/partials/head.html | 22 ++++- static/bergamot/rendering/minimal.bergamot | 22 +++++ 6 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 assets/js/bergamot-helpers.js create mode 100644 assets/js/katex-component.js create mode 100644 layouts/partials/bergamotrenderpreset.html create mode 100644 static/bergamot/rendering/minimal.bergamot diff --git a/assets/js/bergamot-helpers.js b/assets/js/bergamot-helpers.js new file mode 100644 index 0000000..05aecf6 --- /dev/null +++ b/assets/js/bergamot-helpers.js @@ -0,0 +1,98 @@ +const loadedWidgets = {}; +const getButtons = (inputGroup) => { + return { + play: inputGroup.querySelector(".bergamot-play"), + reset: inputGroup.querySelector(".bergamot-reset"), + close: inputGroup.querySelector(".bergamot-close"), + } +} +const setRunning = (inputGroup, running) => { + if (inputGroup !== null) { + const buttons = getButtons(inputGroup); + if (buttons.play) buttons.play.classList.toggle("bergamot-hidden", running); + if (buttons.reset) buttons.reset.classList.toggle("bergamot-hidden", !running); + if (buttons.close) buttons.close.classList.toggle("bergamot-hidden", !running); + } +} + +// The object language parsing is handling by a separate standalone Elm +// application in the ObjectLanguage module, which has two ports: +// +// * `parseString` requests a string to be parsed +// * `parsedString` returns the parsed string, or null +// +// We want there to be a single global ObjectLanguage object, but it works +// on a "subscription" model (we have to give a callback to its port). +// Configure this callback to invoke `resolve` functions from a list, +// so that callers can just get a promise. This way we aren't continuously +// registering more and more handlers for each parsed string, and we can +// use a convenient promise API. +const parsingPromiseResolvers = {}; +const ensureObjectLanguage = () => { + if (!window.Bergamot.ObjectLanguage) { + window.Bergamot.ObjectLanguage = Elm.Bergamot.ObjectLanguage.init({}); + window.Bergamot.ObjectLanguage.ports.parsedString.subscribe(({ string, term }) => { + if (string in parsingPromiseResolvers) { + for (const resolver of parsingPromiseResolvers[string]) { + resolver(term); + } + parsingPromiseResolvers[string] = []; + } + }); + } + return window.Bergamot.ObjectLanguage; +} +const parseString = (str) => { + if (!(str in parsingPromiseResolvers)) { + parsingPromiseResolvers[str] = []; + } + + return new Promise(resolve => { + parsingPromiseResolvers[str].push(resolve); + ensureObjectLanguage().ports.parseString.send(str); + }); +} + +window.Bergamot = {}; +window.Bergamot.run = (inputGroup, nodeId, inputPrompt, rules, renderPreset, input) => { + var app = Elm.Main.init({ + node: document.getElementById(nodeId), + flags: { + inputModes: { + "Languge Term": { "custom": "Language Term" }, + "Query": "query" + }, + renderRules: window.Bergamot.renderPresets[renderPreset], + rules, input + } + }); + app.ports.convertInput.subscribe(async ({ mode, input }) => { + let query = await parseString(input); + if (query !== null) { + query = inputPrompt.replace("TERM", query); + app.ports.receiveConverted.send({ input, result: { query } }); + } else { + app.ports.receiveConverted.send({ input, result: { error: "Unable to parse object language term" } }); + } + }); + loadedWidgets[nodeId] = { app, parentNode: inputGroup ? inputGroup.parentElement : null }; + setRunning(inputGroup, true); +}; +window.Bergamot.runPreset = (inputGroup, nodeId, presetName) => { + const preset = window.Bergamot.presets[presetName]; + window.Bergamot.run(inputGroup, nodeId, preset.inputPrompt, preset.rules, preset.renderPreset, preset.query || ""); +}; +window.Bergamot.close = (inputGroup, nodeId) => { + if (!(nodeId in loadedWidgets)) return; + + const placeholderDiv = document.createElement('div'); + placeholderDiv.id = nodeId; + + const widget = loadedWidgets[nodeId]; + const elmRoot = widget.parentNode.querySelector(".bergamot-root"); + elmRoot.replaceWith(placeholderDiv) + delete loadedWidgets[nodeId]; + setRunning(inputGroup, false); +} +window.Bergamot.presets = {}; +window.Bergamot.renderPresets = {}; diff --git a/assets/js/katex-component.js b/assets/js/katex-component.js new file mode 100644 index 0000000..df9310b --- /dev/null +++ b/assets/js/katex-component.js @@ -0,0 +1,31 @@ +class KatexExpressionShim extends HTMLElement { + static observedAttributes = ["expression", "katex-options"]; + targetSpan; + + constructor() { + super(); + } + + doRender() { + if (!this.targetSpan) return; + + const options = this.hasAttribute("katex-options") ? + this.getAttribute("katex-options") : {}; + katex.render( + this.getAttribute("expression"), + this.targetSpan, + JSON.parse(options) + ); + } + + connectedCallback() { + this.targetSpan = document.createElement('span'); + this.appendChild(this.targetSpan); + this.doRender(); + } + + attributeChangedCallback(name, oldValue, newValue) { + this.doRender(); + } +} +customElements.define("katex-expression", KatexExpressionShim); diff --git a/config.toml b/config.toml index d058908..e52fbb6 100644 --- a/config.toml +++ b/config.toml @@ -1,4 +1,7 @@ [params] katexCssUrl = "//static.danilafe.com/katex/katex.min.css" +katexJsUrl = "//static.danilafe.com/katex/katex.min.js" normalizeCssUrl = "//static.danilafe.com/normalize/normalize.css" visNetworkJsUrl = "//static.danilafe.com/vis-network/vis-network.min.js" +bergamotJsUrl = "//static.danilafe.com/bergamot/bergamot.js" +bergamotObjectLanguageJsUrl = "//static.danilafe.com/bergamot/objectlang.js" diff --git a/layouts/partials/bergamotrenderpreset.html b/layouts/partials/bergamotrenderpreset.html new file mode 100644 index 0000000..623cece --- /dev/null +++ b/layouts/partials/bergamotrenderpreset.html @@ -0,0 +1,6 @@ + diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 0a76010..2cffc6c 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -43,13 +43,33 @@ {{ end }} - {{ if .Site.IsServer }} + {{ if hugo.IsServer }} {{ end }} + {{ if .Params.bergamot }} + + + {{ $katexComponentJs := resources.Get "js/katex-component.js" | resources.Minify }} + + + + {{ $bergamotHelpers := resources.Get "js/bergamot-helpers.js" | resources.Minify }} + + {{ $bergamotStyle := resources.Get "scss/bergamot.scss" | resources.ToCSS | resources.Minify }} + {{ partial "defercss.html" (dict "url" $bergamotStyle.Permalink "extra" "") }} + {{ if .Params.bergamot.render_presets }} + {{ range $name, $rulefile := .Params.bergamot.render_presets }} + {{ partial "bergamotrenderpreset.html" (dict "name" $name "file" $rulefile) }} + {{ end }} + {{ else }} + {{ partial "bergamotrenderpreset.html" (dict "name" "default" "file" "minimal.bergamot") }} + {{ end }} + {{ end }} + {{ with .Site.Params.plausibleAnalyticsDomain }} diff --git a/static/bergamot/rendering/minimal.bergamot b/static/bergamot/rendering/minimal.bergamot new file mode 100644 index 0000000..0dc262a --- /dev/null +++ b/static/bergamot/rendering/minimal.bergamot @@ -0,0 +1,22 @@ +LatexListNil @ latexlist(nil, nil) <-; +LatexListCons @ latexlist(cons(?x, ?xs), cons(?l_x, ?l_s)) <- latex(?x, ?l_x), latexlist(?xs, ?l_s); + +IntercalateNil @ intercalate(?sep, nil, nil) <-; +IntercalateConsCons @ intercalate(?sep, cons(?x_1, cons(?x_2, ?xs)), cons(?x_1, cons(?sep, ?ys))) <- intercalate(?sep, cons(?x_2, ?xs), ?ys); +IntercalateConsNil @ intercalate(?sep, cons(?x, nil), cons(?x, nil)) <-; + +NonEmpty @ nonempty(cons(?x, ?xs)) <-; + +LatexInt @ latex(?i, ?l) <- int(?i), tostring(?i, ?l); +LatexFloat @ latex(?f, ?l) <- float(?f), tostring(?f, ?l); +LatexStr @ latex(?s, ?l) <- str(?s), escapestring(?s, ?l_1), latexifystring(?s, ?l_2), join(["\\texttt{\"", ?l_2, "\"}"], ?l); +LatexMeta @ latex(metavariable(?l), ?l) <-; +LatexLit @ latex(lit(?i), ?l) <- latex(?i, ?l); +LatexVar @ latex(var(?s), ?l) <- latex(?s, ?l); + +LatexIsInt @ latex(int(?e), ?l) <- latex(?e, ?l_e), join([?l_e, " \\in \\texttt{Int}"], ?l); +LatexIsFloat @ latex(float(?e), ?l) <- latex(?e, ?l_e), join([?l_e, " \\in \\texttt{Float}"], ?l); +LatexIsNum @ latex(num(?e), ?l) <- latex(?e, ?l_e), join([?l_e, " \\in \\texttt{Num}"], ?l); +LatexIsStr @ latex(str(?e), ?l) <- latex(?e, ?l_e), join([?l_e, " \\in \\texttt{Str}"], ?l); +LatexSym @ latex(?s, ?l) <- sym(?s), tostring(?s, ?l_1), join(["\\text{", ?l_1,"}"], ?l); +LatexCall @ latex(?c, ?l) <- call(?c, ?n, ?ts), nonempty(?ts), latexlist(?ts, ?lts_1), intercalate(", ", ?lts_1, ?lts_2), join(?lts_2, ?lts_3), join(["\\text{", ?n, "}", "(", ?lts_3, ")"], ?l);