From 6230c8ab83bdd9408d7021c3c50a0b9086b85cbb Mon Sep 17 00:00:00 2001 From: onespaceman Date: Mon, 4 Mar 2024 12:35:02 -0500 Subject: [PATCH] Custom admonitions (#786) --- web/cm_plugins/admonition.ts | 73 ++++++++++++--------------------- web/styles/colors.scss | 26 ++++-------- web/styles/editor.scss | 14 +++++-- web/styles/theme.scss | 18 ++++---- website/Markdown/Admonitions.md | 12 ++++++ 5 files changed, 67 insertions(+), 76 deletions(-) diff --git a/web/cm_plugins/admonition.ts b/web/cm_plugins/admonition.ts index ee9bc2f7..efd08463 100644 --- a/web/cm_plugins/admonition.ts +++ b/web/cm_plugins/admonition.ts @@ -9,16 +9,14 @@ import { import { Client } from "../client.ts"; import { decoratorStateField, isCursorInRange } from "./util.ts"; -type AdmonitionType = "note" | "warning"; - const ADMONITION_REGEX = - /^>( *)\*{2}(Note|Warning)\*{2}( *)(.*)(?:\n([\s\S]*))?/im; + /^>( *)(?:\*{2}|\[!)(.*?)(\*{2}|\])( *)(.*)(?:\n([\s\S]*))?/im; const ADMONITION_LINE_SPLIT_REGEX = /\n>/gm; class AdmonitionIconWidget extends WidgetType { constructor( readonly pos: number, - readonly type: AdmonitionType, + readonly type: string, readonly editorView: EditorView, ) { super(); @@ -35,30 +33,14 @@ class AdmonitionIconWidget extends WidgetType { }); }); - switch (this.type) { - case "note": - outerDiv.insertAdjacentHTML( - "beforeend", - '', - ); - break; - case "warning": - outerDiv.insertAdjacentHTML( - "beforeend", - '', - ); - break; - default: - // - } - return outerDiv; } } type AdmonitionFields = { preSpaces: string; - admonitionType: AdmonitionType; + admonitionType: string; + postSyntax: string postSpaces: string; admonitionTitle: string; admonitionContent: string; @@ -75,19 +57,25 @@ type AdmonitionFields = { // > **note** I am an Admonition Title // > admonition text // +// or +// > [!note] I am an Admonition Title +// > admonition text + function extractAdmonitionFields(rawText: string): AdmonitionFields | null { const regexResults = rawText.match(ADMONITION_REGEX); if (regexResults) { const preSpaces = regexResults[1] || ""; - const admonitionType = regexResults[2].toLowerCase() as AdmonitionType; - const postSpaces = regexResults[3] || ""; - const admonitionTitle: string = regexResults[4] || ""; - const admonitionContent: string = regexResults[5] || ""; + const admonitionType = regexResults[2]; + const postSyntax = regexResults[3]; + const postSpaces = regexResults[4] || ""; + const admonitionTitle: string = regexResults[5] || ""; + const admonitionContent: string = regexResults[6] || ""; return { preSpaces, admonitionType, + postSyntax, postSpaces, admonitionTitle, admonitionContent, @@ -117,7 +105,7 @@ export function admonitionPlugin(editor: Client) { return; } - const { preSpaces, admonitionType, postSpaces } = extractedFields; + const { preSpaces, admonitionType, postSyntax, postSpaces } = extractedFields; // A blockquote is actually rendered as many divs, one per line. // We need to keep track of the `from` offsets here, so we can attach css @@ -130,35 +118,25 @@ export function admonitionPlugin(editor: Client) { accum += line.length + 2; }); - // `from` and `to` range info for switching out **info|warning** text with correct + // `from` and `to` range info for switching out keyword text with correct // icon further down. const iconRange = { from: from + 1, - to: from + preSpaces.length + 2 + admonitionType.length + 2 + + to: from + preSpaces.length + 2 + admonitionType.length + postSyntax.length + postSpaces.length + 1, }; const classes = ["sb-admonition"]; - switch (admonitionType) { - case "note": - classes.push("sb-admonition-note"); - break; - case "warning": - classes.push("sb-admonition-warning"); - break; - default: - // - } - // The first div is the title, attach relevant css classes + // The first div is the title, attach title css class widgets.push( Decoration.line({ - class: "sb-admonition-title " + classes.join(" "), + class: "sb-admonition-title", }).range(fromOffsets[0]), ); - // If cursor is not within the first line, replace the **note|warning** text - // with the correct icon + // If cursor is not within the first line, replace the keyword text + // with the icon if ( !isCursorInRange(state, [ from, @@ -178,10 +156,13 @@ export function admonitionPlugin(editor: Client) { } // Each line of the blockquote is spread across separate divs, attach - // relevant css classes here. - fromOffsets.slice(1).forEach((fromOffset) => { + // relevant css classes and attribute here. + fromOffsets.forEach((fromOffset) => { widgets.push( - Decoration.line({ class: classes.join(" ") }).range(fromOffset), + Decoration.line({ + attributes: { admonition: admonitionType }, + class: "sb-admonition", + }).range(fromOffset), ); }); } diff --git a/web/styles/colors.scss b/web/styles/colors.scss index e4ea13fb..0fab49d6 100644 --- a/web/styles/colors.scss +++ b/web/styles/colors.scss @@ -302,28 +302,18 @@ color: var(--editor-meta-color); } - .sb-admonition.sb-admonition-note { - border-left-color: var(--editor-admonition-note-border-color); + // Admonitions + + .sb-admonition { + border-left-color: var(--admonition-color); } - .sb-admonition.sb-admonition-warning { - border-left-color: var(--editor-admonition-warning-border-color); + .sb-admonition-title { + background-color: color-mix(in srgb, var(--admonition-color), transparent 90%) } - .sb-admonition-title.sb-admonition-note { - background-color: var(--editor-admonition-note-background-color); - } - - .sb-admonition-title.sb-admonition-warning { - background-color: var(--editor-admonition-warning-background-color); - } - - .sb-admonition-note .sb-admonition-icon { - color: var(--editor-admonition-note-border-color); - } - - .sb-admonition-warning .sb-admonition-icon { - color: var(--editor-admonition-warning-border-color); + .sb-admonition-icon { + background-color: var(--admonition-color); } // Frontmatter diff --git a/web/styles/editor.scss b/web/styles/editor.scss index 6723730f..32c508ba 100644 --- a/web/styles/editor.scss +++ b/web/styles/editor.scss @@ -400,8 +400,8 @@ cursor: pointer; margin: 0 3px; } - - .sb-code-copy-button > svg { + + .sb-code-copy-button>svg { height: 1rem; width: 1rem; } @@ -582,10 +582,16 @@ } .sb-admonition-icon { + mask: var(--admonition-icon) no-repeat; + -webkit-mask: var(--admonition-icon) no-repeat; + mask-size: cover; + -webkit-mask-size: cover; + width: 1.1em; + height: 1.1em; display: inline-flex; vertical-align: middle; - padding-left: 16px; - padding-right: 8px; + margin-left: 16px; + margin-right: 8px; } .sb-frontmatter-marker { diff --git a/web/styles/theme.scss b/web/styles/theme.scss index ecece997..b69386c3 100644 --- a/web/styles/theme.scss +++ b/web/styles/theme.scss @@ -96,10 +96,6 @@ html { --editor-code-operator-color: #808080; --editor-code-info-color: var(--subtle-color); --editor-code-atom-color: #5a0000; - --editor-admonition-note-border-color: rgb(0, 184, 212); - --editor-admonition-note-background-color: rgba(0, 184, 212, 0.1); - --editor-admonition-warning-border-color: rgb(255, 145, 0); - --editor-admonition-warning-background-color: rgba(255, 145, 0, 0.1); --editor-frontmatter-background-color: rgba(255, 246, 189, 0.3); --editor-frontmatter-color: var(--subtle-color); --editor-frontmatter-marker-color: #89000080; @@ -215,10 +211,6 @@ html[data-theme="dark"] { --editor-code-number-color: #986db9; --editor-code-info-color: var(--subtle-color); --editor-code-atom-color: #d6222e; - --editor-admonition-note-border-color: rgb(0, 184, 212); - --editor-admonition-note-background-color: rgba(0, 184, 212, 0.2); - --editor-admonition-warning-border-color: rgb(255, 145, 0); - --editor-admonition-warning-background-color: rgba(255, 145, 0, 0.2); --editor-frontmatter-background-color: rgb(41, 40, 35, 0.5); --editor-frontmatter-color: var(--subtle-color); --editor-frontmatter-marker-color: #fff; @@ -229,4 +221,14 @@ html[data-theme="dark"] { --editor-directive-mark-color: #ba0303; --editor-directive-color: #898989; --editor-directive-background-color: #4c4c4c7d; +} + +.sb-admonition[admonition="note"] { + --admonition-icon: url('data:image/svg+xml,'); + --admonition-color: #00b8d4; +} + +.sb-admonition[admonition="warning"] { + --admonition-icon: url('data:image/svg+xml,'); + --admonition-color: #ff9100; } \ No newline at end of file diff --git a/website/Markdown/Admonitions.md b/website/Markdown/Admonitions.md index facf1197..c8bd2ead 100644 --- a/website/Markdown/Admonitions.md +++ b/website/Markdown/Admonitions.md @@ -5,3 +5,15 @@ Silverbullet supports [admonitions](https://github.com/community/community/discu > **warning** This is a > warning admonition + +Custom admonitions can be added in [[STYLES]] using the following format: + +```css +// Replace the keyword with a word or phrase of your choice +.sb-admonition[admonition="keyword"] { + // The icon can be a link or an embedded image like shown here + --admonition-icon: url('data:image/svg+xml,'); + // The accent color + --admonition-color: green; +} +```