@vectojs/markdown 0.13.0 → 0.14.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/dist/Markdown.d.ts +62 -0
- package/dist/blockAffordances.d.ts +179 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +467 -54
- package/dist/index.mjs +437 -25
- package/package.json +2 -2
package/dist/Markdown.d.ts
CHANGED
|
@@ -162,6 +162,34 @@ export interface MarkdownOptions {
|
|
|
162
162
|
selectable?: boolean;
|
|
163
163
|
/** Emit a `vecto:markdown:parse` User Timing measure. Default `false`. */
|
|
164
164
|
userTiming?: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* Draw copy / download controls in the top-right corner of code blocks and
|
|
167
|
+
* tables. Default `false`.
|
|
168
|
+
*
|
|
169
|
+
* Opt-in rather than on by default because it adds two focusable stops per such
|
|
170
|
+
* block to the tab order, which a document with many code fences would make
|
|
171
|
+
* tedious to navigate past, and because a reader who cannot act on a control
|
|
172
|
+
* (no clipboard permission, no filesystem) is better served by not being
|
|
173
|
+
* offered one.
|
|
174
|
+
*/
|
|
175
|
+
blockAffordances?: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Writes text to the clipboard for the copy controls.
|
|
178
|
+
*
|
|
179
|
+
* Injectable because the real path (`navigator.clipboard.writeText`) is absent
|
|
180
|
+
* in jsdom and, in a browser, rejects a write that did not originate in a user
|
|
181
|
+
* gesture — so a test can only assert the payload, never the platform call.
|
|
182
|
+
* Defaults to `navigator.clipboard.writeText` when available.
|
|
183
|
+
*/
|
|
184
|
+
writeClipboard?: (text: string) => void;
|
|
185
|
+
/**
|
|
186
|
+
* Saves a generated file for the download controls.
|
|
187
|
+
*
|
|
188
|
+
* Defaults to an anchor-click download that revokes its object URL. Injectable
|
|
189
|
+
* for the same reason as {@link MarkdownOptions.writeClipboard}: jsdom has no
|
|
190
|
+
* download behaviour to observe.
|
|
191
|
+
*/
|
|
192
|
+
saveFile?: (filename: string, content: string, mimeType: string) => void;
|
|
165
193
|
}
|
|
166
194
|
/**
|
|
167
195
|
* Renders Markdown content into a VectoJS entity tree using {@link marked}.
|
|
@@ -185,6 +213,18 @@ export declare class Markdown extends UIComponent {
|
|
|
185
213
|
theme: Required<MarkdownTheme>;
|
|
186
214
|
onLinkClick?: (url: string) => void;
|
|
187
215
|
selectable: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Whether code blocks and tables carry copy / download controls.
|
|
218
|
+
*
|
|
219
|
+
* Read when a block entity is built, so it affects blocks rendered from here on
|
|
220
|
+
* rather than retroactively; a document does not rebuild to gain or lose an
|
|
221
|
+
* affordance.
|
|
222
|
+
*/
|
|
223
|
+
blockAffordances: boolean;
|
|
224
|
+
/** Clipboard writer used by the copy controls. */
|
|
225
|
+
writeClipboard: (text: string) => void;
|
|
226
|
+
/** File saver used by the download controls. */
|
|
227
|
+
saveFile: (filename: string, content: string, mimeType: string) => void;
|
|
188
228
|
private activeBlockMetrics;
|
|
189
229
|
/**
|
|
190
230
|
* Called after a streamed append has re-laid-out the document.
|
|
@@ -578,6 +618,28 @@ export declare class Markdown extends UIComponent {
|
|
|
578
618
|
* policy for a zero-dimension source is a separate decision from notifying
|
|
579
619
|
* the scene, which is the actual defect here.
|
|
580
620
|
*/
|
|
621
|
+
/**
|
|
622
|
+
* Wraps a block in its copy / download controls, or returns it untouched.
|
|
623
|
+
*
|
|
624
|
+
* The controls are built lazily through `make` so a document with
|
|
625
|
+
* `blockAffordances` off pays nothing — not the closures, not the measurement
|
|
626
|
+
* `BlockAffordanceButton` does in its constructor.
|
|
627
|
+
*/
|
|
628
|
+
private withBlockAffordances;
|
|
629
|
+
/** Copy and download controls for one fenced code block. */
|
|
630
|
+
private codeBlockAffordances;
|
|
631
|
+
/** Copy (as Markdown) and download (as CSV) controls for one table. */
|
|
632
|
+
private tableAffordances;
|
|
633
|
+
/**
|
|
634
|
+
* Button styling for the affordances, derived from the document theme.
|
|
635
|
+
*
|
|
636
|
+
* Themed rather than hardcoded so a light-theme document does not get the dark
|
|
637
|
+
* default palette. `focusColor` is set explicitly from the theme's accent
|
|
638
|
+
* because `Button`'s default cyan is tuned for the dark palette and reads as
|
|
639
|
+
* off-brand elsewhere — while a focus ring is the one affordance a keyboard
|
|
640
|
+
* user cannot do without.
|
|
641
|
+
*/
|
|
642
|
+
private affordanceButtonOptions;
|
|
581
643
|
private paragraphImage;
|
|
582
644
|
/** One table cell entity, shared by the render arm and the streamed-table path. */
|
|
583
645
|
private tableCellRichText;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Take-away affordances for block content: copy to the clipboard, download as a
|
|
3
|
+
* file.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `Markdown.ts` rather than added to it because none of this is
|
|
6
|
+
* parsing or rendering — it is serialization plus two platform primitives — and
|
|
7
|
+
* that file is already 4.8k lines.
|
|
8
|
+
*
|
|
9
|
+
* The reference implementation is `streamdown` (clone `e5deed3`,
|
|
10
|
+
* `packages/streamdown/lib/`), read and recorded in
|
|
11
|
+
* `vectojs-docs/forge/findings/upstream/README.md`. Two details of its `save()`
|
|
12
|
+
* (`lib/utils.ts:35`) are borrowed deliberately: revoking the object URL, and the
|
|
13
|
+
* UTF-8 BOM for CSV. Both are commonly omitted and each is a real defect when
|
|
14
|
+
* missing — the first leaks a blob for the document's lifetime, the second makes
|
|
15
|
+
* Excel on Windows read the file in the system ANSI codepage and corrupt every
|
|
16
|
+
* non-ASCII cell.
|
|
17
|
+
*/
|
|
18
|
+
import type { A11yAttributes, Entity, LayoutControlledProperty } from '@vectojs/core';
|
|
19
|
+
import { Button, type ButtonOptions, UIComponent } from '@vectojs/ui';
|
|
20
|
+
/** Extension for a fence info string, without the dot. `txt` when unrecognised. */
|
|
21
|
+
export declare function extensionForLanguage(lang: string): string;
|
|
22
|
+
/** MIME type for a downloaded code block. */
|
|
23
|
+
export declare function mimeForLanguage(lang: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Escapes one CSV field per RFC 4180.
|
|
26
|
+
*
|
|
27
|
+
* A field containing a comma, a quote or a newline is wrapped in quotes, and an
|
|
28
|
+
* internal quote is doubled. Anything else is returned unchanged, so the common
|
|
29
|
+
* case allocates nothing.
|
|
30
|
+
*/
|
|
31
|
+
export declare function escapeCsvField(value: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Escapes one Markdown table cell.
|
|
34
|
+
*
|
|
35
|
+
* Backslash first, then pipe: doing it the other way escapes the backslash that
|
|
36
|
+
* the pipe escape just introduced, which is why `streamdown` documents the order
|
|
37
|
+
* at `lib/table/utils.ts:137`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function escapeMarkdownTableCell(cell: string): string;
|
|
40
|
+
/** Column alignment as `marked` reports it on a table token. */
|
|
41
|
+
export type TableAlign = 'left' | 'center' | 'right' | null;
|
|
42
|
+
/** Plain-text table content, extracted from the token before entities are built. */
|
|
43
|
+
export interface TableContent {
|
|
44
|
+
headers: string[];
|
|
45
|
+
rows: string[][];
|
|
46
|
+
align: readonly TableAlign[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Serializes a table as CSV, prefixed with a UTF-8 BOM.
|
|
50
|
+
*
|
|
51
|
+
* Rows are `\r\n`-separated per RFC 4180.
|
|
52
|
+
*
|
|
53
|
+
* The BOM lives here rather than in {@link defaultSaveFile} — where `streamdown`
|
|
54
|
+
* puts it — so that the guarantee survives a caller supplying its own
|
|
55
|
+
* `saveFile`. Excel on Windows reads a BOM-less file in the system ANSI codepage
|
|
56
|
+
* and corrupts every non-ASCII cell, and that would be a silent,
|
|
57
|
+
* locale-dependent defect for anyone who replaced only the platform primitive.
|
|
58
|
+
* A property of the CSV belongs to the CSV.
|
|
59
|
+
*/
|
|
60
|
+
export declare function tableToCsv(table: TableContent): string;
|
|
61
|
+
/**
|
|
62
|
+
* Serializes a table back to GitHub-flavoured Markdown.
|
|
63
|
+
*
|
|
64
|
+
* The alignment row is reproduced from the token's own `align`, so a copied table
|
|
65
|
+
* re-lexes to the same alignment rather than silently becoming left-aligned.
|
|
66
|
+
*/
|
|
67
|
+
export declare function tableToMarkdown(table: TableContent): string;
|
|
68
|
+
/** Writes text to the clipboard, where the platform offers one. */
|
|
69
|
+
export declare function defaultWriteClipboard(text: string): void;
|
|
70
|
+
/**
|
|
71
|
+
* Downloads generated content as a file.
|
|
72
|
+
*
|
|
73
|
+
* Mirrors `streamdown`'s `save()` in revoking the object URL once the click has
|
|
74
|
+
* been dispatched. The other borrowed detail, the UTF-8 BOM for CSV, lives in
|
|
75
|
+
* {@link tableToCsv} instead — see there for why.
|
|
76
|
+
*/
|
|
77
|
+
export declare function defaultSaveFile(filename: string, content: string, mimeType: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* A copy or download control drawn in a block's top-right corner.
|
|
80
|
+
*
|
|
81
|
+
* Extends `@vectojs/ui`'s `Button` rather than hand-rolling an a11y hotspot: that
|
|
82
|
+
* class already projects `tag: 'button'` with a label, drives its focus ring from
|
|
83
|
+
* real DOM focus/blur, and handles hover and the disabled state. The repo rule
|
|
84
|
+
* against reimplementing what a `@vectojs/*` package provides applies to
|
|
85
|
+
* affordances too, and a bespoke hotspot would have to re-earn focus-ring
|
|
86
|
+
* behaviour `Button` already has.
|
|
87
|
+
*
|
|
88
|
+
* What this adds is transient success feedback. The label changes to a
|
|
89
|
+
* confirmation for `FEEDBACK_MS` and then reverts, which is the only signal a copy
|
|
90
|
+
* gives — nothing else about the document changes, so without it a reader cannot
|
|
91
|
+
* tell a working control from a broken one.
|
|
92
|
+
*/
|
|
93
|
+
export declare class BlockAffordanceButton extends Button {
|
|
94
|
+
private readonly act;
|
|
95
|
+
/** How long the confirmation label stays up, in ms. */
|
|
96
|
+
static readonly FEEDBACK_MS = 1600;
|
|
97
|
+
private readonly restingLabel;
|
|
98
|
+
private readonly successLabel;
|
|
99
|
+
private feedbackTimer;
|
|
100
|
+
constructor(label: string, successLabel: string, act: () => void, opts?: ButtonOptions);
|
|
101
|
+
/**
|
|
102
|
+
* Runs the action, then shows the confirmation.
|
|
103
|
+
*
|
|
104
|
+
* The action runs first and a throw propagates: a clipboard write the browser
|
|
105
|
+
* rejected must not be reported as a success.
|
|
106
|
+
*/
|
|
107
|
+
private run;
|
|
108
|
+
private setTransientLabel;
|
|
109
|
+
/**
|
|
110
|
+
* The label a reader hears is the one they see, transient confirmation
|
|
111
|
+
* included, so an AT user gets the same feedback a sighted user does.
|
|
112
|
+
*/
|
|
113
|
+
getA11yAttributes(): A11yAttributes;
|
|
114
|
+
/** Clears the pending revert so a destroyed block leaves no timer behind. */
|
|
115
|
+
destroy(): void;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Wraps one block and positions its affordances in the top-right corner.
|
|
119
|
+
*
|
|
120
|
+
* A wrapper is necessary rather than adding the buttons to the block directly,
|
|
121
|
+
* because both candidate parents already own their children's geometry: `Stack`
|
|
122
|
+
* positions each child in flow, and `Table` recomputes `x`/`y`/`width`/`height`
|
|
123
|
+
* for every child from its column widths (`Table.ts:66`). A button added to
|
|
124
|
+
* either would be moved on the next layout. This owns only its own children's
|
|
125
|
+
* placement and delegates its size to the block, so the surrounding document
|
|
126
|
+
* lays out exactly as it did before.
|
|
127
|
+
*/
|
|
128
|
+
export declare class BlockWithAffordances extends UIComponent {
|
|
129
|
+
readonly block: Entity;
|
|
130
|
+
private readonly controls;
|
|
131
|
+
/** Gap between the block's edges and the controls, in px. */
|
|
132
|
+
private static readonly INSET;
|
|
133
|
+
/** Gap between adjacent controls, in px. */
|
|
134
|
+
private static readonly GAP;
|
|
135
|
+
constructor(block: Entity, controls: readonly BlockAffordanceButton[]);
|
|
136
|
+
/**
|
|
137
|
+
* Places the controls right-aligned along the block's top edge.
|
|
138
|
+
*
|
|
139
|
+
* Laid out right-to-left from the block's right edge so the first control in
|
|
140
|
+
* the list ends up leftmost, which keeps DOM order (and therefore tab order and
|
|
141
|
+
* the a11y reading order) matching the visual order.
|
|
142
|
+
*/
|
|
143
|
+
private layoutAffordances;
|
|
144
|
+
/**
|
|
145
|
+
* Re-places the controls after the block's own box changed.
|
|
146
|
+
*
|
|
147
|
+
* Called by the owner when a block is resized or its content grew; the controls
|
|
148
|
+
* are anchored to the right edge, so a width change moves them.
|
|
149
|
+
*/
|
|
150
|
+
refreshAffordances(): void;
|
|
151
|
+
/** The wrapper is a pass-through: its size is the block's size. */
|
|
152
|
+
getLayoutControlledProperties(): ReadonlyArray<LayoutControlledProperty>;
|
|
153
|
+
/**
|
|
154
|
+
* Projected as a group so assistive technology reports one labelled region
|
|
155
|
+
* containing the block and its controls, rather than two unrelated siblings.
|
|
156
|
+
*/
|
|
157
|
+
getA11yAttributes(): A11yAttributes;
|
|
158
|
+
render(): void;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Extracts plain-text table content from a `marked` table token.
|
|
162
|
+
*
|
|
163
|
+
* Reads the token rather than the built `Table` entity because the entity holds
|
|
164
|
+
* `RichText` children, not strings — reconstructing cell text from spans would
|
|
165
|
+
* have to reverse the inline formatting, and the token still has the source.
|
|
166
|
+
*
|
|
167
|
+
* `cell.text` is the cell's raw inline Markdown, which is what a copy should
|
|
168
|
+
* preserve: `**bold**` copied out of a table and pasted into another Markdown
|
|
169
|
+
* document should still be bold.
|
|
170
|
+
*/
|
|
171
|
+
export declare function tableContentOf(token: {
|
|
172
|
+
header: ReadonlyArray<{
|
|
173
|
+
text: string;
|
|
174
|
+
}>;
|
|
175
|
+
rows: ReadonlyArray<ReadonlyArray<{
|
|
176
|
+
text: string;
|
|
177
|
+
}>>;
|
|
178
|
+
align: readonly TableAlign[];
|
|
179
|
+
}): TableContent;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from './Markdown';
|
|
2
|
+
export { BlockAffordanceButton, BlockWithAffordances, escapeCsvField, escapeMarkdownTableCell, extensionForLanguage, mimeForLanguage, tableContentOf, tableToCsv, tableToMarkdown, } from './blockAffordances';
|
|
3
|
+
export type { TableAlign, TableContent } from './blockAffordances';
|
|
2
4
|
export { parseFrontMatterFields, scanFrontMatter } from './frontMatter';
|
|
3
5
|
export type { FrontMatterScan } from './frontMatter';
|
|
4
6
|
export type { IncompleteMarkdownMode, StreamController, StreamControllerOptions, StreamControllerState, StreamPacingOptions, } from './StreamController';
|