@foldkit/markdown 0.0.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 +127 -0
- package/content.d.ts +4 -0
- package/dist/ast/ast.d.ts +322 -0
- package/dist/ast/ast.d.ts.map +1 -0
- package/dist/ast/ast.js +116 -0
- package/dist/ast/index.d.ts +2 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/island/index.d.ts +2 -0
- package/dist/island/index.d.ts.map +1 -0
- package/dist/island/index.js +1 -0
- package/dist/island/island.d.ts +20 -0
- package/dist/island/island.d.ts.map +1 -0
- package/dist/island/island.js +1 -0
- package/dist/view/index.d.ts +2 -0
- package/dist/view/index.d.ts.map +1 -0
- package/dist/view/index.js +1 -0
- package/dist/view/view.d.ts +87 -0
- package/dist/view/view.d.ts.map +1 -0
- package/dist/view/view.js +164 -0
- package/dist/vite/normalize.d.ts +18 -0
- package/dist/vite/normalize.d.ts.map +1 -0
- package/dist/vite/normalize.js +157 -0
- package/dist/vite/public.d.ts +3 -0
- package/dist/vite/public.d.ts.map +1 -0
- package/dist/vite/public.js +1 -0
- package/dist/vite/vite.d.ts +19 -0
- package/dist/vite/vite.d.ts.map +1 -0
- package/dist/vite/vite.js +58 -0
- package/package.json +79 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/view/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './view.js';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Html } from 'foldkit/html';
|
|
2
|
+
import { Alignment, Blockquote, CodeBlock, Emphasis, HardBreak, Heading, Image, InlineCode, Link, List, ListItem, MarkdownDocument, Paragraph, Strikethrough, Strong, Table, TableCell, TableRow, Text, ThematicBreak } from '../ast/index.js';
|
|
3
|
+
import type { IslandDefinitions } from '../island/index.js';
|
|
4
|
+
/** Rendered inline content, ready to pass as element children. */
|
|
5
|
+
export type InlineContent = ReadonlyArray<Html | string>;
|
|
6
|
+
/**
|
|
7
|
+
* Renders one island directive. Receives the directive's attributes, the
|
|
8
|
+
* rendered nested blocks (empty for leaf directives), and the zero-based
|
|
9
|
+
* occurrence of this island name in the document, in document order. Use the
|
|
10
|
+
* occurrence index to derive identifiers that must be unique per instance,
|
|
11
|
+
* such as an `h.submodel` slotId.
|
|
12
|
+
*/
|
|
13
|
+
export type IslandView = (attributes: Readonly<Record<string, string>>, content: ReadonlyArray<Html>, occurrenceIndex: number) => Html;
|
|
14
|
+
/** Island views by directive name. */
|
|
15
|
+
export type Islands = Readonly<Record<string, IslandView>>;
|
|
16
|
+
/**
|
|
17
|
+
* One typed island view per definition. Each view receives its attributes
|
|
18
|
+
* already decoded through the island's schema, so the record is exhaustive
|
|
19
|
+
* over the declared island names by construction.
|
|
20
|
+
*/
|
|
21
|
+
export type IslandViewsFor<Definitions extends IslandDefinitions> = Readonly<{
|
|
22
|
+
[Name in keyof Definitions]: (attributes: Definitions[Name]['Type'], content: ReadonlyArray<Html>, occurrenceIndex: number) => Html;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* One view function per markdown node. Container nodes receive their already
|
|
26
|
+
* rendered content alongside the node itself.
|
|
27
|
+
*/
|
|
28
|
+
export type Views = Readonly<{
|
|
29
|
+
Text: (text: Text) => Html | string;
|
|
30
|
+
InlineCode: (inlineCode: InlineCode) => Html;
|
|
31
|
+
HardBreak: (hardBreak: HardBreak) => Html;
|
|
32
|
+
Emphasis: (emphasis: Emphasis, content: InlineContent) => Html;
|
|
33
|
+
Strong: (strong: Strong, content: InlineContent) => Html;
|
|
34
|
+
Strikethrough: (strikethrough: Strikethrough, content: InlineContent) => Html;
|
|
35
|
+
Link: (link: Link, content: InlineContent) => Html;
|
|
36
|
+
Image: (image: Image) => Html;
|
|
37
|
+
Heading: (heading: Heading, content: InlineContent) => Html;
|
|
38
|
+
Paragraph: (paragraph: Paragraph, content: InlineContent) => Html;
|
|
39
|
+
CodeBlock: (codeBlock: CodeBlock) => Html;
|
|
40
|
+
List: (list: List, items: ReadonlyArray<Html>) => Html;
|
|
41
|
+
ListItem: (listItem: ListItem, blocks: ReadonlyArray<Html>) => Html;
|
|
42
|
+
Blockquote: (blockquote: Blockquote, blocks: ReadonlyArray<Html>) => Html;
|
|
43
|
+
ThematicBreak: (thematicBreak: ThematicBreak) => Html;
|
|
44
|
+
Table: (table: Table, headerRow: Html, bodyRows: ReadonlyArray<Html>) => Html;
|
|
45
|
+
TableRow: (tableRow: TableRow, cells: ReadonlyArray<Html>) => Html;
|
|
46
|
+
TableCell: (tableCell: TableCell, content: InlineContent, alignment: Alignment, isHeader: boolean) => Html;
|
|
47
|
+
}>;
|
|
48
|
+
/** Configuration for {@link view} and {@link viewBlocks}. */
|
|
49
|
+
export type ViewConfig = Readonly<{
|
|
50
|
+
islands?: Islands | undefined;
|
|
51
|
+
views?: Partial<Views> | undefined;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Unstyled semantic defaults for every markdown node. Spread these into your
|
|
55
|
+
* own record and replace the nodes you want to restyle:
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const blogViews: Markdown.Views = {
|
|
60
|
+
* ...Markdown.defaultViews,
|
|
61
|
+
* Paragraph: (paragraph, content) =>
|
|
62
|
+
* h.p([h.Class('leading-relaxed text-stone-700')], content),
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare const defaultViews: Views;
|
|
67
|
+
/**
|
|
68
|
+
* Pairs island attribute schemas with typed views, producing the plain
|
|
69
|
+
* {@link Islands} record the fold consumes. Attributes decode through each
|
|
70
|
+
* island's schema before dispatch, and the views record must cover every
|
|
71
|
+
* declared island name. Pass the same definitions to the markdown Vite
|
|
72
|
+
* plugin's `islands` option so invalid directives fail the build instead of
|
|
73
|
+
* reaching this decode.
|
|
74
|
+
*/
|
|
75
|
+
export declare const islandsFor: <const Definitions extends IslandDefinitions>(definitions: Definitions, islandViews: IslandViewsFor<Definitions>) => Islands;
|
|
76
|
+
/**
|
|
77
|
+
* Folds a document into one Html node per top-level block. Use this when the
|
|
78
|
+
* blocks should land directly inside your own container element.
|
|
79
|
+
*/
|
|
80
|
+
export declare const viewBlocks: (document: MarkdownDocument, config?: ViewConfig) => ReadonlyArray<Html>;
|
|
81
|
+
/**
|
|
82
|
+
* Folds a document into a single Html tree. Every node renders through
|
|
83
|
+
* {@link defaultViews} unless overridden in `config.views`, and every Island
|
|
84
|
+
* directive renders through the matching entry in `config.islands`.
|
|
85
|
+
*/
|
|
86
|
+
export declare const view: (document: MarkdownDocument, config?: ViewConfig) => Html;
|
|
87
|
+
//# sourceMappingURL=view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../../src/view/view.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Array, Match as M, Option, Record as Record_, Schema as S, } from 'effect';
|
|
2
|
+
import { html } from 'foldkit/html';
|
|
3
|
+
const h = html();
|
|
4
|
+
const titleAttribute = (maybeTitle) => Option.match(maybeTitle, {
|
|
5
|
+
onNone: () => [],
|
|
6
|
+
onSome: title => [h.Title(title)],
|
|
7
|
+
});
|
|
8
|
+
const startAttribute = (maybeStartNumber) => Option.match(maybeStartNumber, {
|
|
9
|
+
onNone: () => [],
|
|
10
|
+
onSome: startNumber => [h.Start(startNumber)],
|
|
11
|
+
});
|
|
12
|
+
const alignmentAttribute = (alignment) => M.value(alignment).pipe(M.when('None', () => []), M.when('Left', () => [h.Style({ 'text-align': 'left' })]), M.when('Center', () => [h.Style({ 'text-align': 'center' })]), M.when('Right', () => [h.Style({ 'text-align': 'right' })]), M.exhaustive);
|
|
13
|
+
/**
|
|
14
|
+
* Unstyled semantic defaults for every markdown node. Spread these into your
|
|
15
|
+
* own record and replace the nodes you want to restyle:
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const blogViews: Markdown.Views = {
|
|
20
|
+
* ...Markdown.defaultViews,
|
|
21
|
+
* Paragraph: (paragraph, content) =>
|
|
22
|
+
* h.p([h.Class('leading-relaxed text-stone-700')], content),
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
// NOTE: These views deliberately render without vdom keys. A markdown document
|
|
27
|
+
// is immutable data, so a given position never switches branches within one
|
|
28
|
+
// document, and per-branch keys like 'Ordered' or 'Header' would repeat across
|
|
29
|
+
// sibling lists and cells, which corrupts snabbdom's keyed diff. Identity for a
|
|
30
|
+
// whole document belongs on the consumer's key around the rendered output.
|
|
31
|
+
export const defaultViews = {
|
|
32
|
+
Text: ({ value }) => value,
|
|
33
|
+
InlineCode: ({ value }) => h.code([], [value]),
|
|
34
|
+
HardBreak: () => h.br([]),
|
|
35
|
+
Emphasis: (_emphasis, content) => h.em([], content),
|
|
36
|
+
Strong: (_strong, content) => h.strong([], content),
|
|
37
|
+
Strikethrough: (_strikethrough, content) => h.del([], content),
|
|
38
|
+
Link: ({ url, maybeTitle }, content) => h.a([h.Href(url), ...titleAttribute(maybeTitle)], content),
|
|
39
|
+
Image: ({ url, alt, maybeTitle }) => h.img([h.Src(url), h.Alt(alt), ...titleAttribute(maybeTitle)]),
|
|
40
|
+
Heading: ({ level }, content) => M.value(level).pipe(M.withReturnType(), M.when(1, () => h.h1([], content)), M.when(2, () => h.h2([], content)), M.when(3, () => h.h3([], content)), M.when(4, () => h.h4([], content)), M.when(5, () => h.h5([], content)), M.when(6, () => h.h6([], content)), M.exhaustive),
|
|
41
|
+
Paragraph: (_paragraph, content) => h.p([], content),
|
|
42
|
+
CodeBlock: ({ value }) => h.pre([], [h.code([], [value])]),
|
|
43
|
+
List: ({ isOrdered, maybeStartNumber }, items) => {
|
|
44
|
+
if (isOrdered) {
|
|
45
|
+
return h.ol(startAttribute(maybeStartNumber), items);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
return h.ul([], items);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
ListItem: (_listItem, blocks) => h.li([], blocks),
|
|
52
|
+
Blockquote: (_blockquote, blocks) => h.blockquote([], blocks),
|
|
53
|
+
ThematicBreak: () => h.hr([]),
|
|
54
|
+
Table: (_table, headerRow, bodyRows) => h.table([], [h.thead([], [headerRow]), h.tbody([], bodyRows)]),
|
|
55
|
+
TableRow: (_tableRow, cells) => h.tr([], cells),
|
|
56
|
+
TableCell: (_tableCell, content, alignment, isHeader) => {
|
|
57
|
+
if (isHeader) {
|
|
58
|
+
return h.th(alignmentAttribute(alignment), content);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return h.td(alignmentAttribute(alignment), content);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
const inlineView = (views, inline) => M.value(inline).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
66
|
+
Text: views.Text,
|
|
67
|
+
InlineCode: views.InlineCode,
|
|
68
|
+
HardBreak: views.HardBreak,
|
|
69
|
+
Emphasis: emphasis => views.Emphasis(emphasis, inlineContent(views, emphasis.content)),
|
|
70
|
+
Strong: strong => views.Strong(strong, inlineContent(views, strong.content)),
|
|
71
|
+
Strikethrough: strikethrough => views.Strikethrough(strikethrough, inlineContent(views, strikethrough.content)),
|
|
72
|
+
Link: link => views.Link(link, inlineContent(views, link.content)),
|
|
73
|
+
Image: views.Image,
|
|
74
|
+
}));
|
|
75
|
+
const inlineContent = (views, content) => Array.map(content, inline => inlineView(views, inline));
|
|
76
|
+
const alignmentAt = (alignments, columnIndex) => Option.getOrElse(Array.get(alignments, columnIndex), () => 'None');
|
|
77
|
+
const tableRowView = (views, table, row, isHeader) => views.TableRow(row, Array.map(row.cells, (cell, columnIndex) => views.TableCell(cell, inlineContent(views, cell.content), alignmentAt(table.alignments, columnIndex), isHeader)));
|
|
78
|
+
// NOTE: Warns once per island name for the lifetime of the process, not per
|
|
79
|
+
// document, so a missing island in one document suppresses the warning for the
|
|
80
|
+
// same name in every later document. Per-render warnings would flood the
|
|
81
|
+
// console, since the fold runs on every Message dispatch.
|
|
82
|
+
const warnedInvalidAttributeIslandNames = new Set();
|
|
83
|
+
const warnInvalidAttributesOnce = (islandName, detail) => {
|
|
84
|
+
if (!warnedInvalidAttributeIslandNames.has(islandName)) {
|
|
85
|
+
warnedInvalidAttributeIslandNames.add(islandName);
|
|
86
|
+
console.warn(`[@foldkit/markdown] Invalid attributes for island "${islandName}", so it renders nothing. ` +
|
|
87
|
+
`Compile with the markdown plugin's islands option to catch this at build time. ${detail}`);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Pairs island attribute schemas with typed views, producing the plain
|
|
92
|
+
* {@link Islands} record the fold consumes. Attributes decode through each
|
|
93
|
+
* island's schema before dispatch, and the views record must cover every
|
|
94
|
+
* declared island name. Pass the same definitions to the markdown Vite
|
|
95
|
+
* plugin's `islands` option so invalid directives fail the build instead of
|
|
96
|
+
* reaching this decode.
|
|
97
|
+
*/
|
|
98
|
+
export const islandsFor = (definitions, islandViews) => {
|
|
99
|
+
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions */
|
|
100
|
+
const islandViewsByName = islandViews;
|
|
101
|
+
return Record_.map(definitions, (attributesSchema, islandName) => {
|
|
102
|
+
const decodeAttributes = S.decodeUnknownSync(attributesSchema);
|
|
103
|
+
return (attributes, content, occurrenceIndex) => Option.match(Record_.get(islandViewsByName, islandName), {
|
|
104
|
+
onNone: () => {
|
|
105
|
+
warnMissingIslandOnce(islandName);
|
|
106
|
+
return null;
|
|
107
|
+
},
|
|
108
|
+
onSome: renderIsland => {
|
|
109
|
+
try {
|
|
110
|
+
return renderIsland(decodeAttributes(attributes), content, occurrenceIndex);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
warnInvalidAttributesOnce(islandName, error instanceof Error ? error.message : String(error));
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
const warnedIslandNames = new Set();
|
|
121
|
+
const warnMissingIslandOnce = (islandName) => {
|
|
122
|
+
if (!warnedIslandNames.has(islandName)) {
|
|
123
|
+
warnedIslandNames.add(islandName);
|
|
124
|
+
console.warn(`[@foldkit/markdown] No island view registered for "${islandName}", so it renders nothing. ` +
|
|
125
|
+
'Add it to the islands record passed to Markdown.view.');
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const islandView = (views, islands, islandOccurrenceCounts, island) => {
|
|
129
|
+
const occurrenceIndex = islandOccurrenceCounts.get(island.name) ?? 0;
|
|
130
|
+
islandOccurrenceCounts.set(island.name, occurrenceIndex + 1);
|
|
131
|
+
return Option.match(Record_.get(islands, island.name), {
|
|
132
|
+
onNone: () => {
|
|
133
|
+
warnMissingIslandOnce(island.name);
|
|
134
|
+
return null;
|
|
135
|
+
},
|
|
136
|
+
onSome: renderIsland => renderIsland(island.attributes, Array.map(island.blocks, block => blockView(views, islands, islandOccurrenceCounts, block)), occurrenceIndex),
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
const blockView = (views, islands, islandOccurrenceCounts, block) => M.value(block).pipe(M.withReturnType(), M.tagsExhaustive({
|
|
140
|
+
Heading: heading => views.Heading(heading, inlineContent(views, heading.content)),
|
|
141
|
+
Paragraph: paragraph => views.Paragraph(paragraph, inlineContent(views, paragraph.content)),
|
|
142
|
+
CodeBlock: views.CodeBlock,
|
|
143
|
+
List: list => views.List(list, Array.map(list.items, item => views.ListItem(item, Array.map(item.blocks, child => blockView(views, islands, islandOccurrenceCounts, child))))),
|
|
144
|
+
Blockquote: blockquote => views.Blockquote(blockquote, Array.map(blockquote.blocks, child => blockView(views, islands, islandOccurrenceCounts, child))),
|
|
145
|
+
ThematicBreak: views.ThematicBreak,
|
|
146
|
+
Table: table => views.Table(table, tableRowView(views, table, table.headerRow, true), Array.map(table.bodyRows, row => tableRowView(views, table, row, false))),
|
|
147
|
+
Island: island => islandView(views, islands, islandOccurrenceCounts, island),
|
|
148
|
+
}));
|
|
149
|
+
/**
|
|
150
|
+
* Folds a document into one Html node per top-level block. Use this when the
|
|
151
|
+
* blocks should land directly inside your own container element.
|
|
152
|
+
*/
|
|
153
|
+
export const viewBlocks = (document, config = {}) => {
|
|
154
|
+
const views = { ...defaultViews, ...config.views };
|
|
155
|
+
const islands = config.islands ?? {};
|
|
156
|
+
const islandOccurrenceCounts = new Map();
|
|
157
|
+
return Array.map(document.blocks, block => blockView(views, islands, islandOccurrenceCounts, block));
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Folds a document into a single Html tree. Every node renders through
|
|
161
|
+
* {@link defaultViews} unless overridden in `config.views`, and every Island
|
|
162
|
+
* directive renders through the matching entry in `config.islands`.
|
|
163
|
+
*/
|
|
164
|
+
export const view = (document, config = {}) => h.div([], viewBlocks(document, config));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Root } from 'mdast';
|
|
2
|
+
import { MarkdownDocument } from '../ast/index.js';
|
|
3
|
+
import type { IslandDefinitions } from '../island/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link normalizeRoot}. `islands`, when provided, maps each
|
|
6
|
+
* allowed directive name to the schema for its attributes; unknown names,
|
|
7
|
+
* unknown attributes, and attribute values outside the schema all fail with
|
|
8
|
+
* an error naming the offender.
|
|
9
|
+
*/
|
|
10
|
+
export type NormalizeOptions = Readonly<{
|
|
11
|
+
islands?: IslandDefinitions | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Converts a parsed mdast tree into a typed {@link MarkdownDocument}.
|
|
15
|
+
* Throws on any node outside the markdown vocabulary.
|
|
16
|
+
*/
|
|
17
|
+
export declare const normalizeRoot: (root: Root, options?: NormalizeOptions) => MarkdownDocument;
|
|
18
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/vite/normalize.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAKV,IAAI,EAEL,MAAM,OAAO,CAAA;AAId,OAAO,EAeL,gBAAgB,EASjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE7E;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,OAAO,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAA;CACxC,CAAC,CAAA;AAoIF;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,IAAI,EACV,UAAS,gBAAqB,KAC7B,gBAwHF,CAAA"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Array, Match as M, Option, Record as Record_, Schema as S, } from 'effect';
|
|
2
|
+
import { Blockquote, CodeBlock, Emphasis, HardBreak, Heading, Image, InlineCode, Island, Link, List, ListItem, Paragraph, Strikethrough, Strong, Table, TableCell, TableRow, Text, ThematicBreak, } from '../ast/index.js';
|
|
3
|
+
const UNSUPPORTED_GUIDANCE = {
|
|
4
|
+
html: 'Raw HTML is not part of the markdown vocabulary. Use an island directive to render custom views.',
|
|
5
|
+
textDirective: 'Inline directives are not supported. Use a leaf directive (`::Name`) on its own line, or a container directive (`:::Name`).',
|
|
6
|
+
linkReference: 'Reference-style links are not supported. Use an inline link (`[text](url)`).',
|
|
7
|
+
imageReference: 'Reference-style images are not supported. Use an inline image (``).',
|
|
8
|
+
definition: 'Reference-style link definitions are not supported. Use inline links (`[text](url)`).',
|
|
9
|
+
footnoteReference: 'Footnotes are not supported.',
|
|
10
|
+
footnoteDefinition: 'Footnotes are not supported.',
|
|
11
|
+
yaml: 'Frontmatter is not supported. Keep document metadata in application code, for example a typed post registry.',
|
|
12
|
+
'leaf directive label': 'Leaf directive labels (`::Name[label]`) are not supported. Pass information through attributes (`::Name{label="..."}`) instead.',
|
|
13
|
+
};
|
|
14
|
+
const sourceLocation = (node) => {
|
|
15
|
+
const line = node.position?.start.line;
|
|
16
|
+
if (line === undefined) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
return ` (line ${line})`;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const unsupported = (node) => {
|
|
24
|
+
const guidance = UNSUPPORTED_GUIDANCE[node.type] ??
|
|
25
|
+
'It is outside the @foldkit/markdown vocabulary.';
|
|
26
|
+
throw new Error(`Unsupported markdown node "${node.type}"${sourceLocation(node)}. ${guidance}`);
|
|
27
|
+
};
|
|
28
|
+
const normalizeAttributes = (attributes) => Object.fromEntries(Object.entries(attributes ?? {}).map(([name, value]) => [
|
|
29
|
+
name,
|
|
30
|
+
value ?? '',
|
|
31
|
+
]));
|
|
32
|
+
const URL_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
|
|
33
|
+
const SAFE_URL_SCHEME_PATTERN = /^(?:https?|mailto|tel):/i;
|
|
34
|
+
// NOTE: The scheme check runs against the URL with ASCII control characters
|
|
35
|
+
// and spaces stripped, because browsers strip them too, so `java\tscript:`
|
|
36
|
+
// would otherwise smuggle an executable scheme past a plain prefix test.
|
|
37
|
+
const toSafeUrl = (node, url) => {
|
|
38
|
+
const compactUrl = url.replace(/[\u0000-\u0020]/g, '');
|
|
39
|
+
if (URL_SCHEME_PATTERN.test(compactUrl) &&
|
|
40
|
+
!SAFE_URL_SCHEME_PATTERN.test(compactUrl)) {
|
|
41
|
+
throw new Error(`Unsupported URL scheme in "${url}"${sourceLocation(node)}. ` +
|
|
42
|
+
'Link and image URLs may be relative, or use the http:, https:, mailto:, or tel: schemes.');
|
|
43
|
+
}
|
|
44
|
+
return url;
|
|
45
|
+
};
|
|
46
|
+
const toInline = (node) => M.value(node).pipe(M.withReturnType(), M.discriminators('type')({
|
|
47
|
+
text: ({ value }) => Text({ value }),
|
|
48
|
+
inlineCode: ({ value }) => InlineCode({ value }),
|
|
49
|
+
break: () => HardBreak(),
|
|
50
|
+
emphasis: ({ children }) => Emphasis({ content: children.map(toInline) }),
|
|
51
|
+
strong: ({ children }) => Strong({ content: children.map(toInline) }),
|
|
52
|
+
delete: ({ children }) => Strikethrough({ content: children.map(toInline) }),
|
|
53
|
+
link: linkNode => Link({
|
|
54
|
+
url: toSafeUrl(linkNode, linkNode.url),
|
|
55
|
+
maybeTitle: Option.fromNullishOr(linkNode.title),
|
|
56
|
+
content: linkNode.children.map(toInline),
|
|
57
|
+
}),
|
|
58
|
+
image: imageNode => Image({
|
|
59
|
+
url: toSafeUrl(imageNode, imageNode.url),
|
|
60
|
+
alt: imageNode.alt ?? '',
|
|
61
|
+
maybeTitle: Option.fromNullishOr(imageNode.title),
|
|
62
|
+
}),
|
|
63
|
+
}), M.orElse(unsupported));
|
|
64
|
+
const toAlignment = (align) => M.value(align).pipe(M.withReturnType(), M.when('left', () => 'Left'), M.when('center', () => 'Center'), M.when('right', () => 'Right'), M.orElse(() => 'None'));
|
|
65
|
+
const toTableRow = (row) => TableRow({
|
|
66
|
+
cells: row.children.map(cell => TableCell({ content: cell.children.map(toInline) })),
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* Converts a parsed mdast tree into a typed {@link MarkdownDocument}.
|
|
70
|
+
* Throws on any node outside the markdown vocabulary.
|
|
71
|
+
*/
|
|
72
|
+
export const normalizeRoot = (root, options = {}) => {
|
|
73
|
+
const validateIslandAttributes = (directive, attributesSchema, attributes) => {
|
|
74
|
+
const allowedAttributeNames = Object.keys(attributesSchema.fields);
|
|
75
|
+
const unknownAttributeNames = Object.keys(attributes).filter(attributeName => !allowedAttributeNames.includes(attributeName));
|
|
76
|
+
if (Array.isArrayNonEmpty(unknownAttributeNames)) {
|
|
77
|
+
const allowedDescription = Array.match(allowedAttributeNames, {
|
|
78
|
+
onEmpty: () => 'It takes no attributes.',
|
|
79
|
+
onNonEmpty: names => `Allowed attributes: ${names.join(', ')}.`,
|
|
80
|
+
});
|
|
81
|
+
throw new Error(`Unknown attribute "${Array.headNonEmpty(unknownAttributeNames)}" for island "${directive.name}"${sourceLocation(directive)}. ` +
|
|
82
|
+
allowedDescription);
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
S.decodeUnknownSync(attributesSchema)(attributes);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
throw new Error(`Invalid attributes for island "${directive.name}"${sourceLocation(directive)}. ` +
|
|
89
|
+
`${error instanceof Error ? error.message : String(error)}`);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const toIsland = (directive, blocks) => {
|
|
93
|
+
const attributes = normalizeAttributes(directive.attributes);
|
|
94
|
+
const { islands } = options;
|
|
95
|
+
if (islands !== undefined) {
|
|
96
|
+
Option.match(Record_.get(islands, directive.name), {
|
|
97
|
+
onNone: () => {
|
|
98
|
+
throw new Error(`Unknown island "${directive.name}"${sourceLocation(directive)}. ` +
|
|
99
|
+
`Allowed islands: ${Object.keys(islands).join(', ')}.`);
|
|
100
|
+
},
|
|
101
|
+
onSome: attributesSchema => validateIslandAttributes(directive, attributesSchema, attributes),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return Island({ name: directive.name, attributes, blocks });
|
|
105
|
+
};
|
|
106
|
+
const toListItem = (node) => {
|
|
107
|
+
if (node.checked === null || node.checked === undefined) {
|
|
108
|
+
return ListItem({ blocks: node.children.map(toBlock) });
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
return unsupported({ type: 'task list item', position: node.position });
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const toTable = (node) => Array.matchLeft(node.children.map(toTableRow), {
|
|
115
|
+
onEmpty: () => unsupported(node),
|
|
116
|
+
onNonEmpty: (headerRow, bodyRows) => Table({
|
|
117
|
+
alignments: (node.align ?? []).map(toAlignment),
|
|
118
|
+
headerRow,
|
|
119
|
+
bodyRows,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
const toBlock = (node) => M.value(node).pipe(M.withReturnType(), M.discriminators('type')({
|
|
123
|
+
heading: ({ depth, children }) => Heading({ level: depth, content: children.map(toInline) }),
|
|
124
|
+
paragraph: ({ children }) => Paragraph({ content: children.map(toInline) }),
|
|
125
|
+
code: ({ lang, meta, value }) => CodeBlock({
|
|
126
|
+
maybeLanguage: Option.fromNullishOr(lang),
|
|
127
|
+
maybeMeta: Option.fromNullishOr(meta),
|
|
128
|
+
value,
|
|
129
|
+
}),
|
|
130
|
+
list: listNode => {
|
|
131
|
+
const items = listNode.children.map(toListItem);
|
|
132
|
+
// Array.match?
|
|
133
|
+
if (Array.isArrayNonEmpty(items)) {
|
|
134
|
+
return List({
|
|
135
|
+
isOrdered: listNode.ordered === true,
|
|
136
|
+
maybeStartNumber: Option.fromNullishOr(listNode.start),
|
|
137
|
+
items,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
return unsupported(listNode);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
blockquote: ({ children }) => Blockquote({ blocks: children.map(toBlock) }),
|
|
145
|
+
thematicBreak: () => ThematicBreak(),
|
|
146
|
+
table: toTable,
|
|
147
|
+
leafDirective: directive => Array.match(directive.children, {
|
|
148
|
+
onEmpty: () => toIsland(directive, []),
|
|
149
|
+
onNonEmpty: () => unsupported({
|
|
150
|
+
type: 'leaf directive label',
|
|
151
|
+
position: directive.position,
|
|
152
|
+
}),
|
|
153
|
+
}),
|
|
154
|
+
containerDirective: directive => toIsland(directive, directive.children.map(toBlock)),
|
|
155
|
+
}), M.orElse(unsupported));
|
|
156
|
+
return { blocks: root.children.map(toBlock) };
|
|
157
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public.d.ts","sourceRoot":"","sources":["../../src/vite/public.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACnD,YAAY,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { markdown, parseMarkdown } from './vite.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
import { MarkdownDocument } from '../ast/index.js';
|
|
3
|
+
import type { NormalizeOptions } from './normalize.js';
|
|
4
|
+
/** Options for {@link markdown} and {@link parseMarkdown}. */
|
|
5
|
+
export type MarkdownPluginOptions = NormalizeOptions;
|
|
6
|
+
/**
|
|
7
|
+
* Parses markdown source into a typed {@link MarkdownDocument}. Throws on any
|
|
8
|
+
* construct outside the markdown vocabulary, and on malformed options. The
|
|
9
|
+
* {@link markdown} plugin runs this per `.md` module; call it directly for
|
|
10
|
+
* one-off compilation in scripts.
|
|
11
|
+
*/
|
|
12
|
+
export declare const parseMarkdown: (source: string, options?: MarkdownPluginOptions) => MarkdownDocument;
|
|
13
|
+
/**
|
|
14
|
+
* Vite plugin that compiles imported `.md` files at build time into typed
|
|
15
|
+
* document modules. Decode the default export with `decodeDocument` and
|
|
16
|
+
* render it with `Markdown.view`.
|
|
17
|
+
*/
|
|
18
|
+
export declare const markdown: (options?: MarkdownPluginOptions) => Plugin;
|
|
19
|
+
//# sourceMappingURL=vite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite.d.ts","sourceRoot":"","sources":["../../src/vite/vite.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAElC,OAAO,EAAE,gBAAgB,EAAkB,MAAM,iBAAiB,CAAA;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAYtD,8DAA8D;AAC9D,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,CAAA;AAwBpD;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,UAAS,qBAA0B,KAClC,gBACwE,CAAA;AAE3E;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,UAAS,qBAA0B,KAAG,MAgB9D,CAAA"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Schema as S } from 'effect';
|
|
2
|
+
import remarkDirective from 'remark-directive';
|
|
3
|
+
import remarkFrontmatter from 'remark-frontmatter';
|
|
4
|
+
import remarkGfm from 'remark-gfm';
|
|
5
|
+
import remarkParse from 'remark-parse';
|
|
6
|
+
import { unified } from 'unified';
|
|
7
|
+
import { encodeDocument } from '../ast/index.js';
|
|
8
|
+
import { normalizeRoot } from './normalize.js';
|
|
9
|
+
// NOTE: remark-frontmatter is included so that YAML frontmatter parses as one
|
|
10
|
+
// `yaml` node and fails the build with guidance. Without it, remark reads
|
|
11
|
+
// `---` fences as a thematic break plus a setext heading and renders garbage.
|
|
12
|
+
const processor = unified()
|
|
13
|
+
.use(remarkParse)
|
|
14
|
+
.use(remarkFrontmatter)
|
|
15
|
+
.use(remarkGfm)
|
|
16
|
+
.use(remarkDirective)
|
|
17
|
+
.freeze();
|
|
18
|
+
const validateMarkdownPluginOptions = (options) => {
|
|
19
|
+
const islands = options.islands;
|
|
20
|
+
if (islands === undefined) {
|
|
21
|
+
return options;
|
|
22
|
+
}
|
|
23
|
+
for (const [islandName, attributesSchema] of Object.entries(islands)) {
|
|
24
|
+
if (!S.isSchema(attributesSchema) || !('fields' in attributesSchema)) {
|
|
25
|
+
throw new Error(`Island "${islandName}" in markdown plugin options must map to a Schema struct describing its attributes.`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return options;
|
|
29
|
+
};
|
|
30
|
+
const parseWithValidatedOptions = (source, options) => normalizeRoot(processor.parse(source), options);
|
|
31
|
+
/**
|
|
32
|
+
* Parses markdown source into a typed {@link MarkdownDocument}. Throws on any
|
|
33
|
+
* construct outside the markdown vocabulary, and on malformed options. The
|
|
34
|
+
* {@link markdown} plugin runs this per `.md` module; call it directly for
|
|
35
|
+
* one-off compilation in scripts.
|
|
36
|
+
*/
|
|
37
|
+
export const parseMarkdown = (source, options = {}) => parseWithValidatedOptions(source, validateMarkdownPluginOptions(options));
|
|
38
|
+
/**
|
|
39
|
+
* Vite plugin that compiles imported `.md` files at build time into typed
|
|
40
|
+
* document modules. Decode the default export with `decodeDocument` and
|
|
41
|
+
* render it with `Markdown.view`.
|
|
42
|
+
*/
|
|
43
|
+
export const markdown = (options = {}) => {
|
|
44
|
+
const validatedOptions = validateMarkdownPluginOptions(options);
|
|
45
|
+
return {
|
|
46
|
+
name: 'foldkit-markdown',
|
|
47
|
+
transform(source, id) {
|
|
48
|
+
if (!id.endsWith('.md')) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const document = parseWithValidatedOptions(source, validatedOptions);
|
|
52
|
+
return {
|
|
53
|
+
code: `export default ${JSON.stringify(encodeDocument(document))}`,
|
|
54
|
+
map: null,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@foldkit/markdown",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Markdown compiled at build time into typed Foldkit views, with live component islands",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./vite": {
|
|
15
|
+
"types": "./dist/vite/public.d.ts",
|
|
16
|
+
"import": "./dist/vite/public.js"
|
|
17
|
+
},
|
|
18
|
+
"./content": {
|
|
19
|
+
"types": "./content.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"content.d.ts"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"effect": "4.0.0-beta.97",
|
|
29
|
+
"foldkit": "^0",
|
|
30
|
+
"vite": "^7.0.0 || ^8.0.0"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"remark-directive": "^4.0.0",
|
|
34
|
+
"remark-frontmatter": "^5.0.0",
|
|
35
|
+
"remark-gfm": "^4.0.1",
|
|
36
|
+
"remark-parse": "^11.0.0",
|
|
37
|
+
"unified": "^11.0.5"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/mdast": "^4.0.4",
|
|
41
|
+
"@types/unist": "^3.0.3",
|
|
42
|
+
"effect": "4.0.0-beta.97",
|
|
43
|
+
"happy-dom": "^20.10.4",
|
|
44
|
+
"mdast-util-directive": "^3.1.0",
|
|
45
|
+
"rimraf": "^6.1.3",
|
|
46
|
+
"typescript": "^6.0.3",
|
|
47
|
+
"vite": "^8.0.16",
|
|
48
|
+
"vitest": "^4.1.9",
|
|
49
|
+
"foldkit": "0.128.1"
|
|
50
|
+
},
|
|
51
|
+
"keywords": [
|
|
52
|
+
"foldkit",
|
|
53
|
+
"markdown",
|
|
54
|
+
"vite-plugin",
|
|
55
|
+
"islands",
|
|
56
|
+
"mdast"
|
|
57
|
+
],
|
|
58
|
+
"author": "Devin Jameson",
|
|
59
|
+
"license": "MIT",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/foldkit/foldkit.git",
|
|
63
|
+
"directory": "packages/markdown"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=18.0.0"
|
|
70
|
+
},
|
|
71
|
+
"scripts": {
|
|
72
|
+
"clean": "rimraf dist *.tsbuildinfo",
|
|
73
|
+
"build": "pnpm run clean && tsc -b tsconfig.build.json",
|
|
74
|
+
"watch": "tsc -b tsconfig.build.json --watch",
|
|
75
|
+
"lint": "pnpm --filter @foldkit/oxlint-plugin build && oxlint src",
|
|
76
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
77
|
+
"test": "vitest run"
|
|
78
|
+
}
|
|
79
|
+
}
|