@memorysmithapp/markdown-renderer 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +37 -2
- package/dist/CopyButton.d.ts +27 -0
- package/dist/CopyButton.js +69 -0
- package/dist/LinkChoice.d.ts +28 -0
- package/dist/LinkChoice.js +181 -0
- package/dist/Markdown.d.ts +20 -0
- package/dist/Markdown.js +261 -0
- package/dist/MermaidDiagram.d.ts +16 -0
- package/dist/MermaidDiagram.js +70 -0
- package/dist/NoteBody.d.ts +15 -0
- package/dist/NoteBody.js +79 -0
- package/dist/Transclusion.d.ts +11 -0
- package/dist/Transclusion.js +66 -0
- package/dist/address.d.ts +53 -0
- package/dist/address.js +69 -0
- package/dist/delegated.d.ts +39 -0
- package/dist/delegated.js +63 -0
- package/dist/highlight.d.ts +54 -0
- package/dist/highlight.js +96 -0
- package/dist/host.d.ts +135 -0
- package/dist/host.js +49 -0
- package/dist/image-dimensions.d.ts +33 -0
- package/dist/image-dimensions.js +42 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/markdown.d.ts +68 -0
- package/dist/markdown.js +395 -0
- package/dist/remark-callouts.d.ts +40 -0
- package/dist/remark-callouts.js +108 -0
- package/dist/remark-ring.d.ts +68 -0
- package/dist/remark-ring.js +178 -0
- package/dist/strings.d.ts +55 -0
- package/dist/strings.js +64 -0
- package/dist/tasklist.d.ts +25 -0
- package/dist/tasklist.js +80 -0
- package/dist/transclusion.d.ts +76 -0
- package/dist/transclusion.js +161 -0
- package/dist/wikilink-address.d.ts +8 -0
- package/dist/wikilink-address.js +13 -0
- package/package.json +73 -3
- package/styles.css +865 -0
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type NoteStrings } from './strings.js';
|
|
3
|
+
/**
|
|
4
|
+
* The host contract: everything the renderer needs from the notebook a note lives in, given by
|
|
5
|
+
* whoever draws it.
|
|
6
|
+
*
|
|
7
|
+
* The package knows no API, no router, no i18n library and no query cache. What MemorySmith reads
|
|
8
|
+
* from those reaches the component as these props: functions that answer about the notebook, and
|
|
9
|
+
* the words the page shows. The host decides; the package draws.
|
|
10
|
+
*
|
|
11
|
+
* **Every default is the safe one.** With no `resolve`, every link is pending; with no `keeps`, no
|
|
12
|
+
* embed reaches a file; with no `renderLink`, an internal address is a plain anchor; with no
|
|
13
|
+
* `strings`, the words are MemorySmith's English.
|
|
14
|
+
*/
|
|
15
|
+
export interface NoteHost {
|
|
16
|
+
/**
|
|
17
|
+
* The address of the one note a link target names, compared after NFC, or `null` when no single
|
|
18
|
+
* note answers it. The target is a name, literal: never slugified, never stripped of an extension.
|
|
19
|
+
*/
|
|
20
|
+
readonly resolve?: (name: string) => string | null;
|
|
21
|
+
/**
|
|
22
|
+
* How many notes a target reaches, by name or, when no name matches, by alias (§5.2). One is a
|
|
23
|
+
* link; more than one is a choice; none is pending. Defaults to one when `resolve` answers and
|
|
24
|
+
* none otherwise.
|
|
25
|
+
*/
|
|
26
|
+
readonly reaches?: (name: string) => number;
|
|
27
|
+
/**
|
|
28
|
+
* The notes a target no single note answers may lead to, asked when its link is clicked. With it,
|
|
29
|
+
* such a link opens the choice in place — or goes straight to the note, when one answers by
|
|
30
|
+
* alias. Without it, the link is pending and asks nothing.
|
|
31
|
+
*/
|
|
32
|
+
readonly choose?: (name: string) => Promise<LinkChoices>;
|
|
33
|
+
/** The address of the host's page about a target, which a choice link carries for a new tab. */
|
|
34
|
+
readonly targetAddress?: (name: string) => string | null;
|
|
35
|
+
/** Goes to an internal address, through the host's router. Defaults to `location.assign`. */
|
|
36
|
+
readonly navigate?: (address: string) => void;
|
|
37
|
+
/**
|
|
38
|
+
* The note a target names, for an embed to draw in place: its body as the host keeps it, its name
|
|
39
|
+
* and its address. Asked only for a target exactly one note reaches; `null` draws the embed as
|
|
40
|
+
* pending. Without it, every embed of a note is pending. The package never fetches.
|
|
41
|
+
*/
|
|
42
|
+
readonly loadNote?: (name: string) => Promise<EmbeddedNote | null>;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the reader may tick the task boxes of this note. Defaults to `false`: every box shows
|
|
45
|
+
* its state and none answers a click.
|
|
46
|
+
*/
|
|
47
|
+
readonly writable?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* A task box was ticked. The ordinal counts the boxes of the note as the host gave it, frontmatter
|
|
50
|
+
* included; the host applies it with `toggleTaskAt`, a one-character change of its bytes. The
|
|
51
|
+
* package never writes the note.
|
|
52
|
+
*/
|
|
53
|
+
readonly onToggleTask?: (ordinal: number) => void;
|
|
54
|
+
/** Whether the notebook keeps a file under that name. Only an embed reaches a file. */
|
|
55
|
+
readonly keeps?: (name: string) => boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Draws a file the notebook keeps where a note embeds it: an image, audio, video, or a card. The
|
|
58
|
+
* dimensions are the ones the embed declared, in CSS pixels, or `null`.
|
|
59
|
+
*/
|
|
60
|
+
readonly renderAttachment?: (file: AttachmentRequest) => ReactNode;
|
|
61
|
+
/** Draws a link to an internal address, through the host's router. */
|
|
62
|
+
readonly renderLink?: (link: LinkRequest) => ReactNode;
|
|
63
|
+
/** The words the page shows, in the reader's language; each one left out is MemorySmith's English. */
|
|
64
|
+
readonly strings?: Partial<NoteStrings>;
|
|
65
|
+
/** The theme the host draws in, which picks the theme of a Mermaid diagram. Defaults to `light`. */
|
|
66
|
+
readonly theme?: 'light' | 'dark';
|
|
67
|
+
/**
|
|
68
|
+
* Whether a note that fetched images from other sites says so, naming them, under the body
|
|
69
|
+
* (§7.10). Defaults to `true`: the specification forbids only silence. A host that discloses it
|
|
70
|
+
* elsewhere turns it off and reads the hosts with `remoteImageHosts`.
|
|
71
|
+
*/
|
|
72
|
+
readonly remoteNotice?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/** What a target that no single note answers may lead to. */
|
|
75
|
+
export interface LinkChoices {
|
|
76
|
+
/** What matched: the names of notes, their aliases, or nothing. */
|
|
77
|
+
readonly by: 'name' | 'alias' | null;
|
|
78
|
+
readonly notes: readonly LinkChoiceNote[];
|
|
79
|
+
}
|
|
80
|
+
/** A note a choice offers. */
|
|
81
|
+
export interface LinkChoiceNote {
|
|
82
|
+
/** Its name, or `null` for a note that has none. */
|
|
83
|
+
readonly name: string | null;
|
|
84
|
+
/** Its address, which the host follows. */
|
|
85
|
+
readonly address: string;
|
|
86
|
+
/** The folders from the root down to the one that holds it. */
|
|
87
|
+
readonly trail: readonly string[];
|
|
88
|
+
/** What the folder that holds it is for, in the notebook's words. */
|
|
89
|
+
readonly folderDescription: string;
|
|
90
|
+
}
|
|
91
|
+
/** A note an embed draws in place. */
|
|
92
|
+
export interface EmbeddedNote {
|
|
93
|
+
/** The note as the host keeps it, frontmatter included. */
|
|
94
|
+
readonly markdown: string;
|
|
95
|
+
/** Its name, or `null` for a note that has none. */
|
|
96
|
+
readonly name: string | null;
|
|
97
|
+
/** Its address, which the caption under the embed links to, or `null`. */
|
|
98
|
+
readonly address: string | null;
|
|
99
|
+
}
|
|
100
|
+
/** A file to draw: its name, and the dimensions the embed declared. */
|
|
101
|
+
export interface AttachmentRequest {
|
|
102
|
+
readonly name: string;
|
|
103
|
+
readonly width: number | null;
|
|
104
|
+
readonly height: number | null;
|
|
105
|
+
}
|
|
106
|
+
/** A link to draw through the host's router. */
|
|
107
|
+
export interface LinkRequest {
|
|
108
|
+
readonly href: string;
|
|
109
|
+
readonly className?: string;
|
|
110
|
+
readonly children: ReactNode;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Every member of the contract, by name. The `satisfies` is what makes it complete: a member added
|
|
114
|
+
* to `NoteHost` and not listed here does not compile.
|
|
115
|
+
*/
|
|
116
|
+
export declare const HOST_KEYS: (keyof NoteHost)[];
|
|
117
|
+
/** The host contract with every default applied. */
|
|
118
|
+
export interface ResolvedHost {
|
|
119
|
+
readonly resolve: (name: string) => string | null;
|
|
120
|
+
readonly reaches: (name: string) => number;
|
|
121
|
+
readonly choose: ((name: string) => Promise<LinkChoices>) | null;
|
|
122
|
+
readonly targetAddress: (name: string) => string | null;
|
|
123
|
+
readonly navigate: (address: string) => void;
|
|
124
|
+
readonly remoteNotice: boolean;
|
|
125
|
+
readonly loadNote: ((name: string) => Promise<EmbeddedNote | null>) | null;
|
|
126
|
+
readonly keeps: (name: string) => boolean;
|
|
127
|
+
readonly renderAttachment: ((file: AttachmentRequest) => ReactNode) | null;
|
|
128
|
+
readonly renderLink: (link: LinkRequest) => ReactNode;
|
|
129
|
+
readonly strings: NoteStrings;
|
|
130
|
+
readonly theme: 'light' | 'dark';
|
|
131
|
+
}
|
|
132
|
+
export declare function resolveHost(host: NoteHost): ResolvedHost;
|
|
133
|
+
export declare const HostProvider: import("react").Provider<ResolvedHost>;
|
|
134
|
+
/** The host the note is drawn under, read by every part of the surface. */
|
|
135
|
+
export declare function useHost(): ResolvedHost;
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
import { withDefaults } from './strings.js';
|
|
4
|
+
/**
|
|
5
|
+
* Every member of the contract, by name. The `satisfies` is what makes it complete: a member added
|
|
6
|
+
* to `NoteHost` and not listed here does not compile.
|
|
7
|
+
*/
|
|
8
|
+
export const HOST_KEYS = Object.keys({
|
|
9
|
+
resolve: 1,
|
|
10
|
+
reaches: 1,
|
|
11
|
+
choose: 1,
|
|
12
|
+
targetAddress: 1,
|
|
13
|
+
navigate: 1,
|
|
14
|
+
loadNote: 1,
|
|
15
|
+
writable: 1,
|
|
16
|
+
onToggleTask: 1,
|
|
17
|
+
keeps: 1,
|
|
18
|
+
renderAttachment: 1,
|
|
19
|
+
renderLink: 1,
|
|
20
|
+
strings: 1,
|
|
21
|
+
theme: 1,
|
|
22
|
+
remoteNotice: 1,
|
|
23
|
+
});
|
|
24
|
+
const nothing = () => null;
|
|
25
|
+
const never = () => false;
|
|
26
|
+
const plainLink = ({ href, className, children }) => (_jsx("a", { className: className, href: href, children: children }));
|
|
27
|
+
export function resolveHost(host) {
|
|
28
|
+
const resolve = host.resolve ?? nothing;
|
|
29
|
+
return {
|
|
30
|
+
resolve,
|
|
31
|
+
reaches: host.reaches ?? ((name) => (resolve(name) === null ? 0 : 1)),
|
|
32
|
+
choose: host.choose ?? null,
|
|
33
|
+
targetAddress: host.targetAddress ?? nothing,
|
|
34
|
+
navigate: host.navigate ?? ((address) => window.location.assign(address)),
|
|
35
|
+
remoteNotice: host.remoteNotice ?? true,
|
|
36
|
+
loadNote: host.loadNote ?? null,
|
|
37
|
+
keeps: host.keeps ?? never,
|
|
38
|
+
renderAttachment: host.renderAttachment ?? null,
|
|
39
|
+
renderLink: host.renderLink ?? plainLink,
|
|
40
|
+
strings: withDefaults(host.strings),
|
|
41
|
+
theme: host.theme ?? 'light',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const HostContext = createContext(resolveHost({}));
|
|
45
|
+
export const HostProvider = HostContext.Provider;
|
|
46
|
+
/** The host the note is drawn under, read by every part of the surface. */
|
|
47
|
+
export function useHost() {
|
|
48
|
+
return useContext(HostContext);
|
|
49
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pipe inside the alt text of an image, which separates the description
|
|
3
|
+
* from the dimensions (`SPEC.md` §3.14).
|
|
4
|
+
*
|
|
5
|
+
* `` is 100 by 145 CSS pixels and
|
|
6
|
+
* `` is 100 wide with the aspect ratio kept. What precedes
|
|
7
|
+
* the pipe is the **description** and is never dropped — it is what a person
|
|
8
|
+
* gets when the image does not load, and it is what a screen reader says.
|
|
9
|
+
*
|
|
10
|
+
* **A value that is neither form is not a dimension.** ``
|
|
11
|
+
* keeps `Engelbart|large` as the whole description, because deleting what an
|
|
12
|
+
* author wrote into an accessibility label is the worse of the two failures.
|
|
13
|
+
* Until the specification declared this form, an imported note displayed the
|
|
14
|
+
* pipe and the digits on screen and mandated the whole string as the label.
|
|
15
|
+
*/
|
|
16
|
+
export interface ImageAlt {
|
|
17
|
+
/** What a reader hears, and sees when the image does not load. */
|
|
18
|
+
readonly description: string;
|
|
19
|
+
readonly width: number | null;
|
|
20
|
+
readonly height: number | null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* What follows a pipe, when it is a dimension and not text.
|
|
24
|
+
*
|
|
25
|
+
* The wikilink embed of an attachment carries the same thing the alt text of a
|
|
26
|
+
* Markdown image does — `![[engelbart.jpg|100x145]]` and
|
|
27
|
+
* `` mean one thing — so both read it here.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readDimensions(value: string): {
|
|
30
|
+
width: number;
|
|
31
|
+
height: number | null;
|
|
32
|
+
} | null;
|
|
33
|
+
export declare function readImageAlt(alt: string): ImageAlt;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pipe inside the alt text of an image, which separates the description
|
|
3
|
+
* from the dimensions (`SPEC.md` §3.14).
|
|
4
|
+
*
|
|
5
|
+
* `` is 100 by 145 CSS pixels and
|
|
6
|
+
* `` is 100 wide with the aspect ratio kept. What precedes
|
|
7
|
+
* the pipe is the **description** and is never dropped — it is what a person
|
|
8
|
+
* gets when the image does not load, and it is what a screen reader says.
|
|
9
|
+
*
|
|
10
|
+
* **A value that is neither form is not a dimension.** ``
|
|
11
|
+
* keeps `Engelbart|large` as the whole description, because deleting what an
|
|
12
|
+
* author wrote into an accessibility label is the worse of the two failures.
|
|
13
|
+
* Until the specification declared this form, an imported note displayed the
|
|
14
|
+
* pipe and the digits on screen and mandated the whole string as the label.
|
|
15
|
+
*/
|
|
16
|
+
/** `100` or `100x145`, and nothing else. */
|
|
17
|
+
const DIMENSIONS = /^(\d{1,5})(?:x(\d{1,5}))?$/;
|
|
18
|
+
/**
|
|
19
|
+
* What follows a pipe, when it is a dimension and not text.
|
|
20
|
+
*
|
|
21
|
+
* The wikilink embed of an attachment carries the same thing the alt text of a
|
|
22
|
+
* Markdown image does — `![[engelbart.jpg|100x145]]` and
|
|
23
|
+
* `` mean one thing — so both read it here.
|
|
24
|
+
*/
|
|
25
|
+
export function readDimensions(value) {
|
|
26
|
+
const measured = DIMENSIONS.exec(value.trim());
|
|
27
|
+
if (!measured)
|
|
28
|
+
return null;
|
|
29
|
+
return {
|
|
30
|
+
width: Number(measured[1]),
|
|
31
|
+
height: measured[2] === undefined ? null : Number(measured[2]),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function readImageAlt(alt) {
|
|
35
|
+
const at = alt.lastIndexOf('|');
|
|
36
|
+
if (at === -1)
|
|
37
|
+
return { description: alt, width: null, height: null };
|
|
38
|
+
const measured = readDimensions(alt.slice(at + 1));
|
|
39
|
+
if (!measured)
|
|
40
|
+
return { description: alt, width: null, height: null };
|
|
41
|
+
return { description: alt.slice(0, at).trim(), width: measured.width, height: measured.height };
|
|
42
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { NoteBody, type NoteBodyProps } from './NoteBody.js';
|
|
2
|
+
export type { AttachmentRequest, EmbeddedNote, LinkChoiceNote, LinkChoices, LinkRequest, NoteHost } from './host.js';
|
|
3
|
+
export { ENGLISH, type NoteStrings } from './strings.js';
|
|
4
|
+
export { followable } from './address.js';
|
|
5
|
+
export { readDimensions, readImageAlt, type ImageAlt } from './image-dimensions.js';
|
|
6
|
+
export { codeRegions, fenceRegions, headingKey, remoteImageHosts, resolveWikilinks, splitFrontmatter, tableRegions, toUnixNewlines, type SplitDocument, } from './markdown.js';
|
|
7
|
+
export { ordinalAt, taskBoxes, taskCheckedAt, toggleTaskAt } from './tasklist.js';
|
|
8
|
+
export { DELEGATED_TO_THE_BASE_PARSER, unclassified } from './delegated.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The entry point of @memorysmithapp/markdown-renderer.
|
|
2
|
+
export { NoteBody } from './NoteBody.js';
|
|
3
|
+
export { ENGLISH } from './strings.js';
|
|
4
|
+
export { followable } from './address.js';
|
|
5
|
+
export { readDimensions, readImageAlt } from './image-dimensions.js';
|
|
6
|
+
export { codeRegions, fenceRegions, headingKey, remoteImageHosts, resolveWikilinks, splitFrontmatter, tableRegions, toUnixNewlines, } from './markdown.js';
|
|
7
|
+
export { ordinalAt, taskBoxes, taskCheckedAt, toggleTaskAt } from './tasklist.js';
|
|
8
|
+
export { DELEGATED_TO_THE_BASE_PARSER, unclassified } from './delegated.js';
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface SplitDocument {
|
|
2
|
+
frontmatter: Record<string, string>;
|
|
3
|
+
/**
|
|
4
|
+
* Which of those keys the notebook wrote as a list, whether in a dash block or
|
|
5
|
+
* inline. The values are flattened to one comma-separated string so the
|
|
6
|
+
* table can print them, and that flattening is lossy: `tags: a, b` and a
|
|
7
|
+
* two-item list read the same afterwards. The set keeps the difference,
|
|
8
|
+
* because a list is drawn as chips and a sentence is not, and guessing from
|
|
9
|
+
* the commas would turn a prose value that happens to have one into chips.
|
|
10
|
+
*/
|
|
11
|
+
lists: Set<string>;
|
|
12
|
+
body: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A note is bytes the notebook wrote, and a notebook written on Windows, or in a
|
|
16
|
+
* desktop editor, or pasted from anywhere, carries CRLF. Every reader in this
|
|
17
|
+
* module starts here, because a carriage return left at the end of a line is
|
|
18
|
+
* invisible in the source and fatal to a regex: `.` does not match one, so
|
|
19
|
+
* `(.*)$` fails on exactly the lines that have a value.
|
|
20
|
+
*/
|
|
21
|
+
export declare function toUnixNewlines(raw: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Where the body of a note starts: after its frontmatter, which a host shows as properties and
|
|
24
|
+
* not as prose. The cut is the one MemorySmith's reading page makes, so the ordinals of the task
|
|
25
|
+
* boxes below it are counted over the whole note, as the host stores it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function bodyStart(text: string): number;
|
|
28
|
+
/**
|
|
29
|
+
* The frontmatter block, read the way MemorySmith's reading page reads it to show a note's
|
|
30
|
+
* properties. The package does not draw it — `NoteBody` starts after it — and exports this so a
|
|
31
|
+
* host that draws the properties reads the same values MemorySmith does.
|
|
32
|
+
*/
|
|
33
|
+
export declare function splitFrontmatter(input: string): SplitDocument;
|
|
34
|
+
/** The half-open ranges of `body` that are fenced blocks. */
|
|
35
|
+
export declare function fenceRegions(body: string): Array<[number, number]>;
|
|
36
|
+
/** The half-open ranges of `body` that are code, fenced or inline, in order. */
|
|
37
|
+
export declare function codeRegions(body: string): Array<[number, number]>;
|
|
38
|
+
/** Whether an offset falls inside one of them. */
|
|
39
|
+
export declare function insideCode(regions: Array<[number, number]>, at: number): boolean;
|
|
40
|
+
export declare function tableRegions(body: string): Array<[number, number]>;
|
|
41
|
+
/** Whether an offset falls inside one of them. */
|
|
42
|
+
export declare function insideTable(regions: Array<[number, number]>, at: number): boolean;
|
|
43
|
+
/** Runs a rewrite over everything except code, which is copied through. */
|
|
44
|
+
export declare function outsideCode(body: string, rewrite: (text: string) => string): string;
|
|
45
|
+
export declare function resolveWikilinks(body: string, resolve: (name: string) => string | null,
|
|
46
|
+
/**
|
|
47
|
+
* Whether the notebook keeps a FILE under that name. The extension of a
|
|
48
|
+
* name decides nothing, so what a target is cannot be read off it: it is
|
|
49
|
+
* read off what the notebook holds, which is the host's to answer.
|
|
50
|
+
*/
|
|
51
|
+
keeps?: (name: string) => boolean): string;
|
|
52
|
+
/**
|
|
53
|
+
* The key a **heading** is matched by, which is how an anchor finds the
|
|
54
|
+
* section it names inside a note.
|
|
55
|
+
*
|
|
56
|
+
* **It is not the key of a note, and there is no longer one of those.** A link
|
|
57
|
+
* resolves against the name, literally and case-exact (§5.2), so the
|
|
58
|
+
* slug this file used to compute — the second implementation of a rule the
|
|
59
|
+
* kernel also implemented, and the one that drifted — is gone. What is
|
|
60
|
+
* left is this: an anchor and a heading are two spellings of the same phrase,
|
|
61
|
+
* written by the same person minutes apart, and folding case and punctuation
|
|
62
|
+
* between them is what makes `#Artigo 75` find `## Artigo 75`.
|
|
63
|
+
*
|
|
64
|
+
* Nothing outside this comparison reads it, and nothing else in the renderer
|
|
65
|
+
* computes a key from a name: the address of a note is the host's to give.
|
|
66
|
+
*/
|
|
67
|
+
export declare function headingKey(name: string): string;
|
|
68
|
+
export declare function remoteImageHosts(body: string): string[];
|