From 16cf58f9d79dc4b29d7a6adbd523cd3df590c0d9 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Fri, 17 Nov 2023 16:50:43 +0100 Subject: [PATCH] Add callouts --- index.html | 2 - src/elements/index.mjs | 2 +- src/elements/markdown/callout.mjs | 197 ++++++++++------------------- src/elements/markdown/index.mjs | 1 + src/elements/markdown/renderer.mjs | 18 ++- src/index.mjs | 5 +- src/utils/case.mjs | 9 ++ style/base.css | 33 +++-- 8 files changed, 113 insertions(+), 154 deletions(-) create mode 100644 src/utils/case.mjs diff --git a/index.html b/index.html index b496e46..4632c51 100644 --- a/index.html +++ b/index.html @@ -249,8 +249,6 @@ {Wikilink} - \ No newline at end of file diff --git a/src/elements/index.mjs b/src/elements/index.mjs index 7e92bac..f61c415 100644 --- a/src/elements/index.mjs +++ b/src/elements/index.mjs @@ -1,5 +1,5 @@ export {CanvasElement, NodeFileElement, NodeGroupElement, NodeTextElement, EdgeElement} from "./canvas/index.mjs" -export {MarkdownElement, HashtagElement, WikilinkElement, FrontMatterElement, MathElement} from "./markdown/index.mjs" +export {MarkdownElement, HashtagElement, WikilinkElement, FrontMatterElement, MathElement, CalloutElement} from "./markdown/index.mjs" export {DisplayElement} from "./display.mjs" export {VaultElement} from "./vault.mjs" export {BrowseElement} from "./browse.mjs" diff --git a/src/elements/markdown/callout.mjs b/src/elements/markdown/callout.mjs index b16271b..09dec6a 100644 --- a/src/elements/markdown/callout.mjs +++ b/src/elements/markdown/callout.mjs @@ -1,10 +1,6 @@ import {CustomElement} from "../base.mjs"; -export class CalloutElement extends CustomElement { - static get template() { - return document.getElementById("template-callout") - } - +export class CalloutElement extends HTMLElement { /** * The kind of callout this element represents, in UPPERCASE. * @@ -44,71 +40,6 @@ export class CalloutElement extends CustomElement { this.setAttribute("collapse", value) } - /** - * The title of this callout, in UPPERCASE, as casing is handled by CSS, or `undefined`, if the {@link kind} should be used. - * - * @return {string|undefined} - */ - get admonition() { - return this.getAttribute("admonition")?.toUpperCase() - } - set admonition(value) { - if(value === undefined) { - this.removeAttribute("admonition") - return - } - this.setAttribute("admonition", value.toUpperCase()) - } - - /** - * The contents of this callout, or `undefined`, if there are none. - * - * @return {string} - */ - get contents() { - const value = this.getAttribute("contents") - if(value === "undefined") { - return undefined - } - return value - } - set contents(value) { - if(value === undefined) { - this.removeAttribute("contents") - return - } - this.setAttribute("contents", value) - } - - /** - * Whether this element should consume its parent {@link onConnect}. - * @return {boolean} - */ - get cronus() { - return this.hasAttribute("cronus") - } - set cronus(value) { - if(value) { - this.setAttribute("cronus", "") - } - else { - this.removeAttribute("cronus") - } - } - - /** - * Replace the contents of the {@link parentElement} with this element. - * Also sets {@link cronus} to `false`. - * @returns {void} - */ - replaceParentElement() { - const grandpa = this.parentElement.parentElement - this.remove() - this.parentElement.remove() - this.cronus = false - grandpa.appendChild(this) - } - /** * The element displaying the admonition of this callout, or `null` if {@link admonition} is `undefined`. * Can be recreated with {@link recreateAdmonitionElement}. @@ -116,6 +47,12 @@ export class CalloutElement extends CustomElement { */ admonitionElement + /** + * The slot where the admonition of this callout will be inserted in. + * @type {HTMLSlotElement} + */ + admonitionSlotElement + /** * Recreate {@link collapseElement} with the current value of {@link collapse}. * {@link collapseElement} must not be null. @@ -125,42 +62,38 @@ export class CalloutElement extends CustomElement { if(this.admonitionElement) { this.admonitionElement.remove() this.admonitionElement = null + this.admonitionSlotElement = null } - this.admonitionElement = document.createElement("summary") - this.admonitionElement.innerText = this.admonition + this.admonitionElement = document.createElement("summary") - this.collapseElement.appendChild(this.admonitionElement) + this.admonitionSlotElement = document.createElement("slot") + this.admonitionSlotElement.name = "callout-admonition" + this.admonitionElement.appendChild(this.admonitionSlotElement) + + this.collapseElement.appendChild(this.admonitionElement) } /** - * The element displaying the contents of this callout, or `null` if {@link contents} is `undefined`. - * Can be recreated with {@link recreateContentsElement}. - * @type {HTMLDivElement|null} + * The slot where the contents of this callout will be inserted in. + * @type {HTMLSlotElement} */ - contentsElement + contentsSlotElement /** - * Recreate {@link contentsElement} with the current value of {@link contents} and {@link collapseElement} or {@link containerElement}. + * Recreate {@link contentsElement} with the current value of {@link contents} and {@link collapseElement}. * @returns {void} */ - recreateContentsElement() { - if(this.contentsElement) { - this.contentsElement.remove() + recreateContentsSlotElement() { + if(this.contentsSlotElement) { + this.contentsSlotElement.remove() this.contentsElement = null } - if(this.contents) { - this.contentsElement = document.createElement("summary") - this.contentsElement.innerText = this.contents + this.contentsSlotElement = document.createElement("slot") + this.contentsSlotElement.name = "callout-contents" - if(this.collapseElement) { - this.collapseElement.appendChild(this.contentsElement) - } - else { - this.containerElement.appendChild(this.contentsElement) - } - } + this.collapseElement.appendChild(this.contentsSlotElement) } /** @@ -178,51 +111,51 @@ export class CalloutElement extends CustomElement { if(this.collapseElement) { this.collapseElement.remove() this.collapseElement = null + this.admonitionElement = null + this.admonitionSlotElement = null + this.contentsElement = null + this.contentsSlotElement = null } - if(this.collapse !== undefined) { - this.collapseElement = document.createElement("details") - this.containerElement.appendChild(this.collapseElement) - } - } - - /** - * The element containing this callout. - * Can be recreated with {@link recreateContainerElement}. - * @type {HTMLQuoteElement} - */ - containerElement - - /** - * The name of the slot where {@link containerElement} should be placed in. - * @type {string} - */ - static CONTAINER_ELEMENT_SLOT = "callout-contents" - - /** - * @returns {void} - */ - recreateContainerElement() { - if(this.containerElement) { - this.containerElement.remove() - this.containerElement = null - } - - this.containerElement = document.createElement("blockquote") - this.containerElement.slot = this.constructor.CONTAINER_ELEMENT_SLOT - this.appendChild(this.containerElement) - } - - onConnect() { - super.onConnect() - if(this.cronus) { - this.replaceParentElement() + if(this.collapse === undefined) { + this.collapseElement = document.createElement("div") } else { - this.recreateContainerElement() - this.recreateCollapseElement() - this.recreateAdmonitionElement() - this.recreateContentsElement() + this.collapseElement = document.createElement("details") + if(this.collapse === "+") { + this.collapseElement.open = true + } + if(this.collapse === "-") { + this.collapseElement.open = false + } } + + this.shadowRoot.appendChild(this.collapseElement) + } + + /** + * Reset the style of the parent {@link HTMLQuoteElement}. + * @returns {void} + */ + resetParentBlockquoteStyle() { + const parentClassList = this.parentElement.classList + + for(const className in parentClassList.entries()) { + if(className.startsWith("callout")) { + parentClassList.remove(className) + } + } + + parentClassList.add("callout") + parentClassList.add(`callout-${this.kind.toLowerCase()}`) + } + + // noinspection JSUnusedGlobalSymbols + connectedCallback() { + this.attachShadow({ mode: "open" }) + this.recreateCollapseElement() + this.recreateAdmonitionElement() + this.recreateContentsSlotElement() + this.resetParentBlockquoteStyle() } } \ No newline at end of file diff --git a/src/elements/markdown/index.mjs b/src/elements/markdown/index.mjs index 3761428..e6028f3 100644 --- a/src/elements/markdown/index.mjs +++ b/src/elements/markdown/index.mjs @@ -3,3 +3,4 @@ export {FrontMatterElement} from "./frontmatter.mjs" export {HashtagElement} from "./hashtag.mjs" export {WikilinkElement} from "./wikilink.mjs" export {MathElement} from "./math.mjs" +export {CalloutElement} from "./callout.mjs" diff --git a/src/elements/markdown/renderer.mjs b/src/elements/markdown/renderer.mjs index e21cf4d..6c2a4f0 100644 --- a/src/elements/markdown/renderer.mjs +++ b/src/elements/markdown/renderer.mjs @@ -1,5 +1,6 @@ import { Marked } from "https://unpkg.com/marked@9.1.2/lib/marked.esm.js"; import { CustomElement } from "../base.mjs"; +import {toTitleCase} from "../../utils/case.mjs"; /** @@ -31,10 +32,13 @@ export class MarkdownElement extends CustomElement { } }, blockquote(raw) { - console.log(raw) const calloutMatch = raw.match(/^\[!(.+)]([-+])? ?([^\n]+)?(?:\n+(.*))?/) if(calloutMatch) { - const [, kind, collapse, admonition, contents] = calloutMatch + const [, kind, collapse, rawAdmonition, rawContents] = calloutMatch + const admonition = [] + const contents = [] + this.lexer.inlineTokens(rawAdmonition, admonition) + this.lexer.blockTokens(rawContents, contents) const result = { type: "callout", raw, @@ -176,8 +180,14 @@ export class MarkdownElement extends CustomElement { name: "callout", level: "block", renderer(token) { - console.log(token) - return `` + let admonition = this.parser.parseInline(token.admonition) + const contents = this.parser.parse(token.contents) + + if(admonition === "") { + admonition = toTitleCase(token.kind) + } + + return `${admonition}
${contents}
` } } ], diff --git a/src/index.mjs b/src/index.mjs index 93232d6..0d0e826 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -11,7 +11,9 @@ import { MarkdownElement, VaultElement, BrowseElement, - LandingElement, MathElement, + LandingElement, + MathElement, + CalloutElement, } from "./elements/index.mjs"; customElements.define("x-landing", LandingElement) @@ -27,4 +29,5 @@ customElements.define("x-frontmatter", FrontMatterElement) customElements.define("x-hashtag", HashtagElement) customElements.define("x-wikilink", WikilinkElement) customElements.define("x-math", MathElement) +customElements.define("x-callout", CalloutElement) customElements.define("x-browse", BrowseElement, {extends: "body"}) \ No newline at end of file diff --git a/src/utils/case.mjs b/src/utils/case.mjs new file mode 100644 index 0000000..67222df --- /dev/null +++ b/src/utils/case.mjs @@ -0,0 +1,9 @@ +// https://stackoverflow.com/a/196991/4334568 +export function toTitleCase(str) { + return str.replace( + /\w\S*/g, + function(txt) { + return txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase(); + } + ); +} diff --git a/style/base.css b/style/base.css index e7802c3..ed0ef34 100644 --- a/style/base.css +++ b/style/base.css @@ -85,7 +85,7 @@ blockquote { margin-block-end: 0; margin-inline-start: 0; margin-inline-end: 0; - padding-left: 1.5ex; + padding-left: 12px; } .math { @@ -128,53 +128,58 @@ h1 a:hover, .wikilink.wikilink-heading:hover { } .callout { + background-color: color-mix(in srgb, var(--color-accent) 15%, transparent); +} +.callout [slot="callout-admonition"] { + color: var(--color-accent); + font-weight: bold; } .callout-abstract, .callout-summary, .callout-tldr { - --color-callout: var(--color-callout-cyan); + --color-accent: var(--color-callout-cyan); } -.callout-info { - --color-callout: var(--color-callout-blue); +.callout-info, .callout-note, .callout-default { + --color-accent: var(--color-callout-blue); } .callout-todo { - --color-callout: var(--color-callout-blue) + --color-accent: var(--color-callout-blue) } .callout-tip, .callout-hint, .callout-important { - --color-callout: var(--color-callout-cyan); + --color-accent: var(--color-callout-cyan); } .callout-success, .callout-check, .callout-done { - --color-callout: var(--color-callout-green); + --color-accent: var(--color-callout-green); } .callout-question, .callout-help, .callout-faq { - --color-callout: var(--color-callout-orange); + --color-accent: var(--color-callout-orange); } .callout-warning, .callout-caution, .callout-attention { - --color-callout: var(--color-callout-orange); + --color-accent: var(--color-callout-orange); } .callout-failure, .callout-fail, .callout-mission { - --color-callout: var(--color-callout-red); + --color-accent: var(--color-callout-red); } .callout-danger, .callout-error { - --color-callout: var(--color-callout-red); + --color-accent: var(--color-callout-red); } .callout-bug { - --color-callout: var(--color-callout-red); + --color-accent: var(--color-callout-red); } .callout-example { - --color-callout: var(--color-callout-purple); + --color-accent: var(--color-callout-purple); } .callout-quote, .callout-cite { - --color-callout: var(--color-callout-gray); + --color-accent: var(--color-callout-gray); }