@memorysmithapp/markdown-renderer 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -2
- package/dist/Markdown.d.ts +12 -0
- package/dist/Markdown.js +57 -13
- package/dist/MermaidDiagram.d.ts +3 -3
- package/dist/MermaidDiagram.js +8 -9
- package/dist/NoteBody.js +7 -3
- package/dist/PropertiesBlock.d.ts +18 -0
- package/dist/PropertiesBlock.js +107 -0
- package/dist/Transclusion.d.ts +1 -1
- package/dist/Transclusion.js +29 -13
- package/dist/host.d.ts +16 -3
- package/dist/host.js +9 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/markdown.d.ts +5 -4
- package/dist/markdown.js +5 -4
- package/dist/properties.d.ts +50 -0
- package/dist/properties.js +144 -0
- package/dist/remark-callouts.js +5 -3
- package/dist/slots.d.ts +36 -0
- package/dist/slots.js +124 -0
- package/dist/strings.d.ts +7 -0
- package/dist/strings.js +4 -0
- package/dist/theme.d.ts +283 -0
- package/dist/theme.js +107 -0
- package/package.json +7 -3
- package/structure.css +57 -0
- package/styles.css +6 -863
- package/theme-default.css +998 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// The entry point of @memorysmithapp/markdown-renderer.
|
|
2
2
|
export { NoteBody } from './NoteBody.js';
|
|
3
3
|
export { ENGLISH } from './strings.js';
|
|
4
|
+
export { customPropertyOf, DEFAULT_THEME, defineTheme, THEME_PROPERTIES, THEME_SLOTS, } from './theme.js';
|
|
4
5
|
export { followable } from './address.js';
|
|
5
6
|
export { readDimensions, readImageAlt } from './image-dimensions.js';
|
|
6
7
|
export { codeRegions, fenceRegions, headingKey, remoteImageHosts, resolveWikilinks, splitFrontmatter, tableRegions, toUnixNewlines, } from './markdown.js';
|
|
7
8
|
export { ordinalAt, taskBoxes, taskCheckedAt, toggleTaskAt } from './tasklist.js';
|
|
9
|
+
export { readProperties } from './properties.js';
|
|
8
10
|
export { DELEGATED_TO_THE_BASE_PARSER, unclassified } from './delegated.js';
|
package/dist/markdown.d.ts
CHANGED
|
@@ -20,15 +20,16 @@ export interface SplitDocument {
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function toUnixNewlines(raw: string): string;
|
|
22
22
|
/**
|
|
23
|
-
* Where the body of a note starts: after its frontmatter, which
|
|
24
|
-
*
|
|
23
|
+
* Where the body of a note starts: after its frontmatter, which the theme draws apart from the
|
|
24
|
+
* body (`readProperties`). The cut is the one MemorySmith's reading page makes, so the ordinals of the task
|
|
25
25
|
* boxes below it are counted over the whole note, as the host stores it.
|
|
26
26
|
*/
|
|
27
27
|
export declare function bodyStart(text: string): number;
|
|
28
28
|
/**
|
|
29
29
|
* The frontmatter block, read the way MemorySmith's reading page reads it to show a note's
|
|
30
|
-
* properties
|
|
31
|
-
*
|
|
30
|
+
* properties, its lists joined by commas. The renderer draws the frontmatter from
|
|
31
|
+
* `readProperties`, which keeps each item and each shape apart; this stays for a host that reads
|
|
32
|
+
* the values MemorySmith does.
|
|
32
33
|
*/
|
|
33
34
|
export declare function splitFrontmatter(input: string): SplitDocument;
|
|
34
35
|
/** The half-open ranges of `body` that are fenced blocks. */
|
package/dist/markdown.js
CHANGED
|
@@ -12,8 +12,8 @@ export function toUnixNewlines(raw) {
|
|
|
12
12
|
return raw.replace(/\r\n?/g, '\n');
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Where the body of a note starts: after its frontmatter, which
|
|
16
|
-
*
|
|
15
|
+
* Where the body of a note starts: after its frontmatter, which the theme draws apart from the
|
|
16
|
+
* body (`readProperties`). The cut is the one MemorySmith's reading page makes, so the ordinals of the task
|
|
17
17
|
* boxes below it are counted over the whole note, as the host stores it.
|
|
18
18
|
*/
|
|
19
19
|
export function bodyStart(text) {
|
|
@@ -32,8 +32,9 @@ function unquote(value) {
|
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* The frontmatter block, read the way MemorySmith's reading page reads it to show a note's
|
|
35
|
-
* properties
|
|
36
|
-
*
|
|
35
|
+
* properties, its lists joined by commas. The renderer draws the frontmatter from
|
|
36
|
+
* `readProperties`, which keeps each item and each shape apart; this stays for a host that reads
|
|
37
|
+
* the values MemorySmith does.
|
|
37
38
|
*/
|
|
38
39
|
export function splitFrontmatter(input) {
|
|
39
40
|
const raw = toUnixNewlines(input);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frontmatter of a note, read for drawing (§6).
|
|
3
|
+
*
|
|
4
|
+
* The frontmatter is content: what the author wrote about the note. The
|
|
5
|
+
* renderer reads it the way the specification reads it and hands it to the
|
|
6
|
+
* theme, which decides how — and whether — it is drawn. The default theme
|
|
7
|
+
* draws `name` as the note's title and every other entry as a property, by
|
|
8
|
+
* the shape of its value.
|
|
9
|
+
*
|
|
10
|
+
* It reads the subset of YAML §6.2 names — scalars, inline lists and dash
|
|
11
|
+
* lists, with one layer of matching quotes stripped — and nothing else; a line
|
|
12
|
+
* it does not read is left out, never guessed at. A value written as a
|
|
13
|
+
* wikilink, `"[[Note]]"`, is kept as written: the theme draws it as a link,
|
|
14
|
+
* resolved by the host as a link of the body is. That is a decision of the
|
|
15
|
+
* reading surface; it adds no edge, and what an Indexer reads is unchanged.
|
|
16
|
+
*/
|
|
17
|
+
/** The shape of a value, which decides how it is drawn (§6.3). */
|
|
18
|
+
export type PropertyShape = 'list' | 'date' | 'boolean' | 'text' | 'prose' | 'empty';
|
|
19
|
+
/** One entry of the frontmatter, other than `name`. */
|
|
20
|
+
export interface PropertyEntry {
|
|
21
|
+
/** The key as the author wrote it, in their language. */
|
|
22
|
+
readonly key: string;
|
|
23
|
+
readonly shape: PropertyShape;
|
|
24
|
+
/**
|
|
25
|
+
* The value as written, one layer of quotes stripped: one item, or each item of a list. A
|
|
26
|
+
* theme reads a boolean or a date from it, and shows what was typed where it shows the bytes.
|
|
27
|
+
*/
|
|
28
|
+
readonly values: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
/** The frontmatter, read. */
|
|
31
|
+
export interface NoteProperties {
|
|
32
|
+
/** The name of the note: `name:` when it is a single text value (§6.5), else `null`. */
|
|
33
|
+
readonly name: string | null;
|
|
34
|
+
/** Every other entry, in the order written. */
|
|
35
|
+
readonly entries: readonly PropertyEntry[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The frontmatter of a note, or no name and no entries when it has none. The block begins on the
|
|
39
|
+
* first line with `---` and ends at the next line that is `---` (§6.1).
|
|
40
|
+
*/
|
|
41
|
+
export declare function readProperties(markdown: string): NoteProperties;
|
|
42
|
+
/**
|
|
43
|
+
* What a value may carry that is a link: a wikilink, a Markdown link to the web, or a bare web
|
|
44
|
+
* address — as MemorySmith's properties read it, and nothing else of Markdown.
|
|
45
|
+
*/
|
|
46
|
+
export declare const VALUE_LINK: RegExp;
|
|
47
|
+
/** Whether a boolean value reads true: `true` or `yes`, in any case. */
|
|
48
|
+
export declare function isTrue(value: string): boolean;
|
|
49
|
+
/** The reserved keys a theme draws as properties (§6.4): `name` is the title, not a property. */
|
|
50
|
+
export declare const DRAWN_RESERVED_KEYS: readonly string[];
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frontmatter of a note, read for drawing (§6).
|
|
3
|
+
*
|
|
4
|
+
* The frontmatter is content: what the author wrote about the note. The
|
|
5
|
+
* renderer reads it the way the specification reads it and hands it to the
|
|
6
|
+
* theme, which decides how — and whether — it is drawn. The default theme
|
|
7
|
+
* draws `name` as the note's title and every other entry as a property, by
|
|
8
|
+
* the shape of its value.
|
|
9
|
+
*
|
|
10
|
+
* It reads the subset of YAML §6.2 names — scalars, inline lists and dash
|
|
11
|
+
* lists, with one layer of matching quotes stripped — and nothing else; a line
|
|
12
|
+
* it does not read is left out, never guessed at. A value written as a
|
|
13
|
+
* wikilink, `"[[Note]]"`, is kept as written: the theme draws it as a link,
|
|
14
|
+
* resolved by the host as a link of the body is. That is a decision of the
|
|
15
|
+
* reading surface; it adds no edge, and what an Indexer reads is unchanged.
|
|
16
|
+
*/
|
|
17
|
+
/** Above this a single value is prose rather than a category (§6.3). */
|
|
18
|
+
const CATEGORY_CEILING = 40;
|
|
19
|
+
const KEY = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/;
|
|
20
|
+
const DASH = /^\s+-\s+(.*)$/;
|
|
21
|
+
const DATE = /^(\d{4})-(\d{2})-(\d{2})/;
|
|
22
|
+
const BOOLEAN = /^(true|false|yes|no)$/i;
|
|
23
|
+
/** A value that is one wikilink and nothing else, which YAML would otherwise read as a nested list. */
|
|
24
|
+
const WIKILINK_VALUE = /^\[\[[^\]]+\]\]$/;
|
|
25
|
+
function unquote(value) {
|
|
26
|
+
const trimmed = value.trim();
|
|
27
|
+
const quoted = /^(["'])([\s\S]*)\1$/.exec(trimmed);
|
|
28
|
+
return (quoted ? (quoted[2] ?? '') : trimmed).trim();
|
|
29
|
+
}
|
|
30
|
+
/** The items of an inline list, split on the commas outside quotes and outside a wikilink. */
|
|
31
|
+
function inlineItems(inside) {
|
|
32
|
+
const items = [];
|
|
33
|
+
let current = '';
|
|
34
|
+
let quote = null;
|
|
35
|
+
let depth = 0;
|
|
36
|
+
for (let i = 0; i < inside.length; i++) {
|
|
37
|
+
const character = inside[i];
|
|
38
|
+
if (quote) {
|
|
39
|
+
if (character === quote)
|
|
40
|
+
quote = null;
|
|
41
|
+
}
|
|
42
|
+
else if (character === '"' || character === "'") {
|
|
43
|
+
quote = character;
|
|
44
|
+
}
|
|
45
|
+
else if (inside.startsWith('[[', i)) {
|
|
46
|
+
depth += 1;
|
|
47
|
+
current += '[[';
|
|
48
|
+
i += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
else if (inside.startsWith(']]', i) && depth > 0) {
|
|
52
|
+
depth -= 1;
|
|
53
|
+
current += ']]';
|
|
54
|
+
i += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
else if (character === ',' && depth === 0) {
|
|
58
|
+
items.push(current);
|
|
59
|
+
current = '';
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
current += character;
|
|
63
|
+
}
|
|
64
|
+
items.push(current);
|
|
65
|
+
return items.map(unquote).filter((item) => item.length > 0);
|
|
66
|
+
}
|
|
67
|
+
function isDate(value) {
|
|
68
|
+
const match = DATE.exec(value);
|
|
69
|
+
if (!match)
|
|
70
|
+
return false;
|
|
71
|
+
const [, year, month, day] = match.map(Number);
|
|
72
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
73
|
+
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
|
74
|
+
}
|
|
75
|
+
function scalarEntry(key, raw) {
|
|
76
|
+
const value = unquote(raw);
|
|
77
|
+
if (value.length === 0)
|
|
78
|
+
return { key, shape: 'empty', values: [] };
|
|
79
|
+
if (isDate(value))
|
|
80
|
+
return { key, shape: 'date', values: [value] };
|
|
81
|
+
if (BOOLEAN.test(value))
|
|
82
|
+
return { key, shape: 'boolean', values: [value] };
|
|
83
|
+
return { key, shape: value.length > CATEGORY_CEILING ? 'prose' : 'text', values: [value] };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The frontmatter of a note, or no name and no entries when it has none. The block begins on the
|
|
87
|
+
* first line with `---` and ends at the next line that is `---` (§6.1).
|
|
88
|
+
*/
|
|
89
|
+
export function readProperties(markdown) {
|
|
90
|
+
const text = markdown.replace(/\r\n?/g, '\n');
|
|
91
|
+
const none = { name: null, entries: [] };
|
|
92
|
+
if (!text.startsWith('---\n'))
|
|
93
|
+
return none;
|
|
94
|
+
const end = text.indexOf('\n---', 3);
|
|
95
|
+
if (end === -1)
|
|
96
|
+
return none;
|
|
97
|
+
const lines = text.slice(4, end).split('\n');
|
|
98
|
+
const entries = [];
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
const pair = KEY.exec(line);
|
|
101
|
+
if (pair) {
|
|
102
|
+
const raw = (pair[2] ?? '').trim();
|
|
103
|
+
const inline = raw.startsWith('[') && raw.endsWith(']') && !WIKILINK_VALUE.test(raw);
|
|
104
|
+
entries.push({ key: pair[1], raw, items: inline ? inlineItems(raw.slice(1, -1)) : null });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const dash = DASH.exec(line);
|
|
108
|
+
const last = entries[entries.length - 1];
|
|
109
|
+
// A dash list belongs to the key above it, which was written with no value.
|
|
110
|
+
if (dash && last && (last.items !== null || last.raw === '')) {
|
|
111
|
+
const item = unquote(dash[1] ?? '');
|
|
112
|
+
if (item.length > 0)
|
|
113
|
+
last.items = [...(last.items ?? []), item];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let name = null;
|
|
117
|
+
const read = [];
|
|
118
|
+
for (const { key, raw, items } of entries) {
|
|
119
|
+
if (key === 'name') {
|
|
120
|
+
// Only a single text value names a note; any other shape leaves it unnamed (§6.5).
|
|
121
|
+
const value = items === null ? unquote(raw) : '';
|
|
122
|
+
name = value.length > 0 ? value : null;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// A list written with no item, `[]`, is an empty value, never a list (§6.3).
|
|
126
|
+
read.push(items === null
|
|
127
|
+
? scalarEntry(key, raw)
|
|
128
|
+
: items.length === 0
|
|
129
|
+
? { key, shape: 'empty', values: [] }
|
|
130
|
+
: { key, shape: 'list', values: items });
|
|
131
|
+
}
|
|
132
|
+
return { name, entries: read };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* What a value may carry that is a link: a wikilink, a Markdown link to the web, or a bare web
|
|
136
|
+
* address — as MemorySmith's properties read it, and nothing else of Markdown.
|
|
137
|
+
*/
|
|
138
|
+
export const VALUE_LINK = /\[\[([^\]|]+?)(?:\|([^\]]+?))?\]\]|\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)|(https?:\/\/[^\s,]+)/g;
|
|
139
|
+
/** Whether a boolean value reads true: `true` or `yes`, in any case. */
|
|
140
|
+
export function isTrue(value) {
|
|
141
|
+
return /^(true|yes)$/i.test(value.trim());
|
|
142
|
+
}
|
|
143
|
+
/** The reserved keys a theme draws as properties (§6.4): `name` is the title, not a property. */
|
|
144
|
+
export const DRAWN_RESERVED_KEYS = ['aliases', 'tags'];
|
package/dist/remark-callouts.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* can tell the difference.
|
|
20
20
|
*/
|
|
21
21
|
/** `[!type]`, an optional fold marker, and whatever titles it. */
|
|
22
|
-
const MARKER = /^\[!([A-Za-z][\w-]*)\][+-]?[ \t]*(.*)$/;
|
|
22
|
+
const MARKER = /^\[!([A-Za-z][\w-]*)\]([+-]?)[ \t]*(.*)$/;
|
|
23
23
|
export function remarkCallouts(options = {}) {
|
|
24
24
|
return (tree) => descend(tree, options);
|
|
25
25
|
}
|
|
@@ -42,10 +42,12 @@ function rewrite(quote, options) {
|
|
|
42
42
|
if (!marker)
|
|
43
43
|
return;
|
|
44
44
|
const kind = (marker[1] ?? '').toLowerCase();
|
|
45
|
-
const { title, body } = split(marker[
|
|
45
|
+
const { title, body } = split(marker[3] ?? '', broke === -1 ? null : lead.value.slice(broke + 1), inline);
|
|
46
46
|
quote.data = {
|
|
47
47
|
hName: 'div',
|
|
48
|
-
|
|
48
|
+
// The fold marker travels too, for a theme that folds; the default theme
|
|
49
|
+
// draws a callout open whatever it says, as MemorySmith's page does.
|
|
50
|
+
hProperties: { className: ['callout'], 'data-callout': kind, ...(marker[2] ? { 'data-fold': marker[2] } : {}) },
|
|
49
51
|
};
|
|
50
52
|
quote.children = [
|
|
51
53
|
{
|
package/dist/slots.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slots of a theme, where the reading surface hands a drawn notation over.
|
|
3
|
+
*
|
|
4
|
+
* Each component here draws exactly what the default theme draws when the
|
|
5
|
+
* theme leaves its slot empty, so a note with no theme is the same DOM it was
|
|
6
|
+
* before slots existed, and calls the slot with the notation already
|
|
7
|
+
* interpreted when the theme fills it. Nothing a slot is given lets it reach
|
|
8
|
+
* the source: what it draws is decided before it is called.
|
|
9
|
+
*/
|
|
10
|
+
import { type HTMLAttributes, type ReactElement, type ReactNode } from 'react';
|
|
11
|
+
import type { ThemeComponents } from './theme.js';
|
|
12
|
+
/** The slot a theme fills, or `undefined`. */
|
|
13
|
+
export declare function useSlot<K extends keyof ThemeComponents>(name: K): ThemeComponents[K] | undefined;
|
|
14
|
+
/** Draws a slot as a component, so a slot may use hooks. */
|
|
15
|
+
export declare function drawSlot<P extends object>(slot: (props: P) => ReactNode, props: P): ReactNode;
|
|
16
|
+
type WithNode<T> = T & {
|
|
17
|
+
node?: unknown;
|
|
18
|
+
};
|
|
19
|
+
/** Whether a code element sits in a fenced block, which is what tells a block from a span. */
|
|
20
|
+
export declare const InBlock: import("react").Context<boolean>;
|
|
21
|
+
/** A `div` of the tree: a callout or its title, which `remarkCallouts` marks, or any other. */
|
|
22
|
+
export declare function MarkdownDiv({ className, children, node: _node, ...rest }: WithNode<HTMLAttributes<HTMLDivElement>>): import("react").JSX.Element;
|
|
23
|
+
export declare function MarkdownTable({ children, node, ...rest }: WithNode<HTMLAttributes<HTMLTableElement>>): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
|
|
24
|
+
export declare const headings: {
|
|
25
|
+
h1: (props: HTMLAttributes<HTMLHeadingElement>) => import("react").JSX.Element;
|
|
26
|
+
h2: (props: HTMLAttributes<HTMLHeadingElement>) => import("react").JSX.Element;
|
|
27
|
+
h3: (props: HTMLAttributes<HTMLHeadingElement>) => import("react").JSX.Element;
|
|
28
|
+
h4: (props: HTMLAttributes<HTMLHeadingElement>) => import("react").JSX.Element;
|
|
29
|
+
h5: (props: HTMLAttributes<HTMLHeadingElement>) => import("react").JSX.Element;
|
|
30
|
+
h6: (props: HTMLAttributes<HTMLHeadingElement>) => import("react").JSX.Element;
|
|
31
|
+
};
|
|
32
|
+
export declare function MarkdownBlockquote({ children, node: _node, ...rest }: WithNode<HTMLAttributes<HTMLQuoteElement>>): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
|
|
33
|
+
export declare function MarkdownSection({ children, node: _node, ...rest }: WithNode<HTMLAttributes<HTMLElement>>): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
|
|
34
|
+
/** Whether a code element is being drawn inside a fenced block. */
|
|
35
|
+
export declare function useInBlock(): boolean;
|
|
36
|
+
export {};
|
package/dist/slots.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The slots of a theme, where the reading surface hands a drawn notation over.
|
|
4
|
+
*
|
|
5
|
+
* Each component here draws exactly what the default theme draws when the
|
|
6
|
+
* theme leaves its slot empty, so a note with no theme is the same DOM it was
|
|
7
|
+
* before slots existed, and calls the slot with the notation already
|
|
8
|
+
* interpreted when the theme fills it. Nothing a slot is given lets it reach
|
|
9
|
+
* the source: what it draws is decided before it is called.
|
|
10
|
+
*/
|
|
11
|
+
import { Children, createContext, createElement, Fragment, isValidElement, useContext, } from 'react';
|
|
12
|
+
import { useHost } from './host.js';
|
|
13
|
+
/** The slot a theme fills, or `undefined`. */
|
|
14
|
+
export function useSlot(name) {
|
|
15
|
+
return useHost().theme.components?.[name];
|
|
16
|
+
}
|
|
17
|
+
/** Draws a slot as a component, so a slot may use hooks. */
|
|
18
|
+
export function drawSlot(slot, props) {
|
|
19
|
+
return createElement(slot, props);
|
|
20
|
+
}
|
|
21
|
+
/** Whether a code element sits in a fenced block, which is what tells a block from a span. */
|
|
22
|
+
export const InBlock = createContext(false);
|
|
23
|
+
// ── Callouts ─────────────────────────────────────────────────────────────────
|
|
24
|
+
/** The title `remarkCallouts` built, carried to the callout that holds it. */
|
|
25
|
+
function CalloutTitle({ children }) {
|
|
26
|
+
return _jsx("div", { className: "callout-title", children: children });
|
|
27
|
+
}
|
|
28
|
+
function Callout({ kind, fold, children }) {
|
|
29
|
+
const { theme } = useHost();
|
|
30
|
+
const slot = useSlot('callout');
|
|
31
|
+
const iconSlot = useSlot('calloutIcon');
|
|
32
|
+
const icon = theme.icons && Object.hasOwn(theme.icons, kind)
|
|
33
|
+
? theme.icons[kind]
|
|
34
|
+
: iconSlot
|
|
35
|
+
? drawSlot(iconSlot, { kind })
|
|
36
|
+
: null;
|
|
37
|
+
const parts = Children.toArray(children);
|
|
38
|
+
// The title arrives as the `div` react-markdown draws through `MarkdownDiv`.
|
|
39
|
+
const at = parts.findIndex((part) => isValidElement(part) &&
|
|
40
|
+
part.type === MarkdownDiv &&
|
|
41
|
+
(part.props.className?.split(/\s+/) ?? []).includes('callout-title'));
|
|
42
|
+
const title = at === -1 ? null : parts[at].props.children;
|
|
43
|
+
const titled = icon === null ? (_jsx(CalloutTitle, { children: title })) : (
|
|
44
|
+
// An icon of the theme's takes the place of the one the stylesheet draws.
|
|
45
|
+
_jsxs("div", { className: "callout-title has-icon", children: [_jsx("span", { className: "callout-icon", "aria-hidden": "true", children: icon }), title] }));
|
|
46
|
+
if (slot) {
|
|
47
|
+
const body = parts.filter((_, index) => index !== at);
|
|
48
|
+
return drawSlot(slot, { kind, fold, title, icon, children: body });
|
|
49
|
+
}
|
|
50
|
+
return (_jsx("div", { className: "callout", "data-callout": kind, ...(fold ? { 'data-fold': fold } : {}), children: parts.map((part, index) => (index === at ? _jsx(Fragment, { children: titled }, "title") : part)) }));
|
|
51
|
+
}
|
|
52
|
+
/** A `div` of the tree: a callout or its title, which `remarkCallouts` marks, or any other. */
|
|
53
|
+
export function MarkdownDiv({ className, children, node: _node, ...rest }) {
|
|
54
|
+
const classes = className?.split(/\s+/) ?? [];
|
|
55
|
+
const data = rest;
|
|
56
|
+
if (classes.includes('callout-title'))
|
|
57
|
+
return _jsx(CalloutTitle, { children: children });
|
|
58
|
+
if (classes.includes('callout')) {
|
|
59
|
+
const fold = data['data-fold'];
|
|
60
|
+
return (_jsx(Callout, { kind: String(data['data-callout'] ?? ''), fold: fold === '+' || fold === '-' ? fold : null, children: children }));
|
|
61
|
+
}
|
|
62
|
+
return (_jsx("div", { className: className, ...rest, children: children }));
|
|
63
|
+
}
|
|
64
|
+
/** The alignment of each column, read from the first row's cells. */
|
|
65
|
+
function alignOf(node) {
|
|
66
|
+
const rows = [];
|
|
67
|
+
const walk = (element) => {
|
|
68
|
+
if (element.tagName === 'tr')
|
|
69
|
+
rows.push(element);
|
|
70
|
+
else
|
|
71
|
+
for (const child of element.children ?? [])
|
|
72
|
+
walk(child);
|
|
73
|
+
};
|
|
74
|
+
if (node)
|
|
75
|
+
walk(node);
|
|
76
|
+
const first = rows[0];
|
|
77
|
+
return (first?.children ?? [])
|
|
78
|
+
.filter((cell) => cell.tagName === 'th' || cell.tagName === 'td')
|
|
79
|
+
.map((cell) => {
|
|
80
|
+
const align = cell.properties?.['align'];
|
|
81
|
+
return align === 'left' || align === 'center' || align === 'right' ? align : null;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export function MarkdownTable({ children, node, ...rest }) {
|
|
85
|
+
const slot = useSlot('table');
|
|
86
|
+
if (slot)
|
|
87
|
+
return drawSlot(slot, { align: alignOf(node), children });
|
|
88
|
+
return _jsx("table", { ...rest, children: children });
|
|
89
|
+
}
|
|
90
|
+
function Heading({ level, className, children, node: _node, ...rest }) {
|
|
91
|
+
const slot = useSlot('heading');
|
|
92
|
+
// The heading of the footnotes is the footnotes slot's, not a heading of the note.
|
|
93
|
+
if (slot && !className?.split(/\s+/).includes('footnotes-label'))
|
|
94
|
+
return drawSlot(slot, { level, children });
|
|
95
|
+
return createElement(`h${level}`, { className, ...rest }, children);
|
|
96
|
+
}
|
|
97
|
+
export const headings = {
|
|
98
|
+
h1: (props) => _jsx(Heading, { level: 1, ...props }),
|
|
99
|
+
h2: (props) => _jsx(Heading, { level: 2, ...props }),
|
|
100
|
+
h3: (props) => _jsx(Heading, { level: 3, ...props }),
|
|
101
|
+
h4: (props) => _jsx(Heading, { level: 4, ...props }),
|
|
102
|
+
h5: (props) => _jsx(Heading, { level: 5, ...props }),
|
|
103
|
+
h6: (props) => _jsx(Heading, { level: 6, ...props }),
|
|
104
|
+
};
|
|
105
|
+
export function MarkdownBlockquote({ children, node: _node, ...rest }) {
|
|
106
|
+
const slot = useSlot('blockquote');
|
|
107
|
+
if (slot)
|
|
108
|
+
return drawSlot(slot, { children });
|
|
109
|
+
return _jsx("blockquote", { ...rest, children: children });
|
|
110
|
+
}
|
|
111
|
+
export function MarkdownSection({ children, node: _node, ...rest }) {
|
|
112
|
+
const slot = useSlot('footnotes');
|
|
113
|
+
const { strings } = useHost();
|
|
114
|
+
const footnotes = rest['data-footnotes'] !== undefined;
|
|
115
|
+
if (slot && footnotes) {
|
|
116
|
+
const notes = Children.toArray(children).filter((child) => !(isValidElement(child) && child.props.id === 'footnote-label'));
|
|
117
|
+
return drawSlot(slot, { label: strings.footnotes, children: notes });
|
|
118
|
+
}
|
|
119
|
+
return _jsx("section", { ...rest, children: children });
|
|
120
|
+
}
|
|
121
|
+
/** Whether a code element is being drawn inside a fenced block. */
|
|
122
|
+
export function useInBlock() {
|
|
123
|
+
return useContext(InBlock);
|
|
124
|
+
}
|
package/dist/strings.d.ts
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
export interface NoteStrings {
|
|
9
9
|
/** The title of an untitled callout, by kind (`note`, `warning`, …). */
|
|
10
10
|
readonly callouts: Readonly<Record<string, string>>;
|
|
11
|
+
/** The heading of the properties a note's frontmatter declares. */
|
|
12
|
+
readonly properties: string;
|
|
13
|
+
/** The locale a date of a property is written in, as `Intl` takes it: `en-US`, `pt-BR`. */
|
|
14
|
+
readonly locale: string;
|
|
15
|
+
/** How a boolean property reads, true and false. */
|
|
16
|
+
readonly yes: string;
|
|
17
|
+
readonly no: string;
|
|
11
18
|
/** The heading of the footnotes gathered at the end of a note. */
|
|
12
19
|
readonly footnotes: string;
|
|
13
20
|
/** The label of the way back from a footnote to its reference, counted from 1. */
|
package/dist/strings.js
CHANGED
|
@@ -31,6 +31,10 @@ const CALLOUTS = {
|
|
|
31
31
|
/** The words MemorySmith shows in English, used for every word a host does not give. */
|
|
32
32
|
export const ENGLISH = {
|
|
33
33
|
callouts: CALLOUTS,
|
|
34
|
+
properties: 'Properties',
|
|
35
|
+
locale: 'en-US',
|
|
36
|
+
yes: 'Yes',
|
|
37
|
+
no: 'No',
|
|
34
38
|
footnotes: 'Notes',
|
|
35
39
|
footnoteBack: (number) => `Back to reference ${number}`,
|
|
36
40
|
pendingLink: 'Pending link: the target note does not exist yet.',
|