@markmap/core 0.0.0-stage → 1.0.0-next.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 +4 -2
- package/dist/content.d.ts +75 -0
- package/dist/geometry.d.ts +25 -0
- package/dist/id.d.ts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +236 -0
- package/dist/layout.d.ts +63 -0
- package/dist/mdast.d.ts +28 -0
- package/dist/options.d.ts +64 -0
- package/dist/primitives.d.ts +108 -0
- package/dist/theme.d.ts +71 -0
- package/dist/tree.d.ts +10 -0
- package/dist/types.d.ts +25 -0
- package/package.json +32 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Gerald
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @markmap/core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Markmap types, tree utilities and plugin contracts.
|
|
4
|
+
|
|
5
|
+
See the [Markmap README](https://github.com/markmap/markmap#readme) for how the packages fit together.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Root as HastRoot } from 'hast';
|
|
2
|
+
import type { Rect, Size } from './geometry';
|
|
3
|
+
import type { FontStyle, Paint, Primitive } from './primitives';
|
|
4
|
+
import type { Measurer, Theme } from './theme';
|
|
5
|
+
import type { Inline, MarkmapNode } from './types';
|
|
6
|
+
/** Font and color of a piece of text. */
|
|
7
|
+
export interface TextStyle {
|
|
8
|
+
font: FontStyle;
|
|
9
|
+
fill: Paint;
|
|
10
|
+
}
|
|
11
|
+
/** Drawing around a styled span. Spans may wrap across lines; each line gets its own piece. */
|
|
12
|
+
export interface Decoration {
|
|
13
|
+
/** Background behind the span. */
|
|
14
|
+
background?: Paint;
|
|
15
|
+
/** Corner radius of the background. */
|
|
16
|
+
radius?: number;
|
|
17
|
+
/** Horizontal space added inside both ends of the span, in px. */
|
|
18
|
+
padding?: number;
|
|
19
|
+
/** Line through the middle of the text. */
|
|
20
|
+
strike?: Paint;
|
|
21
|
+
/** Makes the span a link. Unsafe URLs must be dropped before this point. */
|
|
22
|
+
href?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface StyleResult {
|
|
25
|
+
font?: Partial<FontStyle>;
|
|
26
|
+
fill?: Paint;
|
|
27
|
+
decoration?: Decoration;
|
|
28
|
+
}
|
|
29
|
+
export interface InlineContext {
|
|
30
|
+
theme: Theme;
|
|
31
|
+
measurer: Measurer;
|
|
32
|
+
node: MarkmapNode;
|
|
33
|
+
/** Label wrap width in px; `0` or missing when labels do not wrap. */
|
|
34
|
+
wrapWidth?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Report work that will change a measurement, such as an image loading to learn its
|
|
37
|
+
* size. The caller renders again once it settles; plugins should cache the result so
|
|
38
|
+
* the next measurement is final.
|
|
39
|
+
*/
|
|
40
|
+
defer?(task: Promise<unknown>): void;
|
|
41
|
+
}
|
|
42
|
+
export interface AtomSize extends Size {
|
|
43
|
+
/** Distance from the top of the atom to the text baseline it sits on. */
|
|
44
|
+
baseline: number;
|
|
45
|
+
}
|
|
46
|
+
/** Draws an inline node as an unbreakable box, e.g. a checkbox, formula or icon. */
|
|
47
|
+
export interface AtomRenderer<T extends Inline = Inline> {
|
|
48
|
+
measure(inline: T, style: TextStyle, ctx: InlineContext): AtomSize;
|
|
49
|
+
/** Primitives in text coordinates. `box` is where the atom was placed. */
|
|
50
|
+
render(inline: T, box: Rect, style: TextStyle, ctx: InlineContext): Primitive[];
|
|
51
|
+
}
|
|
52
|
+
export type InlineStyler<T extends Inline = Inline> = (inline: T, style: TextStyle, ctx: InlineContext) => StyleResult;
|
|
53
|
+
export interface ContentPlugin {
|
|
54
|
+
name: string;
|
|
55
|
+
/** Rewrite a node's inline content before layout, e.g. to add a checkbox. */
|
|
56
|
+
transform?(content: Inline[], node: MarkmapNode): Inline[];
|
|
57
|
+
/** Style the text inside inline nodes, keyed by inline type. */
|
|
58
|
+
styles?: {
|
|
59
|
+
[K in Inline['type']]?: InlineStyler<Extract<Inline, {
|
|
60
|
+
type: K;
|
|
61
|
+
}>>;
|
|
62
|
+
};
|
|
63
|
+
/** Draw inline nodes as boxes, keyed by inline type. */
|
|
64
|
+
atoms?: {
|
|
65
|
+
[K in Inline['type']]?: AtomRenderer<Extract<Inline, {
|
|
66
|
+
type: K;
|
|
67
|
+
}>>;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Change the HTML of a note popover, e.g. to draw math. Runs after the
|
|
71
|
+
* Markdown's HTML has been sanitized, so markup added here is trusted as is,
|
|
72
|
+
* including `raw` nodes. Never copy unescaped text from the tree into raw markup.
|
|
73
|
+
*/
|
|
74
|
+
popover?(tree: HastRoot): void | Promise<void>;
|
|
75
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface Point {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
}
|
|
5
|
+
export interface Size {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
export interface Rect extends Point, Size {
|
|
10
|
+
}
|
|
11
|
+
/** Affine transform `[a, b, c, d, e, f]`, as in SVG `matrix()` and canvas `setTransform()`. */
|
|
12
|
+
export type Matrix = [number, number, number, number, number, number];
|
|
13
|
+
export type Side = 'left' | 'right' | 'top' | 'bottom';
|
|
14
|
+
/** Where an edge leaves or enters a node, and which side it faces. */
|
|
15
|
+
export interface Anchor extends Point {
|
|
16
|
+
side: Side;
|
|
17
|
+
}
|
|
18
|
+
export declare function translate(x: number, y: number): Matrix;
|
|
19
|
+
/** Scale by `k` around the point (`cx`, `cy`). */
|
|
20
|
+
export declare function scaleAround(k: number, cx: number, cy: number): Matrix;
|
|
21
|
+
/** `a` applied after `b`, as in SVG `transform="matrix(a) matrix(b)"`. */
|
|
22
|
+
export declare function multiply(a: Matrix, b: Matrix): Matrix;
|
|
23
|
+
export declare function unionRects(rects: Iterable<Rect>): Rect;
|
|
24
|
+
export declare function expandRect(rect: Rect, by: number): Rect;
|
|
25
|
+
export declare function oppositeSide(side: Side): Side;
|
package/dist/id.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MarkmapNode } from './types';
|
|
2
|
+
export declare const ROOT_ID = "root";
|
|
3
|
+
/** 32-bit FNV-1a hash of a string, in base 36. */
|
|
4
|
+
export declare function hashString(input: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Assign stable ids in place. The root is always `root`. Every other id is a hash of
|
|
7
|
+
* the parent id and the node's plain text, so ids survive edits elsewhere in the
|
|
8
|
+
* document. Siblings with the same text are told apart by their occurrence index.
|
|
9
|
+
*/
|
|
10
|
+
export declare function assignIds(root: MarkmapNode): MarkmapNode;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type * from './content';
|
|
2
|
+
export type * from './layout';
|
|
3
|
+
export type * from './mdast';
|
|
4
|
+
export type * from './primitives';
|
|
5
|
+
export type * from './theme';
|
|
6
|
+
export type * from './types';
|
|
7
|
+
export * from './geometry';
|
|
8
|
+
export * from './id';
|
|
9
|
+
export * from './tree';
|
|
10
|
+
export * from './options';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// src/geometry.ts
|
|
2
|
+
function translate(x, y) {
|
|
3
|
+
return [
|
|
4
|
+
1,
|
|
5
|
+
0,
|
|
6
|
+
0,
|
|
7
|
+
1,
|
|
8
|
+
x,
|
|
9
|
+
y
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
/** Scale by `k` around the point (`cx`, `cy`). */
|
|
13
|
+
function scaleAround(k, cx, cy) {
|
|
14
|
+
return [
|
|
15
|
+
k,
|
|
16
|
+
0,
|
|
17
|
+
0,
|
|
18
|
+
k,
|
|
19
|
+
cx * (1 - k),
|
|
20
|
+
cy * (1 - k)
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
/** `a` applied after `b`, as in SVG `transform="matrix(a) matrix(b)"`. */
|
|
24
|
+
function multiply(a, b) {
|
|
25
|
+
const [a0, a1, a2, a3, a4, a5] = a;
|
|
26
|
+
const [b0, b1, b2, b3, b4, b5] = b;
|
|
27
|
+
return [
|
|
28
|
+
a0 * b0 + a2 * b1,
|
|
29
|
+
a1 * b0 + a3 * b1,
|
|
30
|
+
a0 * b2 + a2 * b3,
|
|
31
|
+
a1 * b2 + a3 * b3,
|
|
32
|
+
a0 * b4 + a2 * b5 + a4,
|
|
33
|
+
a1 * b4 + a3 * b5 + a5
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
function unionRects(rects) {
|
|
37
|
+
let [x0, y0, x1, y1] = [
|
|
38
|
+
Infinity,
|
|
39
|
+
Infinity,
|
|
40
|
+
-Infinity,
|
|
41
|
+
-Infinity
|
|
42
|
+
];
|
|
43
|
+
for (const r of rects) {
|
|
44
|
+
x0 = Math.min(x0, r.x);
|
|
45
|
+
y0 = Math.min(y0, r.y);
|
|
46
|
+
x1 = Math.max(x1, r.x + r.width);
|
|
47
|
+
y1 = Math.max(y1, r.y + r.height);
|
|
48
|
+
}
|
|
49
|
+
if (x0 > x1) return {
|
|
50
|
+
x: 0,
|
|
51
|
+
y: 0,
|
|
52
|
+
width: 0,
|
|
53
|
+
height: 0
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
x: x0,
|
|
57
|
+
y: y0,
|
|
58
|
+
width: x1 - x0,
|
|
59
|
+
height: y1 - y0
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function expandRect(rect, by) {
|
|
63
|
+
return {
|
|
64
|
+
x: rect.x - by,
|
|
65
|
+
y: rect.y - by,
|
|
66
|
+
width: rect.width + by * 2,
|
|
67
|
+
height: rect.height + by * 2
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
var OPPOSITE = {
|
|
71
|
+
left: "right",
|
|
72
|
+
right: "left",
|
|
73
|
+
top: "bottom",
|
|
74
|
+
bottom: "top"
|
|
75
|
+
};
|
|
76
|
+
function oppositeSide(side) {
|
|
77
|
+
return OPPOSITE[side];
|
|
78
|
+
}
|
|
79
|
+
// src/tree.ts
|
|
80
|
+
/** Visit every node in pre-order. */
|
|
81
|
+
function walkTree(root, visitor) {
|
|
82
|
+
const visit = (node, parent, depth) => {
|
|
83
|
+
if (visitor(node, parent, depth) === false) return;
|
|
84
|
+
for (const child of node.children) visit(child, node, depth + 1);
|
|
85
|
+
};
|
|
86
|
+
visit(root, void 0, 0);
|
|
87
|
+
}
|
|
88
|
+
/** Return the path from the root to the node with `id`, both included, or `undefined`. */
|
|
89
|
+
function findPath(root, id) {
|
|
90
|
+
if (root.id === id) return [root];
|
|
91
|
+
for (const child of root.children) {
|
|
92
|
+
const path = findPath(child, id);
|
|
93
|
+
if (path) return [root, ...path];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function findNode(root, id) {
|
|
97
|
+
return findPath(root, id)?.at(-1);
|
|
98
|
+
}
|
|
99
|
+
/** Plain text of inline content, as used for ids, search and accessibility labels. */
|
|
100
|
+
function toPlainText(content) {
|
|
101
|
+
let text = "";
|
|
102
|
+
for (const node of content) if ("value" in node) text += node.value;
|
|
103
|
+
else if (node.type === "image" || node.type === "imageReference") text += node.alt ?? "";
|
|
104
|
+
else if (node.type === "break") text += "\n";
|
|
105
|
+
else if ("children" in node) text += toPlainText(node.children);
|
|
106
|
+
return text;
|
|
107
|
+
}
|
|
108
|
+
// src/id.ts
|
|
109
|
+
var ROOT_ID = "root";
|
|
110
|
+
/** 32-bit FNV-1a hash of a string, in base 36. */
|
|
111
|
+
function hashString(input) {
|
|
112
|
+
let hash = 2166136261;
|
|
113
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
114
|
+
hash ^= input.charCodeAt(i);
|
|
115
|
+
hash = Math.imul(hash, 16777619);
|
|
116
|
+
}
|
|
117
|
+
return (hash >>> 0).toString(36).padStart(7, "0");
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Assign stable ids in place. The root is always `root`. Every other id is a hash of
|
|
121
|
+
* the parent id and the node's plain text, so ids survive edits elsewhere in the
|
|
122
|
+
* document. Siblings with the same text are told apart by their occurrence index.
|
|
123
|
+
*/
|
|
124
|
+
function assignIds(root) {
|
|
125
|
+
const assign = (node, id) => {
|
|
126
|
+
node.id = id;
|
|
127
|
+
const seen = /* @__PURE__ */ new Map();
|
|
128
|
+
for (const child of node.children) {
|
|
129
|
+
const text = toPlainText(child.content);
|
|
130
|
+
const index = seen.get(text) ?? 0;
|
|
131
|
+
seen.set(text, index + 1);
|
|
132
|
+
assign(child, hashString(`${id}\0${text}${index ? `\0${index}` : ""}`));
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
assign(root, ROOT_ID);
|
|
136
|
+
return root;
|
|
137
|
+
}
|
|
138
|
+
// src/options.ts
|
|
139
|
+
/**
|
|
140
|
+
* A CSS value that cannot break out of a declaration or a `<style>` element.
|
|
141
|
+
* Colors from Markdown end up in embedded CSS, so they must pass this check.
|
|
142
|
+
*/
|
|
143
|
+
function isSafeCSSValue(value) {
|
|
144
|
+
return value.length > 0 && value.length <= 100 && !/[;{}<>\\'"]|\/\*/.test(value);
|
|
145
|
+
}
|
|
146
|
+
var validators = {
|
|
147
|
+
number(min, integer = false) {
|
|
148
|
+
return (value, key) => {
|
|
149
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return { warning: `\`${key}\` must be a number` };
|
|
150
|
+
if (value < min || integer && !Number.isInteger(value)) return { warning: `\`${key}\` must be ${integer ? "an integer " : ""}>= ${min}` };
|
|
151
|
+
return { value };
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
/** A short non-empty string, such as a name to look up. */
|
|
155
|
+
string() {
|
|
156
|
+
return (value, key) => typeof value === "string" && value.trim() && value.length <= 100 ? { value: value.trim() } : { warning: `\`${key}\` must be a non-empty string` };
|
|
157
|
+
},
|
|
158
|
+
oneOf(values) {
|
|
159
|
+
return (value, key) => values.includes(value) ? { value } : { warning: `\`${key}\` must be one of ${values.map((v) => `\`${v}\``).join(", ")}` };
|
|
160
|
+
},
|
|
161
|
+
boolean() {
|
|
162
|
+
return (value, key) => typeof value === "boolean" ? { value } : { warning: `\`${key}\` must be true or false` };
|
|
163
|
+
},
|
|
164
|
+
/** One CSS color or a list of them; always normalized to a list. Unsafe values are dropped. */
|
|
165
|
+
colors() {
|
|
166
|
+
return (value, key) => {
|
|
167
|
+
const colors = typeof value === "string" ? [value] : value;
|
|
168
|
+
if (!Array.isArray(colors) || !colors.length) return { warning: `\`${key}\` must be a color or a list of colors` };
|
|
169
|
+
const safe = colors.filter((c) => typeof c === "string" && isSafeCSSValue(c.trim())).map((c) => c.trim());
|
|
170
|
+
return {
|
|
171
|
+
value: safe.length ? safe : void 0,
|
|
172
|
+
warning: safe.length < colors.length ? `\`${key}\` has invalid colors, which were ignored` : void 0
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var CORE_SCHEMA = {
|
|
178
|
+
foldLevel: validators.number(0, true),
|
|
179
|
+
wrapWidth: validators.number(0),
|
|
180
|
+
levelGap: validators.number(0),
|
|
181
|
+
siblingGap: validators.number(0),
|
|
182
|
+
zoom: validators.boolean(),
|
|
183
|
+
pan: validators.boolean(),
|
|
184
|
+
fitPadding: validators.number(0),
|
|
185
|
+
fitMaxScale: validators.number(.01),
|
|
186
|
+
minScale: validators.number(.001),
|
|
187
|
+
maxScale: validators.number(.01),
|
|
188
|
+
duration: validators.number(0),
|
|
189
|
+
layout: validators.string(),
|
|
190
|
+
theme: validators.string(),
|
|
191
|
+
colorScheme: validators.oneOf([
|
|
192
|
+
"light",
|
|
193
|
+
"dark",
|
|
194
|
+
"auto"
|
|
195
|
+
])
|
|
196
|
+
};
|
|
197
|
+
/** Validate untrusted options against a schema. */
|
|
198
|
+
function validateOptions(input, schema) {
|
|
199
|
+
const options = {};
|
|
200
|
+
const rest = {};
|
|
201
|
+
const warnings = [];
|
|
202
|
+
if (input == null) return {
|
|
203
|
+
options,
|
|
204
|
+
rest,
|
|
205
|
+
warnings
|
|
206
|
+
};
|
|
207
|
+
if (typeof input !== "object" || Array.isArray(input)) return {
|
|
208
|
+
options,
|
|
209
|
+
rest,
|
|
210
|
+
warnings: ["`markmap` options must be an object"]
|
|
211
|
+
};
|
|
212
|
+
for (const [key, raw] of Object.entries(input)) {
|
|
213
|
+
const validate = Object.hasOwn(schema, key) ? schema[key] : void 0;
|
|
214
|
+
if (!validate) {
|
|
215
|
+
rest[key] = raw;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const { value, warning } = validate(raw, key);
|
|
219
|
+
if (warning) warnings.push(warning);
|
|
220
|
+
if (value !== void 0) options[key] = value;
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
options,
|
|
224
|
+
rest,
|
|
225
|
+
warnings
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/** Validate the theme-independent options. Theme keys are returned in `rest`. */
|
|
229
|
+
function readJSONOptions(input) {
|
|
230
|
+
return validateOptions(input, CORE_SCHEMA);
|
|
231
|
+
}
|
|
232
|
+
/** Warnings for keys that no schema claimed. */
|
|
233
|
+
function unknownOptionWarnings(rest) {
|
|
234
|
+
return Object.keys(rest).map((key) => `Unknown option \`${key}\``);
|
|
235
|
+
}
|
|
236
|
+
export { ROOT_ID, assignIds, expandRect, findNode, findPath, hashString, isSafeCSSValue, multiply, oppositeSide, readJSONOptions, scaleAround, toPlainText, translate, unionRects, unknownOptionWarnings, validateOptions, validators, walkTree };
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Point, Rect, Side, Size } from './geometry';
|
|
2
|
+
import type { NodeContext } from './theme';
|
|
3
|
+
import type { MarkmapNode } from './types';
|
|
4
|
+
export interface LayoutNode {
|
|
5
|
+
node: MarkmapNode;
|
|
6
|
+
parent?: LayoutNode;
|
|
7
|
+
/** Visible children: empty when the node is folded. */
|
|
8
|
+
children: LayoutNode[];
|
|
9
|
+
depth: number;
|
|
10
|
+
folded: boolean;
|
|
11
|
+
rect: Rect;
|
|
12
|
+
/**
|
|
13
|
+
* The way this node grows away from its parent, and its children away from it. Edges
|
|
14
|
+
* leave the parent on this side and enter the node on the opposite one. For the root,
|
|
15
|
+
* the side of its fold toggle.
|
|
16
|
+
*/
|
|
17
|
+
direction: Side;
|
|
18
|
+
}
|
|
19
|
+
export interface LayoutResult {
|
|
20
|
+
/** Visible nodes in pre-order. */
|
|
21
|
+
nodes: LayoutNode[];
|
|
22
|
+
byId: Map<string, LayoutNode>;
|
|
23
|
+
/** Union of all node rects. */
|
|
24
|
+
bounds: Rect;
|
|
25
|
+
}
|
|
26
|
+
export interface LayoutOptions {
|
|
27
|
+
/** Box size of each visible node. */
|
|
28
|
+
sizes: ReadonlyMap<string, Size>;
|
|
29
|
+
folded?: ReadonlySet<string>;
|
|
30
|
+
/** Gap between a node and its children. */
|
|
31
|
+
levelGap?: number;
|
|
32
|
+
/** Gap between siblings; doubled between cousins. */
|
|
33
|
+
siblingGap?: number;
|
|
34
|
+
}
|
|
35
|
+
/** A node in one animation frame: where it is and how present it is. */
|
|
36
|
+
export interface FrameNode {
|
|
37
|
+
node: MarkmapNode;
|
|
38
|
+
ctx: NodeContext;
|
|
39
|
+
rect: Rect;
|
|
40
|
+
/** `1` when fully shown, `0` when gone. Themes decide how it looks; the default is a fade. */
|
|
41
|
+
presence: number;
|
|
42
|
+
}
|
|
43
|
+
/** One frame. Extra fields on the nodes are kept by interpolation. */
|
|
44
|
+
export interface LayoutFrame<N extends FrameNode = FrameNode> {
|
|
45
|
+
nodes: N[];
|
|
46
|
+
}
|
|
47
|
+
export interface InterpolateContext<N extends FrameNode = FrameNode> {
|
|
48
|
+
/** A point on one side of a frame node, as the theme draws it. */
|
|
49
|
+
anchor(item: N, side: Side): Point;
|
|
50
|
+
}
|
|
51
|
+
/** Places nodes, and optionally decides how they move between two placements. */
|
|
52
|
+
export interface Layout {
|
|
53
|
+
name: string;
|
|
54
|
+
layout(root: MarkmapNode, options: LayoutOptions): LayoutResult;
|
|
55
|
+
/**
|
|
56
|
+
* The frame `t` (already eased, 0 to 1) of the way from `from` to `to`. `from` may be
|
|
57
|
+
* a frame of an earlier animation. Nodes only in `to` are entering, nodes only in
|
|
58
|
+
* `from` are leaving; return entering nodes in `to` order, then leaving ones. Defaults
|
|
59
|
+
* to straight-line moves, with entering and leaving nodes growing out of and shrinking
|
|
60
|
+
* into their nearest ancestor's anchor.
|
|
61
|
+
*/
|
|
62
|
+
interpolate?<N extends FrameNode>(from: LayoutFrame<N>, to: LayoutFrame<N>, t: number, ctx: InterpolateContext<N>): LayoutFrame<N>;
|
|
63
|
+
}
|
package/dist/mdast.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Node, Parent, PhrasingContent } from 'mdast';
|
|
2
|
+
/** `==highlighted==` text. */
|
|
3
|
+
export interface Mark extends Parent {
|
|
4
|
+
type: 'mark';
|
|
5
|
+
children: PhrasingContent[];
|
|
6
|
+
}
|
|
7
|
+
/** A task-list checkbox, added before a node's label at render time. */
|
|
8
|
+
export interface Checkbox extends Node {
|
|
9
|
+
type: 'checkbox';
|
|
10
|
+
checked: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Marks a node that has notes, added after its label at render time. */
|
|
13
|
+
export interface NoteMarker extends Node {
|
|
14
|
+
type: 'noteMarker';
|
|
15
|
+
count: number;
|
|
16
|
+
}
|
|
17
|
+
declare module 'mdast' {
|
|
18
|
+
interface PhrasingContentMap {
|
|
19
|
+
mark: Mark;
|
|
20
|
+
checkbox: Checkbox;
|
|
21
|
+
noteMarker: NoteMarker;
|
|
22
|
+
}
|
|
23
|
+
interface RootContentMap {
|
|
24
|
+
mark: Mark;
|
|
25
|
+
checkbox: Checkbox;
|
|
26
|
+
noteMarker: NoteMarker;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options that can be written in Markdown frontmatter under the `markmap` key and
|
|
3
|
+
* apply to every theme. Themes add their own keys, see `Theme.options`.
|
|
4
|
+
*/
|
|
5
|
+
export interface MarkmapJSONOptions {
|
|
6
|
+
/** Fold nodes at this depth and deeper when first shown. Leave out to expand everything. */
|
|
7
|
+
foldLevel?: number;
|
|
8
|
+
/** Wrap labels wider than this, in px. `0` disables wrapping. */
|
|
9
|
+
wrapWidth?: number;
|
|
10
|
+
levelGap?: number;
|
|
11
|
+
siblingGap?: number;
|
|
12
|
+
zoom?: boolean;
|
|
13
|
+
pan?: boolean;
|
|
14
|
+
fitPadding?: number;
|
|
15
|
+
fitMaxScale?: number;
|
|
16
|
+
/** Smallest scale to zoom out to. `fit()` may go further to show everything. */
|
|
17
|
+
minScale?: number;
|
|
18
|
+
/** Largest scale to zoom in to. */
|
|
19
|
+
maxScale?: number;
|
|
20
|
+
/** Length of fold, update and fit animations, in ms. `0` disables them. */
|
|
21
|
+
duration?: number;
|
|
22
|
+
/** Name of a registered layout, such as `two-sided` or `org-chart`. */
|
|
23
|
+
layout?: string;
|
|
24
|
+
/** Name of a registered theme, such as `cards`. Its own options apply on top. */
|
|
25
|
+
theme?: string;
|
|
26
|
+
/** `light`, `dark`, or `auto` to follow the reader's system setting. */
|
|
27
|
+
colorScheme?: 'light' | 'dark' | 'auto';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Checks one untrusted option value. Return `value` to keep it (possibly normalized),
|
|
31
|
+
* `warning` to report a problem, or both when part of the value was dropped.
|
|
32
|
+
*/
|
|
33
|
+
export type OptionValidator = (value: unknown, key: string) => {
|
|
34
|
+
value?: unknown;
|
|
35
|
+
warning?: string;
|
|
36
|
+
};
|
|
37
|
+
export type OptionSchema = Record<string, OptionValidator>;
|
|
38
|
+
/**
|
|
39
|
+
* A CSS value that cannot break out of a declaration or a `<style>` element.
|
|
40
|
+
* Colors from Markdown end up in embedded CSS, so they must pass this check.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isSafeCSSValue(value: string): boolean;
|
|
43
|
+
export declare const validators: {
|
|
44
|
+
number(min: number, integer?: boolean): OptionValidator;
|
|
45
|
+
/** A short non-empty string, such as a name to look up. */
|
|
46
|
+
string(): OptionValidator;
|
|
47
|
+
oneOf<T extends string>(values: readonly T[]): OptionValidator;
|
|
48
|
+
boolean(): OptionValidator;
|
|
49
|
+
/** One CSS color or a list of them; always normalized to a list. Unsafe values are dropped. */
|
|
50
|
+
colors(): OptionValidator;
|
|
51
|
+
};
|
|
52
|
+
export interface ValidatedOptions<T> {
|
|
53
|
+
options: T;
|
|
54
|
+
/** Keys the schema does not know, left for another schema (such as the theme's). */
|
|
55
|
+
rest: Record<string, unknown>;
|
|
56
|
+
/** Problems found, one per ignored or partly ignored value. */
|
|
57
|
+
warnings: string[];
|
|
58
|
+
}
|
|
59
|
+
/** Validate untrusted options against a schema. */
|
|
60
|
+
export declare function validateOptions<T = Record<string, unknown>>(input: unknown, schema: OptionSchema): ValidatedOptions<T>;
|
|
61
|
+
/** Validate the theme-independent options. Theme keys are returned in `rest`. */
|
|
62
|
+
export declare function readJSONOptions(input: unknown): ValidatedOptions<MarkmapJSONOptions>;
|
|
63
|
+
/** Warnings for keys that no schema claimed. */
|
|
64
|
+
export declare function unknownOptionWarnings(rest: Record<string, unknown>): string[];
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Matrix, Rect } from './geometry';
|
|
2
|
+
/** A CSS color, usually `var(--mm-…)` so themes and user CSS apply. */
|
|
3
|
+
export type Paint = string;
|
|
4
|
+
export type Backend = 'svg' | 'canvas';
|
|
5
|
+
export interface FontStyle {
|
|
6
|
+
family: string;
|
|
7
|
+
/** Font size in px. */
|
|
8
|
+
size: number;
|
|
9
|
+
weight: number;
|
|
10
|
+
style: 'normal' | 'italic';
|
|
11
|
+
/** Line height in px. */
|
|
12
|
+
lineHeight: number;
|
|
13
|
+
}
|
|
14
|
+
/** A piece of text with one font and color. `x`/`y` are the baseline start, relative to the text primitive. */
|
|
15
|
+
export interface TextRun {
|
|
16
|
+
text: string;
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
font: FontStyle;
|
|
20
|
+
fill?: Paint;
|
|
21
|
+
/** Measured width, when known, so backends can skip measuring text they only sketch. */
|
|
22
|
+
width?: number;
|
|
23
|
+
}
|
|
24
|
+
interface PrimitiveBase {
|
|
25
|
+
/** Backends that can draw this primitive. Omit for all. */
|
|
26
|
+
backends?: Backend[];
|
|
27
|
+
}
|
|
28
|
+
export interface GroupPrimitive extends PrimitiveBase {
|
|
29
|
+
kind: 'group';
|
|
30
|
+
/** Identity for patching and animation, unique among all groups. */
|
|
31
|
+
key?: string;
|
|
32
|
+
nodeId?: string;
|
|
33
|
+
transform?: Matrix;
|
|
34
|
+
opacity?: number;
|
|
35
|
+
/** Hide what falls outside this rectangle, in the group's own coordinates. */
|
|
36
|
+
clip?: Rect;
|
|
37
|
+
/**
|
|
38
|
+
* Where the group draws, in its parent's coordinates, if that is cheap to tell.
|
|
39
|
+
* Backends can skip top-level groups that are out of view.
|
|
40
|
+
*/
|
|
41
|
+
bounds?: Rect;
|
|
42
|
+
children: Primitive[];
|
|
43
|
+
}
|
|
44
|
+
export interface RectPrimitive extends PrimitiveBase {
|
|
45
|
+
kind: 'rect';
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
w: number;
|
|
49
|
+
h: number;
|
|
50
|
+
r?: number;
|
|
51
|
+
fill?: Paint;
|
|
52
|
+
stroke?: Paint;
|
|
53
|
+
strokeWidth?: number;
|
|
54
|
+
}
|
|
55
|
+
export interface PathPrimitive extends PrimitiveBase {
|
|
56
|
+
kind: 'path';
|
|
57
|
+
d: string;
|
|
58
|
+
fill?: Paint;
|
|
59
|
+
stroke?: Paint;
|
|
60
|
+
strokeWidth?: number;
|
|
61
|
+
}
|
|
62
|
+
/** Pre-wrapped, pre-measured text. */
|
|
63
|
+
export interface TextPrimitive extends PrimitiveBase {
|
|
64
|
+
kind: 'text';
|
|
65
|
+
x: number;
|
|
66
|
+
y: number;
|
|
67
|
+
runs: TextRun[];
|
|
68
|
+
}
|
|
69
|
+
export interface ImagePrimitive extends PrimitiveBase {
|
|
70
|
+
kind: 'image';
|
|
71
|
+
x: number;
|
|
72
|
+
y: number;
|
|
73
|
+
w: number;
|
|
74
|
+
h: number;
|
|
75
|
+
src: string;
|
|
76
|
+
/** Drawn instead when a backend finds the image cannot be loaded. */
|
|
77
|
+
fallback?: Primitive[];
|
|
78
|
+
}
|
|
79
|
+
/** A hit area that opens `href`. Draws nothing. */
|
|
80
|
+
export interface LinkPrimitive extends PrimitiveBase {
|
|
81
|
+
kind: 'link';
|
|
82
|
+
href: string;
|
|
83
|
+
rect: Rect;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* An interactive-only hit area that triggers a view action, such as opening a node's
|
|
87
|
+
* notes. Draws nothing and is never exported.
|
|
88
|
+
*/
|
|
89
|
+
export interface HitPrimitive extends PrimitiveBase {
|
|
90
|
+
kind: 'hit';
|
|
91
|
+
rect: Rect;
|
|
92
|
+
action: string;
|
|
93
|
+
}
|
|
94
|
+
/** Interactive-only HTML. Never exported. */
|
|
95
|
+
export interface OverlayPrimitive extends PrimitiveBase {
|
|
96
|
+
kind: 'overlay';
|
|
97
|
+
rect: Rect;
|
|
98
|
+
html: () => HTMLElement;
|
|
99
|
+
}
|
|
100
|
+
/** Escape hatch for drawing a backend cannot express with other primitives. */
|
|
101
|
+
export interface CustomPrimitive extends PrimitiveBase {
|
|
102
|
+
kind: 'custom';
|
|
103
|
+
/** SVG markup. */
|
|
104
|
+
svg?: () => string;
|
|
105
|
+
canvas?: (ctx: CanvasRenderingContext2D) => void;
|
|
106
|
+
}
|
|
107
|
+
export type Primitive = GroupPrimitive | RectPrimitive | PathPrimitive | TextPrimitive | ImagePrimitive | LinkPrimitive | HitPrimitive | OverlayPrimitive | CustomPrimitive;
|
|
108
|
+
export {};
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Anchor, Matrix, Rect, Side, Size } from './geometry';
|
|
2
|
+
import type { OptionSchema } from './options';
|
|
3
|
+
import type { FontStyle, Paint, Primitive } from './primitives';
|
|
4
|
+
import type { MarkmapNode } from './types';
|
|
5
|
+
/** Measures the advance width of a single-font string, in px. */
|
|
6
|
+
export interface Measurer {
|
|
7
|
+
measure(text: string, font: FontStyle): number;
|
|
8
|
+
}
|
|
9
|
+
export interface NodeContext {
|
|
10
|
+
depth: number;
|
|
11
|
+
/** Ids from the root to this node, both included. Ids are stable, so themes can hash them for colors. */
|
|
12
|
+
path: readonly string[];
|
|
13
|
+
hasChildren: boolean;
|
|
14
|
+
folded: boolean;
|
|
15
|
+
/** The way the node grows away from its parent, from the layout. */
|
|
16
|
+
direction: Side;
|
|
17
|
+
}
|
|
18
|
+
/** What is known about a node before layout: everything but its direction. */
|
|
19
|
+
export type MeasureContext = Omit<NodeContext, 'direction'>;
|
|
20
|
+
export interface EdgeContext {
|
|
21
|
+
source: MarkmapNode;
|
|
22
|
+
target: MarkmapNode;
|
|
23
|
+
/** Context of the target node. */
|
|
24
|
+
node: NodeContext;
|
|
25
|
+
}
|
|
26
|
+
/** How a node looks part way into or out of the drawing. */
|
|
27
|
+
export interface PresenceStyle {
|
|
28
|
+
opacity?: number;
|
|
29
|
+
/** Applied in node-local coordinates (the box starts at 0, 0), before the node's position. */
|
|
30
|
+
transform?: Matrix;
|
|
31
|
+
}
|
|
32
|
+
export type TextAlign = 'start' | 'center' | 'end';
|
|
33
|
+
export interface Theme {
|
|
34
|
+
name: string;
|
|
35
|
+
/** Theme-specific Markdown options, such as colors. Validated before `configure` sees them. */
|
|
36
|
+
options?: OptionSchema;
|
|
37
|
+
/** A copy of the theme with validated `options` applied. */
|
|
38
|
+
configure?(options: Record<string, unknown>): Theme;
|
|
39
|
+
/** Emitted as CSS variables, e.g. `{ 'mm-text': '#333' }` → `--mm-text: #333`. */
|
|
40
|
+
tokens: Record<string, string>;
|
|
41
|
+
/** Tokens that change in dark mode; the rest of `tokens` still applies. */
|
|
42
|
+
darkTokens?: Record<string, string>;
|
|
43
|
+
font: FontStyle;
|
|
44
|
+
/** Font family for inline code. */
|
|
45
|
+
codeFontFamily: string;
|
|
46
|
+
textFill: Paint;
|
|
47
|
+
node: {
|
|
48
|
+
/** `[top, right, bottom, left]` around the content. */
|
|
49
|
+
padding: [number, number, number, number];
|
|
50
|
+
minHeight?: number;
|
|
51
|
+
/** Box size from content size. Defaults to content plus padding, at least `minHeight` tall. */
|
|
52
|
+
measure?(content: Size, node: MarkmapNode, ctx: MeasureContext): Size;
|
|
53
|
+
/** Alignment of wrapped label lines. Defaults to `start`. */
|
|
54
|
+
textAlign?(ctx: NodeContext): TextAlign;
|
|
55
|
+
/**
|
|
56
|
+
* How a node looks while `presence` goes from 0 (absent) to 1 (shown), as it enters
|
|
57
|
+
* or leaves. Its edge gets the same opacity. Defaults to fading.
|
|
58
|
+
*/
|
|
59
|
+
presence?(presence: number, box: Size, ctx: NodeContext): PresenceStyle;
|
|
60
|
+
/** Chrome in node-local coordinates (the box starts at 0, 0). */
|
|
61
|
+
render(node: MarkmapNode, box: Size, ctx: NodeContext): Primitive[];
|
|
62
|
+
/**
|
|
63
|
+
* Where edges meet the node on one side, in layout coordinates. Edges to children
|
|
64
|
+
* leave on `ctx.direction` and the edge from the parent enters on the opposite side.
|
|
65
|
+
*/
|
|
66
|
+
anchor(rect: Rect, side: Side, ctx: NodeContext): Anchor;
|
|
67
|
+
};
|
|
68
|
+
edge: {
|
|
69
|
+
render(from: Anchor, to: Anchor, ctx: EdgeContext): Primitive[];
|
|
70
|
+
};
|
|
71
|
+
}
|
package/dist/tree.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Inline, MarkmapNode } from './types';
|
|
2
|
+
/** Return `false` from the visitor to skip a node's children. */
|
|
3
|
+
export type TreeVisitor = (node: MarkmapNode, parent: MarkmapNode | undefined, depth: number) => void | false;
|
|
4
|
+
/** Visit every node in pre-order. */
|
|
5
|
+
export declare function walkTree(root: MarkmapNode, visitor: TreeVisitor): void;
|
|
6
|
+
/** Return the path from the root to the node with `id`, both included, or `undefined`. */
|
|
7
|
+
export declare function findPath(root: MarkmapNode, id: string): MarkmapNode[] | undefined;
|
|
8
|
+
export declare function findNode(root: MarkmapNode, id: string): MarkmapNode | undefined;
|
|
9
|
+
/** Plain text of inline content, as used for ids, search and accessibility labels. */
|
|
10
|
+
export declare function toPlainText(content: readonly Inline[]): string;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BlockContent, DefinitionContent, PhrasingContent } from 'mdast';
|
|
2
|
+
/** Inline content of a node label: a subset of mdast phrasing content. */
|
|
3
|
+
export type Inline = PhrasingContent;
|
|
4
|
+
/** A block that is not a heading or list item, attached to the node that owns it. */
|
|
5
|
+
export type MdastBlock = BlockContent | DefinitionContent;
|
|
6
|
+
export interface Note {
|
|
7
|
+
block: MdastBlock;
|
|
8
|
+
/** Number of children that precede this block in source order. */
|
|
9
|
+
after: number;
|
|
10
|
+
}
|
|
11
|
+
export interface MarkmapNode {
|
|
12
|
+
/** Stable id derived from the parent path and content. */
|
|
13
|
+
id: string;
|
|
14
|
+
type: 'heading' | 'listItem' | (string & {});
|
|
15
|
+
content: Inline[];
|
|
16
|
+
notes: Note[];
|
|
17
|
+
/** Plugin data such as fold, checked, color or icon. */
|
|
18
|
+
data: Record<string, unknown>;
|
|
19
|
+
/** Source offsets, for click-to-source and graph-to-Markdown editing. */
|
|
20
|
+
position?: {
|
|
21
|
+
start: number;
|
|
22
|
+
end: number;
|
|
23
|
+
};
|
|
24
|
+
children: MarkmapNode[];
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markmap/core",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0-next.0",
|
|
4
|
+
"description": "Markmap types, tree utilities and plugin contracts",
|
|
5
|
+
"homepage": "https://github.com/markmap/markmap#readme",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/markmap/markmap.git",
|
|
10
|
+
"directory": "packages/core"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@types/hast": "^3.0.5",
|
|
28
|
+
"@types/mdast": "^4.0.4"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
32
|
+
"build:types": "tsc -b",
|
|
33
|
+
"build:js": "vite build"
|
|
34
|
+
}
|
|
6
35
|
}
|