@vp-tw/dirwell 0.0.0-stage → 0.1.0-alpha.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +182 -2
- package/THEME_PACKAGE_CONTRACT.md +64 -0
- package/THEMING.md +88 -0
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +306 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/bun.d.mts +7 -0
- package/dist/bun.mjs +7 -0
- package/dist/bun.mjs.map +1 -0
- package/dist/config-BJG2Zc3Q.d.mts +34 -0
- package/dist/dev-server-Q7NC1wiB.mjs +132 -0
- package/dist/dev-server-Q7NC1wiB.mjs.map +1 -0
- package/dist/esbuild.d.mts +7 -0
- package/dist/esbuild.mjs +7 -0
- package/dist/esbuild.mjs.map +1 -0
- package/dist/farm.d.mts +7 -0
- package/dist/farm.mjs +7 -0
- package/dist/farm.mjs.map +1 -0
- package/dist/generator-CtX3iHhK.mjs +1783 -0
- package/dist/generator-CtX3iHhK.mjs.map +1 -0
- package/dist/index.d.mts +47 -0
- package/dist/index.mjs +74 -0
- package/dist/index.mjs.map +1 -0
- package/dist/model-CWjYM17j.d.mts +100 -0
- package/dist/rolldown.d.mts +7 -0
- package/dist/rolldown.mjs +7 -0
- package/dist/rolldown.mjs.map +1 -0
- package/dist/rollup.d.mts +7 -0
- package/dist/rollup.mjs +7 -0
- package/dist/rollup.mjs.map +1 -0
- package/dist/rsbuild.d.mts +7 -0
- package/dist/rsbuild.mjs +7 -0
- package/dist/rsbuild.mjs.map +1 -0
- package/dist/rspack.d.mts +7 -0
- package/dist/rspack.mjs +7 -0
- package/dist/rspack.mjs.map +1 -0
- package/dist/theme-components-CXzvdQY-.mjs +8 -0
- package/dist/theme-components-CXzvdQY-.mjs.map +1 -0
- package/dist/theme-components-DW0fv9_C.d.mts +141 -0
- package/dist/theme-components.d.mts +2 -0
- package/dist/theme-components.mjs +2 -0
- package/dist/theme-runtime.js +1128 -0
- package/dist/theme-worker.js +27 -0
- package/dist/unplugin-CKRUCTgW.d.mts +16 -0
- package/dist/unplugin-p6AXhL4F.mjs +421 -0
- package/dist/unplugin-p6AXhL4F.mjs.map +1 -0
- package/dist/unplugin.d.mts +2 -0
- package/dist/unplugin.mjs +2 -0
- package/dist/vite-JBsvRvuW.mjs +282 -0
- package/dist/vite-JBsvRvuW.mjs.map +1 -0
- package/dist/vite.d.mts +12 -0
- package/dist/vite.mjs +2 -0
- package/dist/vscode-icons/NOTICE.txt +16 -0
- package/dist/vscode-icons/default_file.svg +1 -0
- package/dist/vscode-icons/default_folder.svg +1 -0
- package/dist/vscode-icons/file_type_css.svg +1 -0
- package/dist/vscode-icons/file_type_html.svg +1 -0
- package/dist/vscode-icons/file_type_image.svg +1 -0
- package/dist/vscode-icons/file_type_js.svg +1 -0
- package/dist/vscode-icons/file_type_json.svg +1 -0
- package/dist/vscode-icons/file_type_markdown.svg +1 -0
- package/dist/vscode-icons/file_type_pdf2.svg +1 -0
- package/dist/vscode-icons/file_type_shell.svg +1 -0
- package/dist/vscode-icons/file_type_text.svg +1 -0
- package/dist/vscode-icons/file_type_typescript.svg +1 -0
- package/dist/vscode-icons/file_type_yaml.svg +1 -0
- package/dist/vscode-icons/file_type_zip.svg +1 -0
- package/dist/webpack.d.mts +7 -0
- package/dist/webpack.mjs +7 -0
- package/dist/webpack.mjs.map +1 -0
- package/package.json +133 -4
|
@@ -0,0 +1,1783 @@
|
|
|
1
|
+
import { t as resolveThemeComponents } from "./theme-components-CXzvdQY-.mjs";
|
|
2
|
+
import { loadConfig } from "c12";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import picomatch from "picomatch";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
//#region src/filters.ts
|
|
8
|
+
function normalizePathPatterns(value, name) {
|
|
9
|
+
if (value === void 0) return [];
|
|
10
|
+
const patterns = typeof value === "string" ? [value] : value;
|
|
11
|
+
if (!Array.isArray(patterns) || patterns.some((pattern) => typeof pattern !== "string" || pattern.length === 0 || pattern.startsWith("/") || /^[A-Za-z]:/.test(pattern) || pattern.startsWith("!") || pattern.includes("\\") || pattern.split("/").some((segment) => segment === "." || segment === ".."))) throw new TypeError(`${name} must contain root-relative glob patterns using forward slashes`);
|
|
12
|
+
return patterns;
|
|
13
|
+
}
|
|
14
|
+
function isWithin(parent, candidate) {
|
|
15
|
+
const relative = path.relative(parent, candidate);
|
|
16
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
17
|
+
}
|
|
18
|
+
/** Select physical source paths; parent directories remain when a descendant matches. */
|
|
19
|
+
async function selectSourcePaths(sourceDir, outputDir, include, exclude, ignoredPaths = []) {
|
|
20
|
+
const ignored = ignoredPaths.map((value) => path.resolve(value));
|
|
21
|
+
const included = normalizePathPatterns(include, "include").map((pattern) => picomatch(pattern, {
|
|
22
|
+
dot: true,
|
|
23
|
+
nonegate: true
|
|
24
|
+
}));
|
|
25
|
+
const excluded = normalizePathPatterns(exclude, "exclude").map((pattern) => picomatch(pattern, {
|
|
26
|
+
dot: true,
|
|
27
|
+
nonegate: true
|
|
28
|
+
}));
|
|
29
|
+
const selected = /* @__PURE__ */ new Set([""]);
|
|
30
|
+
const symlinks = /* @__PURE__ */ new Map();
|
|
31
|
+
async function visit(directory, relativeDir, inheritedInclude) {
|
|
32
|
+
let hasSelected = false;
|
|
33
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
34
|
+
const absolutePath = path.join(directory, entry.name);
|
|
35
|
+
if (isWithin(outputDir, absolutePath) || ignored.some((value) => isWithin(value, absolutePath))) continue;
|
|
36
|
+
const relativePath = path.posix.join(relativeDir, entry.name);
|
|
37
|
+
const directoryLike = entry.isDirectory();
|
|
38
|
+
const matches = (matcher) => matcher(relativePath) || directoryLike && matcher(`${relativePath}/`);
|
|
39
|
+
if (excluded.some(matches)) continue;
|
|
40
|
+
const includedHere = inheritedInclude || included.some(matches);
|
|
41
|
+
if (directoryLike) {
|
|
42
|
+
const hasSelectedChild = await visit(absolutePath, relativePath, includedHere);
|
|
43
|
+
if (included.length === 0 || includedHere || hasSelectedChild) {
|
|
44
|
+
selected.add(relativePath);
|
|
45
|
+
hasSelected = true;
|
|
46
|
+
}
|
|
47
|
+
} else if (included.length === 0 || includedHere) {
|
|
48
|
+
selected.add(relativePath);
|
|
49
|
+
hasSelected = true;
|
|
50
|
+
if (entry.isSymbolicLink()) symlinks.set(relativePath, await readlink(absolutePath));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return hasSelected;
|
|
54
|
+
}
|
|
55
|
+
await visit(sourceDir, "", false);
|
|
56
|
+
const mirroredSymlinks = /* @__PURE__ */ new Set();
|
|
57
|
+
for (const [relativePath, target] of symlinks) {
|
|
58
|
+
if (path.isAbsolute(target)) continue;
|
|
59
|
+
const targetPath = path.resolve(sourceDir, path.dirname(relativePath), target);
|
|
60
|
+
if (!isWithin(sourceDir, targetPath)) continue;
|
|
61
|
+
const targetRelativePath = path.relative(sourceDir, targetPath).split(path.sep).join("/");
|
|
62
|
+
if (selected.has(targetRelativePath)) mirroredSymlinks.add(relativePath);
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
selected,
|
|
66
|
+
symlinks: new Set(symlinks.keys()),
|
|
67
|
+
mirroredSymlinks
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/config.ts
|
|
72
|
+
function defineConfig(config) {
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
function assertOptionalBoolean(value, key) {
|
|
76
|
+
if (value !== void 0 && typeof value !== "boolean") throw new TypeError(`${key} must be a boolean`);
|
|
77
|
+
}
|
|
78
|
+
function assertOptionalString(value, key) {
|
|
79
|
+
if (value !== void 0 && (typeof value !== "string" || value.length === 0)) throw new TypeError(`${key} must be a non-empty string`);
|
|
80
|
+
}
|
|
81
|
+
function validateConfig(value) {
|
|
82
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Dirwell config must resolve to an object");
|
|
83
|
+
const config = value;
|
|
84
|
+
assertOptionalString(config.root, "root");
|
|
85
|
+
assertOptionalString(config.outDir, "outDir");
|
|
86
|
+
assertOptionalString(config.base, "base");
|
|
87
|
+
normalizePathPatterns(config.include, "include");
|
|
88
|
+
normalizePathPatterns(config.exclude, "exclude");
|
|
89
|
+
if (config.extends !== void 0 && typeof config.extends !== "string" && (!Array.isArray(config.extends) || config.extends.some((entry) => typeof entry !== "string" || entry.length === 0))) throw new TypeError("extends must be a string or an array of non-empty strings");
|
|
90
|
+
assertOptionalBoolean(config.mirror, "mirror");
|
|
91
|
+
if (config.mode !== void 0 && config.mode !== "mpa" && config.mode !== "ssg") throw new TypeError("mode must be either \"ssg\" or \"mpa\"");
|
|
92
|
+
if (config.urls !== void 0 && config.urls !== "relative" && config.urls !== "base" && config.urls !== "html-base") throw new TypeError("urls must be \"relative\", \"base\", or \"html-base\"");
|
|
93
|
+
if (config.outputName !== void 0 && typeof config.outputName !== "function" && (typeof config.outputName !== "string" || config.outputName.length === 0)) throw new TypeError("outputName must be a non-empty string or function");
|
|
94
|
+
if (config.theme !== void 0) {
|
|
95
|
+
const theme = config.theme;
|
|
96
|
+
if (typeof theme !== "object" || theme === null || typeof theme.render !== "function") throw new TypeError("theme must implement render(context)");
|
|
97
|
+
}
|
|
98
|
+
if (config.server !== void 0) {
|
|
99
|
+
if (typeof config.server !== "object" || config.server === null) throw new TypeError("server must be an object");
|
|
100
|
+
const server = config.server;
|
|
101
|
+
assertOptionalString(server.host, "server.host");
|
|
102
|
+
if (server.port !== void 0 && (typeof server.port !== "number" || !Number.isInteger(server.port) || server.port < 0)) throw new TypeError("server.port must be a non-negative integer");
|
|
103
|
+
}
|
|
104
|
+
if (config.sort !== void 0) {
|
|
105
|
+
if (typeof config.sort !== "object" || config.sort === null) throw new TypeError("sort must be an object");
|
|
106
|
+
const sort = config.sort;
|
|
107
|
+
if (sort.field !== void 0 && (typeof sort.field !== "string" || ![
|
|
108
|
+
"name",
|
|
109
|
+
"modified",
|
|
110
|
+
"size"
|
|
111
|
+
].includes(sort.field))) throw new TypeError("sort.field must be \"name\", \"modified\", or \"size\"");
|
|
112
|
+
if (sort.nameMode !== void 0 && (typeof sort.nameMode !== "string" || ![
|
|
113
|
+
"unicode",
|
|
114
|
+
"locale",
|
|
115
|
+
"natural"
|
|
116
|
+
].includes(sort.nameMode))) throw new TypeError("sort.nameMode must be \"unicode\", \"locale\", or \"natural\"");
|
|
117
|
+
if (sort.direction !== void 0 && sort.direction !== "asc" && sort.direction !== "desc") throw new TypeError("sort.direction must be \"asc\" or \"desc\"");
|
|
118
|
+
assertOptionalBoolean(sort.directoriesFirst, "sort.directoriesFirst");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function loadDirwellConfig(cwd, command) {
|
|
122
|
+
const loaded = await loadConfig({
|
|
123
|
+
name: "dirwell",
|
|
124
|
+
cwd,
|
|
125
|
+
context: { command },
|
|
126
|
+
defaults: {},
|
|
127
|
+
rcFile: false,
|
|
128
|
+
globalRc: false
|
|
129
|
+
});
|
|
130
|
+
validateConfig(loaded.config);
|
|
131
|
+
return {
|
|
132
|
+
config: loaded.config,
|
|
133
|
+
configFile: loaded._configFile ?? null
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function resolveGenerateOptions(cwd, config, overrides = {}) {
|
|
137
|
+
return {
|
|
138
|
+
sourceDir: path.resolve(cwd, overrides.root ?? config.root ?? "."),
|
|
139
|
+
outputDir: path.resolve(cwd, overrides.outDir ?? config.outDir ?? "dist"),
|
|
140
|
+
...overrides.base === void 0 && config.base === void 0 ? {} : { base: overrides.base ?? config.base },
|
|
141
|
+
...overrides.mode === void 0 && config.mode === void 0 ? {} : { mode: overrides.mode ?? config.mode },
|
|
142
|
+
...config.mirror === void 0 ? {} : { mirror: config.mirror },
|
|
143
|
+
...config.include === void 0 ? {} : { include: config.include },
|
|
144
|
+
...config.exclude === void 0 ? {} : { exclude: config.exclude },
|
|
145
|
+
...config.outputName === void 0 ? {} : { outputName: config.outputName },
|
|
146
|
+
...config.symlinks === void 0 ? {} : { symlinks: config.symlinks },
|
|
147
|
+
...config.sort === void 0 ? {} : { sort: config.sort },
|
|
148
|
+
...config.theme === void 0 ? {} : { theme: config.theme },
|
|
149
|
+
...overrides.urls === void 0 && config.urls === void 0 ? {} : { urlStrategy: overrides.urls ?? config.urls }
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/timestamp.ts
|
|
154
|
+
/** Keep generated dates readable without JavaScript or a viewer-specific time zone. */
|
|
155
|
+
function utcTimestamp(value) {
|
|
156
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value)) return null;
|
|
157
|
+
const instant = new Date(value);
|
|
158
|
+
if (Number.isNaN(instant.getTime())) return null;
|
|
159
|
+
const datetime = instant.toISOString();
|
|
160
|
+
return {
|
|
161
|
+
datetime,
|
|
162
|
+
label: `${datetime.slice(0, 16).replace("T", " ")} UTC`
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/theme-default/components.ts
|
|
167
|
+
function escapeHtml(value) {
|
|
168
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
169
|
+
}
|
|
170
|
+
function renderSelectControl(name, options) {
|
|
171
|
+
return `<select data-${name}>${options.map(([value, label]) => `<option value="${escapeHtml(value)}">${escapeHtml(label)}</option>`).join("")}</select>`;
|
|
172
|
+
}
|
|
173
|
+
function renderTypeFilters() {
|
|
174
|
+
return `<fieldset class="type-filters" data-type-filters><legend class="visually-hidden">Show file types</legend>${[
|
|
175
|
+
["directory", "Folders"],
|
|
176
|
+
["file", "Files"],
|
|
177
|
+
["link", "Links"]
|
|
178
|
+
].map(([value, label]) => `<label><input type="checkbox" value="${value}" data-type-filter checked><span>${label}</span></label>`).join("")}</fieldset>`;
|
|
179
|
+
}
|
|
180
|
+
function formatSize(bytes) {
|
|
181
|
+
const units = [
|
|
182
|
+
"B",
|
|
183
|
+
"KB",
|
|
184
|
+
"MB",
|
|
185
|
+
"GB",
|
|
186
|
+
"TB"
|
|
187
|
+
];
|
|
188
|
+
let value = bytes;
|
|
189
|
+
let unit = 0;
|
|
190
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
191
|
+
value /= 1024;
|
|
192
|
+
unit += 1;
|
|
193
|
+
}
|
|
194
|
+
return `${value.toFixed(unit === 0 || value >= 100 ? 0 : 1)} ${units[unit]}`;
|
|
195
|
+
}
|
|
196
|
+
function renderSize(entry) {
|
|
197
|
+
if (entry.symlink === null) return entry.kind === "directory" ? "directory" : formatSize(entry.metadata.size);
|
|
198
|
+
const symlink = entry.symlink;
|
|
199
|
+
const targetSize = !(symlink.isBroken || symlink.isOutsideRoot || symlink.isTargetExcluded) && symlink.targetKind === "directory" ? "folder" : symlink.targetSize === null || symlink.targetSize === void 0 ? null : formatSize(symlink.targetSize);
|
|
200
|
+
return `<span class="size-stack"><span>Link <b>${formatSize(entry.metadata.size)}</b></span>${targetSize === null ? "" : `<span>Target <b>${targetSize}</b></span>`}</span>`;
|
|
201
|
+
}
|
|
202
|
+
const iconPaths = {
|
|
203
|
+
"arrow-up": "<path d=\"M12 19V5m-7 7 7-7 7 7\"/>",
|
|
204
|
+
"chevron-right": "<path d=\"m9 18 6-6-6-6\"/>",
|
|
205
|
+
file: "<path d=\"M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5z\"/><polyline points=\"14 2 14 8 20 8\"/>",
|
|
206
|
+
folder: "<path d=\"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.9 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"/>",
|
|
207
|
+
link: "<path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/>",
|
|
208
|
+
moon: "<path d=\"M20.99 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 20.99 12.79z\"/>",
|
|
209
|
+
search: "<circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.3-4.3\"/>",
|
|
210
|
+
sun: "<circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2M12 20v2M4.93 4.93l1.42 1.42M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.42\"/>"
|
|
211
|
+
};
|
|
212
|
+
function safeExternalHref(value) {
|
|
213
|
+
if (value === void 0) return null;
|
|
214
|
+
try {
|
|
215
|
+
const url = new URL(value);
|
|
216
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
|
|
217
|
+
} catch {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const ledgerReadmeUrl = "https://github.com/vp-tw/dirwell/blob/main/src/theme-default/README.md";
|
|
222
|
+
function controlIcon(name, size = 16) {
|
|
223
|
+
return `<svg class="icon" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${iconPaths[name]}</svg>`;
|
|
224
|
+
}
|
|
225
|
+
const defaultThemeComponents = {
|
|
226
|
+
Icon: ({ label, name, size = 16, src, darkSrc }) => src === void 0 ? label === void 0 ? controlIcon(name, size) : `<svg class="icon" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" role="img" aria-label="${escapeHtml(label)}">${iconPaths[name]}</svg>` : `<img class="icon file-icon${darkSrc === void 0 ? "" : " file-icon--light"}" width="20" height="20" src="${escapeHtml(src)}" alt="" loading="lazy">${darkSrc === void 0 ? "" : `<img class="icon file-icon file-icon--dark" width="20" height="20" src="${escapeHtml(darkSrc)}" alt="" loading="lazy">`}`,
|
|
227
|
+
Breadcrumbs: ({ items }) => `<nav class="breadcrumbs" aria-label="Breadcrumb">${items.map((item) => item.href === null ? `<span aria-current="page">${escapeHtml(item.label)}</span>` : `<a href="${escapeHtml(item.href)}">${escapeHtml(item.label)}</a>`).join("<span class=\"separator\" aria-hidden=\"true\">/</span>")}</nav>`,
|
|
228
|
+
Toolbar: ({ colorScheme, fuzzySearch, globalSearch, keyboardNavigation, searchIcon, sorting }) => {
|
|
229
|
+
const shortcutAttributes = keyboardNavigation ? " aria-keyshortcuts=\"/\" aria-describedby=\"keyboard-shortcuts\"" : "";
|
|
230
|
+
const searchAll = globalSearch ? `<button class="search-all" type="button" data-global-open>Search all files</button>` : "";
|
|
231
|
+
const search = fuzzySearch ? `<div class="search-cluster"><label class="search">${searchIcon}<span class="visually-hidden">Search this folder</span><input type="search" autocomplete="off" placeholder="Search this folder"${shortcutAttributes} data-search-input></label>${renderTypeFilters()}${searchAll}</div>` : searchAll;
|
|
232
|
+
const scheme = colorScheme ? `<label class="scheme">Theme${renderSelectControl("color-scheme", [
|
|
233
|
+
["system", "System"],
|
|
234
|
+
["light", "Light"],
|
|
235
|
+
["dark", "Dark"]
|
|
236
|
+
])}</label>` : "";
|
|
237
|
+
const sort = sorting ? `<details class="sort-panel"><summary>Sort</summary><div class="sort-menu"><label>Sort by${renderSelectControl("sort-field", [
|
|
238
|
+
["name", "Name"],
|
|
239
|
+
["modified", "Modified"],
|
|
240
|
+
["size", "Size"]
|
|
241
|
+
])}</label><label data-name-mode-control>Name order${renderSelectControl("name-mode", [
|
|
242
|
+
["natural", "Natural (numbers)"],
|
|
243
|
+
["locale", "Locale"],
|
|
244
|
+
["unicode", "Unicode"]
|
|
245
|
+
])}</label><label>Direction${renderSelectControl("sort-direction", [["asc", "Ascending"], ["desc", "Descending"]])}</label><label><input type="checkbox" data-directories-first> Folders first</label></div></details>` : "";
|
|
246
|
+
return search || scheme || sort ? `<div class="toolbar">${search}<div class="toolbar-actions">${sort}${scheme}</div></div>` : "";
|
|
247
|
+
},
|
|
248
|
+
EntryRow: ({ entry, icon, index, navigation }) => {
|
|
249
|
+
const directoryLike = entry.kind === "directory" || entry.symlink?.targetKind === "directory";
|
|
250
|
+
const size = renderSize(entry);
|
|
251
|
+
const modified = utcTimestamp(entry.metadata.times.modifiedAt);
|
|
252
|
+
const linkAttributes = navigation.exitsExplorer ? " target=\"_blank\" rel=\"noopener\"" : "";
|
|
253
|
+
const label = `${escapeHtml(entry.name)}${directoryLike ? "/" : ""}`;
|
|
254
|
+
const unavailable = entry.symlink?.isBroken || entry.symlink?.isOutsideRoot || entry.symlink?.isTargetExcluded;
|
|
255
|
+
const target = entry.symlink === null ? "" : unavailable ? `<span class="target"><span class="target-status">Target unavailable</span> · <span class="target-unavailable">${escapeHtml(entry.symlink.target)}</span></span>` : entry.symlink.isCycle ? `<span class="target"><span class="target-status">Cycle</span> · ${escapeHtml(entry.symlink.target)}</span>` : `<span class="target">→ ${escapeHtml(entry.symlink.target)}</span>`;
|
|
256
|
+
const searchText = escapeHtml(`${entry.name} ${entry.symlink?.target ?? ""}`.toLowerCase());
|
|
257
|
+
const name = `<span class="entry-name">${icon}<span>${label}</span></span>`;
|
|
258
|
+
const kind = directoryLike ? "directory" : entry.kind === "symlink" && entry.symlink?.targetKind === null ? "link" : "file";
|
|
259
|
+
return `<li class="entry" data-entry data-order="${index}" data-search="${searchText}" data-name="${escapeHtml(entry.name)}" data-size="${entry.kind === "directory" ? 0 : entry.metadata.size}" data-modified="${modified === null ? 0 : Date.parse(modified.datetime)}" data-directory="${String(directoryLike)}" data-link="${String(entry.kind === "symlink")}" data-kind="${kind}">
|
|
260
|
+
<span class="identity">${navigation.href === null ? `<span class="name unavailable">${name}</span>` : `<a class="name" href="${escapeHtml(navigation.href)}"${linkAttributes}>${name}</a>`}${target}</span>
|
|
261
|
+
<span class="kind">${size}</span>
|
|
262
|
+
${modified === null ? "<span class=\"modified\">Unknown</span>" : `<time datetime="${modified.datetime}">${modified.label}</time>`}
|
|
263
|
+
</li>`;
|
|
264
|
+
},
|
|
265
|
+
EntryList: ({ directory, parentHref, rows, sorting }) => {
|
|
266
|
+
return `<div class="entry-head">${[
|
|
267
|
+
"name",
|
|
268
|
+
"size",
|
|
269
|
+
"modified"
|
|
270
|
+
].map((field) => sorting ? `<button class="sort-heading" type="button" data-sort-heading="${field}">${field}<span class="sort-indicator">${controlIcon("arrow-up", 14)}</span></button>` : `<span class="sort-heading">${field}</span>`).join("")}</div><ul class="entries" data-entry-list>${directory.depth > 0 && parentHref !== null ? `<li class="entry" data-parent><a class="name" href="${escapeHtml(parentHref)}">../</a><span class="kind">parent</span><span></span></li>` : ""}${rows}</ul>`;
|
|
271
|
+
},
|
|
272
|
+
EmptyState: ({ message }) => `<p class="empty" data-empty hidden>${escapeHtml(message)}</p>`,
|
|
273
|
+
Footer: ({ iconNoticeHref, iconNoticeKind, keyboardNavigation, parentHref, project }) => {
|
|
274
|
+
const shortcuts = keyboardNavigation ? `<p class="shortcuts" id="keyboard-shortcuts"><kbd>/</kbd> search <kbd>↑</kbd><kbd>↓</kbd> browse <kbd>Esc</kbd> clear${parentHref === null ? "" : " <kbd>Backspace</kbd> parent"}</p>` : "";
|
|
275
|
+
const authorHref = safeExternalHref(project.authorUrl);
|
|
276
|
+
const author = authorHref === null ? escapeHtml(project.author) : `<a href="${escapeHtml(authorHref)}" target="_blank" rel="noopener">${escapeHtml(project.author)}</a>`;
|
|
277
|
+
const name = `<a href="${escapeHtml(project.repositoryUrl)}" target="_blank" rel="noopener">${escapeHtml(project.name)}</a>`;
|
|
278
|
+
const ledger = `<a href="${ledgerReadmeUrl}" target="_blank" rel="noopener">Ledger</a>`;
|
|
279
|
+
return `<footer><p class="project-meta">${project.name === "Dirwell" && project.author === "VdustR" ? `${name} <span class="meta-item">· ${ledger} by ${author}</span>` : `<span class="project-credit">${name} by ${author}</span> <span class="meta-item">· ${ledger}</span>`}${iconNoticeHref !== void 0 && iconNoticeKind !== "built-in" ? ` <span class="meta-item">· <a href="${escapeHtml(iconNoticeHref)}" target="_blank" rel="noopener">Icon licenses</a></span>` : ""}</p>${shortcuts}</footer>`;
|
|
280
|
+
},
|
|
281
|
+
PageShell: ({ assets, breadcrumbs, directory, documentBaseHref, emptyState, entryList, footer, parentHref, runtimeConfig, styles, toolbar, visiblePath }) => `<!doctype html>
|
|
282
|
+
<html lang="en" data-theme="system"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="light dark">${documentBaseHref === null ? "" : `<base href="${escapeHtml(documentBaseHref)}">`}<title>${escapeHtml(visiblePath)} · Files</title><style>${styles}</style></head>
|
|
283
|
+
<body><main data-explorer data-config="${escapeHtml(JSON.stringify(runtimeConfig))}"${parentHref === null ? "" : ` data-parent-href="${escapeHtml(parentHref)}"`}>
|
|
284
|
+
<header><div class="chrome">${breadcrumbs}<p class="summary"><span data-visible-count>${directory.entries.length}</span> <span data-count-label>${directory.entries.length === 1 ? "entry" : "entries"}</span></p></div><h1 class="visually-hidden">${escapeHtml(visiblePath)}</h1></header>
|
|
285
|
+
${toolbar}${entryList}${runtimeConfig.entriesHref ? "<p class=\"folder-loading\" data-folder-loading>Loading this folder…</p><noscript><style>[data-folder-loading]{display:none}</style><p class=\"folder-loading\">This large folder needs JavaScript. Use SSG mode for a complete HTML listing.</p></noscript>" : ""}${emptyState}<p class="visually-hidden" aria-live="polite" data-live-status></p>${footer}</main>${runtimeConfig.globalSearch ? `<dialog class="global-search" data-global-dialog aria-labelledby="global-search-title"><div class="global-search-head"><h2 id="global-search-title">Search all files</h2><button type="button" class="dialog-close" data-global-close aria-label="Close search">Close</button></div><p>Search across the published directory. The index loads only after you type.</p><div class="global-search-controls"><label class="search"><span class="visually-hidden">Search all files</span><input type="search" autocomplete="off" placeholder="Type a name or path" data-global-input></label>${renderTypeFilters()}</div><p class="global-search-status" data-global-status role="status">Enter a search to begin.</p><ul class="entries global-results" data-global-results></ul></dialog>` : ""}${assets}</body></html>`
|
|
286
|
+
};
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/theme-default/styles.ts
|
|
289
|
+
const defaultStyles = `
|
|
290
|
+
:root {
|
|
291
|
+
color-scheme: light dark;
|
|
292
|
+
--dw-control-height: 2.75rem;
|
|
293
|
+
--dw-sticky-header-height: 3.4rem;
|
|
294
|
+
--dw-sticky-columns-height: calc(var(--dw-control-height) + 2px);
|
|
295
|
+
--paper: #f3f0e8;
|
|
296
|
+
--surface: #fbfaf6;
|
|
297
|
+
--ink: #171815;
|
|
298
|
+
--muted: #64655f;
|
|
299
|
+
--rule: #c9c5ba;
|
|
300
|
+
--soft-rule: #e3dfd5;
|
|
301
|
+
--accent: #174ea6;
|
|
302
|
+
--accent-strong: #103973;
|
|
303
|
+
--focus: #005fcc;
|
|
304
|
+
--hover: #f0eee7;
|
|
305
|
+
--control: #fff;
|
|
306
|
+
--status: #34705c;
|
|
307
|
+
--warning: #9a4616;
|
|
308
|
+
--shadow: 0 30px 80px rgba(46, 46, 40, 0.14);
|
|
309
|
+
}
|
|
310
|
+
:root[data-theme="light"] {
|
|
311
|
+
color-scheme: light;
|
|
312
|
+
--paper: #f3f0e8;
|
|
313
|
+
--surface: #fbfaf6;
|
|
314
|
+
--ink: #171815;
|
|
315
|
+
--muted: #64655f;
|
|
316
|
+
--rule: #c9c5ba;
|
|
317
|
+
--soft-rule: #e3dfd5;
|
|
318
|
+
--accent: #174ea6;
|
|
319
|
+
--accent-strong: #103973;
|
|
320
|
+
--focus: #005fcc;
|
|
321
|
+
--hover: #f0eee7;
|
|
322
|
+
--control: #fff;
|
|
323
|
+
--status: #34705c;
|
|
324
|
+
--warning: #9a4616;
|
|
325
|
+
--shadow: 0 30px 80px rgba(46, 46, 40, 0.14);
|
|
326
|
+
}
|
|
327
|
+
:root[data-theme="dark"] {
|
|
328
|
+
color-scheme: dark;
|
|
329
|
+
--paper: #11130f;
|
|
330
|
+
--surface: #191b17;
|
|
331
|
+
--ink: #f2f0e8;
|
|
332
|
+
--muted: #b1b4ab;
|
|
333
|
+
--rule: #474a42;
|
|
334
|
+
--soft-rule: #30332d;
|
|
335
|
+
--accent: #91bff0;
|
|
336
|
+
--accent-strong: #b7d5f4;
|
|
337
|
+
--focus: #78b7f4;
|
|
338
|
+
--hover: #22251f;
|
|
339
|
+
--control: #10120f;
|
|
340
|
+
--status: #8bc0aa;
|
|
341
|
+
--warning: #f0a06d;
|
|
342
|
+
--shadow: 0 30px 80px rgba(0, 0, 0, 0.28);
|
|
343
|
+
}
|
|
344
|
+
@media (prefers-color-scheme: dark) {
|
|
345
|
+
:root[data-theme="system"] {
|
|
346
|
+
--paper: #11130f;
|
|
347
|
+
--surface: #191b17;
|
|
348
|
+
--ink: #f2f0e8;
|
|
349
|
+
--muted: #b1b4ab;
|
|
350
|
+
--rule: #474a42;
|
|
351
|
+
--soft-rule: #30332d;
|
|
352
|
+
--accent: #91bff0;
|
|
353
|
+
--accent-strong: #b7d5f4;
|
|
354
|
+
--focus: #78b7f4;
|
|
355
|
+
--hover: #22251f;
|
|
356
|
+
--control: #10120f;
|
|
357
|
+
--status: #8bc0aa;
|
|
358
|
+
--warning: #f0a06d;
|
|
359
|
+
--shadow: 0 30px 80px rgba(0, 0, 0, 0.28);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
* {
|
|
363
|
+
box-sizing: border-box;
|
|
364
|
+
}
|
|
365
|
+
html {
|
|
366
|
+
min-height: 100%;
|
|
367
|
+
scroll-padding-top: calc(
|
|
368
|
+
var(--dw-sticky-header-height) + var(--dw-sticky-columns-height) + 0.5rem
|
|
369
|
+
);
|
|
370
|
+
background: var(--paper);
|
|
371
|
+
color: var(--ink);
|
|
372
|
+
}
|
|
373
|
+
body {
|
|
374
|
+
min-width: 0;
|
|
375
|
+
margin: 0;
|
|
376
|
+
padding: clamp(1rem, 5vw, 4rem);
|
|
377
|
+
font:
|
|
378
|
+
15px/1.45 ui-sans-serif,
|
|
379
|
+
system-ui,
|
|
380
|
+
-apple-system,
|
|
381
|
+
BlinkMacSystemFont,
|
|
382
|
+
"Segoe UI",
|
|
383
|
+
sans-serif;
|
|
384
|
+
font-variant-numeric: tabular-nums;
|
|
385
|
+
}
|
|
386
|
+
button,
|
|
387
|
+
input,
|
|
388
|
+
select {
|
|
389
|
+
font: inherit;
|
|
390
|
+
}
|
|
391
|
+
main {
|
|
392
|
+
width: min(72rem, 100%);
|
|
393
|
+
margin: 0 auto;
|
|
394
|
+
background: var(--surface);
|
|
395
|
+
border: 1px solid var(--rule);
|
|
396
|
+
border-radius: 0.875rem;
|
|
397
|
+
box-shadow: var(--shadow);
|
|
398
|
+
overflow: visible;
|
|
399
|
+
}
|
|
400
|
+
main > header {
|
|
401
|
+
position: sticky;
|
|
402
|
+
top: 0;
|
|
403
|
+
z-index: 3;
|
|
404
|
+
background: var(--surface);
|
|
405
|
+
border-radius: calc(0.875rem - 1px) calc(0.875rem - 1px) 0 0;
|
|
406
|
+
}
|
|
407
|
+
main[data-sticky-header-disabled] > header {
|
|
408
|
+
position: static;
|
|
409
|
+
}
|
|
410
|
+
.chrome {
|
|
411
|
+
display: flex;
|
|
412
|
+
flex-wrap: wrap;
|
|
413
|
+
min-height: 3.4rem;
|
|
414
|
+
align-items: center;
|
|
415
|
+
justify-content: space-between;
|
|
416
|
+
gap: 1rem;
|
|
417
|
+
padding: 0 1.4rem;
|
|
418
|
+
border-bottom: 1px solid var(--soft-rule);
|
|
419
|
+
}
|
|
420
|
+
.breadcrumbs {
|
|
421
|
+
display: flex;
|
|
422
|
+
min-width: 0;
|
|
423
|
+
align-items: center;
|
|
424
|
+
flex-wrap: wrap;
|
|
425
|
+
gap: 0.15rem;
|
|
426
|
+
font-size: 0.95rem;
|
|
427
|
+
font-weight: 650;
|
|
428
|
+
}
|
|
429
|
+
.breadcrumbs a,
|
|
430
|
+
.breadcrumbs span {
|
|
431
|
+
color: inherit;
|
|
432
|
+
overflow-wrap: anywhere;
|
|
433
|
+
min-width: 0;
|
|
434
|
+
}
|
|
435
|
+
.breadcrumbs a {
|
|
436
|
+
color: var(--accent);
|
|
437
|
+
text-decoration-thickness: 0.08em;
|
|
438
|
+
display: inline-flex;
|
|
439
|
+
min-height: var(--dw-control-height);
|
|
440
|
+
align-items: center;
|
|
441
|
+
padding: 0 0.2rem;
|
|
442
|
+
}
|
|
443
|
+
.separator {
|
|
444
|
+
display: inline-flex;
|
|
445
|
+
color: var(--muted);
|
|
446
|
+
font-weight: 400;
|
|
447
|
+
}
|
|
448
|
+
.summary {
|
|
449
|
+
margin: 0;
|
|
450
|
+
color: var(--muted);
|
|
451
|
+
font-size: 0.76rem;
|
|
452
|
+
letter-spacing: 0.08em;
|
|
453
|
+
text-transform: uppercase;
|
|
454
|
+
white-space: nowrap;
|
|
455
|
+
}
|
|
456
|
+
.toolbar {
|
|
457
|
+
display: flex;
|
|
458
|
+
justify-content: space-between;
|
|
459
|
+
gap: 1rem;
|
|
460
|
+
padding: 1.25rem 1.4rem;
|
|
461
|
+
align-items: flex-start;
|
|
462
|
+
}
|
|
463
|
+
.search-cluster {
|
|
464
|
+
display: flex;
|
|
465
|
+
flex-wrap: wrap;
|
|
466
|
+
min-width: 0;
|
|
467
|
+
flex: 1;
|
|
468
|
+
gap: 0.6rem;
|
|
469
|
+
align-items: center;
|
|
470
|
+
}
|
|
471
|
+
.search {
|
|
472
|
+
display: flex;
|
|
473
|
+
align-items: center;
|
|
474
|
+
gap: 0.55rem;
|
|
475
|
+
width: min(28rem, 100%);
|
|
476
|
+
min-height: var(--dw-control-height);
|
|
477
|
+
padding: 0.5rem 0.7rem;
|
|
478
|
+
border: 1px solid var(--rule);
|
|
479
|
+
border-radius: 0.5rem;
|
|
480
|
+
background: var(--control);
|
|
481
|
+
}
|
|
482
|
+
.search:focus-within {
|
|
483
|
+
outline: 3px solid var(--focus);
|
|
484
|
+
outline-offset: 2px;
|
|
485
|
+
}
|
|
486
|
+
.search input {
|
|
487
|
+
width: 100%;
|
|
488
|
+
min-width: 0;
|
|
489
|
+
border: 0;
|
|
490
|
+
outline: 0;
|
|
491
|
+
background: transparent;
|
|
492
|
+
color: var(--ink);
|
|
493
|
+
}
|
|
494
|
+
.search input::placeholder {
|
|
495
|
+
color: var(--muted);
|
|
496
|
+
opacity: 1;
|
|
497
|
+
}
|
|
498
|
+
.type-filters {
|
|
499
|
+
display: flex;
|
|
500
|
+
flex-wrap: nowrap;
|
|
501
|
+
align-items: center;
|
|
502
|
+
min-width: 0;
|
|
503
|
+
min-height: var(--dw-control-height);
|
|
504
|
+
max-width: 100%;
|
|
505
|
+
margin: 0;
|
|
506
|
+
padding: 0;
|
|
507
|
+
border: 1px solid var(--rule);
|
|
508
|
+
border-radius: 0.5rem;
|
|
509
|
+
background: var(--control);
|
|
510
|
+
color: var(--ink);
|
|
511
|
+
font-size: 0.78rem;
|
|
512
|
+
overflow: hidden;
|
|
513
|
+
}
|
|
514
|
+
.type-filters label {
|
|
515
|
+
position: relative;
|
|
516
|
+
display: inline-flex;
|
|
517
|
+
align-items: center;
|
|
518
|
+
justify-content: center;
|
|
519
|
+
min-height: calc(var(--dw-control-height) - 2px);
|
|
520
|
+
padding: 0.35rem 0.7rem;
|
|
521
|
+
white-space: nowrap;
|
|
522
|
+
cursor: pointer;
|
|
523
|
+
}
|
|
524
|
+
.type-filters label + label {
|
|
525
|
+
border-left: 1px solid var(--rule);
|
|
526
|
+
}
|
|
527
|
+
.type-filters label:hover {
|
|
528
|
+
background: var(--hover);
|
|
529
|
+
}
|
|
530
|
+
.type-filters label:has(input:checked) {
|
|
531
|
+
background: color-mix(in srgb, var(--accent) 16%, var(--control));
|
|
532
|
+
color: var(--accent-strong);
|
|
533
|
+
}
|
|
534
|
+
.type-filters label:has(input:focus-visible) {
|
|
535
|
+
box-shadow: inset 0 0 0 3px var(--focus);
|
|
536
|
+
}
|
|
537
|
+
.type-filters input {
|
|
538
|
+
position: absolute;
|
|
539
|
+
width: 1px;
|
|
540
|
+
height: 1px;
|
|
541
|
+
margin: 0;
|
|
542
|
+
opacity: 0;
|
|
543
|
+
}
|
|
544
|
+
.sort-menu select {
|
|
545
|
+
min-height: var(--dw-control-height);
|
|
546
|
+
border: 1px solid var(--rule);
|
|
547
|
+
border-radius: 0.5rem;
|
|
548
|
+
background: var(--control);
|
|
549
|
+
color: var(--ink);
|
|
550
|
+
padding: 0.35rem 1.8rem 0.35rem 0.55rem;
|
|
551
|
+
}
|
|
552
|
+
.search-all,
|
|
553
|
+
.dialog-close {
|
|
554
|
+
min-height: var(--dw-control-height);
|
|
555
|
+
border: 1px solid var(--rule);
|
|
556
|
+
border-radius: 0.5rem;
|
|
557
|
+
padding: 0.4rem 0.75rem;
|
|
558
|
+
background: var(--control);
|
|
559
|
+
color: var(--accent);
|
|
560
|
+
white-space: nowrap;
|
|
561
|
+
cursor: pointer;
|
|
562
|
+
}
|
|
563
|
+
.search-all:hover,
|
|
564
|
+
.dialog-close:hover {
|
|
565
|
+
background: var(--hover);
|
|
566
|
+
}
|
|
567
|
+
.global-search {
|
|
568
|
+
width: min(58rem, calc(100vw - 2rem));
|
|
569
|
+
max-height: min(85vh, 55rem);
|
|
570
|
+
padding: 0;
|
|
571
|
+
border: 1px solid var(--rule);
|
|
572
|
+
border-radius: 0.875rem;
|
|
573
|
+
background: var(--surface);
|
|
574
|
+
color: var(--ink);
|
|
575
|
+
box-shadow: var(--shadow);
|
|
576
|
+
}
|
|
577
|
+
.global-search::backdrop {
|
|
578
|
+
background: rgba(10, 12, 10, 0.62);
|
|
579
|
+
}
|
|
580
|
+
.global-search-head,
|
|
581
|
+
.global-search-controls {
|
|
582
|
+
display: flex;
|
|
583
|
+
align-items: center;
|
|
584
|
+
gap: 0.75rem;
|
|
585
|
+
padding: 1rem 1.25rem;
|
|
586
|
+
}
|
|
587
|
+
.global-search-head {
|
|
588
|
+
flex-wrap: wrap;
|
|
589
|
+
justify-content: space-between;
|
|
590
|
+
border-bottom: 1px solid var(--soft-rule);
|
|
591
|
+
}
|
|
592
|
+
.global-search h2 {
|
|
593
|
+
margin: 0;
|
|
594
|
+
font-size: 1.2rem;
|
|
595
|
+
}
|
|
596
|
+
.global-search > p {
|
|
597
|
+
margin: 0;
|
|
598
|
+
padding: 0.75rem 1.25rem;
|
|
599
|
+
color: var(--muted);
|
|
600
|
+
}
|
|
601
|
+
.global-search-controls .search {
|
|
602
|
+
flex: 1;
|
|
603
|
+
}
|
|
604
|
+
.global-results {
|
|
605
|
+
max-height: 50vh;
|
|
606
|
+
overflow: auto;
|
|
607
|
+
}
|
|
608
|
+
.virtual-spacer {
|
|
609
|
+
display: block;
|
|
610
|
+
padding: 0;
|
|
611
|
+
margin: 0;
|
|
612
|
+
border: 0;
|
|
613
|
+
pointer-events: none;
|
|
614
|
+
}
|
|
615
|
+
.toolbar-actions {
|
|
616
|
+
display: flex;
|
|
617
|
+
align-items: flex-start;
|
|
618
|
+
gap: 0.6rem;
|
|
619
|
+
}
|
|
620
|
+
.scheme {
|
|
621
|
+
display: inline-flex;
|
|
622
|
+
align-items: center;
|
|
623
|
+
gap: 0.45rem;
|
|
624
|
+
color: var(--muted);
|
|
625
|
+
font-size: 0.78rem;
|
|
626
|
+
white-space: nowrap;
|
|
627
|
+
}
|
|
628
|
+
.scheme select {
|
|
629
|
+
min-height: var(--dw-control-height);
|
|
630
|
+
border: 1px solid var(--rule);
|
|
631
|
+
border-radius: 0.5rem;
|
|
632
|
+
padding: 0.35rem 1.65rem 0.35rem 0.6rem;
|
|
633
|
+
background: var(--control);
|
|
634
|
+
color: var(--ink);
|
|
635
|
+
cursor: pointer;
|
|
636
|
+
}
|
|
637
|
+
.sort-panel {
|
|
638
|
+
position: relative;
|
|
639
|
+
}
|
|
640
|
+
.sort-panel > summary {
|
|
641
|
+
display: flex;
|
|
642
|
+
min-height: var(--dw-control-height);
|
|
643
|
+
align-items: center;
|
|
644
|
+
list-style: none;
|
|
645
|
+
border: 1px solid var(--rule);
|
|
646
|
+
border-radius: 0.5rem;
|
|
647
|
+
background: var(--control);
|
|
648
|
+
padding: 0.45rem 0.7rem;
|
|
649
|
+
cursor: pointer;
|
|
650
|
+
}
|
|
651
|
+
.sort-panel > summary::-webkit-details-marker {
|
|
652
|
+
display: none;
|
|
653
|
+
}
|
|
654
|
+
.sort-panel[open] > summary {
|
|
655
|
+
outline: 2px solid var(--focus);
|
|
656
|
+
outline-offset: 1px;
|
|
657
|
+
}
|
|
658
|
+
.sort-menu {
|
|
659
|
+
position: absolute;
|
|
660
|
+
z-index: 2;
|
|
661
|
+
right: 0;
|
|
662
|
+
top: calc(100% + 0.4rem);
|
|
663
|
+
display: grid;
|
|
664
|
+
grid-template-columns: minmax(0, 1fr);
|
|
665
|
+
width: 16rem;
|
|
666
|
+
gap: 0.7rem;
|
|
667
|
+
padding: 1rem;
|
|
668
|
+
border: 1px solid var(--rule);
|
|
669
|
+
border-radius: 0.75rem;
|
|
670
|
+
background: var(--surface);
|
|
671
|
+
box-shadow: var(--shadow);
|
|
672
|
+
}
|
|
673
|
+
.sort-menu select {
|
|
674
|
+
min-width: 0;
|
|
675
|
+
width: 100%;
|
|
676
|
+
}
|
|
677
|
+
.sort-menu label {
|
|
678
|
+
display: grid;
|
|
679
|
+
gap: 0.3rem;
|
|
680
|
+
color: var(--muted);
|
|
681
|
+
font-size: 0.76rem;
|
|
682
|
+
}
|
|
683
|
+
.sort-menu label[hidden] {
|
|
684
|
+
display: none;
|
|
685
|
+
}
|
|
686
|
+
.sort-menu label:last-child {
|
|
687
|
+
display: flex;
|
|
688
|
+
align-items: center;
|
|
689
|
+
color: var(--ink);
|
|
690
|
+
}
|
|
691
|
+
.entry-head,
|
|
692
|
+
.entry {
|
|
693
|
+
display: grid;
|
|
694
|
+
grid-template-columns: minmax(12rem, 1fr) minmax(8rem, auto) 12rem;
|
|
695
|
+
gap: 1.25rem;
|
|
696
|
+
}
|
|
697
|
+
.entry-head {
|
|
698
|
+
position: sticky;
|
|
699
|
+
top: var(--dw-sticky-header-height);
|
|
700
|
+
z-index: 2;
|
|
701
|
+
min-height: var(--dw-control-height);
|
|
702
|
+
align-items: center;
|
|
703
|
+
padding: 0 1.4rem;
|
|
704
|
+
border-top: 1px solid var(--soft-rule);
|
|
705
|
+
border-bottom: 1px solid var(--soft-rule);
|
|
706
|
+
background: var(--paper);
|
|
707
|
+
}
|
|
708
|
+
.sort-heading {
|
|
709
|
+
display: inline-flex;
|
|
710
|
+
min-height: var(--dw-control-height);
|
|
711
|
+
align-items: center;
|
|
712
|
+
gap: 0.25rem;
|
|
713
|
+
width: max-content;
|
|
714
|
+
border: 0;
|
|
715
|
+
background: transparent;
|
|
716
|
+
color: var(--muted);
|
|
717
|
+
padding: 0;
|
|
718
|
+
font-size: 0.68rem;
|
|
719
|
+
font-weight: 720;
|
|
720
|
+
letter-spacing: 0.09em;
|
|
721
|
+
text-transform: uppercase;
|
|
722
|
+
cursor: pointer;
|
|
723
|
+
}
|
|
724
|
+
button.sort-heading {
|
|
725
|
+
min-width: var(--dw-control-height);
|
|
726
|
+
}
|
|
727
|
+
.sort-heading[aria-pressed="true"] {
|
|
728
|
+
color: var(--accent);
|
|
729
|
+
}
|
|
730
|
+
.sort-indicator {
|
|
731
|
+
display: none;
|
|
732
|
+
align-items: center;
|
|
733
|
+
}
|
|
734
|
+
.sort-heading[aria-pressed="true"] .sort-indicator {
|
|
735
|
+
display: inline-flex;
|
|
736
|
+
}
|
|
737
|
+
.sort-heading[data-direction="desc"] .sort-indicator {
|
|
738
|
+
transform: rotate(180deg);
|
|
739
|
+
}
|
|
740
|
+
.sort-heading .sort-indicator .icon {
|
|
741
|
+
color: currentColor;
|
|
742
|
+
}
|
|
743
|
+
.entries {
|
|
744
|
+
list-style: none;
|
|
745
|
+
margin: 0;
|
|
746
|
+
padding: 0;
|
|
747
|
+
overflow-anchor: none;
|
|
748
|
+
}
|
|
749
|
+
.entry {
|
|
750
|
+
min-height: 3.25rem;
|
|
751
|
+
align-items: center;
|
|
752
|
+
padding: 0.72rem 1.4rem;
|
|
753
|
+
border-bottom: 1px solid var(--soft-rule);
|
|
754
|
+
}
|
|
755
|
+
.entry:hover,
|
|
756
|
+
.entry[data-active="true"] {
|
|
757
|
+
background: var(--hover);
|
|
758
|
+
}
|
|
759
|
+
.entry[hidden] {
|
|
760
|
+
display: none;
|
|
761
|
+
}
|
|
762
|
+
a {
|
|
763
|
+
color: var(--accent);
|
|
764
|
+
text-decoration-thickness: 0.08em;
|
|
765
|
+
text-underline-offset: 0.22em;
|
|
766
|
+
}
|
|
767
|
+
.identity {
|
|
768
|
+
display: flex;
|
|
769
|
+
min-width: 0;
|
|
770
|
+
flex-wrap: wrap;
|
|
771
|
+
row-gap: 0.15rem;
|
|
772
|
+
}
|
|
773
|
+
.entry-name {
|
|
774
|
+
display: inline-flex;
|
|
775
|
+
align-items: center;
|
|
776
|
+
gap: 0.55rem;
|
|
777
|
+
}
|
|
778
|
+
.name {
|
|
779
|
+
font-weight: 650;
|
|
780
|
+
overflow-wrap: anywhere;
|
|
781
|
+
}
|
|
782
|
+
.entry .name {
|
|
783
|
+
text-decoration: none;
|
|
784
|
+
}
|
|
785
|
+
.entry .name:hover,
|
|
786
|
+
.entry .name:focus-visible {
|
|
787
|
+
text-decoration: underline;
|
|
788
|
+
}
|
|
789
|
+
.icon {
|
|
790
|
+
display: inline-block;
|
|
791
|
+
flex: none;
|
|
792
|
+
vertical-align: -0.15em;
|
|
793
|
+
color: var(--status);
|
|
794
|
+
}
|
|
795
|
+
.file-icon {
|
|
796
|
+
width: 1.25rem;
|
|
797
|
+
height: 1.25rem;
|
|
798
|
+
object-fit: contain;
|
|
799
|
+
}
|
|
800
|
+
.file-icon--dark,
|
|
801
|
+
:root[data-theme="dark"] .file-icon--light {
|
|
802
|
+
display: none;
|
|
803
|
+
}
|
|
804
|
+
:root[data-theme="dark"] .file-icon--dark {
|
|
805
|
+
display: inline-block;
|
|
806
|
+
}
|
|
807
|
+
@media (prefers-color-scheme: dark) {
|
|
808
|
+
:root[data-theme="system"] .file-icon--light {
|
|
809
|
+
display: none;
|
|
810
|
+
}
|
|
811
|
+
:root[data-theme="system"] .file-icon--dark {
|
|
812
|
+
display: inline-block;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
.target {
|
|
816
|
+
flex-basis: 100%;
|
|
817
|
+
padding-left: 1.8rem;
|
|
818
|
+
color: var(--muted);
|
|
819
|
+
font:
|
|
820
|
+
500 0.8rem/1.6 ui-monospace,
|
|
821
|
+
SFMono-Regular,
|
|
822
|
+
monospace;
|
|
823
|
+
overflow-wrap: anywhere;
|
|
824
|
+
}
|
|
825
|
+
.target-status {
|
|
826
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
827
|
+
font-weight: 650;
|
|
828
|
+
}
|
|
829
|
+
.target-unavailable {
|
|
830
|
+
text-decoration: line-through;
|
|
831
|
+
text-decoration-thickness: 1px;
|
|
832
|
+
}
|
|
833
|
+
.unavailable {
|
|
834
|
+
color: var(--muted);
|
|
835
|
+
text-decoration: line-through;
|
|
836
|
+
text-decoration-thickness: 1px;
|
|
837
|
+
}
|
|
838
|
+
.kind,
|
|
839
|
+
time {
|
|
840
|
+
color: var(--muted);
|
|
841
|
+
font-size: 0.82rem;
|
|
842
|
+
}
|
|
843
|
+
.timestamp-button {
|
|
844
|
+
border: 0;
|
|
845
|
+
padding: 0;
|
|
846
|
+
background: transparent;
|
|
847
|
+
text-align: left;
|
|
848
|
+
cursor: pointer;
|
|
849
|
+
min-height: 2.75rem;
|
|
850
|
+
width: fit-content;
|
|
851
|
+
font: inherit;
|
|
852
|
+
}
|
|
853
|
+
.timestamp-button:hover time {
|
|
854
|
+
text-decoration: underline;
|
|
855
|
+
text-underline-offset: 0.2em;
|
|
856
|
+
}
|
|
857
|
+
.timestamp-zone {
|
|
858
|
+
font-weight: 500;
|
|
859
|
+
letter-spacing: 0;
|
|
860
|
+
text-transform: none;
|
|
861
|
+
}
|
|
862
|
+
.timestamp-details {
|
|
863
|
+
position: fixed;
|
|
864
|
+
z-index: 10;
|
|
865
|
+
width: min(26rem, calc(100vw - 1rem));
|
|
866
|
+
max-height: calc(100vh - 1rem);
|
|
867
|
+
overflow: auto;
|
|
868
|
+
padding: 1rem;
|
|
869
|
+
border: 1px solid var(--rule);
|
|
870
|
+
border-radius: 0.75rem;
|
|
871
|
+
background: var(--paper);
|
|
872
|
+
color: var(--ink);
|
|
873
|
+
font-size: 0.85rem;
|
|
874
|
+
}
|
|
875
|
+
.timestamp-details-head {
|
|
876
|
+
display: flex;
|
|
877
|
+
justify-content: space-between;
|
|
878
|
+
align-items: center;
|
|
879
|
+
gap: 1rem;
|
|
880
|
+
}
|
|
881
|
+
.timestamp-details dl {
|
|
882
|
+
margin: 0.75rem 0 0;
|
|
883
|
+
}
|
|
884
|
+
.timestamp-details dt {
|
|
885
|
+
margin-top: 0.75rem;
|
|
886
|
+
color: var(--muted);
|
|
887
|
+
}
|
|
888
|
+
.timestamp-details dd {
|
|
889
|
+
margin: 0.25rem 0 0;
|
|
890
|
+
overflow-wrap: anywhere;
|
|
891
|
+
font-variant-numeric: tabular-nums;
|
|
892
|
+
}
|
|
893
|
+
.size-stack {
|
|
894
|
+
display: grid;
|
|
895
|
+
gap: 0.1rem;
|
|
896
|
+
font-variant-numeric: tabular-nums;
|
|
897
|
+
white-space: nowrap;
|
|
898
|
+
}
|
|
899
|
+
.size-stack b {
|
|
900
|
+
color: var(--ink);
|
|
901
|
+
font-weight: 650;
|
|
902
|
+
}
|
|
903
|
+
a:focus-visible,
|
|
904
|
+
button:focus-visible,
|
|
905
|
+
select:focus-visible,
|
|
906
|
+
summary:focus-visible {
|
|
907
|
+
outline: 3px solid var(--focus);
|
|
908
|
+
outline-offset: 3px;
|
|
909
|
+
}
|
|
910
|
+
.empty {
|
|
911
|
+
margin: 0;
|
|
912
|
+
padding: 2rem 1.4rem;
|
|
913
|
+
color: var(--muted);
|
|
914
|
+
border-bottom: 1px solid var(--soft-rule);
|
|
915
|
+
}
|
|
916
|
+
.folder-loading {
|
|
917
|
+
margin: 0;
|
|
918
|
+
padding: 2rem 1.4rem;
|
|
919
|
+
color: var(--muted);
|
|
920
|
+
}
|
|
921
|
+
::selection {
|
|
922
|
+
background: var(--accent);
|
|
923
|
+
color: var(--surface);
|
|
924
|
+
}
|
|
925
|
+
footer {
|
|
926
|
+
display: flex;
|
|
927
|
+
align-items: center;
|
|
928
|
+
justify-content: space-between;
|
|
929
|
+
gap: 1rem;
|
|
930
|
+
padding: 0.7rem 1.4rem;
|
|
931
|
+
color: var(--muted);
|
|
932
|
+
font-size: 0.78rem;
|
|
933
|
+
}
|
|
934
|
+
.project-meta,
|
|
935
|
+
.shortcuts {
|
|
936
|
+
margin: 0;
|
|
937
|
+
}
|
|
938
|
+
.project-meta {
|
|
939
|
+
line-height: 1.9;
|
|
940
|
+
}
|
|
941
|
+
.meta-item,
|
|
942
|
+
.project-credit {
|
|
943
|
+
white-space: nowrap;
|
|
944
|
+
}
|
|
945
|
+
.shortcuts {
|
|
946
|
+
text-align: right;
|
|
947
|
+
}
|
|
948
|
+
kbd {
|
|
949
|
+
display: inline-block;
|
|
950
|
+
min-width: 1.6em;
|
|
951
|
+
margin: 0 0.12rem;
|
|
952
|
+
padding: 0.02rem 0.3rem;
|
|
953
|
+
border: 1px solid var(--rule);
|
|
954
|
+
border-radius: 0.2rem;
|
|
955
|
+
background: var(--control);
|
|
956
|
+
text-align: center;
|
|
957
|
+
font: inherit;
|
|
958
|
+
}
|
|
959
|
+
.visually-hidden {
|
|
960
|
+
position: absolute !important;
|
|
961
|
+
width: 1px;
|
|
962
|
+
height: 1px;
|
|
963
|
+
padding: 0;
|
|
964
|
+
margin: -1px;
|
|
965
|
+
overflow: hidden;
|
|
966
|
+
clip: rect(0, 0, 0, 0);
|
|
967
|
+
white-space: nowrap;
|
|
968
|
+
border: 0;
|
|
969
|
+
}
|
|
970
|
+
.sort-heading:not(button) {
|
|
971
|
+
cursor: default;
|
|
972
|
+
}
|
|
973
|
+
@media (max-width: 60rem) {
|
|
974
|
+
.toolbar {
|
|
975
|
+
align-items: stretch;
|
|
976
|
+
flex-direction: column;
|
|
977
|
+
}
|
|
978
|
+
.search-cluster {
|
|
979
|
+
align-items: stretch;
|
|
980
|
+
flex-wrap: wrap;
|
|
981
|
+
}
|
|
982
|
+
.search {
|
|
983
|
+
width: 100%;
|
|
984
|
+
}
|
|
985
|
+
.toolbar-actions {
|
|
986
|
+
width: 100%;
|
|
987
|
+
justify-content: space-between;
|
|
988
|
+
}
|
|
989
|
+
.sort-menu {
|
|
990
|
+
right: auto;
|
|
991
|
+
left: 0;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
@media (max-width: 42rem) {
|
|
995
|
+
:root {
|
|
996
|
+
--dw-sticky-columns-height: 0px;
|
|
997
|
+
}
|
|
998
|
+
body {
|
|
999
|
+
padding: 0;
|
|
1000
|
+
background: var(--surface);
|
|
1001
|
+
}
|
|
1002
|
+
main {
|
|
1003
|
+
min-height: 100vh;
|
|
1004
|
+
border: 0;
|
|
1005
|
+
border-radius: 0;
|
|
1006
|
+
box-shadow: none;
|
|
1007
|
+
}
|
|
1008
|
+
main > header {
|
|
1009
|
+
border-radius: 0;
|
|
1010
|
+
}
|
|
1011
|
+
.chrome {
|
|
1012
|
+
padding-inline: 1rem;
|
|
1013
|
+
}
|
|
1014
|
+
.breadcrumbs {
|
|
1015
|
+
flex: 1 1 auto;
|
|
1016
|
+
}
|
|
1017
|
+
.summary {
|
|
1018
|
+
margin-left: auto;
|
|
1019
|
+
}
|
|
1020
|
+
.toolbar {
|
|
1021
|
+
padding: 1rem;
|
|
1022
|
+
}
|
|
1023
|
+
.toolbar-actions {
|
|
1024
|
+
flex-wrap: wrap;
|
|
1025
|
+
}
|
|
1026
|
+
.sort-menu {
|
|
1027
|
+
width: min(16rem, calc(100vw - 2rem));
|
|
1028
|
+
}
|
|
1029
|
+
.global-search-controls {
|
|
1030
|
+
flex-wrap: wrap;
|
|
1031
|
+
}
|
|
1032
|
+
.global-search-controls .search {
|
|
1033
|
+
flex-basis: 100%;
|
|
1034
|
+
}
|
|
1035
|
+
.entry-head {
|
|
1036
|
+
display: none;
|
|
1037
|
+
}
|
|
1038
|
+
.entry {
|
|
1039
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
1040
|
+
gap: 0.25rem 1rem;
|
|
1041
|
+
padding: 0.78rem 1rem;
|
|
1042
|
+
}
|
|
1043
|
+
.entry .name {
|
|
1044
|
+
display: inline-flex;
|
|
1045
|
+
min-height: 2.75rem;
|
|
1046
|
+
align-items: center;
|
|
1047
|
+
}
|
|
1048
|
+
.entry time,
|
|
1049
|
+
.entry .timestamp-button {
|
|
1050
|
+
grid-column: 1/-1;
|
|
1051
|
+
}
|
|
1052
|
+
.kind {
|
|
1053
|
+
text-align: right;
|
|
1054
|
+
}
|
|
1055
|
+
footer {
|
|
1056
|
+
display: flex;
|
|
1057
|
+
flex-direction: column-reverse;
|
|
1058
|
+
align-items: flex-start;
|
|
1059
|
+
gap: 0.25rem;
|
|
1060
|
+
padding: 0.3rem 1rem;
|
|
1061
|
+
}
|
|
1062
|
+
.project-meta a {
|
|
1063
|
+
display: inline-flex;
|
|
1064
|
+
min-height: 2.75rem;
|
|
1065
|
+
align-items: center;
|
|
1066
|
+
}
|
|
1067
|
+
.shortcuts {
|
|
1068
|
+
margin: 0;
|
|
1069
|
+
text-align: left;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
@media (max-width: 19.5rem) {
|
|
1073
|
+
.search-cluster {
|
|
1074
|
+
display: grid;
|
|
1075
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
@media (pointer: coarse) {
|
|
1079
|
+
.entry .name,
|
|
1080
|
+
.project-meta a {
|
|
1081
|
+
display: inline-flex;
|
|
1082
|
+
min-height: 2.75rem;
|
|
1083
|
+
align-items: center;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1087
|
+
* {
|
|
1088
|
+
scroll-behavior: auto !important;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
`;
|
|
1092
|
+
//#endregion
|
|
1093
|
+
//#region src/theme-project.ts
|
|
1094
|
+
const defaultProject = {
|
|
1095
|
+
author: "VdustR",
|
|
1096
|
+
authorUrl: "https://github.com/VdustR",
|
|
1097
|
+
license: "MIT License",
|
|
1098
|
+
licenseUrl: "https://github.com/vp-tw/dirwell/blob/main/LICENSE",
|
|
1099
|
+
name: "Dirwell",
|
|
1100
|
+
repositoryUrl: "https://github.com/vp-tw/dirwell"
|
|
1101
|
+
};
|
|
1102
|
+
function resolveThemeProject(options) {
|
|
1103
|
+
const author = options?.author ?? defaultProject.author;
|
|
1104
|
+
return {
|
|
1105
|
+
author,
|
|
1106
|
+
...options?.authorUrl !== void 0 ? { authorUrl: options.authorUrl } : author === defaultProject.author ? { authorUrl: defaultProject.authorUrl } : {},
|
|
1107
|
+
license: options?.license ?? defaultProject.license,
|
|
1108
|
+
licenseUrl: options?.licenseUrl ?? defaultProject.licenseUrl,
|
|
1109
|
+
name: options?.name ?? defaultProject.name,
|
|
1110
|
+
repositoryUrl: options?.repositoryUrl ?? defaultProject.repositoryUrl
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
//#endregion
|
|
1114
|
+
//#region src/theme-default/vscode-icons.ts
|
|
1115
|
+
const vscodeIconNames = [
|
|
1116
|
+
"default_file",
|
|
1117
|
+
"default_folder",
|
|
1118
|
+
"file_type_css",
|
|
1119
|
+
"file_type_html",
|
|
1120
|
+
"file_type_image",
|
|
1121
|
+
"file_type_js",
|
|
1122
|
+
"file_type_json",
|
|
1123
|
+
"file_type_markdown",
|
|
1124
|
+
"file_type_pdf2",
|
|
1125
|
+
"file_type_shell",
|
|
1126
|
+
"file_type_text",
|
|
1127
|
+
"file_type_typescript",
|
|
1128
|
+
"file_type_yaml",
|
|
1129
|
+
"file_type_zip"
|
|
1130
|
+
];
|
|
1131
|
+
const vscodeIconsByExtension = {
|
|
1132
|
+
bash: "file_type_shell",
|
|
1133
|
+
cjs: "file_type_js",
|
|
1134
|
+
css: "file_type_css",
|
|
1135
|
+
cts: "file_type_typescript",
|
|
1136
|
+
gif: "file_type_image",
|
|
1137
|
+
gz: "file_type_zip",
|
|
1138
|
+
htm: "file_type_html",
|
|
1139
|
+
html: "file_type_html",
|
|
1140
|
+
jpeg: "file_type_image",
|
|
1141
|
+
jpg: "file_type_image",
|
|
1142
|
+
js: "file_type_js",
|
|
1143
|
+
json: "file_type_json",
|
|
1144
|
+
jsonc: "file_type_json",
|
|
1145
|
+
log: "file_type_text",
|
|
1146
|
+
md: "file_type_markdown",
|
|
1147
|
+
mjs: "file_type_js",
|
|
1148
|
+
mts: "file_type_typescript",
|
|
1149
|
+
pdf: "file_type_pdf2",
|
|
1150
|
+
png: "file_type_image",
|
|
1151
|
+
sh: "file_type_shell",
|
|
1152
|
+
svg: "file_type_image",
|
|
1153
|
+
tar: "file_type_zip",
|
|
1154
|
+
text: "file_type_text",
|
|
1155
|
+
ts: "file_type_typescript",
|
|
1156
|
+
tsx: "file_type_typescript",
|
|
1157
|
+
txt: "file_type_text",
|
|
1158
|
+
webp: "file_type_image",
|
|
1159
|
+
yaml: "file_type_yaml",
|
|
1160
|
+
yml: "file_type_yaml",
|
|
1161
|
+
zip: "file_type_zip",
|
|
1162
|
+
zsh: "file_type_shell"
|
|
1163
|
+
};
|
|
1164
|
+
function vscodeIconAssetName(name) {
|
|
1165
|
+
return `vscode-${name}.svg`;
|
|
1166
|
+
}
|
|
1167
|
+
//#endregion
|
|
1168
|
+
//#region src/theme-default/icon-set.ts
|
|
1169
|
+
function assetName(svg) {
|
|
1170
|
+
return `theme-icon-${createHash("sha256").update(svg).digest("hex")}.svg`;
|
|
1171
|
+
}
|
|
1172
|
+
function prepareVariant(variant, assets) {
|
|
1173
|
+
const add = (svg) => {
|
|
1174
|
+
if (typeof svg !== "string" || !/^\s*<svg\b/.test(svg)) throw new TypeError("Theme icons must be SVG markup");
|
|
1175
|
+
const name = assetName(svg);
|
|
1176
|
+
assets[name] = svg;
|
|
1177
|
+
return name;
|
|
1178
|
+
};
|
|
1179
|
+
const byExtension = Object.fromEntries(Object.entries(variant.byExtension ?? {}).map(([extension, svg]) => {
|
|
1180
|
+
if (!/^[a-z0-9][a-z0-9+_-]*$/.test(extension)) throw new TypeError(`Invalid icon extension: ${extension}`);
|
|
1181
|
+
return [extension, add(svg)];
|
|
1182
|
+
}));
|
|
1183
|
+
return {
|
|
1184
|
+
file: add(variant.file),
|
|
1185
|
+
folder: add(variant.folder),
|
|
1186
|
+
byExtension
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
function prepareIconSet(icons, vscodeIconAssets, vscodeIconNotice) {
|
|
1190
|
+
if (icons === void 0) return {
|
|
1191
|
+
assets: {
|
|
1192
|
+
...vscodeIconAssets,
|
|
1193
|
+
"vscode-icons-NOTICE.txt": vscodeIconNotice
|
|
1194
|
+
},
|
|
1195
|
+
names: { light: {
|
|
1196
|
+
file: vscodeIconAssetName("default_file"),
|
|
1197
|
+
folder: vscodeIconAssetName("default_folder"),
|
|
1198
|
+
byExtension: Object.fromEntries(Object.entries(vscodeIconsByExtension).map(([extension, name]) => [extension, vscodeIconAssetName(name)]))
|
|
1199
|
+
} },
|
|
1200
|
+
noticeName: "vscode-icons-NOTICE.txt"
|
|
1201
|
+
};
|
|
1202
|
+
if (icons.notice !== void 0 && (typeof icons.notice !== "string" || icons.notice.trim() === "")) throw new TypeError("Theme icon attribution notice must be non-empty text");
|
|
1203
|
+
const assets = icons.notice === void 0 ? {} : { "theme-icons-NOTICE.txt": icons.notice };
|
|
1204
|
+
const light = prepareVariant(icons.light, assets);
|
|
1205
|
+
const dark = icons.dark === void 0 ? void 0 : prepareVariant(icons.dark, assets);
|
|
1206
|
+
return {
|
|
1207
|
+
assets,
|
|
1208
|
+
names: {
|
|
1209
|
+
light,
|
|
1210
|
+
...dark === void 0 ? {} : { dark }
|
|
1211
|
+
},
|
|
1212
|
+
...icons.notice === void 0 ? {} : { noticeName: "theme-icons-NOTICE.txt" }
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
function iconNameForEntry(entry, variant) {
|
|
1216
|
+
if (entry.kind === "directory" || entry.symlink?.targetKind === "directory") return variant.folder;
|
|
1217
|
+
const extension = entry.name.slice(entry.name.lastIndexOf(".") + 1).toLowerCase();
|
|
1218
|
+
return Object.hasOwn(variant.byExtension, extension) ? variant.byExtension[extension] : variant.file;
|
|
1219
|
+
}
|
|
1220
|
+
function iconHrefs(names, assetHref) {
|
|
1221
|
+
const resolve = (variant) => ({
|
|
1222
|
+
file: assetHref(variant.file),
|
|
1223
|
+
folder: assetHref(variant.folder),
|
|
1224
|
+
byExtension: Object.fromEntries(Object.entries(variant.byExtension).map(([extension, name]) => [extension, assetHref(name)]))
|
|
1225
|
+
});
|
|
1226
|
+
return {
|
|
1227
|
+
light: resolve(names.light),
|
|
1228
|
+
...names.dark === void 0 ? {} : { dark: resolve(names.dark) }
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
//#endregion
|
|
1232
|
+
//#region src/theme-default.ts
|
|
1233
|
+
const runtimeSource = await readFile(new URL("./theme-runtime.js", import.meta.url), "utf8");
|
|
1234
|
+
const workerSource = await readFile(new URL("./theme-worker.js", import.meta.url), "utf8");
|
|
1235
|
+
const vscodeIconAssets = Object.fromEntries(await Promise.all(vscodeIconNames.map(async (name) => [vscodeIconAssetName(name), await readFile(new URL(`./vscode-icons/${name}.svg`, import.meta.url), "utf8")])));
|
|
1236
|
+
const vscodeIconNotice = await readFile(new URL("./vscode-icons/NOTICE.txt", import.meta.url), "utf8");
|
|
1237
|
+
function createBreadcrumbs(relativePath, hrefForDirectory) {
|
|
1238
|
+
const segments = relativePath === "" ? [] : relativePath.split("/");
|
|
1239
|
+
return [{
|
|
1240
|
+
href: segments.length === 0 ? null : hrefForDirectory(""),
|
|
1241
|
+
isCurrent: segments.length === 0,
|
|
1242
|
+
label: segments.length === 0 ? "/" : "Home"
|
|
1243
|
+
}, ...segments.map((label, index) => ({
|
|
1244
|
+
href: index === segments.length - 1 ? null : hrefForDirectory(segments.slice(0, index + 1).join("/")),
|
|
1245
|
+
isCurrent: index === segments.length - 1,
|
|
1246
|
+
label
|
|
1247
|
+
}))];
|
|
1248
|
+
}
|
|
1249
|
+
function createDefaultTheme(options = {}) {
|
|
1250
|
+
const colorScheme = options.colorScheme ?? true;
|
|
1251
|
+
const fuzzySearch = options.fuzzySearch ?? true;
|
|
1252
|
+
const globalSearch = options.globalSearch ?? true;
|
|
1253
|
+
const keyboardNavigation = options.keyboardNavigation ?? true;
|
|
1254
|
+
const sorting = options.sorting ?? true;
|
|
1255
|
+
const virtualizeAfter = options.virtualizeAfter ?? 500;
|
|
1256
|
+
const project = resolveThemeProject(options.project);
|
|
1257
|
+
if (!Number.isSafeInteger(virtualizeAfter) || virtualizeAfter < 0) throw new RangeError("virtualizeAfter must be a non-negative integer");
|
|
1258
|
+
const interactive = colorScheme || fuzzySearch || globalSearch || keyboardNavigation || sorting;
|
|
1259
|
+
const layers = options.components === void 0 ? [] : Array.isArray(options.components) ? options.components : [options.components];
|
|
1260
|
+
const components = resolveThemeComponents(defaultThemeComponents, ...layers);
|
|
1261
|
+
const iconSet = prepareIconSet(options.icons, vscodeIconAssets, vscodeIconNotice);
|
|
1262
|
+
return {
|
|
1263
|
+
name: "ledger",
|
|
1264
|
+
render({ assetHref, directory, documentBaseHref, exitsExplorerFor, hrefFor, hrefForDirectory, searchIndexHref, sort, mode }) {
|
|
1265
|
+
const visiblePath = directory.current.relativePath === "" ? "/" : `/${directory.current.relativePath}/`;
|
|
1266
|
+
const virtualized = mode === "mpa" && interactive && directory.entries.length > virtualizeAfter && components.EntryList === defaultThemeComponents.EntryList && components.EntryRow === defaultThemeComponents.EntryRow;
|
|
1267
|
+
const renderedRows = virtualized ? [] : directory.entries.map((entry, index) => {
|
|
1268
|
+
const directoryLike = entry.kind === "directory" || entry.symlink?.targetKind === "directory";
|
|
1269
|
+
const iconName = entry.kind === "symlink" ? "link" : directoryLike ? "folder" : "file";
|
|
1270
|
+
return components.EntryRow({
|
|
1271
|
+
entry,
|
|
1272
|
+
icon: components.Icon({
|
|
1273
|
+
name: iconName,
|
|
1274
|
+
src: assetHref(iconNameForEntry(entry, iconSet.names.light)),
|
|
1275
|
+
...iconSet.names.dark === void 0 ? {} : { darkSrc: assetHref(iconNameForEntry(entry, iconSet.names.dark)) }
|
|
1276
|
+
}),
|
|
1277
|
+
index,
|
|
1278
|
+
navigation: {
|
|
1279
|
+
exitsExplorer: exitsExplorerFor(entry),
|
|
1280
|
+
href: hrefFor(entry)
|
|
1281
|
+
}
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
const entriesAssetName = virtualized ? `entries-${createHash("sha256").update(directory.current.relativePath).digest("hex").slice(0, 16)}.json` : null;
|
|
1285
|
+
const rows = renderedRows.join("");
|
|
1286
|
+
const assets = interactive ? `<script src="${escapeHtml(assetHref("dirwell.runtime.js"))}" type="module"><\/script>` : "";
|
|
1287
|
+
const runtimeConfig = {
|
|
1288
|
+
colorScheme,
|
|
1289
|
+
fuzzySearch,
|
|
1290
|
+
globalSearch,
|
|
1291
|
+
keyboardNavigation,
|
|
1292
|
+
icons: iconHrefs(iconSet.names, assetHref),
|
|
1293
|
+
searchIndexHref,
|
|
1294
|
+
...entriesAssetName === null ? {} : { entriesHref: assetHref(entriesAssetName) },
|
|
1295
|
+
...entriesAssetName === null ? {} : { workerHref: assetHref("dirwell.worker.js") },
|
|
1296
|
+
sort,
|
|
1297
|
+
sorting
|
|
1298
|
+
};
|
|
1299
|
+
const parentHref = directory.parent === null ? null : hrefForDirectory(directory.parent.relativePath);
|
|
1300
|
+
return {
|
|
1301
|
+
html: components.PageShell({
|
|
1302
|
+
assets,
|
|
1303
|
+
breadcrumbs: components.Breadcrumbs({ items: createBreadcrumbs(directory.current.relativePath, hrefForDirectory) }),
|
|
1304
|
+
directory,
|
|
1305
|
+
documentBaseHref,
|
|
1306
|
+
emptyState: components.EmptyState({ message: "No matching entries." }),
|
|
1307
|
+
entryList: components.EntryList({
|
|
1308
|
+
directory,
|
|
1309
|
+
parentHref,
|
|
1310
|
+
rows,
|
|
1311
|
+
sorting
|
|
1312
|
+
}),
|
|
1313
|
+
footer: components.Footer({
|
|
1314
|
+
...iconSet.noticeName === void 0 ? {} : {
|
|
1315
|
+
iconNoticeHref: assetHref(iconSet.noticeName),
|
|
1316
|
+
iconNoticeKind: options.icons === void 0 ? "built-in" : "custom"
|
|
1317
|
+
},
|
|
1318
|
+
keyboardNavigation,
|
|
1319
|
+
parentHref,
|
|
1320
|
+
project
|
|
1321
|
+
}),
|
|
1322
|
+
parentHref,
|
|
1323
|
+
runtimeConfig,
|
|
1324
|
+
styles: defaultStyles,
|
|
1325
|
+
toolbar: components.Toolbar({
|
|
1326
|
+
colorScheme,
|
|
1327
|
+
fuzzySearch,
|
|
1328
|
+
globalSearch,
|
|
1329
|
+
keyboardNavigation,
|
|
1330
|
+
searchIcon: components.Icon({ name: "search" }),
|
|
1331
|
+
sorting
|
|
1332
|
+
}),
|
|
1333
|
+
visiblePath
|
|
1334
|
+
}),
|
|
1335
|
+
assets: {
|
|
1336
|
+
...iconSet.assets,
|
|
1337
|
+
...interactive ? { "dirwell.runtime.js": runtimeSource } : {},
|
|
1338
|
+
...entriesAssetName === null ? {} : { "dirwell.worker.js": workerSource },
|
|
1339
|
+
...entriesAssetName === null ? {} : { [entriesAssetName]: JSON.stringify({
|
|
1340
|
+
rows: directory.entries.map((entry) => ({
|
|
1341
|
+
name: entry.name,
|
|
1342
|
+
size: entry.metadata.size,
|
|
1343
|
+
modifiedAt: entry.metadata.times.modifiedAt,
|
|
1344
|
+
kind: entry.kind,
|
|
1345
|
+
targetKind: entry.symlink?.targetKind ?? null,
|
|
1346
|
+
targetSize: entry.symlink?.targetSize ?? null,
|
|
1347
|
+
target: entry.symlink?.target ?? null,
|
|
1348
|
+
isCycle: entry.symlink?.isCycle ?? false,
|
|
1349
|
+
isBroken: entry.symlink?.isBroken ?? false,
|
|
1350
|
+
isOutsideRoot: entry.symlink?.isOutsideRoot ?? false,
|
|
1351
|
+
isTargetUnavailable: Boolean(entry.symlink?.isBroken || entry.symlink?.isOutsideRoot || entry.symlink?.isTargetExcluded),
|
|
1352
|
+
href: hrefFor(entry),
|
|
1353
|
+
exitsExplorer: exitsExplorerFor(entry)
|
|
1354
|
+
})),
|
|
1355
|
+
version: 2
|
|
1356
|
+
}) }
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
const defaultTheme = createDefaultTheme();
|
|
1363
|
+
//#endregion
|
|
1364
|
+
//#region src/generator.ts
|
|
1365
|
+
const indexPattern = /^index\.html?$/i;
|
|
1366
|
+
const defaultSort = {
|
|
1367
|
+
direction: "asc",
|
|
1368
|
+
directoriesFirst: true,
|
|
1369
|
+
field: "name",
|
|
1370
|
+
nameMode: "natural"
|
|
1371
|
+
};
|
|
1372
|
+
const defaultOutputName = ({ entries }) => entries.some((entry) => entry.kind === "file" && indexPattern.test(entry.name)) ? entries.some((entry) => entry.name.toLowerCase() === "_dirwell.html") ? null : "_dirwell.html" : "index.html";
|
|
1373
|
+
function entryKind(stats) {
|
|
1374
|
+
if (stats.isDirectory()) return "directory";
|
|
1375
|
+
if (stats.isFile()) return "file";
|
|
1376
|
+
if (stats.isSymbolicLink()) return "symlink";
|
|
1377
|
+
return "other";
|
|
1378
|
+
}
|
|
1379
|
+
function metadata(stats) {
|
|
1380
|
+
return {
|
|
1381
|
+
device: stats.dev,
|
|
1382
|
+
groupId: stats.gid,
|
|
1383
|
+
hardLinkCount: stats.nlink,
|
|
1384
|
+
inode: stats.ino,
|
|
1385
|
+
mode: stats.mode,
|
|
1386
|
+
ownerId: stats.uid,
|
|
1387
|
+
size: stats.size,
|
|
1388
|
+
times: {
|
|
1389
|
+
accessedAt: stats.atime.toISOString(),
|
|
1390
|
+
changedAt: stats.ctime.toISOString(),
|
|
1391
|
+
createdAt: stats.birthtime.toISOString(),
|
|
1392
|
+
modifiedAt: stats.mtime.toISOString()
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
function isInsideRoot(rootPath, candidatePath) {
|
|
1397
|
+
const relative = path.relative(rootPath, candidatePath);
|
|
1398
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
1399
|
+
}
|
|
1400
|
+
async function describeEntry({ absolutePath, relativePath, rootRealPath, ancestors, followSymlinks, selectedPaths }) {
|
|
1401
|
+
const linkStats = await lstat(absolutePath);
|
|
1402
|
+
const kind = entryKind(linkStats);
|
|
1403
|
+
let symlink = null;
|
|
1404
|
+
if (kind === "symlink") {
|
|
1405
|
+
const target = await readlink(absolutePath);
|
|
1406
|
+
let resolvedPath = null;
|
|
1407
|
+
let targetKind = null;
|
|
1408
|
+
let targetSize = null;
|
|
1409
|
+
try {
|
|
1410
|
+
resolvedPath = await realpath(absolutePath);
|
|
1411
|
+
const targetStats = await stat(absolutePath);
|
|
1412
|
+
targetKind = entryKind(targetStats);
|
|
1413
|
+
targetSize = targetStats.size;
|
|
1414
|
+
} catch {}
|
|
1415
|
+
const isOutsideRoot = resolvedPath === null || !isInsideRoot(rootRealPath, resolvedPath);
|
|
1416
|
+
const isCycle = resolvedPath !== null && ancestors.has(resolvedPath);
|
|
1417
|
+
const targetRelativePath = resolvedPath !== null && !isOutsideRoot ? path.relative(rootRealPath, resolvedPath).split(path.sep).join("/") : null;
|
|
1418
|
+
const isTargetExcluded = targetRelativePath !== null && selectedPaths !== null && !selectedPaths.has(targetRelativePath);
|
|
1419
|
+
symlink = {
|
|
1420
|
+
target,
|
|
1421
|
+
resolvedPath,
|
|
1422
|
+
targetRelativePath,
|
|
1423
|
+
targetKind,
|
|
1424
|
+
targetSize: isOutsideRoot || isTargetExcluded || targetKind === "directory" ? null : targetSize,
|
|
1425
|
+
isBroken: resolvedPath === null,
|
|
1426
|
+
isCycle,
|
|
1427
|
+
isOutsideRoot,
|
|
1428
|
+
isTargetExcluded,
|
|
1429
|
+
wasFollowed: followSymlinks && targetKind === "directory" && !isCycle && !isOutsideRoot && !isTargetExcluded
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
return {
|
|
1433
|
+
name: path.basename(absolutePath),
|
|
1434
|
+
absolutePath,
|
|
1435
|
+
relativePath,
|
|
1436
|
+
kind,
|
|
1437
|
+
metadata: metadata(linkStats),
|
|
1438
|
+
symlink
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function assertSafeOutputName(name) {
|
|
1442
|
+
if (name.length === 0 || path.basename(name) !== name || name === "." || name === "..") throw new Error(`Output name must be a single safe filename: ${name}`);
|
|
1443
|
+
}
|
|
1444
|
+
function normalizeBase(value) {
|
|
1445
|
+
if (value.includes("?") || value.includes("#")) throw new TypeError("base must not contain a query string or fragment");
|
|
1446
|
+
if (/^https?:\/\//i.test(value)) {
|
|
1447
|
+
const url = new URL(value);
|
|
1448
|
+
url.pathname = `${url.pathname.replace(/\/+$/, "")}/`;
|
|
1449
|
+
return url.href;
|
|
1450
|
+
}
|
|
1451
|
+
if (!value.startsWith("/")) throw new TypeError("base must start with \"/\" or be an absolute HTTP(S) URL");
|
|
1452
|
+
return `${value.replace(/\/+$/, "")}/`;
|
|
1453
|
+
}
|
|
1454
|
+
function encodeLogicalPath(value) {
|
|
1455
|
+
return value.split("/").filter((segment) => segment.length > 0).map((segment) => encodeURIComponent(segment)).join("/");
|
|
1456
|
+
}
|
|
1457
|
+
function isDirectoryLike(entry) {
|
|
1458
|
+
return entry.kind === "directory" || entry.symlink?.targetKind === "directory";
|
|
1459
|
+
}
|
|
1460
|
+
function isUnavailableSymlink(entry) {
|
|
1461
|
+
return Boolean(entry.symlink?.isBroken || entry.symlink?.isOutsideRoot || entry.symlink?.isTargetExcluded);
|
|
1462
|
+
}
|
|
1463
|
+
function compareUnicode(left, right) {
|
|
1464
|
+
const leftPoints = [...left];
|
|
1465
|
+
const rightPoints = [...right];
|
|
1466
|
+
const length = Math.min(leftPoints.length, rightPoints.length);
|
|
1467
|
+
for (let index = 0; index < length; index += 1) {
|
|
1468
|
+
const difference = (leftPoints[index]?.codePointAt(0) ?? 0) - (rightPoints[index]?.codePointAt(0) ?? 0);
|
|
1469
|
+
if (difference !== 0) return difference;
|
|
1470
|
+
}
|
|
1471
|
+
return leftPoints.length - rightPoints.length;
|
|
1472
|
+
}
|
|
1473
|
+
function compareNames(left, right, mode) {
|
|
1474
|
+
if (mode === "unicode") return compareUnicode(left, right);
|
|
1475
|
+
return left.localeCompare(right, void 0, {
|
|
1476
|
+
numeric: mode === "natural",
|
|
1477
|
+
sensitivity: "base"
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
function compareEntries(left, right, options = {}) {
|
|
1481
|
+
const resolved = {
|
|
1482
|
+
...defaultSort,
|
|
1483
|
+
...options
|
|
1484
|
+
};
|
|
1485
|
+
if (resolved.directoriesFirst && isDirectoryLike(left) !== isDirectoryLike(right)) return isDirectoryLike(left) ? -1 : 1;
|
|
1486
|
+
let result;
|
|
1487
|
+
if (resolved.field === "modified") result = left.metadata.times.modifiedAt.localeCompare(right.metadata.times.modifiedAt);
|
|
1488
|
+
else if (resolved.field === "size") result = (left.kind === "directory" ? 0 : left.metadata.size) - (right.kind === "directory" ? 0 : right.metadata.size);
|
|
1489
|
+
else result = compareNames(left.name, right.name, resolved.nameMode);
|
|
1490
|
+
if (result === 0) result = compareNames(left.name, right.name, resolved.nameMode);
|
|
1491
|
+
return resolved.direction === "asc" ? result : -result;
|
|
1492
|
+
}
|
|
1493
|
+
function rawLinkFilename(entry) {
|
|
1494
|
+
return `${createHash("sha256").update(entry.relativePath).update("\0").update(entry.symlink?.target ?? "").digest("hex").slice(0, 16)}.txt`;
|
|
1495
|
+
}
|
|
1496
|
+
async function generateExplorer(options) {
|
|
1497
|
+
return generateExplorerSkippingPaths(options, []);
|
|
1498
|
+
}
|
|
1499
|
+
/** Internal entry point for hosts that stage previews outside their published output. */
|
|
1500
|
+
async function generateExplorerSkippingPaths(options, ignoredPaths) {
|
|
1501
|
+
const sourceDir = path.resolve(options.sourceDir);
|
|
1502
|
+
const outputDir = path.resolve(options.outputDir);
|
|
1503
|
+
const ignoredSourcePaths = ignoredPaths.map((value) => path.resolve(value));
|
|
1504
|
+
const rootRealPath = await realpath(sourceDir);
|
|
1505
|
+
const outputName = options.outputName ?? defaultOutputName;
|
|
1506
|
+
const resolveOutputName = typeof outputName === "string" ? () => outputName : outputName;
|
|
1507
|
+
const theme = options.theme ?? defaultTheme;
|
|
1508
|
+
const mode = options.mode ?? "ssg";
|
|
1509
|
+
const urlStrategy = options.urlStrategy ?? "relative";
|
|
1510
|
+
const base = normalizeBase(options.base ?? "/");
|
|
1511
|
+
const followSymlinks = options.symlinks?.follow ?? false;
|
|
1512
|
+
const boundary = options.symlinks?.boundary ?? "root";
|
|
1513
|
+
const onCycle = options.symlinks?.onCycle ?? "skip";
|
|
1514
|
+
const filtersActive = options.include !== void 0 || options.exclude !== void 0 || ignoredSourcePaths.length > 0;
|
|
1515
|
+
const plans = [];
|
|
1516
|
+
if (isInsideRoot(outputDir, sourceDir)) throw new Error("outputDir must not contain the sourceDir");
|
|
1517
|
+
try {
|
|
1518
|
+
await lstat(path.join(sourceDir, "__dirwell"));
|
|
1519
|
+
throw new Error("Dirwell reserves the source-root path \"__dirwell\"");
|
|
1520
|
+
} catch (error) {
|
|
1521
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {} else throw error;
|
|
1522
|
+
}
|
|
1523
|
+
const outputIsWithinSource = isInsideRoot(sourceDir, outputDir);
|
|
1524
|
+
const selection = await selectSourcePaths(sourceDir, outputDir, options.include, options.exclude, ignoredSourcePaths);
|
|
1525
|
+
const stageParent = outputIsWithinSource ? path.dirname(sourceDir) : path.dirname(outputDir);
|
|
1526
|
+
await mkdir(stageParent, { recursive: true });
|
|
1527
|
+
const stagedOutputDir = await mkdtemp(path.join(stageParent, `.${path.basename(outputDir)}-dirwell-`));
|
|
1528
|
+
const buildOutputDir = stagedOutputDir;
|
|
1529
|
+
try {
|
|
1530
|
+
if (options.mirror ?? true) {
|
|
1531
|
+
await rm(buildOutputDir, {
|
|
1532
|
+
recursive: true,
|
|
1533
|
+
force: true
|
|
1534
|
+
});
|
|
1535
|
+
await mkdir(buildOutputDir, { recursive: true });
|
|
1536
|
+
await cp(sourceDir, buildOutputDir, {
|
|
1537
|
+
recursive: true,
|
|
1538
|
+
dereference: false,
|
|
1539
|
+
preserveTimestamps: true,
|
|
1540
|
+
filter: (sourcePath) => {
|
|
1541
|
+
const relativePath = path.relative(sourceDir, sourcePath).split(path.sep).join("/");
|
|
1542
|
+
return selection.selected.has(relativePath) && (!selection.symlinks.has(relativePath) || selection.mirroredSymlinks.has(relativePath));
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
await rm(stagedOutputDir, {
|
|
1548
|
+
recursive: true,
|
|
1549
|
+
force: true
|
|
1550
|
+
});
|
|
1551
|
+
throw error;
|
|
1552
|
+
}
|
|
1553
|
+
async function visit(logicalDir, physicalDir, ancestors) {
|
|
1554
|
+
const physicalRealPath = await realpath(physicalDir);
|
|
1555
|
+
if (ancestors.has(physicalRealPath)) {
|
|
1556
|
+
if (onCycle === "error") throw new Error(`Symlink cycle at ${logicalDir}`);
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
const nextAncestors = new Set(ancestors).add(physicalRealPath);
|
|
1560
|
+
const names = (await readdir(physicalDir)).filter((name) => {
|
|
1561
|
+
if (outputIsWithinSource && isInsideRoot(outputDir, path.join(physicalDir, name))) return false;
|
|
1562
|
+
if (ignoredSourcePaths.some((value) => isInsideRoot(value, path.join(physicalDir, name)))) return false;
|
|
1563
|
+
if (!filtersActive) return true;
|
|
1564
|
+
const relativePath = path.relative(rootRealPath, path.join(physicalRealPath, name));
|
|
1565
|
+
return selection.selected.has(relativePath.split(path.sep).join("/"));
|
|
1566
|
+
});
|
|
1567
|
+
const entries = await Promise.all(names.map((name) => describeEntry({
|
|
1568
|
+
absolutePath: path.join(physicalDir, name),
|
|
1569
|
+
relativePath: path.posix.join(logicalDir, name),
|
|
1570
|
+
rootRealPath,
|
|
1571
|
+
ancestors: nextAncestors,
|
|
1572
|
+
followSymlinks,
|
|
1573
|
+
selectedPaths: filtersActive ? selection.selected : null
|
|
1574
|
+
})));
|
|
1575
|
+
entries.sort((left, right) => compareEntries(left, right, options.sort));
|
|
1576
|
+
const describedCurrent = await describeEntry({
|
|
1577
|
+
absolutePath: physicalDir,
|
|
1578
|
+
relativePath: logicalDir,
|
|
1579
|
+
rootRealPath,
|
|
1580
|
+
ancestors,
|
|
1581
|
+
followSymlinks,
|
|
1582
|
+
selectedPaths: filtersActive ? selection.selected : null
|
|
1583
|
+
});
|
|
1584
|
+
if (describedCurrent.kind !== "directory") throw new Error(`${physicalDir} is not a directory`);
|
|
1585
|
+
const current = {
|
|
1586
|
+
...describedCurrent,
|
|
1587
|
+
kind: "directory"
|
|
1588
|
+
};
|
|
1589
|
+
const directory = {
|
|
1590
|
+
root: await describeEntry({
|
|
1591
|
+
absolutePath: sourceDir,
|
|
1592
|
+
relativePath: "",
|
|
1593
|
+
rootRealPath,
|
|
1594
|
+
ancestors: /* @__PURE__ */ new Set(),
|
|
1595
|
+
followSymlinks,
|
|
1596
|
+
selectedPaths: filtersActive ? selection.selected : null
|
|
1597
|
+
}),
|
|
1598
|
+
current,
|
|
1599
|
+
parent: logicalDir === "" ? null : await describeEntry({
|
|
1600
|
+
absolutePath: path.dirname(physicalDir),
|
|
1601
|
+
relativePath: path.posix.dirname(logicalDir) === "." ? "" : path.posix.dirname(logicalDir),
|
|
1602
|
+
rootRealPath,
|
|
1603
|
+
ancestors,
|
|
1604
|
+
followSymlinks,
|
|
1605
|
+
selectedPaths: filtersActive ? selection.selected : null
|
|
1606
|
+
}),
|
|
1607
|
+
depth: logicalDir === "" ? 0 : logicalDir.split("/").length,
|
|
1608
|
+
entries
|
|
1609
|
+
};
|
|
1610
|
+
const name = await resolveOutputName(directory);
|
|
1611
|
+
if (name !== null) assertSafeOutputName(name);
|
|
1612
|
+
plans.push({
|
|
1613
|
+
directory,
|
|
1614
|
+
logicalDir,
|
|
1615
|
+
outputName: name
|
|
1616
|
+
});
|
|
1617
|
+
for (const entry of entries) {
|
|
1618
|
+
if (entry.kind === "directory") {
|
|
1619
|
+
await visit(path.posix.join(logicalDir, entry.name), entry.absolutePath, nextAncestors);
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
if (!followSymlinks || entry.symlink?.targetKind !== "directory") continue;
|
|
1623
|
+
const physicalLinkPath = path.relative(rootRealPath, path.join(physicalRealPath, entry.name)).split(path.sep).join("/");
|
|
1624
|
+
if ((options.mirror ?? true) && !selection.mirroredSymlinks.has(physicalLinkPath)) continue;
|
|
1625
|
+
if (entry.symlink.isCycle) {
|
|
1626
|
+
if (onCycle === "error") throw new Error(`Symlink cycle at ${entry.relativePath}`);
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
if (entry.symlink.resolvedPath === null) continue;
|
|
1630
|
+
if (boundary === "root" && entry.symlink.isOutsideRoot) continue;
|
|
1631
|
+
if (filtersActive && (entry.symlink.targetRelativePath === null || !selection.selected.has(entry.symlink.targetRelativePath))) continue;
|
|
1632
|
+
await visit(path.posix.join(logicalDir, entry.name), entry.symlink.resolvedPath, nextAncestors);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
try {
|
|
1636
|
+
await visit("", sourceDir, /* @__PURE__ */ new Set());
|
|
1637
|
+
const generatedPages = new Map(plans.filter((plan) => plan.outputName !== null).map((plan) => [plan.logicalDir, plan.outputName]));
|
|
1638
|
+
const generatedDirectories = new Set(generatedPages.keys());
|
|
1639
|
+
const pagePathForDirectory = (logicalPath) => {
|
|
1640
|
+
const pageName = generatedPages.get(logicalPath);
|
|
1641
|
+
return pageName === void 0 || pageName === "index.html" ? logicalPath : path.posix.join(logicalPath, pageName);
|
|
1642
|
+
};
|
|
1643
|
+
const sharedAssets = /* @__PURE__ */ new Map();
|
|
1644
|
+
const rawLinkFiles = /* @__PURE__ */ new Map();
|
|
1645
|
+
const searchEntries = /* @__PURE__ */ new Map();
|
|
1646
|
+
for (const { directory, logicalDir } of theme.searchIndex === false ? [] : plans) for (const entry of directory.entries) {
|
|
1647
|
+
if (searchEntries.has(entry.relativePath)) continue;
|
|
1648
|
+
const targetLogicalPath = isUnavailableSymlink(entry) ? null : entry.symlink?.targetRelativePath !== null && entry.symlink !== null ? entry.symlink.targetRelativePath : path.posix.join(logicalDir, entry.name);
|
|
1649
|
+
const directoryLike = isDirectoryLike(entry);
|
|
1650
|
+
const linkedPath = directoryLike && targetLogicalPath !== null ? pagePathForDirectory(targetLogicalPath) : targetLogicalPath;
|
|
1651
|
+
let href = null;
|
|
1652
|
+
if (entry.symlink !== null && isUnavailableSymlink(entry)) {
|
|
1653
|
+
const filename = rawLinkFilename(entry);
|
|
1654
|
+
rawLinkFiles.set(filename, entry.symlink.target);
|
|
1655
|
+
href = `raw-links/${filename}`;
|
|
1656
|
+
} else if (linkedPath !== null) href = linkedPath === "" ? "../" : `../${encodeLogicalPath(linkedPath)}${directoryLike && linkedPath === targetLogicalPath ? "/" : ""}`;
|
|
1657
|
+
searchEntries.set(entry.relativePath, {
|
|
1658
|
+
exitsExplorer: isUnavailableSymlink(entry) || targetLogicalPath !== null && (!directoryLike || !generatedDirectories.has(targetLogicalPath)),
|
|
1659
|
+
href,
|
|
1660
|
+
isLink: entry.kind === "symlink",
|
|
1661
|
+
kind: entry.kind,
|
|
1662
|
+
modifiedAt: entry.metadata.times.modifiedAt,
|
|
1663
|
+
name: entry.name,
|
|
1664
|
+
path: entry.relativePath,
|
|
1665
|
+
size: entry.metadata.size,
|
|
1666
|
+
target: entry.symlink?.target ?? null,
|
|
1667
|
+
targetKind: entry.symlink?.targetKind ?? null,
|
|
1668
|
+
targetSize: entry.symlink?.targetSize ?? null,
|
|
1669
|
+
isTargetUnavailable: isUnavailableSymlink(entry),
|
|
1670
|
+
isCycle: entry.symlink?.isCycle ?? false
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
for (const plan of plans) {
|
|
1674
|
+
const { directory, logicalDir, outputName: name } = plan;
|
|
1675
|
+
if (name === null) continue;
|
|
1676
|
+
const targetLogicalPathFor = (entry) => {
|
|
1677
|
+
if (isUnavailableSymlink(entry)) return null;
|
|
1678
|
+
if (entry.symlink?.targetRelativePath !== null && entry.symlink !== null) return entry.symlink.targetRelativePath;
|
|
1679
|
+
return path.posix.join(logicalDir, entry.name);
|
|
1680
|
+
};
|
|
1681
|
+
const hrefForLogicalPath = (targetPath, directoryLike) => {
|
|
1682
|
+
const linkedPath = directoryLike ? pagePathForDirectory(targetPath) : targetPath;
|
|
1683
|
+
const linkToDirectory = directoryLike && linkedPath === targetPath;
|
|
1684
|
+
if (urlStrategy === "relative") return `${path.posix.relative(logicalDir === "" ? "." : logicalDir, linkedPath === "" ? "." : linkedPath).split("/").map((segment) => segment === "." || segment === ".." ? segment : encodeURIComponent(segment)).join("/") || "."}${linkToDirectory ? "/" : ""}`;
|
|
1685
|
+
const encodedTarget = encodeLogicalPath(linkedPath);
|
|
1686
|
+
if (encodedTarget === "") return urlStrategy === "base" ? base : "./";
|
|
1687
|
+
const suffix = `${encodedTarget}${linkToDirectory ? "/" : ""}`;
|
|
1688
|
+
return urlStrategy === "base" ? `${base}${suffix}` : suffix;
|
|
1689
|
+
};
|
|
1690
|
+
const hrefFor = (entry) => {
|
|
1691
|
+
if (entry.symlink !== null && isUnavailableSymlink(entry)) {
|
|
1692
|
+
const filename = rawLinkFilename(entry);
|
|
1693
|
+
rawLinkFiles.set(filename, entry.symlink.target);
|
|
1694
|
+
return hrefForLogicalPath(path.posix.join("__dirwell", "raw-links", filename), false);
|
|
1695
|
+
}
|
|
1696
|
+
const targetLogicalPath = targetLogicalPathFor(entry);
|
|
1697
|
+
if (targetLogicalPath === null) return null;
|
|
1698
|
+
const directoryLike = entry.kind === "directory" || entry.symlink?.targetKind === "directory";
|
|
1699
|
+
return hrefForLogicalPath(targetLogicalPath, directoryLike);
|
|
1700
|
+
};
|
|
1701
|
+
const page = await theme.render({
|
|
1702
|
+
directory,
|
|
1703
|
+
documentBaseHref: urlStrategy === "html-base" ? base : null,
|
|
1704
|
+
outputName: name,
|
|
1705
|
+
mode,
|
|
1706
|
+
searchIndexHref: hrefForLogicalPath(path.posix.join("__dirwell", "search-index.json"), false),
|
|
1707
|
+
sort: {
|
|
1708
|
+
...defaultSort,
|
|
1709
|
+
...options.sort
|
|
1710
|
+
},
|
|
1711
|
+
assetHref: (assetName) => {
|
|
1712
|
+
assertSafeOutputName(assetName);
|
|
1713
|
+
return hrefForLogicalPath(mode === "ssg" ? path.posix.join(logicalDir, assetName) : path.posix.join("__dirwell", assetName), false);
|
|
1714
|
+
},
|
|
1715
|
+
hrefFor,
|
|
1716
|
+
hrefForDirectory: (relativePath) => hrefForLogicalPath(relativePath, true),
|
|
1717
|
+
exitsExplorerFor: (entry) => {
|
|
1718
|
+
if (isUnavailableSymlink(entry)) return true;
|
|
1719
|
+
const targetLogicalPath = targetLogicalPathFor(entry);
|
|
1720
|
+
const directoryLike = entry.kind === "directory" || entry.symlink?.targetKind === "directory";
|
|
1721
|
+
return targetLogicalPath !== null && (!directoryLike || !generatedDirectories.has(targetLogicalPath));
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
const targetDir = path.join(buildOutputDir, logicalDir);
|
|
1725
|
+
await mkdir(targetDir, { recursive: true });
|
|
1726
|
+
await writeFile(path.join(targetDir, name), page.html);
|
|
1727
|
+
for (const [assetName, contents] of Object.entries(page.assets ?? {})) {
|
|
1728
|
+
assertSafeOutputName(assetName);
|
|
1729
|
+
if (mode === "ssg") await writeFile(path.join(targetDir, assetName), contents);
|
|
1730
|
+
else {
|
|
1731
|
+
const bytes = Buffer.from(contents);
|
|
1732
|
+
const existing = sharedAssets.get(assetName);
|
|
1733
|
+
if (existing !== void 0 && !Buffer.from(existing).equals(bytes)) throw new Error(`Theme emitted conflicting shared asset: ${assetName}`);
|
|
1734
|
+
sharedAssets.set(assetName, bytes);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
if (mode === "mpa" && sharedAssets.size > 0) {
|
|
1739
|
+
const assetDirectory = path.join(buildOutputDir, "__dirwell");
|
|
1740
|
+
await mkdir(assetDirectory, { recursive: true });
|
|
1741
|
+
for (const [assetName, contents] of sharedAssets) await writeFile(path.join(assetDirectory, assetName), contents);
|
|
1742
|
+
}
|
|
1743
|
+
if (rawLinkFiles.size > 0) {
|
|
1744
|
+
const rawLinkDirectory = path.join(buildOutputDir, "__dirwell", "raw-links");
|
|
1745
|
+
await mkdir(rawLinkDirectory, { recursive: true });
|
|
1746
|
+
for (const [filename, target] of rawLinkFiles) await writeFile(path.join(rawLinkDirectory, filename), target);
|
|
1747
|
+
}
|
|
1748
|
+
if (theme.searchIndex !== false) {
|
|
1749
|
+
const generatedAssetDirectory = path.join(buildOutputDir, "__dirwell");
|
|
1750
|
+
await mkdir(generatedAssetDirectory, { recursive: true });
|
|
1751
|
+
const indexedEntries = [...searchEntries.values()];
|
|
1752
|
+
const shards = [];
|
|
1753
|
+
for (let offset = 0; offset < indexedEntries.length; offset += 512) {
|
|
1754
|
+
const filename = `search-${String(shards.length).padStart(5, "0")}.json`;
|
|
1755
|
+
shards.push(filename);
|
|
1756
|
+
await writeFile(path.join(generatedAssetDirectory, filename), JSON.stringify({ entries: indexedEntries.slice(offset, offset + 512) }));
|
|
1757
|
+
}
|
|
1758
|
+
await writeFile(path.join(generatedAssetDirectory, "search-index.json"), JSON.stringify({
|
|
1759
|
+
count: indexedEntries.length,
|
|
1760
|
+
shards,
|
|
1761
|
+
version: 3
|
|
1762
|
+
}));
|
|
1763
|
+
}
|
|
1764
|
+
await rm(outputDir, {
|
|
1765
|
+
recursive: true,
|
|
1766
|
+
force: true
|
|
1767
|
+
});
|
|
1768
|
+
await mkdir(path.dirname(outputDir), { recursive: true });
|
|
1769
|
+
await rename(stagedOutputDir, outputDir);
|
|
1770
|
+
} finally {
|
|
1771
|
+
await rm(stagedOutputDir, {
|
|
1772
|
+
recursive: true,
|
|
1773
|
+
force: true
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
async function readGeneratedPage(filePath) {
|
|
1778
|
+
return readFile(filePath, "utf8");
|
|
1779
|
+
}
|
|
1780
|
+
//#endregion
|
|
1781
|
+
export { readGeneratedPage as a, resolveThemeProject as c, utcTimestamp as d, defineConfig as f, selectSourcePaths as g, validateConfig as h, generateExplorerSkippingPaths as i, defaultThemeComponents as l, resolveGenerateOptions as m, defaultOutputName as n, createDefaultTheme as o, loadDirwellConfig as p, generateExplorer as r, defaultTheme as s, compareEntries as t, escapeHtml as u };
|
|
1782
|
+
|
|
1783
|
+
//# sourceMappingURL=generator-CtX3iHhK.mjs.map
|