comark-table 0.7.6 → 0.8.1
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/{client-C5bRu9NG.mjs → client-CDdgxJF0.mjs} +4 -60
- package/dist/client.mjs +1 -1
- package/dist/html.mjs +2 -2
- package/dist/index.mjs +1 -433
- package/dist/react.mjs +2 -2
- package/dist/src-BV7IF9ld.mjs +463 -0
- package/dist/style.css +1 -1
- package/dist/theme-WGjRIAmX.mjs +141 -0
- package/dist/vue.mjs +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ The plugin adds every problem to `meta.issues`, so a host can reject a document
|
|
|
42
42
|
|
|
43
43
|
| Renderer | Import |
|
|
44
44
|
| --- | --- |
|
|
45
|
-
| `@comark/html` | `comark-table/html`: the script and styles are inlined
|
|
45
|
+
| `@comark/html` | `comark-table/html`: the script and styles are inlined |
|
|
46
46
|
| `@comark/vue` | `comark-table/vue` |
|
|
47
47
|
| `@comark/react` | `comark-table/react` |
|
|
48
48
|
| `@comark/svelte` | `comark-table/svelte` |
|
|
@@ -1,60 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const tokens = [
|
|
3
|
-
[
|
|
4
|
-
"background",
|
|
5
|
-
"ui-bg",
|
|
6
|
-
"background",
|
|
7
|
-
"#fff",
|
|
8
|
-
"#0a0a0a"
|
|
9
|
-
],
|
|
10
|
-
[
|
|
11
|
-
"foreground",
|
|
12
|
-
"ui-text-highlighted",
|
|
13
|
-
"foreground",
|
|
14
|
-
"#0a0a0a",
|
|
15
|
-
"#fafafa"
|
|
16
|
-
],
|
|
17
|
-
[
|
|
18
|
-
"muted",
|
|
19
|
-
"ui-text-muted",
|
|
20
|
-
"muted-foreground",
|
|
21
|
-
"#737373",
|
|
22
|
-
"#a1a1a1"
|
|
23
|
-
],
|
|
24
|
-
[
|
|
25
|
-
"border",
|
|
26
|
-
"ui-border",
|
|
27
|
-
"border",
|
|
28
|
-
"#e5e5e5",
|
|
29
|
-
"rgb(255 255 255/.1)"
|
|
30
|
-
],
|
|
31
|
-
[
|
|
32
|
-
"accent",
|
|
33
|
-
"ui-bg-elevated",
|
|
34
|
-
"accent",
|
|
35
|
-
"#f5f5f5",
|
|
36
|
-
"#262626"
|
|
37
|
-
],
|
|
38
|
-
[
|
|
39
|
-
"ring",
|
|
40
|
-
"ui-primary",
|
|
41
|
-
"ring",
|
|
42
|
-
"#a1a1a1",
|
|
43
|
-
"#737373"
|
|
44
|
-
]
|
|
45
|
-
];
|
|
46
|
-
const resolve = (scheme) => tokens.map((token) => `--_comark-${token[0]}:var(--comark-${token[0]},var(--${token[1]},var(--${token[2]},${token[scheme]})))`).join(";");
|
|
47
|
-
/**
|
|
48
|
-
* Resolves the `--comark-*` tokens on `selector`, falling back to Nuxt UI's variables, then shadcn/ui's, then shadcn's
|
|
49
|
-
* neutral theme. Nuxt UI's corners are twice its `--ui-radius`, so the radius halves shadcn's to double it back.
|
|
50
|
-
* They resolve on the component rather than `:root`, so a `.dark` class anywhere above it switches the theme, and a
|
|
51
|
-
* `--comark-*` set on any ancestor still inherits down. `:where()` keeps them at zero specificity.
|
|
52
|
-
*/
|
|
53
|
-
function theme(selector) {
|
|
54
|
-
const where = `:where(${selector})`;
|
|
55
|
-
return `${where}{${resolve(3)};--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){${where}{${resolve(4)}}}`;
|
|
56
|
-
}
|
|
57
|
-
//#endregion
|
|
1
|
+
import { n as theme } from "./theme-WGjRIAmX.mjs";
|
|
58
2
|
//#region src/styles.ts
|
|
59
3
|
/**
|
|
60
4
|
* A table that scrolls sideways in place, with a Copy button and a menu of formats above it and headers that sort, on the shared
|
|
@@ -62,7 +6,7 @@ function theme(selector) {
|
|
|
62
6
|
* so any unlayered rule of the host's wins.
|
|
63
7
|
*/
|
|
64
8
|
const css = `@layer comark{${[
|
|
65
|
-
theme(".ctb"),
|
|
9
|
+
theme(".ctb", "shadow"),
|
|
66
10
|
".ctb{margin:1.5rem 0}",
|
|
67
11
|
".ctb-scroll{overflow-x:auto}",
|
|
68
12
|
".ctb-scroll>table{margin:0}",
|
|
@@ -77,7 +21,7 @@ const css = `@layer comark{${[
|
|
|
77
21
|
"@media (hover:hover){.ctb-copy>button:hover,.ctb-menu button:hover{background:var(--_comark-accent);color:var(--_comark-foreground)}}",
|
|
78
22
|
".ctb-copy-main svg+svg,.ctb-copy-main[data-state=done] svg:first-child{display:none}",
|
|
79
23
|
".ctb-copy-main[data-state=done] svg+svg{display:block}",
|
|
80
|
-
".ctb-menu{position:absolute;top:calc(100% + .25rem);right:-1px;z-index:10;display:grid;min-width:15rem;padding:.25rem;border:1px solid var(--_comark-border);border-radius:var(--_comark-radius);background:var(--_comark-background);box-shadow:
|
|
24
|
+
".ctb-menu{position:absolute;top:calc(100% + .25rem);right:-1px;z-index:10;display:grid;min-width:15rem;padding:.25rem;border:1px solid var(--_comark-border);border-radius:var(--_comark-radius);background:var(--_comark-background);box-shadow:var(--_comark-shadow)}",
|
|
81
25
|
".ctb-menu[hidden]{display:none}",
|
|
82
26
|
".ctb-menu button{display:grid;grid-template-columns:.875rem 1fr;gap:.5rem;align-items:start;padding:.375rem .5rem;border:0;border-radius:calc(var(--_comark-radius) - 4px);background:none;color:var(--_comark-foreground);font:inherit;text-align:start;cursor:pointer}",
|
|
83
27
|
".ctb-menu button:focus-visible{outline:none;background:var(--_comark-accent)}",
|
|
@@ -91,7 +35,7 @@ const css = `@layer comark{${[
|
|
|
91
35
|
"html[data-table-ready] .ctb-sort::after{content:'↕';color:var(--_comark-muted);font-weight:400}",
|
|
92
36
|
"html[data-table-ready] [aria-sort='ascending']>.ctb-sort::after{content:'↑';color:inherit}",
|
|
93
37
|
"html[data-table-ready] [aria-sort='descending']>.ctb-sort::after{content:'↓';color:inherit}",
|
|
94
|
-
".ctb-copy>button:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)}",
|
|
38
|
+
".ctb-copy>button:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)!important}",
|
|
95
39
|
".ctb-sort:focus-visible{outline:none;border-radius:calc(var(--_comark-radius) - 4px);box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)!important}",
|
|
96
40
|
"@media print{.ctb-bar{display:none}.ctb-sort::after{content:none!important}}"
|
|
97
41
|
].join("")}}`;
|
package/dist/client.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as bar, r as css, n as start } from "./client-
|
|
1
|
+
export { t as bar, r as css, n as start } from "./client-CDdgxJF0.mjs";
|
package/dist/html.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import src_default,
|
|
2
|
-
import { n as start, r as css, t as bar } from "./client-
|
|
1
|
+
import { n as src_default, t as TABLE_TAG } from "./src-BV7IF9ld.mjs";
|
|
2
|
+
import { n as start, r as css, t as bar } from "./client-CDdgxJF0.mjs";
|
|
3
3
|
//#region src/html.ts
|
|
4
4
|
const started = /* @__PURE__ */ new WeakSet();
|
|
5
5
|
/** The table script as one inline `<script>`, once per render. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,433 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { resolveAttributes } from "comark/render";
|
|
3
|
-
//#region src/index.ts
|
|
4
|
-
/** The tag the plugin's tables carry, since `::table` shares its name with every Markdown table. */
|
|
5
|
-
const TABLE_TAG = "data-table";
|
|
6
|
-
const FORMATS = /* @__PURE__ */ new Set([
|
|
7
|
-
"csv",
|
|
8
|
-
"tsv",
|
|
9
|
-
"json"
|
|
10
|
-
]);
|
|
11
|
-
const NUMBER = /^[+-]?(?:\d[\d,]*(?:\.\d+)?|\.\d+)(?:\s*\S{1,4})?$/;
|
|
12
|
-
const isElement = (node) => Array.isArray(node) && node[0] !== null;
|
|
13
|
-
const childrenOf = ([, , ...rest]) => rest;
|
|
14
|
-
const isContent = (node) => Array.isArray(node) ? node[0] !== null : node.trim() !== "";
|
|
15
|
-
const textOf = (node) => !Array.isArray(node) ? node : isElement(node) ? childrenOf(node).map(textOf).join("") : "";
|
|
16
|
-
const text = (value) => value === void 0 || value === null || value instanceof Object ? "" : String(value).trim();
|
|
17
|
-
const on = (value) => value !== void 0 && value !== false && text(value) !== "false";
|
|
18
|
-
const errorText = (error) => error instanceof Error ? error.message : String(error);
|
|
19
|
-
const isMarkdownTable = (node) => node[0] === "table" && childrenOf(node).some((child) => isElement(child) && child[0] === "thead");
|
|
20
|
-
const PROPS = [
|
|
21
|
-
"src",
|
|
22
|
-
"format",
|
|
23
|
-
"sort",
|
|
24
|
-
"copy"
|
|
25
|
-
];
|
|
26
|
-
const isBlock = (node) => node[0] === "table" && (!isMarkdownTable(node) || Object.keys(node[1]).some((key) => PROPS.includes(key.replace(/^:/, ""))));
|
|
27
|
-
const TONES = {
|
|
28
|
-
success: "var(--ui-success,#16a34a)",
|
|
29
|
-
warning: "var(--ui-warning,#ca8a04)",
|
|
30
|
-
error: "var(--ui-error,var(--destructive,#dc2626))",
|
|
31
|
-
info: "var(--ui-info,#2563eb)"
|
|
32
|
-
};
|
|
33
|
-
const isKnownTone = (tone) => Object.hasOwn(TONES, tone);
|
|
34
|
-
const TONED = /^\[(.*)\]\{\s*tone=(?:"([^"]*)"|'([^']*)'|([^\s}]*))\s*\}$/;
|
|
35
|
-
const isTone = (node) => isElement(node) && node[0] === "span" && Object.hasOwn(node[1], "tone");
|
|
36
|
-
const cellNode = (cell) => {
|
|
37
|
-
const toned = TONED.exec(cell);
|
|
38
|
-
return toned ? [
|
|
39
|
-
"span",
|
|
40
|
-
{ tone: toned[2] ?? toned[3] ?? toned[4] },
|
|
41
|
-
toned[1] ?? ""
|
|
42
|
-
] : cell;
|
|
43
|
-
};
|
|
44
|
-
const cellText = (cell) => TONED.exec(cell)?.[1] ?? cell;
|
|
45
|
-
/** RFC 4180: quoted cells may hold the separator, line breaks, and `""` for a quote. */
|
|
46
|
-
function delimited(source, separator) {
|
|
47
|
-
const rows = [];
|
|
48
|
-
let row = [];
|
|
49
|
-
let cell = "";
|
|
50
|
-
let quoted = false;
|
|
51
|
-
for (let index = 0; index < source.length; index++) {
|
|
52
|
-
const char = source[index];
|
|
53
|
-
if (quoted) {
|
|
54
|
-
if (char !== "\"") cell += char;
|
|
55
|
-
else if (source[index + 1] === "\"") {
|
|
56
|
-
cell += "\"";
|
|
57
|
-
index++;
|
|
58
|
-
} else quoted = false;
|
|
59
|
-
} else if (char === "\"" && !cell.trim()) {
|
|
60
|
-
quoted = true;
|
|
61
|
-
cell = "";
|
|
62
|
-
} else if (char === separator) {
|
|
63
|
-
row.push(cell.trim());
|
|
64
|
-
cell = "";
|
|
65
|
-
} else if (char === "\n") {
|
|
66
|
-
row.push(cell.trim());
|
|
67
|
-
rows.push(row);
|
|
68
|
-
row = [];
|
|
69
|
-
cell = "";
|
|
70
|
-
} else if (char !== "\r") cell += char;
|
|
71
|
-
}
|
|
72
|
-
if (quoted) return void 0;
|
|
73
|
-
row.push(cell.trim());
|
|
74
|
-
rows.push(row);
|
|
75
|
-
return rows.filter((cells) => cells.some(Boolean));
|
|
76
|
-
}
|
|
77
|
-
/** TSV is tab-separated text without CSV's quote syntax; quotes are data. */
|
|
78
|
-
function tsv(source) {
|
|
79
|
-
return source.split("\n").map((line) => line.split(" ").map((cell) => cell.trim())).filter((cells) => cells.some(Boolean));
|
|
80
|
-
}
|
|
81
|
-
const value = (item) => item === null || item === void 0 ? "" : item instanceof Object ? JSON.stringify(item) : text(item);
|
|
82
|
-
/** The header and rows of a file or a fence, or why there are none. */
|
|
83
|
-
function rowsOf(source, format) {
|
|
84
|
-
if (format === "json") {
|
|
85
|
-
let parsed;
|
|
86
|
-
try {
|
|
87
|
-
parsed = JSON.parse(source);
|
|
88
|
-
} catch (error) {
|
|
89
|
-
return { issue: {
|
|
90
|
-
rule: "comark/table/invalid-json",
|
|
91
|
-
message: `its JSON doesn't parse: ${errorText(error)}. Fix the JSON.`
|
|
92
|
-
} };
|
|
93
|
-
}
|
|
94
|
-
if (!Array.isArray(parsed) || !parsed.every((item) => item instanceof Object && !Array.isArray(item))) return { issue: {
|
|
95
|
-
rule: "comark/table/json-not-rows",
|
|
96
|
-
message: "its JSON must be an array of objects, one per row, such as `[{\"name\": \"a\", \"size\": 1}]`."
|
|
97
|
-
} };
|
|
98
|
-
const items = parsed;
|
|
99
|
-
const columns = [...new Set(items.flatMap((item) => Object.keys(item)))];
|
|
100
|
-
return { rows: [columns, ...items.map((item) => columns.map((column) => value(item[column])))] };
|
|
101
|
-
}
|
|
102
|
-
const normalized = source.replace(/\r\n?/g, "\n");
|
|
103
|
-
const rows = format === "tsv" ? tsv(normalized) : delimited(normalized, ",");
|
|
104
|
-
return rows ? { rows } : { issue: {
|
|
105
|
-
rule: "comark/table/unclosed-quote",
|
|
106
|
-
message: "a quoted cell never ends. Close it with `\"`, and write a quote inside one as `\"\"`."
|
|
107
|
-
} };
|
|
108
|
-
}
|
|
109
|
-
function build(rows, sort, name, issues) {
|
|
110
|
-
const [header = [], ...body] = rows;
|
|
111
|
-
if (!header.length || !body.length) {
|
|
112
|
-
issues.push({
|
|
113
|
-
rule: "comark/table/no-rows",
|
|
114
|
-
message: `${name} has no rows. Give it a header row and at least one row of data.`
|
|
115
|
-
});
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const uneven = body.findIndex((row) => row.length !== header.length);
|
|
119
|
-
if (uneven !== -1) issues.push({
|
|
120
|
-
rule: "comark/table/uneven-row",
|
|
121
|
-
message: `${name}: row ${uneven + 1} has ${body[uneven]?.length} cells, but the header has ${header.length}. Give every row one cell per column.`
|
|
122
|
-
});
|
|
123
|
-
const numeric = header.map((_, column) => body.every((row) => !row[column] || NUMBER.test(cellText(row[column]))));
|
|
124
|
-
const style = (column) => numeric[column] ? { style: "text-align:right" } : {};
|
|
125
|
-
const table = [
|
|
126
|
-
"table",
|
|
127
|
-
{},
|
|
128
|
-
[
|
|
129
|
-
"thead",
|
|
130
|
-
{},
|
|
131
|
-
[
|
|
132
|
-
"tr",
|
|
133
|
-
{},
|
|
134
|
-
...header.map((label, column) => [
|
|
135
|
-
"th",
|
|
136
|
-
style(column),
|
|
137
|
-
cellNode(label)
|
|
138
|
-
])
|
|
139
|
-
]
|
|
140
|
-
],
|
|
141
|
-
[
|
|
142
|
-
"tbody",
|
|
143
|
-
{},
|
|
144
|
-
...body.map((row) => [
|
|
145
|
-
"tr",
|
|
146
|
-
{},
|
|
147
|
-
...header.map((_, column) => [
|
|
148
|
-
"td",
|
|
149
|
-
style(column),
|
|
150
|
-
cellNode(row[column] ?? "")
|
|
151
|
-
])
|
|
152
|
-
])
|
|
153
|
-
]
|
|
154
|
-
];
|
|
155
|
-
color(table, name, issues);
|
|
156
|
-
if (sort) sortable(table);
|
|
157
|
-
return table;
|
|
158
|
-
}
|
|
159
|
-
/** Takes `tone` off every span under `node`, unwrapping a span it leaves bare, and returns those spans. */
|
|
160
|
-
function takeTones(node) {
|
|
161
|
-
const spans = [];
|
|
162
|
-
const children = childrenOf(node).flatMap((child) => {
|
|
163
|
-
if (!isElement(child)) return [child];
|
|
164
|
-
spans.push(...takeTones(child));
|
|
165
|
-
if (!isTone(child)) return [child];
|
|
166
|
-
spans.push(child);
|
|
167
|
-
const { tone: _, ...rest } = child[1];
|
|
168
|
-
return Object.keys(rest).length ? [[
|
|
169
|
-
child[0],
|
|
170
|
-
rest,
|
|
171
|
-
...childrenOf(child)
|
|
172
|
-
]] : childrenOf(child);
|
|
173
|
-
});
|
|
174
|
-
node.splice(2, node.length - 2, ...children);
|
|
175
|
-
return spans;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Tints each cell that is one `[text]{tone="error"}` span, and each row whose first cell is, so the span itself never
|
|
179
|
-
* renders. The tint goes on every cell rather than the row, since a page's striped rows paint their cells.
|
|
180
|
-
*/
|
|
181
|
-
function color(table, name, issues) {
|
|
182
|
-
let index = 0;
|
|
183
|
-
for (const section of childrenOf(table)) {
|
|
184
|
-
if (!isElement(section) || section[0] !== "thead" && section[0] !== "tbody") continue;
|
|
185
|
-
for (const row of childrenOf(section)) {
|
|
186
|
-
if (!isElement(row) || row[0] !== "tr") continue;
|
|
187
|
-
const where = section[0] === "thead" ? `${name}, header row` : `${name}, row ${++index}`;
|
|
188
|
-
const cells = childrenOf(row).filter((cell) => isElement(cell) && (cell[0] === "td" || cell[0] === "th"));
|
|
189
|
-
const tones = cells.map((cell) => {
|
|
190
|
-
const content = childrenOf(cell).filter(isContent);
|
|
191
|
-
const [only] = content;
|
|
192
|
-
const filling = content.length === 1 && only && isTone(only) ? only : void 0;
|
|
193
|
-
let found;
|
|
194
|
-
let invalid = false;
|
|
195
|
-
for (const span of takeTones(cell)) {
|
|
196
|
-
const tone = text(span[1].tone);
|
|
197
|
-
if (span !== filling) {
|
|
198
|
-
invalid = true;
|
|
199
|
-
issues.push({
|
|
200
|
-
rule: "comark/table/partial-tone",
|
|
201
|
-
message: `${where}: the \`tone\` span "[${textOf(span)}]" doesn't fill its cell, so the cell isn't colored. Put the whole cell in the brackets, as \`[${textOf(cell)}]{tone="${tone}"}\`, or drop \`{tone}\`.`
|
|
202
|
-
});
|
|
203
|
-
} else if (!isKnownTone(tone)) {
|
|
204
|
-
invalid = true;
|
|
205
|
-
issues.push({
|
|
206
|
-
rule: "comark/table/unknown-tone",
|
|
207
|
-
message: `${where}: "${tone}" isn't a tone, so the cell isn't colored. Use \`success\`, \`warning\`, \`error\`, or \`info\`.`
|
|
208
|
-
});
|
|
209
|
-
} else found = tone;
|
|
210
|
-
}
|
|
211
|
-
return invalid ? null : found;
|
|
212
|
-
});
|
|
213
|
-
cells.forEach((cell, column) => {
|
|
214
|
-
const tone = tones[column] === void 0 ? tones[0] : tones[column];
|
|
215
|
-
if (!tone) return;
|
|
216
|
-
const tint = `background-color:color-mix(in srgb,${TONES[tone]} 20%,transparent)`;
|
|
217
|
-
cell[1] = {
|
|
218
|
-
...cell[1],
|
|
219
|
-
style: [text(cell[1].style), tint].filter(Boolean).join(";")
|
|
220
|
-
};
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
/** Makes a Markdown table's headers sort buttons. */
|
|
226
|
-
function sortable(table) {
|
|
227
|
-
for (const section of childrenOf(table)) if (isElement(section) && section[0] === "thead") {
|
|
228
|
-
for (const row of childrenOf(section)) if (isElement(row)) {
|
|
229
|
-
for (const th of childrenOf(row)) if (isElement(th) && th[0] === "th") th.splice(2, th.length - 2, [
|
|
230
|
-
"button",
|
|
231
|
-
{
|
|
232
|
-
type: "button",
|
|
233
|
-
class: "ctb-sort"
|
|
234
|
-
},
|
|
235
|
-
...childrenOf(th)
|
|
236
|
-
]);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
function resolvePath(folder, src) {
|
|
241
|
-
const parts = folder ? folder.split("/") : [];
|
|
242
|
-
for (const part of src.split(/[/\\]/)) {
|
|
243
|
-
if (part === "" || part === ".") continue;
|
|
244
|
-
if (part !== "..") parts.push(part);
|
|
245
|
-
else if (parts.pop() === void 0) return void 0;
|
|
246
|
-
}
|
|
247
|
-
return parts.join("/");
|
|
248
|
-
}
|
|
249
|
-
var OutsideRoot = class extends Error {};
|
|
250
|
-
const isMissing = (error) => error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
251
|
-
function diskReader(root) {
|
|
252
|
-
const folder = (root || ".").replace(/[/\\]+$/, "");
|
|
253
|
-
return async (path) => {
|
|
254
|
-
const { readFile, realpath } = await import("node:fs/promises");
|
|
255
|
-
const paths = await import("node:path");
|
|
256
|
-
try {
|
|
257
|
-
const [base, target] = await Promise.all([realpath(folder), realpath(`${folder}/${path}`)]);
|
|
258
|
-
const inside = paths.relative(base, target);
|
|
259
|
-
if (inside === ".." || inside.startsWith(`..${paths.sep}`) || paths.isAbsolute(inside)) throw new OutsideRoot();
|
|
260
|
-
return await readFile(target, "utf8");
|
|
261
|
-
} catch (error) {
|
|
262
|
-
if (isMissing(error)) return void 0;
|
|
263
|
-
throw error;
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
async function fromFile(src, name, run) {
|
|
268
|
-
const fail = (rule, issue) => void run.issues.push({
|
|
269
|
-
rule,
|
|
270
|
-
message: `${name}: ${issue}`
|
|
271
|
-
});
|
|
272
|
-
if (/^[a-z][a-z\d+.-]*:/i.test(src)) return fail("comark/table/remote-src", "`src` must be a local file; a table can't read a URL.");
|
|
273
|
-
const path = resolvePath(src.startsWith("/") ? "" : run.base, src);
|
|
274
|
-
const leaves = "the path leaves the root. Move the file under the root or widen `root`.";
|
|
275
|
-
if (path === void 0) return fail("comark/table/path-outside-root", leaves);
|
|
276
|
-
let source;
|
|
277
|
-
try {
|
|
278
|
-
source = await run.read(path);
|
|
279
|
-
} catch (error) {
|
|
280
|
-
if (error instanceof OutsideRoot) return fail("comark/table/path-outside-root", leaves);
|
|
281
|
-
return fail("comark/table/read-failed", `reading "${path}" failed: ${errorText(error)}. Check the file or the \`read\` option.`);
|
|
282
|
-
}
|
|
283
|
-
if (source === void 0) return fail("comark/table/file-not-found", `file not found. It resolved to "${path}" under the root; check the path or \`base\`.`);
|
|
284
|
-
return {
|
|
285
|
-
source,
|
|
286
|
-
format: path.slice(path.lastIndexOf(".") + 1).toLowerCase()
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
async function block(node, number, data, run) {
|
|
290
|
-
const attrs = resolveAttributes(node[1], data, { parseJson: true });
|
|
291
|
-
const src = text(attrs.src);
|
|
292
|
-
const name = `\`::table\` ${number}${src ? ` ("${src}")` : ""}`;
|
|
293
|
-
for (const key of Object.keys(node[1])) {
|
|
294
|
-
const prop = key.replace(/^:/, "");
|
|
295
|
-
if (!PROPS.includes(prop) && prop !== "id" && prop !== "class") run.issues.push({
|
|
296
|
-
rule: "comark/table/unknown-prop",
|
|
297
|
-
message: `${name}: \`${prop}\` isn't a prop of \`::table\`, so it's ignored. Use \`src\`, \`format\`, \`sort\`, or \`copy\`.`
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
const sort = on(attrs.sort);
|
|
301
|
-
const result = (table) => table ? [
|
|
302
|
-
TABLE_TAG,
|
|
303
|
-
{
|
|
304
|
-
sort,
|
|
305
|
-
copy: attrs.copy === void 0 || on(attrs.copy)
|
|
306
|
-
},
|
|
307
|
-
table
|
|
308
|
-
] : ["div", {}];
|
|
309
|
-
if (!src && isMarkdownTable(node)) {
|
|
310
|
-
const inner = [
|
|
311
|
-
"table",
|
|
312
|
-
{},
|
|
313
|
-
...childrenOf(node)
|
|
314
|
-
];
|
|
315
|
-
if (sort) sortable(inner);
|
|
316
|
-
return result(inner);
|
|
317
|
-
}
|
|
318
|
-
const content = childrenOf(node).filter(isContent);
|
|
319
|
-
if (src && content.length) run.issues.push({
|
|
320
|
-
rule: "comark/table/content-dropped",
|
|
321
|
-
message: `${name} drops what's inside it, since it reads \`src\`. Use one or the other.`
|
|
322
|
-
});
|
|
323
|
-
let source;
|
|
324
|
-
let format = text(attrs.format).toLowerCase();
|
|
325
|
-
if (src) {
|
|
326
|
-
const file = await fromFile(src, name, run);
|
|
327
|
-
if (!file) return result(void 0);
|
|
328
|
-
source = file.source;
|
|
329
|
-
format ||= file.format;
|
|
330
|
-
} else {
|
|
331
|
-
const [inner] = content;
|
|
332
|
-
const fence = content.length === 1 && isElement(inner) && inner[0] === "pre" ? inner : void 0;
|
|
333
|
-
if (!fence) {
|
|
334
|
-
run.issues.push({
|
|
335
|
-
rule: "comark/table/empty-block",
|
|
336
|
-
message: `${name} has nothing to show. Give it \`src="data.csv"\`, or put one Markdown table or one csv, tsv, or json fence inside it.`
|
|
337
|
-
});
|
|
338
|
-
return result(void 0);
|
|
339
|
-
}
|
|
340
|
-
source = textOf(fence);
|
|
341
|
-
format ||= text(fence[1].language).toLowerCase();
|
|
342
|
-
}
|
|
343
|
-
if (!FORMATS.has(format)) {
|
|
344
|
-
run.issues.push({
|
|
345
|
-
rule: "comark/table/unknown-format",
|
|
346
|
-
message: `${name}: can't read ${format ? `"${format}"` : "data without a format"}. Use a .csv, .tsv, or .json file or fence, or set \`format\`.`
|
|
347
|
-
});
|
|
348
|
-
return result(void 0);
|
|
349
|
-
}
|
|
350
|
-
const read = rowsOf(source, format);
|
|
351
|
-
if ("issue" in read) {
|
|
352
|
-
run.issues.push({
|
|
353
|
-
rule: read.issue.rule,
|
|
354
|
-
message: `${name}: ${read.issue.message}`
|
|
355
|
-
});
|
|
356
|
-
return result(void 0);
|
|
357
|
-
}
|
|
358
|
-
return result(build(read.rows, sort, name, run.issues));
|
|
359
|
-
}
|
|
360
|
-
/**
|
|
361
|
-
* Turns `::table{src="bench.csv"}`, a `::table` around a csv, tsv, or json fence or a Markdown table, and every bare
|
|
362
|
-
* ` ```csv ` or ` ```tsv ` fence into a table while parsing, with buttons that copy it as CSV or Markdown and, with
|
|
363
|
-
* `sort`, headers that sort it. Problems are added to `meta.issues`.
|
|
364
|
-
*/
|
|
365
|
-
var src_default = defineComarkPlugin((options = {}) => ({
|
|
366
|
-
name: "comark-table",
|
|
367
|
-
async post(state) {
|
|
368
|
-
const { tree } = state;
|
|
369
|
-
const data = {
|
|
370
|
-
frontmatter: tree.frontmatter,
|
|
371
|
-
meta: tree.meta,
|
|
372
|
-
data: {},
|
|
373
|
-
props: {}
|
|
374
|
-
};
|
|
375
|
-
const base = resolvePath("", options.base ?? "");
|
|
376
|
-
const run = {
|
|
377
|
-
read: options.read ?? diskReader(options.root ?? "."),
|
|
378
|
-
base: base ?? "",
|
|
379
|
-
issues: []
|
|
380
|
-
};
|
|
381
|
-
if (base === void 0) run.issues.push({
|
|
382
|
-
rule: "comark/table/base-outside-root",
|
|
383
|
-
message: `table: \`base\` "${options.base ?? ""}" leaves the root. Set it to the document's folder under \`root\`.`
|
|
384
|
-
});
|
|
385
|
-
let tables = 0;
|
|
386
|
-
const tint = (nodes) => {
|
|
387
|
-
for (const node of nodes) {
|
|
388
|
-
if (!isElement(node) || node[0] === "pre" || node[0] === "code") continue;
|
|
389
|
-
if (isMarkdownTable(node)) color(node, `Markdown table ${++tables}`, run.issues);
|
|
390
|
-
else tint(childrenOf(node));
|
|
391
|
-
}
|
|
392
|
-
};
|
|
393
|
-
tint(tree.nodes);
|
|
394
|
-
const count = {
|
|
395
|
-
block: 0,
|
|
396
|
-
fence: 0
|
|
397
|
-
};
|
|
398
|
-
const walk = async (nodes, start) => {
|
|
399
|
-
for (let index = start; index < nodes.length; index++) {
|
|
400
|
-
const node = nodes[index];
|
|
401
|
-
if (!node || !isElement(node)) continue;
|
|
402
|
-
const [tag, attrs] = node;
|
|
403
|
-
const language = text(attrs.language).toLowerCase();
|
|
404
|
-
if (isBlock(node)) nodes[index] = await block(node, ++count.block, data, run);
|
|
405
|
-
else if (tag === "pre" && options.fences !== false && (language === "csv" || language === "tsv")) {
|
|
406
|
-
const name = `The \`\`\`${language} fence ${++count.fence}`;
|
|
407
|
-
const read = rowsOf(textOf(node), language);
|
|
408
|
-
if ("issue" in read) run.issues.push({
|
|
409
|
-
rule: read.issue.rule,
|
|
410
|
-
message: `${name}: ${read.issue.message}`
|
|
411
|
-
});
|
|
412
|
-
const table = "issue" in read ? void 0 : build(read.rows, false, name, run.issues);
|
|
413
|
-
if (table) nodes[index] = [
|
|
414
|
-
TABLE_TAG,
|
|
415
|
-
{
|
|
416
|
-
sort: false,
|
|
417
|
-
copy: true
|
|
418
|
-
},
|
|
419
|
-
table
|
|
420
|
-
];
|
|
421
|
-
} else if (tag !== "pre" && tag !== "code") await walk(node, 2);
|
|
422
|
-
}
|
|
423
|
-
};
|
|
424
|
-
await walk(tree.nodes, 0);
|
|
425
|
-
tree.meta.issues = [...tree.meta.issues ?? [], ...run.issues.map((issue) => issue.message)];
|
|
426
|
-
tree.meta.issueRules = {
|
|
427
|
-
...tree.meta.issueRules,
|
|
428
|
-
...Object.fromEntries(run.issues.map((issue) => [issue.message, issue.rule]))
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
}));
|
|
432
|
-
//#endregion
|
|
433
|
-
export { TABLE_TAG, src_default as default };
|
|
1
|
+
export { t as TABLE_TAG, n as default } from "./src-BV7IF9ld.mjs";
|
package/dist/react.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import src_default,
|
|
2
|
-
import { n as start, r as css, t as bar } from "./client-
|
|
1
|
+
import { n as src_default, t as TABLE_TAG } from "./src-BV7IF9ld.mjs";
|
|
2
|
+
import { n as start, r as css, t as bar } from "./client-CDdgxJF0.mjs";
|
|
3
3
|
import { createElement, useEffect } from "react";
|
|
4
4
|
//#region src/react.ts
|
|
5
5
|
/** Tables for `@comark/react`. */
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { t as statusColor } from "./theme-WGjRIAmX.mjs";
|
|
2
|
+
import { defineComarkPlugin } from "comark";
|
|
3
|
+
import { resolveAttributes } from "comark/render";
|
|
4
|
+
//#region ../../shared/text.ts
|
|
5
|
+
/**
|
|
6
|
+
* A prop value as text. Comark writes strings, numbers, and booleans, so an object, array, function, or missing value
|
|
7
|
+
* is no text. The `typeof` check, unlike `instanceof`, can't throw on a revoked proxy.
|
|
8
|
+
*/
|
|
9
|
+
function scalar(value) {
|
|
10
|
+
switch (typeof value) {
|
|
11
|
+
case "string": return value;
|
|
12
|
+
case "number":
|
|
13
|
+
case "boolean":
|
|
14
|
+
case "bigint":
|
|
15
|
+
case "symbol": return String(value);
|
|
16
|
+
default: return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** A prop value as text, without its surrounding whitespace, or empty when it has none. */
|
|
20
|
+
const text = (value) => scalar(value)?.trim() ?? "";
|
|
21
|
+
const errorText = (error) => error instanceof Error ? error.message : String(error);
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region ../../shared/nodes.ts
|
|
24
|
+
const isElement = (node) => Array.isArray(node) && node[0] !== null;
|
|
25
|
+
const childrenOf = ([, , ...rest]) => rest;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region ../../shared/disk.ts
|
|
28
|
+
var OutsideRoot = class extends Error {};
|
|
29
|
+
const isMissing = (error) => error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
30
|
+
/**
|
|
31
|
+
* Joins `src` onto `folder` as strings, so the root check runs the same in Node, browsers, and edge runtimes. Returns
|
|
32
|
+
* undefined when `src` climbs out of the folder.
|
|
33
|
+
*/
|
|
34
|
+
function resolvePath(folder, src) {
|
|
35
|
+
const parts = folder ? folder.split("/") : [];
|
|
36
|
+
for (const part of src.split(/[/\\]/)) {
|
|
37
|
+
if (part === "" || part === ".") continue;
|
|
38
|
+
if (part !== "..") parts.push(part);
|
|
39
|
+
else if (parts.pop() === void 0) return void 0;
|
|
40
|
+
}
|
|
41
|
+
return parts.join("/");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Reads files under `root` from disk, and undefined for one that isn't there. Throws `OutsideRoot` for one whose real
|
|
45
|
+
* path leaves the root, which the string check in `resolvePath` can't see through a symlink.
|
|
46
|
+
*/
|
|
47
|
+
function diskReader(root) {
|
|
48
|
+
const folder = (root || ".").replace(/[/\\]+$/, "");
|
|
49
|
+
return async (path) => {
|
|
50
|
+
const { readFile, realpath } = await import("node:fs/promises");
|
|
51
|
+
const paths = await import("node:path");
|
|
52
|
+
try {
|
|
53
|
+
const [base, target] = await Promise.all([realpath(folder), realpath(`${folder}/${path}`)]);
|
|
54
|
+
const inside = paths.relative(base, target);
|
|
55
|
+
if (inside === ".." || inside.startsWith(`..${paths.sep}`) || paths.isAbsolute(inside)) throw new OutsideRoot();
|
|
56
|
+
return await readFile(target, "utf8");
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (isMissing(error)) return void 0;
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/index.ts
|
|
65
|
+
/** The tag the plugin's tables carry, since `::table` shares its name with every Markdown table. */
|
|
66
|
+
const TABLE_TAG = "data-table";
|
|
67
|
+
const FORMATS = /* @__PURE__ */ new Set([
|
|
68
|
+
"csv",
|
|
69
|
+
"tsv",
|
|
70
|
+
"json"
|
|
71
|
+
]);
|
|
72
|
+
const NUMBER = /^[+-]?(?:\d[\d,]*(?:\.\d+)?|\.\d+)(?:\s*\S{1,4})?$/;
|
|
73
|
+
const isContent = (node) => Array.isArray(node) ? node[0] !== null : node.trim() !== "";
|
|
74
|
+
const textOf = (node) => !Array.isArray(node) ? node : isElement(node) ? childrenOf(node).map(textOf).join("") : "";
|
|
75
|
+
const on = (value) => value !== void 0 && value !== false && text(value) !== "false";
|
|
76
|
+
const isMarkdownTable = (node) => node[0] === "table" && childrenOf(node).some((child) => isElement(child) && child[0] === "thead");
|
|
77
|
+
const PROPS = [
|
|
78
|
+
"src",
|
|
79
|
+
"format",
|
|
80
|
+
"sort",
|
|
81
|
+
"copy"
|
|
82
|
+
];
|
|
83
|
+
const isBlock = (node) => node[0] === "table" && (!isMarkdownTable(node) || Object.keys(node[1]).some((key) => PROPS.includes(key.replace(/^:/, ""))));
|
|
84
|
+
const TONES = {
|
|
85
|
+
success: statusColor("success"),
|
|
86
|
+
warning: statusColor("warning"),
|
|
87
|
+
error: statusColor("danger"),
|
|
88
|
+
info: statusColor("info")
|
|
89
|
+
};
|
|
90
|
+
const isKnownTone = (tone) => Object.hasOwn(TONES, tone);
|
|
91
|
+
const TONED = /^\[(.*)\]\{\s*tone=(?:"([^"]*)"|'([^']*)'|([^\s}]*))\s*\}$/;
|
|
92
|
+
const isTone = (node) => isElement(node) && node[0] === "span" && Object.hasOwn(node[1], "tone");
|
|
93
|
+
const cellNode = (cell) => {
|
|
94
|
+
const toned = TONED.exec(cell);
|
|
95
|
+
return toned ? [
|
|
96
|
+
"span",
|
|
97
|
+
{ tone: toned[2] ?? toned[3] ?? toned[4] },
|
|
98
|
+
toned[1] ?? ""
|
|
99
|
+
] : cell;
|
|
100
|
+
};
|
|
101
|
+
const cellText = (cell) => TONED.exec(cell)?.[1] ?? cell;
|
|
102
|
+
/** RFC 4180: quoted cells may hold the separator, line breaks, and `""` for a quote. */
|
|
103
|
+
function delimited(source, separator) {
|
|
104
|
+
const rows = [];
|
|
105
|
+
let row = [];
|
|
106
|
+
let cell = "";
|
|
107
|
+
let quoted = false;
|
|
108
|
+
for (let index = 0; index < source.length; index++) {
|
|
109
|
+
const char = source[index];
|
|
110
|
+
if (quoted) {
|
|
111
|
+
if (char !== "\"") cell += char;
|
|
112
|
+
else if (source[index + 1] === "\"") {
|
|
113
|
+
cell += "\"";
|
|
114
|
+
index++;
|
|
115
|
+
} else quoted = false;
|
|
116
|
+
} else if (char === "\"" && !cell.trim()) {
|
|
117
|
+
quoted = true;
|
|
118
|
+
cell = "";
|
|
119
|
+
} else if (char === separator) {
|
|
120
|
+
row.push(cell.trim());
|
|
121
|
+
cell = "";
|
|
122
|
+
} else if (char === "\n") {
|
|
123
|
+
row.push(cell.trim());
|
|
124
|
+
rows.push(row);
|
|
125
|
+
row = [];
|
|
126
|
+
cell = "";
|
|
127
|
+
} else if (char !== "\r") cell += char;
|
|
128
|
+
}
|
|
129
|
+
if (quoted) return void 0;
|
|
130
|
+
row.push(cell.trim());
|
|
131
|
+
rows.push(row);
|
|
132
|
+
return rows.filter((cells) => cells.some(Boolean));
|
|
133
|
+
}
|
|
134
|
+
/** TSV is tab-separated text without CSV's quote syntax; quotes are data. */
|
|
135
|
+
function tsv(source) {
|
|
136
|
+
return source.split("\n").map((line) => line.split(" ").map((cell) => cell.trim())).filter((cells) => cells.some(Boolean));
|
|
137
|
+
}
|
|
138
|
+
const value = (item) => item === null || item === void 0 ? "" : item instanceof Object ? JSON.stringify(item) : text(item);
|
|
139
|
+
/** The header and rows of a file or a fence, or why there are none. */
|
|
140
|
+
function rowsOf(source, format) {
|
|
141
|
+
if (format === "json") {
|
|
142
|
+
let parsed;
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(source);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
return { issue: {
|
|
147
|
+
rule: "comark/table/invalid-json",
|
|
148
|
+
message: `its JSON doesn't parse: ${errorText(error)}. Fix the JSON.`
|
|
149
|
+
} };
|
|
150
|
+
}
|
|
151
|
+
if (!Array.isArray(parsed) || !parsed.every((item) => item instanceof Object && !Array.isArray(item))) return { issue: {
|
|
152
|
+
rule: "comark/table/json-not-rows",
|
|
153
|
+
message: "its JSON must be an array of objects, one per row, such as `[{\"name\": \"a\", \"size\": 1}]`."
|
|
154
|
+
} };
|
|
155
|
+
const items = parsed;
|
|
156
|
+
const columns = [...new Set(items.flatMap((item) => Object.keys(item)))];
|
|
157
|
+
return { rows: [columns, ...items.map((item) => columns.map((column) => value(item[column])))] };
|
|
158
|
+
}
|
|
159
|
+
const normalized = source.replace(/\r\n?/g, "\n");
|
|
160
|
+
const rows = format === "tsv" ? tsv(normalized) : delimited(normalized, ",");
|
|
161
|
+
return rows ? { rows } : { issue: {
|
|
162
|
+
rule: "comark/table/unclosed-quote",
|
|
163
|
+
message: "a quoted cell never ends. Close it with `\"`, and write a quote inside one as `\"\"`."
|
|
164
|
+
} };
|
|
165
|
+
}
|
|
166
|
+
function build(rows, sort, name, issues) {
|
|
167
|
+
const [header = [], ...body] = rows;
|
|
168
|
+
if (!header.length || !body.length) {
|
|
169
|
+
issues.push({
|
|
170
|
+
rule: "comark/table/no-rows",
|
|
171
|
+
message: `${name} has no rows. Give it a header row and at least one row of data.`
|
|
172
|
+
});
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const uneven = body.findIndex((row) => row.length !== header.length);
|
|
176
|
+
if (uneven !== -1) issues.push({
|
|
177
|
+
rule: "comark/table/uneven-row",
|
|
178
|
+
message: `${name}: row ${uneven + 1} has ${body[uneven]?.length} cells, but the header has ${header.length}. Give every row one cell per column.`
|
|
179
|
+
});
|
|
180
|
+
const numeric = header.map((_, column) => body.every((row) => !row[column] || NUMBER.test(cellText(row[column]))));
|
|
181
|
+
const style = (column) => numeric[column] ? { style: "text-align:right" } : {};
|
|
182
|
+
const table = [
|
|
183
|
+
"table",
|
|
184
|
+
{},
|
|
185
|
+
[
|
|
186
|
+
"thead",
|
|
187
|
+
{},
|
|
188
|
+
[
|
|
189
|
+
"tr",
|
|
190
|
+
{},
|
|
191
|
+
...header.map((label, column) => [
|
|
192
|
+
"th",
|
|
193
|
+
style(column),
|
|
194
|
+
cellNode(label)
|
|
195
|
+
])
|
|
196
|
+
]
|
|
197
|
+
],
|
|
198
|
+
[
|
|
199
|
+
"tbody",
|
|
200
|
+
{},
|
|
201
|
+
...body.map((row) => [
|
|
202
|
+
"tr",
|
|
203
|
+
{},
|
|
204
|
+
...header.map((_, column) => [
|
|
205
|
+
"td",
|
|
206
|
+
style(column),
|
|
207
|
+
cellNode(row[column] ?? "")
|
|
208
|
+
])
|
|
209
|
+
])
|
|
210
|
+
]
|
|
211
|
+
];
|
|
212
|
+
color(table, name, issues);
|
|
213
|
+
if (sort) sortable(table);
|
|
214
|
+
return table;
|
|
215
|
+
}
|
|
216
|
+
/** Takes `tone` off every span under `node`, unwrapping a span it leaves bare, and returns those spans. */
|
|
217
|
+
function takeTones(node) {
|
|
218
|
+
const spans = [];
|
|
219
|
+
const children = childrenOf(node).flatMap((child) => {
|
|
220
|
+
if (!isElement(child)) return [child];
|
|
221
|
+
spans.push(...takeTones(child));
|
|
222
|
+
if (!isTone(child)) return [child];
|
|
223
|
+
spans.push(child);
|
|
224
|
+
const { tone: _, ...rest } = child[1];
|
|
225
|
+
return Object.keys(rest).length ? [[
|
|
226
|
+
child[0],
|
|
227
|
+
rest,
|
|
228
|
+
...childrenOf(child)
|
|
229
|
+
]] : childrenOf(child);
|
|
230
|
+
});
|
|
231
|
+
node.splice(2, node.length - 2, ...children);
|
|
232
|
+
return spans;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Tints each cell that is one `[text]{tone="error"}` span, and each row whose first cell is, so the span itself never
|
|
236
|
+
* renders. The tint goes on every cell rather than the row, since a page's striped rows paint their cells.
|
|
237
|
+
*/
|
|
238
|
+
function color(table, name, issues) {
|
|
239
|
+
let index = 0;
|
|
240
|
+
for (const section of childrenOf(table)) {
|
|
241
|
+
if (!isElement(section) || section[0] !== "thead" && section[0] !== "tbody") continue;
|
|
242
|
+
for (const row of childrenOf(section)) {
|
|
243
|
+
if (!isElement(row) || row[0] !== "tr") continue;
|
|
244
|
+
const where = section[0] === "thead" ? `${name}, header row` : `${name}, row ${++index}`;
|
|
245
|
+
const cells = childrenOf(row).filter((cell) => isElement(cell) && (cell[0] === "td" || cell[0] === "th"));
|
|
246
|
+
const tones = cells.map((cell) => {
|
|
247
|
+
const content = childrenOf(cell).filter(isContent);
|
|
248
|
+
const [only] = content;
|
|
249
|
+
const filling = content.length === 1 && only && isTone(only) ? only : void 0;
|
|
250
|
+
let found;
|
|
251
|
+
let invalid = false;
|
|
252
|
+
for (const span of takeTones(cell)) {
|
|
253
|
+
const tone = text(span[1].tone);
|
|
254
|
+
if (span !== filling) {
|
|
255
|
+
invalid = true;
|
|
256
|
+
issues.push({
|
|
257
|
+
rule: "comark/table/partial-tone",
|
|
258
|
+
message: `${where}: the \`tone\` span "[${textOf(span)}]" doesn't fill its cell, so the cell isn't colored. Put the whole cell in the brackets, as \`[${textOf(cell)}]{tone="${tone}"}\`, or drop \`{tone}\`.`
|
|
259
|
+
});
|
|
260
|
+
} else if (!isKnownTone(tone)) {
|
|
261
|
+
invalid = true;
|
|
262
|
+
issues.push({
|
|
263
|
+
rule: "comark/table/unknown-tone",
|
|
264
|
+
message: `${where}: "${tone}" isn't a tone, so the cell isn't colored. Use \`success\`, \`warning\`, \`error\`, or \`info\`.`
|
|
265
|
+
});
|
|
266
|
+
} else found = tone;
|
|
267
|
+
}
|
|
268
|
+
return invalid ? null : found;
|
|
269
|
+
});
|
|
270
|
+
cells.forEach((cell, column) => {
|
|
271
|
+
const tone = tones[column] === void 0 ? tones[0] : tones[column];
|
|
272
|
+
if (!tone) return;
|
|
273
|
+
const tint = `background-color:color-mix(in srgb,${TONES[tone]} 20%,transparent)`;
|
|
274
|
+
cell[1] = {
|
|
275
|
+
...cell[1],
|
|
276
|
+
style: [text(cell[1].style), tint].filter(Boolean).join(";")
|
|
277
|
+
};
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** Makes a Markdown table's headers sort buttons. */
|
|
283
|
+
function sortable(table) {
|
|
284
|
+
for (const section of childrenOf(table)) if (isElement(section) && section[0] === "thead") {
|
|
285
|
+
for (const row of childrenOf(section)) if (isElement(row)) {
|
|
286
|
+
for (const th of childrenOf(row)) if (isElement(th) && th[0] === "th") th.splice(2, th.length - 2, [
|
|
287
|
+
"button",
|
|
288
|
+
{
|
|
289
|
+
type: "button",
|
|
290
|
+
class: "ctb-sort"
|
|
291
|
+
},
|
|
292
|
+
...childrenOf(th)
|
|
293
|
+
]);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function fromFile(src, name, run) {
|
|
298
|
+
const fail = (rule, issue) => void run.issues.push({
|
|
299
|
+
rule,
|
|
300
|
+
message: `${name}: ${issue}`
|
|
301
|
+
});
|
|
302
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(src)) return fail("comark/table/remote-src", "`src` must be a local file; a table can't read a URL.");
|
|
303
|
+
const path = resolvePath(src.startsWith("/") ? "" : run.base, src);
|
|
304
|
+
const leaves = "the path leaves the root. Move the file under the root or widen `root`.";
|
|
305
|
+
if (path === void 0) return fail("comark/table/path-outside-root", leaves);
|
|
306
|
+
let source;
|
|
307
|
+
try {
|
|
308
|
+
source = await run.read(path);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if (error instanceof OutsideRoot) return fail("comark/table/path-outside-root", leaves);
|
|
311
|
+
return fail("comark/table/read-failed", `reading "${path}" failed: ${errorText(error)}. Check the file or the \`read\` option.`);
|
|
312
|
+
}
|
|
313
|
+
if (source === void 0) return fail("comark/table/file-not-found", `file not found. It resolved to "${path}" under the root; check the path or \`base\`.`);
|
|
314
|
+
return {
|
|
315
|
+
source,
|
|
316
|
+
format: path.slice(path.lastIndexOf(".") + 1).toLowerCase()
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
async function block(node, number, data, run) {
|
|
320
|
+
const attrs = resolveAttributes(node[1], data, { parseJson: true });
|
|
321
|
+
const src = text(attrs.src);
|
|
322
|
+
const name = `\`::table\` ${number}${src ? ` ("${src}")` : ""}`;
|
|
323
|
+
for (const key of Object.keys(node[1])) {
|
|
324
|
+
const prop = key.replace(/^:/, "");
|
|
325
|
+
if (!PROPS.includes(prop) && prop !== "id" && prop !== "class") run.issues.push({
|
|
326
|
+
rule: "comark/table/unknown-prop",
|
|
327
|
+
message: `${name}: \`${prop}\` isn't a prop of \`::table\`, so it's ignored. Use \`src\`, \`format\`, \`sort\`, or \`copy\`.`
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
const sort = on(attrs.sort);
|
|
331
|
+
const result = (table) => table ? [
|
|
332
|
+
TABLE_TAG,
|
|
333
|
+
{
|
|
334
|
+
sort,
|
|
335
|
+
copy: attrs.copy === void 0 || on(attrs.copy)
|
|
336
|
+
},
|
|
337
|
+
table
|
|
338
|
+
] : ["div", {}];
|
|
339
|
+
if (!src && isMarkdownTable(node)) {
|
|
340
|
+
const inner = [
|
|
341
|
+
"table",
|
|
342
|
+
{},
|
|
343
|
+
...childrenOf(node)
|
|
344
|
+
];
|
|
345
|
+
if (sort) sortable(inner);
|
|
346
|
+
return result(inner);
|
|
347
|
+
}
|
|
348
|
+
const content = childrenOf(node).filter(isContent);
|
|
349
|
+
if (src && content.length) run.issues.push({
|
|
350
|
+
rule: "comark/table/content-dropped",
|
|
351
|
+
message: `${name} drops what's inside it, since it reads \`src\`. Use one or the other.`
|
|
352
|
+
});
|
|
353
|
+
let source;
|
|
354
|
+
let format = text(attrs.format).toLowerCase();
|
|
355
|
+
if (src) {
|
|
356
|
+
const file = await fromFile(src, name, run);
|
|
357
|
+
if (!file) return result(void 0);
|
|
358
|
+
source = file.source;
|
|
359
|
+
format ||= file.format;
|
|
360
|
+
} else {
|
|
361
|
+
const [inner] = content;
|
|
362
|
+
const fence = content.length === 1 && isElement(inner) && inner[0] === "pre" ? inner : void 0;
|
|
363
|
+
if (!fence) {
|
|
364
|
+
run.issues.push({
|
|
365
|
+
rule: "comark/table/empty-block",
|
|
366
|
+
message: `${name} has nothing to show. Give it \`src="data.csv"\`, or put one Markdown table or one csv, tsv, or json fence inside it.`
|
|
367
|
+
});
|
|
368
|
+
return result(void 0);
|
|
369
|
+
}
|
|
370
|
+
source = textOf(fence);
|
|
371
|
+
format ||= text(fence[1].language).toLowerCase();
|
|
372
|
+
}
|
|
373
|
+
if (!FORMATS.has(format)) {
|
|
374
|
+
run.issues.push({
|
|
375
|
+
rule: "comark/table/unknown-format",
|
|
376
|
+
message: `${name}: can't read ${format ? `"${format}"` : "data without a format"}. Use a .csv, .tsv, or .json file or fence, or set \`format\`.`
|
|
377
|
+
});
|
|
378
|
+
return result(void 0);
|
|
379
|
+
}
|
|
380
|
+
const read = rowsOf(source, format);
|
|
381
|
+
if ("issue" in read) {
|
|
382
|
+
run.issues.push({
|
|
383
|
+
rule: read.issue.rule,
|
|
384
|
+
message: `${name}: ${read.issue.message}`
|
|
385
|
+
});
|
|
386
|
+
return result(void 0);
|
|
387
|
+
}
|
|
388
|
+
return result(build(read.rows, sort, name, run.issues));
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Turns `::table{src="bench.csv"}`, a `::table` around a csv, tsv, or json fence or a Markdown table, and every bare
|
|
392
|
+
* ` ```csv ` or ` ```tsv ` fence into a table while parsing, with buttons that copy it as CSV or Markdown and, with
|
|
393
|
+
* `sort`, headers that sort it. Problems are added to `meta.issues`.
|
|
394
|
+
*/
|
|
395
|
+
var src_default = defineComarkPlugin((options = {}) => ({
|
|
396
|
+
name: "comark-table",
|
|
397
|
+
async post(state) {
|
|
398
|
+
const { tree } = state;
|
|
399
|
+
const data = {
|
|
400
|
+
frontmatter: tree.frontmatter,
|
|
401
|
+
meta: tree.meta,
|
|
402
|
+
data: {},
|
|
403
|
+
props: {}
|
|
404
|
+
};
|
|
405
|
+
const base = resolvePath("", options.base ?? "");
|
|
406
|
+
const run = {
|
|
407
|
+
read: options.read ?? diskReader(options.root ?? "."),
|
|
408
|
+
base: base ?? "",
|
|
409
|
+
issues: []
|
|
410
|
+
};
|
|
411
|
+
if (base === void 0) run.issues.push({
|
|
412
|
+
rule: "comark/table/base-outside-root",
|
|
413
|
+
message: `table: \`base\` "${options.base ?? ""}" leaves the root. Set it to the document's folder under \`root\`.`
|
|
414
|
+
});
|
|
415
|
+
let tables = 0;
|
|
416
|
+
const tint = (nodes) => {
|
|
417
|
+
for (const node of nodes) {
|
|
418
|
+
if (!isElement(node) || node[0] === "pre" || node[0] === "code") continue;
|
|
419
|
+
if (isMarkdownTable(node)) color(node, `Markdown table ${++tables}`, run.issues);
|
|
420
|
+
else tint(childrenOf(node));
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
tint(tree.nodes);
|
|
424
|
+
const count = {
|
|
425
|
+
block: 0,
|
|
426
|
+
fence: 0
|
|
427
|
+
};
|
|
428
|
+
const walk = async (nodes, start) => {
|
|
429
|
+
for (let index = start; index < nodes.length; index++) {
|
|
430
|
+
const node = nodes[index];
|
|
431
|
+
if (!node || !isElement(node)) continue;
|
|
432
|
+
const [tag, attrs] = node;
|
|
433
|
+
const language = text(attrs.language).toLowerCase();
|
|
434
|
+
if (isBlock(node)) nodes[index] = await block(node, ++count.block, data, run);
|
|
435
|
+
else if (tag === "pre" && options.fences !== false && (language === "csv" || language === "tsv")) {
|
|
436
|
+
const name = `The \`\`\`${language} fence ${++count.fence}`;
|
|
437
|
+
const read = rowsOf(textOf(node), language);
|
|
438
|
+
if ("issue" in read) run.issues.push({
|
|
439
|
+
rule: read.issue.rule,
|
|
440
|
+
message: `${name}: ${read.issue.message}`
|
|
441
|
+
});
|
|
442
|
+
const table = "issue" in read ? void 0 : build(read.rows, false, name, run.issues);
|
|
443
|
+
if (table) nodes[index] = [
|
|
444
|
+
TABLE_TAG,
|
|
445
|
+
{
|
|
446
|
+
sort: false,
|
|
447
|
+
copy: true
|
|
448
|
+
},
|
|
449
|
+
table
|
|
450
|
+
];
|
|
451
|
+
} else if (tag !== "pre" && tag !== "code") await walk(node, 2);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
await walk(tree.nodes, 0);
|
|
455
|
+
tree.meta.issues = [...tree.meta.issues ?? [], ...run.issues.map((issue) => issue.message)];
|
|
456
|
+
tree.meta.issueRules = {
|
|
457
|
+
...tree.meta.issueRules,
|
|
458
|
+
...Object.fromEntries(run.issues.map((issue) => [issue.message, issue.rule]))
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}));
|
|
462
|
+
//#endregion
|
|
463
|
+
export { src_default as n, TABLE_TAG as t };
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
@layer comark{:where(.ctb){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#fff)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#0a0a0a)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#737373)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,#e5e5e5)));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#f5f5f5)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#a1a1a1)));--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){:where(.ctb){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#0a0a0a)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#fafafa)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#a1a1a1)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,rgb(255 255 255/.1))));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#262626)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#737373)))}}.ctb{margin:1.5rem 0}.ctb-scroll{overflow-x:auto}.ctb-scroll>table{margin:0}.ctb-bar{display:flex;align-items:center;justify-content:flex-end;gap:.25rem;margin-bottom:.375rem;font:500 .75rem/1rem var(--_comark-font)}html:not([data-table-ready]) .ctb-bar{display:none}.ctb-copy{position:relative;display:inline-flex;border:1px solid var(--_comark-border);border-radius:calc(var(--_comark-radius) - 4px);background:var(--_comark-background)}.ctb-copy>button{display:inline-grid;place-items:center;height:1.625rem;padding:0;border:0;background:none;color:var(--_comark-muted);font:inherit;cursor:pointer;transition:background-color .15s,color .15s}.ctb-copy-main{width:1.75rem;border-radius:calc(var(--_comark-radius) - 5px) 0 0 calc(var(--_comark-radius) - 5px)}.ctb-copy>.ctb-copy-more{width:1.25rem;border-inline-start:1px solid var(--_comark-border);border-radius:0 calc(var(--_comark-radius) - 5px) calc(var(--_comark-radius) - 5px) 0}.ctb-copy-more svg{width:.75rem;height:.75rem}.ctb-copy-more[aria-expanded=true]{background:var(--_comark-accent);color:var(--_comark-foreground)}@media (hover:hover){.ctb-copy>button:hover,.ctb-menu button:hover{background:var(--_comark-accent);color:var(--_comark-foreground)}}.ctb-copy-main svg+svg,.ctb-copy-main[data-state=done] svg:first-child{display:none}.ctb-copy-main[data-state=done] svg+svg{display:block}.ctb-menu{position:absolute;top:calc(100% + .25rem);right:-1px;z-index:10;display:grid;min-width:15rem;padding:.25rem;border:1px solid var(--_comark-border);border-radius:var(--_comark-radius);background:var(--_comark-background);box-shadow:
|
|
1
|
+
@layer comark{:where(.ctb){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#fff)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#0a0a0a)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#737373)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,#e5e5e5)));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#f5f5f5)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#a1a1a1)));--_comark-shadow:var(--comark-shadow,0 4px 12px rgb(0 0 0/.12));--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){:where(.ctb){--_comark-background:var(--comark-background,var(--ui-bg,var(--background,#0a0a0a)));--_comark-foreground:var(--comark-foreground,var(--ui-text-highlighted,var(--foreground,#fafafa)));--_comark-muted:var(--comark-muted,var(--ui-text-muted,var(--muted-foreground,#a1a1a1)));--_comark-border:var(--comark-border,var(--ui-border,var(--border,rgb(255 255 255/.1))));--_comark-accent:var(--comark-accent,var(--ui-bg-elevated,var(--accent,#262626)));--_comark-ring:var(--comark-ring,var(--ui-primary,var(--ring,#737373)));--_comark-shadow:var(--comark-shadow,0 4px 12px rgb(0 0 0/.5))}}:where(.light,.light *):where(.ctb){--_comark-shadow:var(--comark-shadow,0 4px 12px rgb(0 0 0/.12))}:where(.dark,.dark *):where(.ctb){--_comark-shadow:var(--comark-shadow,0 4px 12px rgb(0 0 0/.5))}.ctb{margin:1.5rem 0}.ctb-scroll{overflow-x:auto}.ctb-scroll>table{margin:0}.ctb-bar{display:flex;align-items:center;justify-content:flex-end;gap:.25rem;margin-bottom:.375rem;font:500 .75rem/1rem var(--_comark-font)}html:not([data-table-ready]) .ctb-bar{display:none}.ctb-copy{position:relative;display:inline-flex;border:1px solid var(--_comark-border);border-radius:calc(var(--_comark-radius) - 4px);background:var(--_comark-background)}.ctb-copy>button{display:inline-grid;place-items:center;height:1.625rem;padding:0;border:0;background:none;color:var(--_comark-muted);font:inherit;cursor:pointer;transition:background-color .15s,color .15s}.ctb-copy-main{width:1.75rem;border-radius:calc(var(--_comark-radius) - 5px) 0 0 calc(var(--_comark-radius) - 5px)}.ctb-copy>.ctb-copy-more{width:1.25rem;border-inline-start:1px solid var(--_comark-border);border-radius:0 calc(var(--_comark-radius) - 5px) calc(var(--_comark-radius) - 5px) 0}.ctb-copy-more svg{width:.75rem;height:.75rem}.ctb-copy-more[aria-expanded=true]{background:var(--_comark-accent);color:var(--_comark-foreground)}@media (hover:hover){.ctb-copy>button:hover,.ctb-menu button:hover{background:var(--_comark-accent);color:var(--_comark-foreground)}}.ctb-copy-main svg+svg,.ctb-copy-main[data-state=done] svg:first-child{display:none}.ctb-copy-main[data-state=done] svg+svg{display:block}.ctb-menu{position:absolute;top:calc(100% + .25rem);right:-1px;z-index:10;display:grid;min-width:15rem;padding:.25rem;border:1px solid var(--_comark-border);border-radius:var(--_comark-radius);background:var(--_comark-background);box-shadow:var(--_comark-shadow)}.ctb-menu[hidden]{display:none}.ctb-menu button{display:grid;grid-template-columns:.875rem 1fr;gap:.5rem;align-items:start;padding:.375rem .5rem;border:0;border-radius:calc(var(--_comark-radius) - 4px);background:none;color:var(--_comark-foreground);font:inherit;text-align:start;cursor:pointer}.ctb-menu button:focus-visible{outline:none;background:var(--_comark-accent)}.ctb-menu svg{margin-top:.125rem}.ctb-menu b{display:block;font-weight:500}.ctb-menu small{display:block;color:var(--_comark-muted);font-size:.75rem;font-weight:400;line-height:1rem}.ctb-status{margin-inline-end:.25rem;color:var(--_comark-muted)}.ctb-sort{all:unset}.ctb-sort{margin:0!important;padding:0!important;border:0!important;background:none!important;box-shadow:none!important;font:inherit!important;color:inherit!important;text-align:inherit!important}html[data-table-ready] .ctb-sort{display:inline-flex;align-items:center;gap:.25rem;cursor:pointer}html[data-table-ready] .ctb-sort::after{content:'↕';color:var(--_comark-muted);font-weight:400}html[data-table-ready] [aria-sort='ascending']>.ctb-sort::after{content:'↑';color:inherit}html[data-table-ready] [aria-sort='descending']>.ctb-sort::after{content:'↓';color:inherit}.ctb-copy>button:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)!important}.ctb-sort:focus-visible{outline:none;border-radius:calc(var(--_comark-radius) - 4px);box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)!important}@media print{.ctb-bar{display:none}.ctb-sort::after{content:none!important}}}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
//#region ../zoom/dist/theme.mjs
|
|
2
|
+
const tokens = [
|
|
3
|
+
[
|
|
4
|
+
"background",
|
|
5
|
+
"ui-bg",
|
|
6
|
+
"background",
|
|
7
|
+
"#fff",
|
|
8
|
+
"#0a0a0a"
|
|
9
|
+
],
|
|
10
|
+
[
|
|
11
|
+
"foreground",
|
|
12
|
+
"ui-text-highlighted",
|
|
13
|
+
"foreground",
|
|
14
|
+
"#0a0a0a",
|
|
15
|
+
"#fafafa"
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
"muted",
|
|
19
|
+
"ui-text-muted",
|
|
20
|
+
"muted-foreground",
|
|
21
|
+
"#737373",
|
|
22
|
+
"#a1a1a1"
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
"border",
|
|
26
|
+
"ui-border",
|
|
27
|
+
"border",
|
|
28
|
+
"#e5e5e5",
|
|
29
|
+
"rgb(255 255 255/.1)"
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
"accent",
|
|
33
|
+
"ui-bg-elevated",
|
|
34
|
+
"accent",
|
|
35
|
+
"#f5f5f5",
|
|
36
|
+
"#262626"
|
|
37
|
+
],
|
|
38
|
+
[
|
|
39
|
+
"ring",
|
|
40
|
+
"ui-primary",
|
|
41
|
+
"ring",
|
|
42
|
+
"#a1a1a1",
|
|
43
|
+
"#737373"
|
|
44
|
+
]
|
|
45
|
+
];
|
|
46
|
+
const colors = {
|
|
47
|
+
success: [
|
|
48
|
+
"success",
|
|
49
|
+
"ui-success",
|
|
50
|
+
"success",
|
|
51
|
+
"#259f56",
|
|
52
|
+
"#59d38c"
|
|
53
|
+
],
|
|
54
|
+
danger: [
|
|
55
|
+
"danger",
|
|
56
|
+
"ui-error",
|
|
57
|
+
"destructive",
|
|
58
|
+
"#ee3533",
|
|
59
|
+
"#fd736d"
|
|
60
|
+
],
|
|
61
|
+
warning: [
|
|
62
|
+
"warning",
|
|
63
|
+
"ui-warning",
|
|
64
|
+
"",
|
|
65
|
+
"#d97706",
|
|
66
|
+
"#fbbf24"
|
|
67
|
+
],
|
|
68
|
+
info: [
|
|
69
|
+
"info",
|
|
70
|
+
"ui-info",
|
|
71
|
+
"",
|
|
72
|
+
"#2563eb",
|
|
73
|
+
"#60a5fa"
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
const groups = {
|
|
77
|
+
status: Object.values(colors),
|
|
78
|
+
primary: [
|
|
79
|
+
[
|
|
80
|
+
"primary",
|
|
81
|
+
"ui-primary",
|
|
82
|
+
"primary",
|
|
83
|
+
"var(--_comark-foreground)",
|
|
84
|
+
"var(--_comark-foreground)"
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
"primary-foreground",
|
|
88
|
+
"ui-text-inverted",
|
|
89
|
+
"primary-foreground",
|
|
90
|
+
"var(--_comark-background)",
|
|
91
|
+
"var(--_comark-background)"
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
"secondary",
|
|
95
|
+
"ui-secondary",
|
|
96
|
+
"",
|
|
97
|
+
"var(--_comark-muted)",
|
|
98
|
+
"var(--_comark-muted)"
|
|
99
|
+
]
|
|
100
|
+
],
|
|
101
|
+
shadow: [[
|
|
102
|
+
"shadow",
|
|
103
|
+
"",
|
|
104
|
+
"",
|
|
105
|
+
"0 4px 12px rgb(0 0 0/.12)",
|
|
106
|
+
"0 4px 12px rgb(0 0 0/.5)"
|
|
107
|
+
]]
|
|
108
|
+
};
|
|
109
|
+
const chain = (token, scheme) => {
|
|
110
|
+
const shadcn = token[2] ? `var(--${token[2]},${token[scheme]})` : token[scheme];
|
|
111
|
+
const nuxt = token[1] ? `var(--${token[1]},${shadcn})` : shadcn;
|
|
112
|
+
return `var(--comark-${token[0]},${nuxt})`;
|
|
113
|
+
};
|
|
114
|
+
const resolve = (list, scheme) => list.map((token) => `--_comark-${token[0]}:${chain(token, scheme)}`).join(";");
|
|
115
|
+
/**
|
|
116
|
+
* A status color for an element that no `theme()` rule reaches, such as a table cell with no wrapper: the same
|
|
117
|
+
* fallbacks as `--_comark-<name>`, in the light shade.
|
|
118
|
+
*/
|
|
119
|
+
function statusColor(name) {
|
|
120
|
+
return chain(colors[name], 3);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Resolves the `--comark-*` tokens on `selector`, falling back to Nuxt UI's variables, then shadcn/ui's, then shadcn's
|
|
124
|
+
* neutral theme. Nuxt UI's corners are twice its `--ui-radius`, so the radius halves shadcn's to double it back.
|
|
125
|
+
* They resolve on the component rather than `:root`, so a `.dark` class anywhere above it switches the theme, and a
|
|
126
|
+
* `--comark-*` set on any ancestor still inherits down. `:where()` keeps them at zero specificity.
|
|
127
|
+
*
|
|
128
|
+
* Every block gets the core tokens. Name `"status"` for `--comark-success`, `-danger`, `-warning`, and `-info`,
|
|
129
|
+
* `"primary"` for `--comark-primary`, `-primary-foreground`, and `-secondary`, and `"shadow"` for `--comark-shadow`, so
|
|
130
|
+
* a block that never draws them doesn't carry them.
|
|
131
|
+
*/
|
|
132
|
+
function theme(selector, ...extras) {
|
|
133
|
+
const where = `:where(${selector})`;
|
|
134
|
+
const added = extras.flatMap((name) => groups[name]);
|
|
135
|
+
const list = [...tokens, ...added];
|
|
136
|
+
const schemed = added.filter((token) => token[3] !== token[4]);
|
|
137
|
+
const classes = schemed.length ? `:where(.light,.light *)${where}{${resolve(schemed, 3)}}:where(.dark,.dark *)${where}{${resolve(schemed, 4)}}` : "";
|
|
138
|
+
return `${where}{${resolve(list, 3)};--_comark-radius:var(--comark-radius,calc(var(--ui-radius,calc(var(--radius,.625rem)/2))*2));--_comark-font:var(--comark-font,var(--font-sans,ui-sans-serif,system-ui,sans-serif))}@media (prefers-color-scheme:dark){${where}{${resolve(list, 4)}}}` + classes;
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { theme as n, statusColor as t };
|
package/dist/vue.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import src_default,
|
|
2
|
-
import { n as start, r as css, t as bar } from "./client-
|
|
1
|
+
import { n as src_default, t as TABLE_TAG } from "./src-BV7IF9ld.mjs";
|
|
2
|
+
import { n as start, r as css, t as bar } from "./client-CDdgxJF0.mjs";
|
|
3
3
|
import { defineComponent, h, onBeforeUnmount, onMounted } from "vue";
|
|
4
4
|
//#region src/vue.ts
|
|
5
5
|
/** Tables for `@comark/vue`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "comark-table",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Tables from CSV, TSV, and JSON files or fences, read while parsing, with copy buttons and sortable headers. A Comark plugin.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"comark",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"react-dom": "^19.3.1",
|
|
58
58
|
"svelte": "^5.57.2",
|
|
59
59
|
"vue": "^3.5.44",
|
|
60
|
-
"comark-zoom": "0.
|
|
60
|
+
"comark-zoom": "0.8.1"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@comark/html": "^0.8.0",
|