Custom admonitions (#786)
parent
241069e16f
commit
6230c8ab83
|
@ -9,16 +9,14 @@ import {
|
||||||
import { Client } from "../client.ts";
|
import { Client } from "../client.ts";
|
||||||
import { decoratorStateField, isCursorInRange } from "./util.ts";
|
import { decoratorStateField, isCursorInRange } from "./util.ts";
|
||||||
|
|
||||||
type AdmonitionType = "note" | "warning";
|
|
||||||
|
|
||||||
const ADMONITION_REGEX =
|
const ADMONITION_REGEX =
|
||||||
/^>( *)\*{2}(Note|Warning)\*{2}( *)(.*)(?:\n([\s\S]*))?/im;
|
/^>( *)(?:\*{2}|\[!)(.*?)(\*{2}|\])( *)(.*)(?:\n([\s\S]*))?/im;
|
||||||
const ADMONITION_LINE_SPLIT_REGEX = /\n>/gm;
|
const ADMONITION_LINE_SPLIT_REGEX = /\n>/gm;
|
||||||
|
|
||||||
class AdmonitionIconWidget extends WidgetType {
|
class AdmonitionIconWidget extends WidgetType {
|
||||||
constructor(
|
constructor(
|
||||||
readonly pos: number,
|
readonly pos: number,
|
||||||
readonly type: AdmonitionType,
|
readonly type: string,
|
||||||
readonly editorView: EditorView,
|
readonly editorView: EditorView,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -35,30 +33,14 @@ class AdmonitionIconWidget extends WidgetType {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
switch (this.type) {
|
|
||||||
case "note":
|
|
||||||
outerDiv.insertAdjacentHTML(
|
|
||||||
"beforeend",
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>',
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "warning":
|
|
||||||
outerDiv.insertAdjacentHTML(
|
|
||||||
"beforeend",
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
return outerDiv;
|
return outerDiv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdmonitionFields = {
|
type AdmonitionFields = {
|
||||||
preSpaces: string;
|
preSpaces: string;
|
||||||
admonitionType: AdmonitionType;
|
admonitionType: string;
|
||||||
|
postSyntax: string
|
||||||
postSpaces: string;
|
postSpaces: string;
|
||||||
admonitionTitle: string;
|
admonitionTitle: string;
|
||||||
admonitionContent: string;
|
admonitionContent: string;
|
||||||
|
@ -75,19 +57,25 @@ type AdmonitionFields = {
|
||||||
// > **note** I am an Admonition Title
|
// > **note** I am an Admonition Title
|
||||||
// > admonition text
|
// > admonition text
|
||||||
//
|
//
|
||||||
|
// or
|
||||||
|
// > [!note] I am an Admonition Title
|
||||||
|
// > admonition text
|
||||||
|
|
||||||
function extractAdmonitionFields(rawText: string): AdmonitionFields | null {
|
function extractAdmonitionFields(rawText: string): AdmonitionFields | null {
|
||||||
const regexResults = rawText.match(ADMONITION_REGEX);
|
const regexResults = rawText.match(ADMONITION_REGEX);
|
||||||
|
|
||||||
if (regexResults) {
|
if (regexResults) {
|
||||||
const preSpaces = regexResults[1] || "";
|
const preSpaces = regexResults[1] || "";
|
||||||
const admonitionType = regexResults[2].toLowerCase() as AdmonitionType;
|
const admonitionType = regexResults[2];
|
||||||
const postSpaces = regexResults[3] || "";
|
const postSyntax = regexResults[3];
|
||||||
const admonitionTitle: string = regexResults[4] || "";
|
const postSpaces = regexResults[4] || "";
|
||||||
const admonitionContent: string = regexResults[5] || "";
|
const admonitionTitle: string = regexResults[5] || "";
|
||||||
|
const admonitionContent: string = regexResults[6] || "";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
preSpaces,
|
preSpaces,
|
||||||
admonitionType,
|
admonitionType,
|
||||||
|
postSyntax,
|
||||||
postSpaces,
|
postSpaces,
|
||||||
admonitionTitle,
|
admonitionTitle,
|
||||||
admonitionContent,
|
admonitionContent,
|
||||||
|
@ -117,7 +105,7 @@ export function admonitionPlugin(editor: Client) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { preSpaces, admonitionType, postSpaces } = extractedFields;
|
const { preSpaces, admonitionType, postSyntax, postSpaces } = extractedFields;
|
||||||
|
|
||||||
// A blockquote is actually rendered as many divs, one per line.
|
// 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
|
// 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;
|
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.
|
// icon further down.
|
||||||
const iconRange = {
|
const iconRange = {
|
||||||
from: from + 1,
|
from: from + 1,
|
||||||
to: from + preSpaces.length + 2 + admonitionType.length + 2 +
|
to: from + preSpaces.length + 2 + admonitionType.length + postSyntax.length +
|
||||||
postSpaces.length + 1,
|
postSpaces.length + 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const classes = ["sb-admonition"];
|
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(
|
widgets.push(
|
||||||
Decoration.line({
|
Decoration.line({
|
||||||
class: "sb-admonition-title " + classes.join(" "),
|
class: "sb-admonition-title",
|
||||||
}).range(fromOffsets[0]),
|
}).range(fromOffsets[0]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// If cursor is not within the first line, replace the **note|warning** text
|
// If cursor is not within the first line, replace the keyword text
|
||||||
// with the correct icon
|
// with the icon
|
||||||
if (
|
if (
|
||||||
!isCursorInRange(state, [
|
!isCursorInRange(state, [
|
||||||
from,
|
from,
|
||||||
|
@ -178,10 +156,13 @@ export function admonitionPlugin(editor: Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each line of the blockquote is spread across separate divs, attach
|
// Each line of the blockquote is spread across separate divs, attach
|
||||||
// relevant css classes here.
|
// relevant css classes and attribute here.
|
||||||
fromOffsets.slice(1).forEach((fromOffset) => {
|
fromOffsets.forEach((fromOffset) => {
|
||||||
widgets.push(
|
widgets.push(
|
||||||
Decoration.line({ class: classes.join(" ") }).range(fromOffset),
|
Decoration.line({
|
||||||
|
attributes: { admonition: admonitionType },
|
||||||
|
class: "sb-admonition",
|
||||||
|
}).range(fromOffset),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,28 +302,18 @@
|
||||||
color: var(--editor-meta-color);
|
color: var(--editor-meta-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-admonition.sb-admonition-note {
|
// Admonitions
|
||||||
border-left-color: var(--editor-admonition-note-border-color);
|
|
||||||
|
.sb-admonition {
|
||||||
|
border-left-color: var(--admonition-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-admonition.sb-admonition-warning {
|
.sb-admonition-title {
|
||||||
border-left-color: var(--editor-admonition-warning-border-color);
|
background-color: color-mix(in srgb, var(--admonition-color), transparent 90%)
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-admonition-title.sb-admonition-note {
|
.sb-admonition-icon {
|
||||||
background-color: var(--editor-admonition-note-background-color);
|
background-color: var(--admonition-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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frontmatter
|
// Frontmatter
|
||||||
|
|
|
@ -401,7 +401,7 @@
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-code-copy-button > svg {
|
.sb-code-copy-button>svg {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -582,10 +582,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-admonition-icon {
|
.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;
|
display: inline-flex;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding-left: 16px;
|
margin-left: 16px;
|
||||||
padding-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-frontmatter-marker {
|
.sb-frontmatter-marker {
|
||||||
|
|
|
@ -96,10 +96,6 @@ html {
|
||||||
--editor-code-operator-color: #808080;
|
--editor-code-operator-color: #808080;
|
||||||
--editor-code-info-color: var(--subtle-color);
|
--editor-code-info-color: var(--subtle-color);
|
||||||
--editor-code-atom-color: #5a0000;
|
--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-background-color: rgba(255, 246, 189, 0.3);
|
||||||
--editor-frontmatter-color: var(--subtle-color);
|
--editor-frontmatter-color: var(--subtle-color);
|
||||||
--editor-frontmatter-marker-color: #89000080;
|
--editor-frontmatter-marker-color: #89000080;
|
||||||
|
@ -215,10 +211,6 @@ html[data-theme="dark"] {
|
||||||
--editor-code-number-color: #986db9;
|
--editor-code-number-color: #986db9;
|
||||||
--editor-code-info-color: var(--subtle-color);
|
--editor-code-info-color: var(--subtle-color);
|
||||||
--editor-code-atom-color: #d6222e;
|
--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-background-color: rgb(41, 40, 35, 0.5);
|
||||||
--editor-frontmatter-color: var(--subtle-color);
|
--editor-frontmatter-color: var(--subtle-color);
|
||||||
--editor-frontmatter-marker-color: #fff;
|
--editor-frontmatter-marker-color: #fff;
|
||||||
|
@ -230,3 +222,13 @@ html[data-theme="dark"] {
|
||||||
--editor-directive-color: #898989;
|
--editor-directive-color: #898989;
|
||||||
--editor-directive-background-color: #4c4c4c7d;
|
--editor-directive-background-color: #4c4c4c7d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sb-admonition[admonition="note"] {
|
||||||
|
--admonition-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>');
|
||||||
|
--admonition-color: #00b8d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sb-admonition[admonition="warning"] {
|
||||||
|
--admonition-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>');
|
||||||
|
--admonition-color: #ff9100;
|
||||||
|
}
|
|
@ -5,3 +5,15 @@ Silverbullet supports [admonitions](https://github.com/community/community/discu
|
||||||
|
|
||||||
> **warning** This is a
|
> **warning** This is a
|
||||||
> warning admonition
|
> 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,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M19.5 12L14.5 17M19.5 12L14.5 7M19.5 12L9.5 12C7.83333 12 4.5 11 4.5 7" stroke="%231C274C" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></g></svg>');
|
||||||
|
// The accent color
|
||||||
|
--admonition-color: green;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
Loading…
Reference in New Issue