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);