@memorysmithapp/markdown-renderer 0.7.1 → 0.8.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 +1 -1
- package/dist/Markdown.d.ts +12 -1
- package/dist/Markdown.js +41 -33
- package/dist/diagnose.js +15 -1
- package/dist/edit/ContextMenu.d.ts +1 -1
- package/dist/edit/ContextMenu.js +1 -1
- package/dist/edit/NoteEditor.js +3 -2
- package/dist/edit/blocks.d.ts +21 -5
- package/dist/edit/blocks.js +328 -56
- package/dist/edit/heights.js +2 -0
- package/dist/edit/image-size.d.ts +26 -10
- package/dist/edit/image-size.js +52 -17
- package/dist/edit/live.js +18 -9
- package/dist/host.d.ts +15 -1
- package/dist/image-dimensions.d.ts +30 -5
- package/dist/image-dimensions.js +39 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kept-image.d.ts +20 -0
- package/dist/kept-image.js +47 -0
- package/dist/markdown.js +2 -4
- package/dist/mode-icons.d.ts +3 -0
- package/dist/mode-icons.js +7 -2
- package/dist/strings.d.ts +16 -1
- package/dist/strings.js +9 -1
- package/dist/theme.d.ts +8 -1
- package/package.json +2 -2
- package/structure.css +46 -8
- package/theme-default.css +48 -7
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ import 'katex/dist/katex.min.css';
|
|
|
18
18
|
<NoteBody
|
|
19
19
|
markdown={note} // the note as you keep it, frontmatter included
|
|
20
20
|
resolve={(name) => addressOfTheOneNoteNamed(name)} // or null: the link is pending
|
|
21
|
-
keeps={(name) => notebookKeepsAFile(name)} //
|
|
21
|
+
keeps={(name) => notebookKeepsAFile(name)} // reached by an embed, and by an image with no host: 
|
|
22
22
|
fileOf={(name) => describe(name)} // { mimeType, bytes, url, description }: drawn by its type, as MemorySmith does
|
|
23
23
|
renderLink={({ href, className, children }) => <a href={href} className={className} onClick={follow(href)}>{children}</a>} // follow: your router's navigate, captured where it is
|
|
24
24
|
loadNote={(name) => fetchTheBodyOf(name)} // for an embedded note; the package never fetches
|
package/dist/Markdown.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnchorHTMLAttributes, type ReactNode } from 'react';
|
|
1
|
+
import { type AnchorHTMLAttributes, type ImgHTMLAttributes, type ReactNode } from 'react';
|
|
2
2
|
import { type FootnoteRef } from './footnotes.js';
|
|
3
3
|
import type { TaskSave } from './task-saves.js';
|
|
4
4
|
/**
|
|
@@ -40,6 +40,17 @@ export interface MarkdownProps {
|
|
|
40
40
|
type WithNode<T> = T & {
|
|
41
41
|
node?: unknown;
|
|
42
42
|
};
|
|
43
|
+
/**
|
|
44
|
+
* An image, with the dimensions the specification puts in its alt text
|
|
45
|
+
* (§3.14). What precedes the pipe is the description and is never dropped;
|
|
46
|
+
* what follows it is width, or width and height, in CSS pixels, or a width as
|
|
47
|
+
* a share of the page at most (#108), and never appears as text. A value that is neither stays part of the description,
|
|
48
|
+
* because deleting an accessibility label is the worse failure.
|
|
49
|
+
*
|
|
50
|
+
* A destination that names no host is a file the notebook keeps, by the last segment of its path
|
|
51
|
+
* (§5.8, #104): it is drawn as the embed of that file is, and never asked of a path under the page.
|
|
52
|
+
*/
|
|
53
|
+
export declare function MarkdownImage({ alt, node: _node, ...rest }: WithNode<ImgHTMLAttributes<HTMLImageElement>>): import("react").JSX.Element;
|
|
43
54
|
export declare function MarkdownAnchor({ href, children, node: _node, ...rest }: WithNode<AnchorHTMLAttributes<HTMLAnchorElement>>): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
|
|
44
55
|
export declare function Markdown({ children, source, onToggleTask, taskSave, writable, footnotes }: MarkdownProps): import("react").JSX.Element;
|
|
45
56
|
export {};
|
package/dist/Markdown.js
CHANGED
|
@@ -7,9 +7,10 @@ import rehypeKatex from 'rehype-katex';
|
|
|
7
7
|
import { followable } from './address.js';
|
|
8
8
|
import { CopyButton } from './CopyButton.js';
|
|
9
9
|
import { fileAddress, fileKind, formatBytes, opensInBrowser, rendersAs } from './file-types.js';
|
|
10
|
+
import { keptImageName } from './kept-image.js';
|
|
10
11
|
import { highlightLanguage, highlightTree, languageToLoad, loadGrammar } from './highlight.js';
|
|
11
12
|
import { useHost } from './host.js';
|
|
12
|
-
import { readDimensions, readImageAlt } from './image-dimensions.js';
|
|
13
|
+
import { readDimensions, readImageAlt, sizedAttributes } from './image-dimensions.js';
|
|
13
14
|
import { choiceTargetOf, LinkChoice } from './LinkChoice.js';
|
|
14
15
|
import { toUnixNewlines } from './markdown.js';
|
|
15
16
|
import { MermaidDiagram } from './MermaidDiagram.js';
|
|
@@ -20,13 +21,21 @@ import { ordinalAt } from './tasklist.js';
|
|
|
20
21
|
/**
|
|
21
22
|
* An image, with the dimensions the specification puts in its alt text
|
|
22
23
|
* (§3.14). What precedes the pipe is the description and is never dropped;
|
|
23
|
-
* what follows it is width, or width and height, in CSS pixels,
|
|
24
|
-
* appears as text. A value that is neither stays part of the description,
|
|
24
|
+
* what follows it is width, or width and height, in CSS pixels, or a width as
|
|
25
|
+
* a share of the page at most (#108), and never appears as text. A value that is neither stays part of the description,
|
|
25
26
|
* because deleting an accessibility label is the worse failure.
|
|
27
|
+
*
|
|
28
|
+
* A destination that names no host is a file the notebook keeps, by the last segment of its path
|
|
29
|
+
* (§5.8, #104): it is drawn as the embed of that file is, and never asked of a path under the page.
|
|
26
30
|
*/
|
|
27
|
-
function MarkdownImage({ alt, node: _node, ...rest }) {
|
|
31
|
+
export function MarkdownImage({ alt, node: _node, ...rest }) {
|
|
28
32
|
const read = readImageAlt(alt ?? '');
|
|
29
|
-
|
|
33
|
+
const name = typeof rest.src === 'string' ? keptImageName(rest.src) : null;
|
|
34
|
+
if (name !== null)
|
|
35
|
+
return _jsx(Attachment, { name: name, size: read, description: read.description, title: rest.title });
|
|
36
|
+
// The description is the hover hint too, so a person who sees the picture can read it (#109); a title written wins.
|
|
37
|
+
const title = rest.title || read.description || undefined;
|
|
38
|
+
return _jsx("img", { ...rest, alt: read.description, title: title, ...sizedAttributes(read) });
|
|
30
39
|
}
|
|
31
40
|
/**
|
|
32
41
|
* A link to a place in the same note: a footnote and its way back (§7.12),
|
|
@@ -56,26 +65,19 @@ function InPageAnchor({ href, children, ...rest }) {
|
|
|
56
65
|
};
|
|
57
66
|
return (_jsx("a", { href: href, onClick: follow, ...rest, children: children }));
|
|
58
67
|
}
|
|
59
|
-
|
|
60
|
-
* A file the notebook keeps, where a note embedded it, drawn by its type — an
|
|
61
|
-
* image, audio, video, or a card — by the host's `renderAttachment`, else from
|
|
62
|
-
* what its `fileOf` says (#77). A name the notebook keeps nothing under is
|
|
63
|
-
* reported the way a pending link is: a notebook is read most while it is
|
|
64
|
-
* being written (§5.8).
|
|
65
|
-
*/
|
|
66
|
-
function Attachment({ name, width, height }) {
|
|
68
|
+
function Attachment({ name, size, ...written }) {
|
|
67
69
|
const host = useHost();
|
|
68
70
|
if (!host.keeps(name))
|
|
69
|
-
return _jsx(AttachmentMissing, { name: name });
|
|
71
|
+
return _jsx(AttachmentMissing, { name: name, label: written.description });
|
|
70
72
|
if (host.renderAttachment)
|
|
71
|
-
return _jsx(_Fragment, { children: host.renderAttachment({ name,
|
|
73
|
+
return _jsx(_Fragment, { children: host.renderAttachment({ name, ...size, ...written }) });
|
|
72
74
|
if (!host.fileOf)
|
|
73
|
-
return _jsx("span", { className: "attachment-loading", children: name });
|
|
74
|
-
return _jsx(KeptAttachment, { name: name,
|
|
75
|
+
return _jsx("span", { className: "attachment-loading", children: written.description || name });
|
|
76
|
+
return _jsx(KeptAttachment, { name: name, size: size, ...written });
|
|
75
77
|
}
|
|
76
|
-
function AttachmentMissing({ name }) {
|
|
78
|
+
function AttachmentMissing({ name, label }) {
|
|
77
79
|
const host = useHost();
|
|
78
|
-
return (_jsx("span", { className: "attachment-missing", title: host.strings.attachmentMissing, children: name }));
|
|
80
|
+
return (_jsx("span", { className: "attachment-missing", title: host.strings.attachmentMissing, "data-name": label ? name : undefined, children: label || name }));
|
|
79
81
|
}
|
|
80
82
|
function isThenable(value) {
|
|
81
83
|
return typeof value === 'object' && value !== null && typeof value.then === 'function';
|
|
@@ -129,30 +131,36 @@ function openInTab(event, url) {
|
|
|
129
131
|
* included — a card with what it is, its size, and the ways out of the page: Open only for what a
|
|
130
132
|
* browser shows, Download always. A theme may draw it through the `attachment` slot.
|
|
131
133
|
*/
|
|
132
|
-
function KeptAttachment({ name,
|
|
134
|
+
function KeptAttachment({ name, size: declared, ...written }) {
|
|
133
135
|
const host = useHost();
|
|
134
136
|
const slot = useSlot('attachment');
|
|
135
137
|
const described = useKeptFile(name);
|
|
136
138
|
if (described === undefined)
|
|
137
|
-
return _jsx("span", { className: "attachment-loading", children: name });
|
|
139
|
+
return _jsx("span", { className: "attachment-loading", children: written.description || name });
|
|
138
140
|
const url = described ? fileAddress(described.url) : '';
|
|
139
141
|
if (!described || url === '')
|
|
140
|
-
return _jsx(AttachmentMissing, { name: name });
|
|
141
|
-
|
|
142
|
+
return _jsx(AttachmentMissing, { name: name, label: written.description });
|
|
143
|
+
// What the note wrote of the file is what the reader gets of it; the file's own when it wrote nothing.
|
|
144
|
+
const description = written.description || described.description;
|
|
145
|
+
const file = {
|
|
146
|
+
...described,
|
|
147
|
+
url,
|
|
148
|
+
downloadUrl: fileAddress(described.downloadUrl ?? described.url) || url,
|
|
149
|
+
...(description ? { description } : {}),
|
|
150
|
+
};
|
|
142
151
|
const shape = rendersAs(file.mimeType);
|
|
143
152
|
const kind = fileKind(file.mimeType, host.strings.fileKinds);
|
|
144
153
|
const size = formatBytes(file.bytes, host.strings.locale);
|
|
145
154
|
const opens = opensInBrowser(file.mimeType);
|
|
146
155
|
if (slot)
|
|
147
|
-
return _jsx(_Fragment, { children: drawSlot(slot, { name, file, shape, kind, size, opens,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return _jsx("img", { className: "attachment-image", src: file.url, alt: file.description || name, ...sized });
|
|
156
|
+
return _jsx(_Fragment, { children: drawSlot(slot, { name, file, shape, kind, size, opens, ...declared, title: written.title ?? null }) });
|
|
157
|
+
const sized = sizedAttributes(declared);
|
|
158
|
+
if (shape === 'image') {
|
|
159
|
+
// Described, its description is also the hover hint, a title written winning (#109); a picture
|
|
160
|
+
// with none takes its name as its `alt` and no hint, which would only say the name again.
|
|
161
|
+
const title = written.title || file.description;
|
|
162
|
+
return _jsx("img", { className: "attachment-image", src: file.url, alt: file.description || name, ...(title ? { title } : {}), ...sized });
|
|
163
|
+
}
|
|
156
164
|
if (shape === 'audio')
|
|
157
165
|
return _jsx("audio", { className: "attachment-player", controls: true, src: file.url });
|
|
158
166
|
if (shape === 'video')
|
|
@@ -178,7 +186,7 @@ export function MarkdownAnchor({ href, children, node: _node, ...rest }) {
|
|
|
178
186
|
const at = address.lastIndexOf('|');
|
|
179
187
|
const measured = at === -1 ? null : readDimensions(address.slice(at + 1));
|
|
180
188
|
const name = measured ? address.slice(0, at) : address;
|
|
181
|
-
return _jsx(Attachment, { name: name,
|
|
189
|
+
return _jsx(Attachment, { name: name, size: measured ?? { width: null, height: null, percent: null } });
|
|
182
190
|
}
|
|
183
191
|
if (href?.startsWith('pending:')) {
|
|
184
192
|
const pending = (label) => (_jsx("span", { className: "wikilink-pending", title: host.strings.pendingLink, children: label }));
|
package/dist/diagnose.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* note, or beside an editor. KaTeX checks a formula as `rehype-katex` would typeset it, and Mermaid
|
|
12
12
|
* reads a diagram as the page would draw it, loaded only when a note carries one.
|
|
13
13
|
*/
|
|
14
|
+
import { keptImageName } from './kept-image.js';
|
|
14
15
|
import katex from 'katex';
|
|
15
16
|
import remarkParse from 'remark-parse';
|
|
16
17
|
import { unified } from 'unified';
|
|
@@ -36,7 +37,7 @@ export const NOTICES = {
|
|
|
36
37
|
'raw-html': { severity: 'notice', section: '7.9', message: 'Raw HTML is drawn as text, tags and all.' },
|
|
37
38
|
'strikethrough-single-tilde': { severity: 'notice', section: '4.3', message: 'One tilde strikes nothing: the tildes stay on the page. Strikethrough is ~~two~~.' },
|
|
38
39
|
'link-pending': { severity: 'hint', section: '7.4', message: 'No note answers this link yet, so it is drawn as pending.' },
|
|
39
|
-
'embed-pending': { severity: 'hint', section: '7.3', message: 'No note and no kept file answers this embed, so it is drawn as
|
|
40
|
+
'embed-pending': { severity: 'hint', section: '7.3', message: 'No note and no kept file answers this embed, or no kept file this image, so it is drawn as pending.' },
|
|
40
41
|
};
|
|
41
42
|
/** A footnote reference left as text, which is one with no definition (§7.12). */
|
|
42
43
|
const UNDEFINED_REFERENCE = /\[\^[^\]\s]+\]/g;
|
|
@@ -106,6 +107,7 @@ export async function diagnose(markdown, options = {}) {
|
|
|
106
107
|
const diagrams = [];
|
|
107
108
|
const referenced = new Set();
|
|
108
109
|
const definitions = [];
|
|
110
|
+
const images = [];
|
|
109
111
|
walk(tree, (node) => {
|
|
110
112
|
const at = span(node);
|
|
111
113
|
if (!at)
|
|
@@ -136,6 +138,11 @@ export async function diagnose(markdown, options = {}) {
|
|
|
136
138
|
else if (node.type === 'footnoteDefinition') {
|
|
137
139
|
definitions.push(node);
|
|
138
140
|
}
|
|
141
|
+
else if (node.type === 'image') {
|
|
142
|
+
const name = keptImageName(node.url ?? '');
|
|
143
|
+
if (name !== null)
|
|
144
|
+
images.push({ name, at });
|
|
145
|
+
}
|
|
139
146
|
});
|
|
140
147
|
for (const definition of definitions) {
|
|
141
148
|
const at = span(definition);
|
|
@@ -164,6 +171,13 @@ export async function diagnose(markdown, options = {}) {
|
|
|
164
171
|
}
|
|
165
172
|
});
|
|
166
173
|
pending(hidden, split, options, report);
|
|
174
|
+
// An image whose destination names no host names a kept file, and only a kept file (§5.8, #104).
|
|
175
|
+
if (options.resolve || options.reaches) {
|
|
176
|
+
const keeps = options.keeps ?? (() => false);
|
|
177
|
+
for (const image of images)
|
|
178
|
+
if (!keeps(image.name))
|
|
179
|
+
report('embed-pending', image.at[0], image.at[1], image.name);
|
|
180
|
+
}
|
|
167
181
|
if (diagrams.length > 0 && options.diagrams !== false) {
|
|
168
182
|
const mermaid = (await import('mermaid')).default;
|
|
169
183
|
for (const diagram of diagrams) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The editor's own menu on a right-click in edit mode (#81): what the toolbar formats and inserts,
|
|
3
|
-
* where the author is,
|
|
3
|
+
* where the author is, after the clipboard's three verbs. It is a `role="menu"` reached by keyboard,
|
|
4
4
|
* kept inside the window, and closed by Escape, a click outside it, a scroll, or an entry run.
|
|
5
5
|
*/
|
|
6
6
|
import { type ReactNode } from 'react';
|
package/dist/edit/ContextMenu.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* The editor's own menu on a right-click in edit mode (#81): what the toolbar formats and inserts,
|
|
4
|
-
* where the author is,
|
|
4
|
+
* where the author is, after the clipboard's three verbs. It is a `role="menu"` reached by keyboard,
|
|
5
5
|
* kept inside the window, and closed by Escape, a click outside it, a scroll, or an entry run.
|
|
6
6
|
*/
|
|
7
7
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
package/dist/edit/NoteEditor.js
CHANGED
|
@@ -261,7 +261,7 @@ export function NoteEditor({ markdown: given, onChange, editable = true, corner,
|
|
|
261
261
|
/** Where the editing menu is open, in the window's coordinates (#81). */
|
|
262
262
|
const [menu, setMenu] = useState(null);
|
|
263
263
|
const openMenu = useRef((x, y) => setMenu({ x, y }));
|
|
264
|
-
/** The menu's groups, from the same commands as the toolbar: Format with a selection, Insert
|
|
264
|
+
/** The menu's groups, from the same commands as the toolbar: Edit first (#110), Format with a selection, Insert. */
|
|
265
265
|
const menuGroups = (target) => {
|
|
266
266
|
const words = resolved.strings.editor;
|
|
267
267
|
const item = (name, action, shortcut) => ({
|
|
@@ -308,7 +308,8 @@ export function NoteEditor({ markdown: given, onChange, editable = true, corner,
|
|
|
308
308
|
{ name: 'attach', label: words.attach, icon: ICONS.attach, run: () => setAttachOpen(true) },
|
|
309
309
|
],
|
|
310
310
|
});
|
|
311
|
-
|
|
311
|
+
// The clipboard's verbs open the menu, as they open every other one a person knows (#110).
|
|
312
|
+
groups.unshift({
|
|
312
313
|
label: words.menuEdit,
|
|
313
314
|
items: [
|
|
314
315
|
{
|
package/dist/edit/blocks.d.ts
CHANGED
|
@@ -29,12 +29,28 @@ export declare function CalloutBlock({ view, text, marker }: {
|
|
|
29
29
|
fold: '+' | '-' | null;
|
|
30
30
|
};
|
|
31
31
|
}): import("react").JSX.Element;
|
|
32
|
+
/** The narrowest an image is sized to, by a field or the handle: something a reader still sees (#105). */
|
|
33
|
+
export declare const MIN_IMAGE_SIZE = 16;
|
|
32
34
|
/**
|
|
33
|
-
* An image drawn in edit mode with
|
|
34
|
-
* its height
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* that
|
|
35
|
+
* An image drawn in edit mode with what its notation writes set in place (#98, #106): its width and
|
|
36
|
+
* its height with a lock between them — locked, the height follows and only the width is written,
|
|
37
|
+
* `|300`; open, both are, `|300x180` — the width's unit, pixels or a share of the page (`|50%`,
|
|
38
|
+
* #108), its description for a Markdown image, a button back to its own size, and a handle at its
|
|
39
|
+
* corner that drags it, keeping the proportion or, with the lock open or Shift held, not. Each
|
|
40
|
+
* writes the dimensions or the description of its notation, and no other byte.
|
|
41
|
+
*
|
|
42
|
+
* A field selects what it holds when it takes the focus, so typing replaces it, and writes on Enter
|
|
43
|
+
* or when it is left; Escape leaves the note as it is. A value outside the range a reader can see
|
|
44
|
+
* — from 16 pixels to the column or the image's own width, the wider — is refused where it was
|
|
45
|
+
* typed, never brought into the range and written (#105).
|
|
46
|
+
*
|
|
47
|
+
* The controls stand under the picture, never over it, and nothing that starts on them or in the
|
|
48
|
+
* moment after reaches the click that shows the image's source (#107): they are all marked as
|
|
49
|
+
* controls, gaps included, and the frame is marked busy while one of them has the focus, while the
|
|
50
|
+
* handle is dragged and for a moment after a pointer or a touch on them.
|
|
51
|
+
*
|
|
52
|
+
* The controls appear once an image is drawn inside it, so an embed of a file that is not an image,
|
|
53
|
+
* or a host's own drawing without one, offers none.
|
|
38
54
|
*/
|
|
39
55
|
export declare function ImageBlock({ view, source, children }: {
|
|
40
56
|
view: EditorView;
|