comark-file-tree 0.0.0-stage → 0.7.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/LICENSE ADDED
@@ -0,0 +1,69 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 onmax
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.
22
+
23
+ ---
24
+
25
+ The icons are Lucide's (https://lucide.dev), under its license:
26
+
27
+ ISC License
28
+
29
+ Copyright (c) 2026 Lucide Icons and Contributors
30
+
31
+ Permission to use, copy, modify, and/or distribute this software for any
32
+ purpose with or without fee is hereby granted, provided that the above
33
+ copyright notice and this permission notice appear in all copies.
34
+
35
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
36
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
37
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
38
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
39
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
40
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
41
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
42
+
43
+ ---
44
+
45
+ The following Lucide icons are derived from the Feather project:
46
+
47
+ airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
48
+
49
+ The MIT License (MIT) (for the icons listed above)
50
+
51
+ Copyright (c) 2013-present Cole Bemis
52
+
53
+ Permission is hereby granted, free of charge, to any person obtaining a copy
54
+ of this software and associated documentation files (the "Software"), to deal
55
+ in the Software without restriction, including without limitation the rights
56
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
57
+ copies of the Software, and to permit persons to whom the Software is
58
+ furnished to do so, subject to the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be included in all
61
+ copies or substantial portions of the Software.
62
+
63
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
64
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
65
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
66
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
67
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
68
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
69
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,49 @@
1
- # Temporary Holding Version
1
+ # comark-file-tree
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://comark-ui.onmax.me/badge.svg?source=npm-version&package=comark-file-tree&icon=npm)](https://npmx.dev/package/comark-file-tree)
4
+ [![npm downloads](https://comark-ui.onmax.me/badge.svg?source=npm-downloads&package=comark-file-tree&period=month&icon=npm)](https://npmx.dev/package/comark-file-tree)
5
+ [![license](https://comark-ui.onmax.me/badge.svg?label=license&value=MIT)](https://npmx.dev/package/comark-file-tree)
6
+ [![docs](https://comark-ui.onmax.me/badge.svg?label=docs&value=comark-ui.onmax.me)](https://comark-ui.onmax.me/file-tree/)
7
+
8
+ A project's folders and files from an indented list in a fenced block, or pasted `tree` output, with added, removed, and changed entries marked and a note on any line. Folders are open `<details>`, so there's no script. A [Comark](https://comark.dev) plugin for HTML, Vue, React, and Svelte.
9
+
10
+ ```sh
11
+ pnpm add comark comark-file-tree
12
+ ```
13
+
14
+ ```ts
15
+ import { createHtmlRenderer } from "@comark/html";
16
+ import fileTree, { components } from "comark-file-tree/html";
17
+
18
+ const render = createHtmlRenderer({ plugins: [fileTree()], components });
19
+ ```
20
+
21
+ ````md
22
+ ::file-tree
23
+ ```
24
+ src/
25
+ + new.ts # added in this change
26
+ - old.ts
27
+ ~ app.vue
28
+ ...
29
+ README.md
30
+ ```
31
+ ::
32
+ ````
33
+
34
+ Indent each level by two spaces, or paste what `tree` prints. A trailing `/` makes a folder; `+`, `-`, and `~` mark an entry added, removed, or changed; ` # ` starts a note; and `...` stands for entries left out. Pass `iconResolver` from comark-icon's `createIcons()` to draw each file's icon; without it, entries get plain glyphs and nothing is fetched.
35
+
36
+ The plugin adds every problem to `meta.issues`, so a host can reject a document before publishing it: a Markdown list instead of a fence (a list turns `__init__.py` bold and drops `[id].vue`), content beside the fence, and an empty block. Each issue names its block and says how to fix it.
37
+
38
+ ## Renderers
39
+
40
+ | Renderer | Import |
41
+ | --- | --- |
42
+ | `@comark/html` | `comark-file-tree/html`: the styles are inlined once per document |
43
+ | `@comark/vue` | `comark-file-tree/vue` |
44
+ | `@comark/react` | `comark-file-tree/react` |
45
+ | `@comark/svelte` | `comark-file-tree/svelte` |
46
+
47
+ Each entry exports the parse plugin as `default` and the renderer's `components`. The Vue, React, and Svelte components add the styles when they mount; import `comark-file-tree/style.css` with your styles so a server-rendered page doesn't shift.
48
+
49
+ The styles follow shadcn/ui: they read its CSS variables, such as `--accent` and `--border`, when the page has them, and use its neutral theme otherwise. Set the shared `--comark-*` tokens to theme every plugin at once; the rules sit in a `comark` CSS layer, so any plain rule of your own wins, except the spacing that draws the tree: the margins, padding, and bullets of its lists, `details`, and `summary` are `!important`, because a page's own list styles are almost always unlayered and would push entries off their guide lines. Override those with `!important` too.
@@ -0,0 +1,113 @@
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 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
58
+ //#region src/styles.ts
59
+ /**
60
+ * A tree in a bordered box, with a guide line down each open folder, on the shared `--comark-*` tokens. Added, removed,
61
+ * and changed entries take `--cft-added`, `--cft-removed`, and `--cft-changed`; removed defaults to shadcn/ui's
62
+ * `--destructive`. The rules sit in the `comark` layer, so any unlayered rule of the host's wins.
63
+ */
64
+ const css = `@layer comark{${[
65
+ theme(".cft"),
66
+ ":where(.cft){--cft-added:#16a34a;--cft-removed:var(--destructive,#dc2626);--cft-changed:#d97706}",
67
+ "@media (prefers-color-scheme:dark){:where(.cft){--cft-added:#4ade80;--cft-removed:var(--destructive,#f87171);--cft-changed:#fbbf24}}",
68
+ ".cft{margin:1.5rem 0;padding:.75rem;overflow-x:auto;border:1px solid var(--_comark-border);border-radius:var(--_comark-radius);background:var(--_comark-background);color:var(--_comark-foreground);font:400 .8125rem/1.25rem ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}",
69
+ ".cft ul{margin:0!important;padding:0!important;list-style:none!important}",
70
+ ".cft ul ul{margin-inline-start:calc(.5rem - .5px)!important;padding-inline-start:.75rem!important;border-inline-start:1px solid var(--_comark-border)}",
71
+ ".cft li{margin:0!important;padding:0!important}",
72
+ ".cft details{margin:0!important;padding:0!important}",
73
+ ".cft summary{display:block;margin:0!important;padding:0!important;list-style:none;border-radius:calc(var(--_comark-radius) - 4px);cursor:pointer}",
74
+ ".cft summary::-webkit-details-marker{display:none}",
75
+ ".cft summary:hover{background:var(--_comark-accent)}",
76
+ ".cft summary:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)}",
77
+ ".cft-entry{display:flex;align-items:center;gap:.375rem;padding:.125rem .25rem;white-space:nowrap}",
78
+ ".cft-glyph{flex:none;width:1rem;height:1rem;color:var(--_comark-muted)}",
79
+ ".cft-entry .ci{flex:none;font-size:1rem}",
80
+ ".cft-note{margin-inline-start:.75rem;color:var(--_comark-muted);font-family:var(--_comark-font);white-space:normal}",
81
+ ".cft-mark{font-weight:600}",
82
+ ".cft [data-state='added']>.cft-entry,.cft [data-state='added']>details>summary>.cft-entry{color:var(--cft-added)}",
83
+ ".cft [data-state='removed']>.cft-entry,.cft [data-state='removed']>details>summary>.cft-entry{color:var(--cft-removed)}",
84
+ ".cft [data-state='removed'] .cft-name{text-decoration:line-through}",
85
+ ".cft [data-state='changed']>.cft-entry,.cft [data-state='changed']>details>summary>.cft-entry{color:var(--cft-changed)}",
86
+ ".cft [data-type='more']{padding:0 .25rem;color:var(--_comark-muted)}"
87
+ ].join("")}}`;
88
+ //#endregion
89
+ //#region src/client.ts
90
+ /** Adds the file tree styles to the page once. The Vue, React, and Svelte components call it when they mount. */
91
+ function addStyles() {
92
+ if (document.querySelector("style[data-comark-file-tree]")) return;
93
+ const style = document.createElement("style");
94
+ style.dataset.comarkFileTree = "";
95
+ style.textContent = css;
96
+ document.head.append(style);
97
+ }
98
+ /**
99
+ * Lucide's folder and file (ISC), each as one path, for entries without an icon. Every renderer draws them in the
100
+ * same `<svg>`, sized for a page without the styles.
101
+ */
102
+ const glyphs = {
103
+ folder: "M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z",
104
+ file: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM14 2v4a2 2 0 0 0 2 2h4"
105
+ };
106
+ /** The sign before an entry's name, so its change reads without color. */
107
+ const marks = /* @__PURE__ */ new Map([
108
+ ["added", "+"],
109
+ ["removed", "-"],
110
+ ["changed", "~"]
111
+ ]);
112
+ //#endregion
113
+ export { css as i, glyphs as n, marks as r, addStyles as t };
@@ -0,0 +1,22 @@
1
+ //#region src/styles.d.ts
2
+ /**
3
+ * A tree in a bordered box, with a guide line down each open folder, on the shared `--comark-*` tokens. Added, removed,
4
+ * and changed entries take `--cft-added`, `--cft-removed`, and `--cft-changed`; removed defaults to shadcn/ui's
5
+ * `--destructive`. The rules sit in the `comark` layer, so any unlayered rule of the host's wins.
6
+ */
7
+ export declare const css: string;
8
+ //#endregion
9
+ //#region src/client.d.ts
10
+ /** Adds the file tree styles to the page once. The Vue, React, and Svelte components call it when they mount. */
11
+ export declare function addStyles(): void;
12
+ /**
13
+ * Lucide's folder and file (ISC), each as one path, for entries without an icon. Every renderer draws them in the
14
+ * same `<svg>`, sized for a page without the styles.
15
+ */
16
+ export declare const glyphs: {
17
+ folder: string;
18
+ file: string;
19
+ };
20
+ /** The sign before an entry's name, so its change reads without color. */
21
+ export declare const marks: Map<string, string>;
22
+ //#endregion
@@ -0,0 +1 @@
1
+ export { t as addStyles, i as css, n as glyphs, r as marks } from "./client-C13QjLJq.mjs";
@@ -0,0 +1,7 @@
1
+ import _default, { EntryState, FileTreeItemAttributes, FileTreeMeta, FileTreeOptions } from "./index.mjs";
2
+ import { NodeHandler } from "comark";
3
+ //#region src/html.d.ts
4
+ /** The HTML renderer for `::file-tree`. Pass it as `components`; it inlines the styles once per document. */
5
+ export declare const components: Record<"file-tree" | "file-tree-item", NodeHandler>;
6
+ //#endregion
7
+ export { type EntryState, type FileTreeItemAttributes, type FileTreeMeta, type FileTreeOptions, _default as default };
package/dist/html.mjs ADDED
@@ -0,0 +1,30 @@
1
+ import src_default from "./index.mjs";
2
+ import { i as css, n as glyphs, r as marks } from "./client-C13QjLJq.mjs";
3
+ import { escapeHtml } from "comark/utils";
4
+ import { iconHtml, iconStyles } from "comark-icon/html";
5
+ //#region src/html.ts
6
+ const styled = /* @__PURE__ */ new WeakSet();
7
+ const text = (value) => value === void 0 ? "" : escapeHtml(String(value));
8
+ const glyph = (d) => `<svg class="cft-glyph" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="${d}"></path></svg>`;
9
+ const hasIcon = (node) => Array.isArray(node) && (Boolean(node[1]?.icon) || node.slice(2).some(hasIcon));
10
+ const fileTree = async (node, state) => {
11
+ const styles = styled.has(state.context) ? "" : `<style data-comark-file-tree>${css}</style>`;
12
+ styled.add(state.context);
13
+ return `${styles}${hasIcon(node) ? iconStyles(state) : ""}<div class="cft"><ul>${await state.flow(node, state)}</ul></div>`;
14
+ };
15
+ const item = async (node, state) => {
16
+ const [, attrs] = node;
17
+ if (attrs.type === "more") return "<li data-type=\"more\">…</li>";
18
+ const folder = attrs.type === "folder";
19
+ const mark = marks.get(String(attrs.state));
20
+ const entry = `<span class="cft-entry">${attrs.icon ? iconHtml(attrs.icon) : glyph(folder ? glyphs.folder : glyphs.file)}${mark ? `<span class="cft-mark">${mark}</span>` : ""}<span class="cft-name">${text(attrs.name)}</span>${attrs.note ? `<span class="cft-note">${text(attrs.note)}</span>` : ""}</span>`;
21
+ const open = `<li data-type="${folder ? "folder" : "file"}"${mark ? ` data-state="${text(attrs.state)}"` : ""}>`;
22
+ return folder ? `${open}<details open><summary>${entry}</summary><ul>${await state.flow(node, state)}</ul></details></li>` : `${open}${entry}</li>`;
23
+ };
24
+ /** The HTML renderer for `::file-tree`. Pass it as `components`; it inlines the styles once per document. */
25
+ const components = {
26
+ "file-tree": fileTree,
27
+ "file-tree-item": item
28
+ };
29
+ //#endregion
30
+ export { components, src_default as default };
@@ -0,0 +1,41 @@
1
+ import { IconData, Icons } from "comark-icon";
2
+ //#region src/index.d.ts
3
+ export type FileTreeOptions = {
4
+ /**
5
+ * Resolves file and folder icons, from `createIcons()` in comark-icon. Without it, files and folders get the same
6
+ * plain glyph each and nothing is fetched. Pass the one the page's other plugins use, so they share its `collections`.
7
+ */
8
+ iconResolver?: Icons;
9
+ /**
10
+ * Icons by file name, such as `{ "nuxt.config.ts": "vscode-icons:file-type-nuxt" }`, or by extension, such as
11
+ * `{ ".vue": "logos:vue" }`, on top of the built-in ones. `folder` and `file` set the fallbacks.
12
+ */
13
+ icons?: Record<string, string>;
14
+ };
15
+ export type FileTreeMeta = {
16
+ /** One readable sentence per problem with a `::file-tree`. Empty when the document has none. Other plugins add theirs too. */
17
+ issues: string[];
18
+ /** The comark-linter rule of each issue, by its sentence, so the linter reports it under its own code. */
19
+ issueRules: Record<string, string>;
20
+ };
21
+ /** How an entry changed: written as `+`, `-`, or `~` before its name. */
22
+ export type EntryState = "added" | "removed" | "changed";
23
+ /**
24
+ * What each line of a tree becomes, as a `file-tree-item` node whose children are its entries. A `more` entry is a
25
+ * `...` placeholder and has no name.
26
+ */
27
+ export type FileTreeItemAttributes = {
28
+ type?: "folder" | "file" | "more";
29
+ name?: string;
30
+ state?: EntryState;
31
+ note?: string;
32
+ icon?: IconData;
33
+ };
34
+ /**
35
+ * Turns `::file-tree` around a fenced block, one entry per line indented by two spaces or pasted from `tree`, into a
36
+ * tree of folders and files. A trailing `/` makes a folder, `+`, `-`, and `~` mark an entry added, removed, or
37
+ * changed, `# note` ends a line with a note, and `...` stands for entries left out. Problems are added to `meta.issues`.
38
+ */
39
+ declare const _default: import("comark").ComarkPluginFactory<FileTreeOptions, FileTreeMeta, {}>;
40
+ //#endregion
41
+ export { _default as default };
package/dist/index.mjs ADDED
@@ -0,0 +1,209 @@
1
+ import { defineComarkPlugin } from "comark";
2
+ import { textContent } from "comark/utils";
3
+ //#region src/index.ts
4
+ const isElement = (node) => Array.isArray(node) && node[0] !== null;
5
+ const isContent = (node) => Array.isArray(node) ? node[0] !== null : node.trim() !== "";
6
+ const childrenOf = (node) => node.slice(2);
7
+ const clip = (value) => {
8
+ const words = value.trim().replace(/\s+/g, " ");
9
+ return words.length > 40 ? `${words.slice(0, 39)}…` : words;
10
+ };
11
+ const ICONS = {
12
+ folder: "vscode-icons:default-folder-opened",
13
+ file: "vscode-icons:default-file",
14
+ ".ts": "vscode-icons:file-type-typescript-official",
15
+ ".mts": "vscode-icons:file-type-typescript-official",
16
+ ".cts": "vscode-icons:file-type-typescript-official",
17
+ ".tsx": "vscode-icons:file-type-reactts",
18
+ ".js": "vscode-icons:file-type-js-official",
19
+ ".mjs": "vscode-icons:file-type-js-official",
20
+ ".cjs": "vscode-icons:file-type-js-official",
21
+ ".jsx": "vscode-icons:file-type-reactjs",
22
+ ".json": "vscode-icons:file-type-json",
23
+ ".md": "vscode-icons:file-type-markdown",
24
+ ".mdx": "vscode-icons:file-type-mdx",
25
+ ".vue": "vscode-icons:file-type-vue",
26
+ ".svelte": "vscode-icons:file-type-svelte",
27
+ ".astro": "vscode-icons:file-type-astro",
28
+ ".css": "vscode-icons:file-type-css",
29
+ ".scss": "vscode-icons:file-type-scss",
30
+ ".html": "vscode-icons:file-type-html",
31
+ ".yaml": "vscode-icons:file-type-yaml",
32
+ ".yml": "vscode-icons:file-type-yaml",
33
+ ".toml": "vscode-icons:file-type-toml",
34
+ ".py": "vscode-icons:file-type-python",
35
+ ".rs": "vscode-icons:file-type-rust",
36
+ ".go": "vscode-icons:file-type-go",
37
+ ".sh": "vscode-icons:file-type-shell",
38
+ ".svg": "vscode-icons:file-type-svg",
39
+ ".png": "vscode-icons:file-type-image",
40
+ ".jpg": "vscode-icons:file-type-image",
41
+ ".jpeg": "vscode-icons:file-type-image",
42
+ ".gif": "vscode-icons:file-type-image",
43
+ ".webp": "vscode-icons:file-type-image",
44
+ ".txt": "vscode-icons:file-type-text",
45
+ ".env": "vscode-icons:file-type-dotenv",
46
+ ".gitignore": "vscode-icons:file-type-git",
47
+ "package.json": "vscode-icons:file-type-npm",
48
+ "tsconfig.json": "vscode-icons:file-type-tsconfig",
49
+ "pnpm-lock.yaml": "vscode-icons:file-type-pnpm",
50
+ "pnpm-workspace.yaml": "vscode-icons:file-type-pnpm",
51
+ dockerfile: "vscode-icons:file-type-docker",
52
+ license: "vscode-icons:file-type-license"
53
+ };
54
+ const STATES = /* @__PURE__ */ new Map([
55
+ ["+", "added"],
56
+ ["-", "removed"],
57
+ ["~", "changed"]
58
+ ]);
59
+ const BRANCHES = /^(?:[│|][\s ]{2,3}|[\s ])*(?:[├└|`+\\](?:──|--)[─-]*[\s ]?)?/u;
60
+ const NOTE = /\s+#\s+(.*)$/;
61
+ const SUMMARY = /^\d+ director(?:y|ies)(?:, \d+ files?)?$/;
62
+ function entry(source) {
63
+ let rest = source;
64
+ const note = NOTE.exec(rest);
65
+ if (note) rest = rest.slice(0, note.index);
66
+ rest = rest.trim();
67
+ const marker = /^([+~-])\s+/.exec(rest);
68
+ if (marker) rest = rest.slice(marker[0].length);
69
+ if (!rest) return void 0;
70
+ const attrs = {};
71
+ if (rest === "..." || rest === "…") attrs.type = "more";
72
+ else if (rest.endsWith("/")) {
73
+ attrs.type = "folder";
74
+ attrs.name = rest.replace(/\/+$/, "");
75
+ } else {
76
+ attrs.type = "file";
77
+ attrs.name = rest;
78
+ }
79
+ const state = STATES.get(marker?.[1] ?? "");
80
+ if (state) attrs.state = state;
81
+ if (note?.[1]?.trim()) attrs.note = note[1].trim();
82
+ return ["file-tree-item", attrs];
83
+ }
84
+ function readTree(source) {
85
+ const roots = [];
86
+ const open = [];
87
+ for (const raw of source.split("\n")) {
88
+ const line = raw.replace(/\s+$/, "");
89
+ const column = BRANCHES.exec(line)?.[0].length ?? 0;
90
+ if (SUMMARY.test(line.slice(column))) continue;
91
+ const item = entry(line.slice(column));
92
+ if (!item) continue;
93
+ while (open.length && (open.at(-1)?.column ?? 0) >= column) open.pop();
94
+ const parent = open.at(-1)?.item;
95
+ if (parent) {
96
+ if (parent[1].type === "file") parent[1].type = "folder";
97
+ parent.push(item);
98
+ } else roots.push(item);
99
+ if (item[1].type !== "more") open.push({
100
+ column,
101
+ item
102
+ });
103
+ }
104
+ const [only] = roots;
105
+ if (roots.length === 1 && only?.[1].name === ".") return childrenOf(only).filter(isElement);
106
+ return roots;
107
+ }
108
+ function iconKey(item, icons) {
109
+ const name = (item[1].name ?? "").toLowerCase();
110
+ if (item[1].type === "folder") return icons[`${name}/`] ?? icons.folder;
111
+ const dot = name.lastIndexOf(".");
112
+ return icons[name] ?? (dot >= 0 ? icons[name.slice(dot)] : void 0) ?? icons.file;
113
+ }
114
+ function* items(nodes) {
115
+ for (const node of nodes) {
116
+ if (!isElement(node)) continue;
117
+ if (node[0] === "file-tree-item") yield node;
118
+ yield* items(childrenOf(node));
119
+ }
120
+ }
121
+ const KINDS = /* @__PURE__ */ new Map([
122
+ ["p", "paragraph"],
123
+ ["ul", "list"],
124
+ ["ol", "list"],
125
+ ["blockquote", "quote"],
126
+ ["table", "table"]
127
+ ]);
128
+ function describe(node) {
129
+ if (!Array.isArray(node)) return `the text "${clip(node)}"`;
130
+ if (!isElement(node)) return "a comment";
131
+ const kind = KINDS.get(node[0]) ?? `\`${node[0]}\``;
132
+ const words = clip(textContent(node));
133
+ return `the ${kind}${words ? ` "${words}"` : ""}`;
134
+ }
135
+ function fileTree(node, name, issues) {
136
+ for (const key of Object.keys(node[1])) {
137
+ const prop = key.replace(/^:/, "");
138
+ if (prop !== "id" && prop !== "class") issues.push({
139
+ rule: "comark/file-tree/unknown-prop",
140
+ message: `${name}: \`${prop}\` isn't a prop of \`::file-tree\`, which takes none, so it's ignored. Remove it.`
141
+ });
142
+ }
143
+ const content = childrenOf(node).filter(isContent);
144
+ const [block] = content.filter((child) => isElement(child) && child[0] === "pre");
145
+ if (!isElement(block)) {
146
+ const list = content.find((child) => isElement(child) && /^[ou]l$/.test(child[0]));
147
+ issues.push(list ? {
148
+ rule: "comark/file-tree/list-instead-of-fence",
149
+ message: `${name} holds a Markdown list, which turns names like \`__init__.py\` bold and drops \`[id].vue\`. Write the tree in a fenced block.`
150
+ } : {
151
+ rule: "comark/file-tree/empty-tree",
152
+ message: `${name} has no tree. Write the tree in a fenced block inside it, one entry per line.`
153
+ });
154
+ return content;
155
+ }
156
+ for (const child of content) if (child !== block) issues.push({
157
+ rule: "comark/file-tree/content-dropped",
158
+ message: `${name}: only one fenced block goes inside, so ${describe(child)} doesn't render. Move it out of the block.`
159
+ });
160
+ const tree = readTree(textContent(block));
161
+ if (!tree.length) issues.push({
162
+ rule: "comark/file-tree/empty-tree",
163
+ message: `${name}'s fenced block is empty. Write one entry per line.`
164
+ });
165
+ node.splice(1, node.length - 1, {}, ...tree);
166
+ return [node];
167
+ }
168
+ /**
169
+ * Turns `::file-tree` around a fenced block, one entry per line indented by two spaces or pasted from `tree`, into a
170
+ * tree of folders and files. A trailing `/` makes a folder, `+`, `-`, and `~` mark an entry added, removed, or
171
+ * changed, `# note` ends a line with a note, and `...` stands for entries left out. Problems are added to `meta.issues`.
172
+ */
173
+ var src_default = defineComarkPlugin((options = {}) => {
174
+ const icons = Object.fromEntries(Object.entries({
175
+ ...ICONS,
176
+ ...options.icons
177
+ }).map(([key, icon]) => [key.toLowerCase(), icon]));
178
+ return {
179
+ name: "comark-file-tree",
180
+ async post(state) {
181
+ const { tree } = state;
182
+ const issues = [];
183
+ let count = 0;
184
+ const transform = (nodes) => nodes.flatMap((node) => {
185
+ if (!isElement(node)) return [node];
186
+ if (node[0] === "file-tree") return fileTree(node, `\`::file-tree\` ${++count}`, issues);
187
+ node.splice(2, node.length - 2, ...transform(childrenOf(node)));
188
+ return [node];
189
+ });
190
+ tree.nodes = transform(tree.nodes);
191
+ if (options.iconResolver) {
192
+ const all = [...items(tree.nodes)].filter((item) => item[1].type !== "more");
193
+ const keys = all.map((item) => iconKey(item, icons));
194
+ const resolved = await options.iconResolver.resolve(keys.filter((key) => Boolean(key)));
195
+ for (const [at, item] of all.entries()) {
196
+ const icon = resolved.get(keys[at] ?? "");
197
+ if (icon && "src" in icon) item[1].icon = icon;
198
+ }
199
+ }
200
+ tree.meta.issues = [...tree.meta.issues ?? [], ...issues.map((issue) => issue.message)];
201
+ tree.meta.issueRules = {
202
+ ...tree.meta.issueRules,
203
+ ...Object.fromEntries(issues.map((issue) => [issue.message, issue.rule]))
204
+ };
205
+ }
206
+ };
207
+ });
208
+ //#endregion
209
+ export { src_default as default };
@@ -0,0 +1,29 @@
1
+ import _default, { EntryState, FileTreeItemAttributes, FileTreeMeta, FileTreeOptions } from "./index.mjs";
2
+ import { IconData } from "comark-icon/react";
3
+ import { ReactNode } from "react";
4
+ //#region src/react.d.ts
5
+ /** `::file-tree` for `@comark/react`. */
6
+ export declare function FileTree({ children }: {
7
+ children?: ReactNode;
8
+ }): import("react").DetailedReactHTMLElement<{
9
+ className: string;
10
+ }, HTMLElement>;
11
+ type FileTreeItemProps = {
12
+ type?: string;
13
+ name?: string;
14
+ state?: string;
15
+ note?: string;
16
+ icon?: IconData;
17
+ children?: ReactNode;
18
+ };
19
+ /** Each entry of a `::file-tree` for `@comark/react`. */
20
+ export declare function FileTreeItem({ type, name, state, note, icon, children }: FileTreeItemProps): import("react").ReactElement<{
21
+ "data-type": string;
22
+ }, string | import("react").JSXElementConstructor<any>>;
23
+ /** Pass these as `components` to `@comark/react`. */
24
+ export declare const components: {
25
+ FileTree: typeof FileTree;
26
+ FileTreeItem: typeof FileTreeItem;
27
+ };
28
+ //#endregion
29
+ export { type EntryState, type FileTreeItemAttributes, type FileTreeMeta, type FileTreeOptions, _default as default };
package/dist/react.mjs ADDED
@@ -0,0 +1,40 @@
1
+ import src_default from "./index.mjs";
2
+ import { n as glyphs, r as marks, t as addStyles } from "./client-C13QjLJq.mjs";
3
+ import { Icon } from "comark-icon/react";
4
+ import { createElement, useEffect } from "react";
5
+ //#region src/react.ts
6
+ const glyph = (d) => createElement("svg", {
7
+ className: "cft-glyph",
8
+ viewBox: "0 0 24 24",
9
+ width: 16,
10
+ height: 16,
11
+ fill: "none",
12
+ stroke: "currentColor",
13
+ strokeWidth: 2,
14
+ strokeLinecap: "round",
15
+ strokeLinejoin: "round",
16
+ "aria-hidden": true
17
+ }, createElement("path", { d }));
18
+ /** `::file-tree` for `@comark/react`. */
19
+ function FileTree({ children }) {
20
+ useEffect(addStyles, []);
21
+ return createElement("div", { className: "cft" }, createElement("ul", null, children));
22
+ }
23
+ /** Each entry of a `::file-tree` for `@comark/react`. */
24
+ function FileTreeItem({ type, name, state, note, icon, children }) {
25
+ if (type === "more") return createElement("li", { "data-type": "more" }, "…");
26
+ const folder = type === "folder";
27
+ const mark = marks.get(String(state));
28
+ const entry = createElement("span", { className: "cft-entry" }, icon?.src ? createElement(Icon, icon) : glyph(folder ? glyphs.folder : glyphs.file), mark ? createElement("span", { className: "cft-mark" }, mark) : null, createElement("span", { className: "cft-name" }, name), note ? createElement("span", { className: "cft-note" }, note) : null);
29
+ return createElement("li", {
30
+ "data-type": folder ? "folder" : "file",
31
+ "data-state": mark ? state : void 0
32
+ }, folder ? createElement("details", { open: true }, createElement("summary", null, entry), createElement("ul", null, children)) : entry);
33
+ }
34
+ /** Pass these as `components` to `@comark/react`. */
35
+ const components = {
36
+ FileTree,
37
+ FileTreeItem
38
+ };
39
+ //#endregion
40
+ export { FileTree, FileTreeItem, components, src_default as default };
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ @layer comark{:where(.cft){--_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(.cft){--_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)))}}:where(.cft){--cft-added:#16a34a;--cft-removed:var(--destructive,#dc2626);--cft-changed:#d97706}@media (prefers-color-scheme:dark){:where(.cft){--cft-added:#4ade80;--cft-removed:var(--destructive,#f87171);--cft-changed:#fbbf24}}.cft{margin:1.5rem 0;padding:.75rem;overflow-x:auto;border:1px solid var(--_comark-border);border-radius:var(--_comark-radius);background:var(--_comark-background);color:var(--_comark-foreground);font:400 .8125rem/1.25rem ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}.cft ul{margin:0!important;padding:0!important;list-style:none!important}.cft ul ul{margin-inline-start:calc(.5rem - .5px)!important;padding-inline-start:.75rem!important;border-inline-start:1px solid var(--_comark-border)}.cft li{margin:0!important;padding:0!important}.cft details{margin:0!important;padding:0!important}.cft summary{display:block;margin:0!important;padding:0!important;list-style:none;border-radius:calc(var(--_comark-radius) - 4px);cursor:pointer}.cft summary::-webkit-details-marker{display:none}.cft summary:hover{background:var(--_comark-accent)}.cft summary:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--_comark-ring) 50%,transparent)}.cft-entry{display:flex;align-items:center;gap:.375rem;padding:.125rem .25rem;white-space:nowrap}.cft-glyph{flex:none;width:1rem;height:1rem;color:var(--_comark-muted)}.cft-entry .ci{flex:none;font-size:1rem}.cft-note{margin-inline-start:.75rem;color:var(--_comark-muted);font-family:var(--_comark-font);white-space:normal}.cft-mark{font-weight:600}.cft [data-state='added']>.cft-entry,.cft [data-state='added']>details>summary>.cft-entry{color:var(--cft-added)}.cft [data-state='removed']>.cft-entry,.cft [data-state='removed']>details>summary>.cft-entry{color:var(--cft-removed)}.cft [data-state='removed'] .cft-name{text-decoration:line-through}.cft [data-state='changed']>.cft-entry,.cft [data-state='changed']>details>summary>.cft-entry{color:var(--cft-changed)}.cft [data-type='more']{padding:0 .25rem;color:var(--_comark-muted)}}
@@ -0,0 +1,10 @@
1
+ <!-- `::file-tree` for `@comark/svelte`. -->
2
+ <script>
3
+ import { onMount } from "svelte";
4
+ import { addStyles } from "comark-file-tree/client";
5
+
6
+ let { children } = $props();
7
+ onMount(addStyles);
8
+ </script>
9
+
10
+ <div class="cft"><ul>{@render children?.()}</ul></div>
@@ -0,0 +1,28 @@
1
+ <!-- Each entry of a `::file-tree` for `@comark/svelte`. -->
2
+ <script>
3
+ import { glyphs, marks } from "comark-file-tree/client";
4
+ import { Icon } from "comark-icon/svelte";
5
+
6
+ let {
7
+ type = undefined,
8
+ name = undefined,
9
+ state = undefined,
10
+ note = undefined,
11
+ icon = undefined,
12
+ children,
13
+ } = $props();
14
+ const folder = $derived(type === "folder");
15
+ const mark = $derived(marks.get(String(state)));
16
+ </script>
17
+
18
+ {#snippet entry()}
19
+ <span class="cft-entry">{#if icon?.src}<Icon {...icon} />{:else}<svg class="cft-glyph" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d={folder ? glyphs.folder : glyphs.file} /></svg>{/if}{#if mark}<span class="cft-mark">{mark}</span>{/if}<span class="cft-name">{name}</span>{#if note}<span class="cft-note">{note}</span>{/if}</span>
20
+ {/snippet}
21
+
22
+ {#if type === "more"}
23
+ <li data-type="more">…</li>
24
+ {:else if folder}
25
+ <li data-type="folder" data-state={mark ? state : undefined}><details open><summary>{@render entry()}</summary><ul>{@render children?.()}</ul></details></li>
26
+ {:else}
27
+ <li data-type="file" data-state={mark ? state : undefined}>{@render entry()}</li>
28
+ {/if}
@@ -0,0 +1,21 @@
1
+ import type { Component, Snippet } from "svelte";
2
+ import type { IconData } from "comark-icon";
3
+
4
+ export { default } from "comark-file-tree";
5
+ export type {
6
+ EntryState,
7
+ FileTreeItemAttributes,
8
+ FileTreeMeta,
9
+ FileTreeOptions,
10
+ } from "comark-file-tree";
11
+
12
+ export declare const FileTree: Component<{ children?: Snippet }>;
13
+ export declare const FileTreeItem: Component<{
14
+ type?: string;
15
+ name?: string;
16
+ state?: string;
17
+ note?: string;
18
+ icon?: IconData;
19
+ children?: Snippet;
20
+ }>;
21
+ export declare const components: { FileTree: typeof FileTree; FileTreeItem: typeof FileTreeItem };
@@ -0,0 +1,7 @@
1
+ // `::file-tree` for `@comark/svelte`. Pass `components` to it; the parse plugin comes from `comark-file-tree`.
2
+ import FileTree from "./FileTree.svelte";
3
+ import FileTreeItem from "./FileTreeItem.svelte";
4
+
5
+ export { default } from "comark-file-tree";
6
+ export { FileTree, FileTreeItem };
7
+ export const components = { FileTree, FileTreeItem };
package/dist/vue.d.mts ADDED
@@ -0,0 +1,47 @@
1
+ import _default, { EntryState, FileTreeItemAttributes, FileTreeMeta, FileTreeOptions } from "./index.mjs";
2
+ import { PropType } from "vue";
3
+ import { IconData } from "comark-icon/vue";
4
+ //#region src/vue.d.ts
5
+ /** `::file-tree` for `@comark/vue`. */
6
+ export declare const FileTree: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
7
+ [key: string]: any;
8
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
9
+ /** Each entry of a `::file-tree` for `@comark/vue`. */
10
+ export declare const FileTreeItem: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
11
+ type: StringConstructor;
12
+ name: StringConstructor;
13
+ state: StringConstructor;
14
+ note: StringConstructor;
15
+ icon: PropType<IconData>;
16
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
17
+ [key: string]: any;
18
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
19
+ type: StringConstructor;
20
+ name: StringConstructor;
21
+ state: StringConstructor;
22
+ note: StringConstructor;
23
+ icon: PropType<IconData>;
24
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
25
+ /** Pass these as `components` to `@comark/vue`. */
26
+ export declare const components: {
27
+ FileTree: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
28
+ [key: string]: any;
29
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
30
+ FileTreeItem: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
31
+ type: StringConstructor;
32
+ name: StringConstructor;
33
+ state: StringConstructor;
34
+ note: StringConstructor;
35
+ icon: PropType<IconData>;
36
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
37
+ [key: string]: any;
38
+ }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
39
+ type: StringConstructor;
40
+ name: StringConstructor;
41
+ state: StringConstructor;
42
+ note: StringConstructor;
43
+ icon: PropType<IconData>;
44
+ }>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
45
+ };
46
+ //#endregion
47
+ export { type EntryState, type FileTreeItemAttributes, type FileTreeMeta, type FileTreeOptions, _default as default };
package/dist/vue.mjs ADDED
@@ -0,0 +1,62 @@
1
+ import src_default from "./index.mjs";
2
+ import { n as glyphs, r as marks, t as addStyles } from "./client-C13QjLJq.mjs";
3
+ import { defineComponent, h, onMounted } from "vue";
4
+ import { Icon } from "comark-icon/vue";
5
+ //#region src/vue.ts
6
+ const glyph = (d) => h("svg", {
7
+ class: "cft-glyph",
8
+ viewBox: "0 0 24 24",
9
+ width: "16",
10
+ height: "16",
11
+ fill: "none",
12
+ stroke: "currentColor",
13
+ "stroke-width": "2",
14
+ "stroke-linecap": "round",
15
+ "stroke-linejoin": "round",
16
+ "aria-hidden": "true"
17
+ }, [h("path", { d })]);
18
+ /** `::file-tree` for `@comark/vue`. */
19
+ const FileTree = defineComponent({
20
+ name: "FileTree",
21
+ inheritAttrs: false,
22
+ setup(_, { slots }) {
23
+ onMounted(addStyles);
24
+ return () => h("div", { class: "cft" }, [h("ul", slots.default?.())]);
25
+ }
26
+ });
27
+ /** Each entry of a `::file-tree` for `@comark/vue`. */
28
+ const FileTreeItem = defineComponent({
29
+ name: "FileTreeItem",
30
+ inheritAttrs: false,
31
+ props: {
32
+ type: String,
33
+ name: String,
34
+ state: String,
35
+ note: String,
36
+ icon: Object
37
+ },
38
+ setup(props, { slots }) {
39
+ return () => {
40
+ if (props.type === "more") return h("li", { "data-type": "more" }, "…");
41
+ const folder = props.type === "folder";
42
+ const mark = marks.get(String(props.state));
43
+ const entry = h("span", { class: "cft-entry" }, [
44
+ props.icon?.src ? h(Icon, { ...props.icon }) : glyph(folder ? glyphs.folder : glyphs.file),
45
+ mark ? h("span", { class: "cft-mark" }, mark) : null,
46
+ h("span", { class: "cft-name" }, props.name),
47
+ props.note ? h("span", { class: "cft-note" }, props.note) : null
48
+ ]);
49
+ return h("li", {
50
+ "data-type": folder ? "folder" : "file",
51
+ "data-state": mark ? props.state : void 0
52
+ }, folder ? [h("details", { open: true }, [h("summary", [entry]), h("ul", slots.default?.())])] : [entry]);
53
+ };
54
+ }
55
+ });
56
+ /** Pass these as `components` to `@comark/vue`. */
57
+ const components = {
58
+ FileTree,
59
+ FileTreeItem
60
+ };
61
+ //#endregion
62
+ export { FileTree, FileTreeItem, components, src_default as default };
package/package.json CHANGED
@@ -1,6 +1,92 @@
1
1
  {
2
2
  "name": "comark-file-tree",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.7.1",
4
+ "description": "A project's folders and files from an indented list or pasted tree output, with added, removed, and changed entries marked. A Comark plugin.",
5
+ "keywords": [
6
+ "comark",
7
+ "file-tree",
8
+ "markdown",
9
+ "mdc",
10
+ "plugin",
11
+ "tree"
12
+ ],
13
+ "homepage": "https://comark-ui.onmax.me/file-tree",
14
+ "bugs": "https://github.com/onmax/comark-ui/issues",
15
+ "license": "MIT",
16
+ "author": "onmax",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/onmax/comark-ui.git",
20
+ "directory": "packages/file-tree"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "type": "module",
26
+ "sideEffects": [
27
+ "*.css"
28
+ ],
29
+ "exports": {
30
+ ".": "./dist/index.mjs",
31
+ "./client": "./dist/client.mjs",
32
+ "./html": "./dist/html.mjs",
33
+ "./react": "./dist/react.mjs",
34
+ "./style.css": "./dist/style.css",
35
+ "./svelte": {
36
+ "types": "./dist/svelte/index.d.ts",
37
+ "svelte": "./dist/svelte/index.js",
38
+ "default": "./dist/svelte/index.js"
39
+ },
40
+ "./vue": "./dist/vue.mjs",
41
+ "./package.json": "./package.json"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "provenance": true
46
+ },
47
+ "dependencies": {
48
+ "comark-icon": "^0.7.1"
49
+ },
50
+ "devDependencies": {
51
+ "@comark/html": "^0.8.1",
52
+ "@comark/react": "^0.8.1",
53
+ "@comark/vue": "^0.8.1",
54
+ "@types/react": "^19.3.0",
55
+ "@types/react-dom": "^19.3.0",
56
+ "comark": "^0.8.1",
57
+ "react": "^19.3.1",
58
+ "react-dom": "^19.3.1",
59
+ "svelte": "^5.57.2",
60
+ "vue": "^3.5.44",
61
+ "comark-zoom": "0.7.1"
62
+ },
63
+ "peerDependencies": {
64
+ "@comark/html": "^0.8.0",
65
+ "comark": "^0.8.0",
66
+ "react": ">=18",
67
+ "svelte": ">=5",
68
+ "vue": ">=3.5"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "@comark/html": {
72
+ "optional": true
73
+ },
74
+ "react": {
75
+ "optional": true
76
+ },
77
+ "svelte": {
78
+ "optional": true
79
+ },
80
+ "vue": {
81
+ "optional": true
82
+ }
83
+ },
84
+ "engines": {
85
+ "node": ">=22.12.0"
86
+ },
87
+ "scripts": {
88
+ "build": "tsdown",
89
+ "dev": "tsdown --watch",
90
+ "doctor": "vite-doctor . --no-cache --max-warnings 0 --format text"
91
+ }
6
92
  }