@thjodann/wk 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -13
- package/dist/core/config.d.ts +11 -0
- package/dist/core/config.js +47 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/site.d.ts +1 -1
- package/dist/core/site.js +514 -26
- package/dist/core/site.js.map +1 -1
- package/dist/core/theme.d.ts +1 -0
- package/dist/core/theme.js +304 -1
- package/dist/core/theme.js.map +1 -1
- package/docs/reference.md +19 -6
- package/docs/setup.md +6 -4
- package/package.json +1 -1
- package/skills/wk/SKILL.md +7 -5
package/dist/core/site.js
CHANGED
|
@@ -187,7 +187,7 @@ function buildSiteFiles(root, options = {}) {
|
|
|
187
187
|
})
|
|
188
188
|
});
|
|
189
189
|
}
|
|
190
|
-
files.push({ fileName: "favicon.svg", content: faviconSvg(model) }, { fileName: "assets/wikiwiki.css", content: siteCss() }, { fileName: "assets/project-theme.css", content: projectThemeCss(model.options.theme) }, { fileName: "assets/search-index.js", content: searchIndexJs(model) }, { fileName: "assets/wikiwiki.js", content: siteJs() }, { fileName: ".nojekyll", content: "" }, { fileName: "site-manifest.json", content: `${JSON.stringify(siteManifest(model), null, 2)}\n` });
|
|
190
|
+
files.push({ fileName: "favicon.svg", content: faviconSvg(model) }, { fileName: "assets/wikiwiki.css", content: siteCss() }, { fileName: "assets/project-theme.css", content: projectThemeCss(model.options.theme, model.assets.fonts) }, { fileName: "assets/search-index.js", content: searchIndexJs(model) }, { fileName: "assets/wikiwiki.js", content: siteJs() }, { fileName: ".nojekyll", content: "" }, { fileName: "site-manifest.json", content: `${JSON.stringify(siteManifest(model), null, 2)}\n` }, ...model.assets.files);
|
|
191
191
|
return files;
|
|
192
192
|
}
|
|
193
193
|
function renderSite(root, options = {}) {
|
|
@@ -198,7 +198,12 @@ function renderSite(root, options = {}) {
|
|
|
198
198
|
return files.map((file) => {
|
|
199
199
|
const outputFile = path_1.default.join(outputPath, file.fileName);
|
|
200
200
|
fs_1.default.mkdirSync(path_1.default.dirname(outputFile), { recursive: true });
|
|
201
|
-
|
|
201
|
+
if (typeof file.content === "string") {
|
|
202
|
+
fs_1.default.writeFileSync(outputFile, file.content, "utf8");
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
fs_1.default.writeFileSync(outputFile, file.content);
|
|
206
|
+
}
|
|
202
207
|
return outputFile;
|
|
203
208
|
});
|
|
204
209
|
}
|
|
@@ -223,12 +228,14 @@ function createSiteModel(root, records, options) {
|
|
|
223
228
|
const articleHrefByLookup = articleLookupMap(audienceRecords.article);
|
|
224
229
|
const counts = Object.fromEntries(schemas_1.recordTypes.map((type) => [type, audienceRecords[type].length]));
|
|
225
230
|
const repoName = options.theme.project_name?.trim() || path_1.default.basename(root);
|
|
231
|
+
const assets = resolveSiteAssets(root, options.theme);
|
|
226
232
|
return {
|
|
227
233
|
root,
|
|
228
234
|
repoName,
|
|
229
235
|
siteTitle: `${repoName} Wiki`,
|
|
230
236
|
siteDescription: options.theme.project_description?.trim() || "A project wiki generated from durable repo knowledge.",
|
|
231
237
|
projectInitials: initialsForName(repoName),
|
|
238
|
+
assets,
|
|
232
239
|
records: audienceRecords,
|
|
233
240
|
flatRecords,
|
|
234
241
|
sourceRecords,
|
|
@@ -263,6 +270,10 @@ function renderLayout(options) {
|
|
|
263
270
|
: `${options.title} - ${options.model.siteTitle}`;
|
|
264
271
|
const defaultColorScheme = defaultThemeColorScheme(options.model.options.theme);
|
|
265
272
|
const defaultThemeAttribute = defaultColorScheme === "auto" ? "" : ` data-theme="${defaultColorScheme}"`;
|
|
273
|
+
const homeBrandAsset = options.active === "home" ? homeBrandAssetHtml(options.model, options.fileName) : "";
|
|
274
|
+
const homeBrandAssetMarkup = homeBrandAsset ? ` ${homeBrandAsset}\n` : "";
|
|
275
|
+
const homeHasWordmark = options.active === "home" && options.model.assets.wordmark !== undefined;
|
|
276
|
+
const h1Class = homeHasWordmark ? ` class="visually-hidden"` : "";
|
|
266
277
|
return `${exports.siteGeneratedNotice}
|
|
267
278
|
<!doctype html>
|
|
268
279
|
<html lang="en" data-default-theme="${defaultColorScheme}"${defaultThemeAttribute}>
|
|
@@ -272,7 +283,7 @@ function renderLayout(options) {
|
|
|
272
283
|
<meta name="generator" content="Wikiwiki">
|
|
273
284
|
<title>${escapeHtml(htmlTitle)}</title>
|
|
274
285
|
<script>${themeBootScript(defaultColorScheme)}</script>
|
|
275
|
-
|
|
286
|
+
${faviconLink(options.model, options.fileName, prefix)}
|
|
276
287
|
<link rel="stylesheet" href="${prefix}assets/wikiwiki.css">
|
|
277
288
|
<link rel="stylesheet" href="${prefix}assets/project-theme.css">
|
|
278
289
|
<script src="${prefix}assets/search-index.js" defer></script>
|
|
@@ -284,7 +295,7 @@ function renderLayout(options) {
|
|
|
284
295
|
<aside class="sidebar" aria-label="Wiki navigation">
|
|
285
296
|
<div class="sidebar-top">
|
|
286
297
|
<a class="brand" href="${hrefFrom(options.fileName, "index.html")}" aria-label="Wiki home">
|
|
287
|
-
|
|
298
|
+
${brandMarkHtml(options.model, options.fileName)}
|
|
288
299
|
<span>
|
|
289
300
|
<strong>${escapeHtml(options.model.repoName)}</strong>
|
|
290
301
|
<small>Project wiki</small>
|
|
@@ -302,8 +313,8 @@ function renderLayout(options) {
|
|
|
302
313
|
</div>
|
|
303
314
|
</aside>
|
|
304
315
|
<main id="content" class="content">
|
|
305
|
-
<header class="page-header">
|
|
306
|
-
<h1>${escapeHtml(options.title)}</h1>
|
|
316
|
+
<header class="page-header${homeBrandAsset ? " has-brand-asset" : ""}">
|
|
317
|
+
${homeBrandAssetMarkup} <h1${h1Class}>${escapeHtml(options.title)}</h1>
|
|
307
318
|
<p>${escapeHtml(options.description)}</p>
|
|
308
319
|
</header>
|
|
309
320
|
${options.body}
|
|
@@ -324,6 +335,41 @@ function themeChoiceButton(mode, label, title, defaultColorScheme) {
|
|
|
324
335
|
const pressed = mode === defaultColorScheme ? "true" : "false";
|
|
325
336
|
return `<button type="button" data-theme-choice="${mode}" aria-pressed="${pressed}" title="${escapeAttribute(title)}">${escapeHtml(label)}</button>`;
|
|
326
337
|
}
|
|
338
|
+
function faviconLink(model, currentFile, fallbackPrefix) {
|
|
339
|
+
const asset = model.assets.favicon;
|
|
340
|
+
if (!asset) {
|
|
341
|
+
return `<link rel="icon" href="${fallbackPrefix}favicon.svg" type="image/svg+xml">`;
|
|
342
|
+
}
|
|
343
|
+
const type = asset.mimeType ? ` type="${escapeAttribute(asset.mimeType)}"` : "";
|
|
344
|
+
const darkHref = hrefFrom(currentFile, asset.outputFileName);
|
|
345
|
+
const lightHref = model.assets.faviconLight ? hrefFrom(currentFile, model.assets.faviconLight.outputFileName) : darkHref;
|
|
346
|
+
const lightType = model.assets.faviconLight?.mimeType ?? asset.mimeType;
|
|
347
|
+
const faviconData = [
|
|
348
|
+
`data-wikiwiki-favicon`,
|
|
349
|
+
`data-favicon-dark="${darkHref}"`,
|
|
350
|
+
`data-favicon-light="${lightHref}"`,
|
|
351
|
+
...(asset.mimeType ? [`data-favicon-dark-type="${escapeAttribute(asset.mimeType)}"`] : []),
|
|
352
|
+
...(lightType ? [`data-favicon-light-type="${escapeAttribute(lightType)}"`] : [])
|
|
353
|
+
].join(" ");
|
|
354
|
+
return `<link rel="icon" href="${darkHref}"${type} ${faviconData}>`;
|
|
355
|
+
}
|
|
356
|
+
function brandMarkHtml(model, currentFile) {
|
|
357
|
+
const asset = model.assets.sidebarMark;
|
|
358
|
+
if (!asset) {
|
|
359
|
+
return `<span class="brand-mark">${escapeHtml(model.projectInitials)}</span>`;
|
|
360
|
+
}
|
|
361
|
+
return `<span class="brand-mark has-image"><img src="${hrefFrom(currentFile, asset.outputFileName)}" alt="" aria-hidden="true"></span>`;
|
|
362
|
+
}
|
|
363
|
+
function homeBrandAssetHtml(model, currentFile) {
|
|
364
|
+
const asset = model.assets.wordmark ?? model.assets.logo;
|
|
365
|
+
if (!asset) {
|
|
366
|
+
return "";
|
|
367
|
+
}
|
|
368
|
+
const label = asset.kind === "wordmark" ? "wordmark" : "logo";
|
|
369
|
+
return `<div class="home-brand-asset ${asset.kind}">
|
|
370
|
+
<img src="${hrefFrom(currentFile, asset.outputFileName)}" alt="${escapeAttribute(`${model.repoName} ${label}`)}">
|
|
371
|
+
</div>`;
|
|
372
|
+
}
|
|
327
373
|
function renderNav(model, currentFile, active) {
|
|
328
374
|
const categoryLinks = model.visibleCategories
|
|
329
375
|
.map((category) => navLink(currentFile, category.fileName, category.navLabel, active === category.type, model.counts[category.type]))
|
|
@@ -822,22 +868,94 @@ function recordCard(record, currentFile, _model) {
|
|
|
822
868
|
</article>`;
|
|
823
869
|
}
|
|
824
870
|
function formatText(text, context) {
|
|
825
|
-
const trimmed = text.trim();
|
|
871
|
+
const trimmed = text.replace(/\r\n/g, "\n").trim();
|
|
826
872
|
if (!trimmed) {
|
|
827
873
|
return "<p>Not recorded.</p>";
|
|
828
874
|
}
|
|
829
|
-
|
|
830
|
-
return blocks.map((block) => formatBlock(block, context)).join("\n");
|
|
875
|
+
return formatMarkdownBlocks(trimmed.split("\n"), context);
|
|
831
876
|
}
|
|
832
|
-
function
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
877
|
+
function formatMarkdownBlocks(lines, context) {
|
|
878
|
+
const blocks = [];
|
|
879
|
+
let index = 0;
|
|
880
|
+
while (index < lines.length) {
|
|
881
|
+
const line = lines[index] ?? "";
|
|
882
|
+
const trimmed = line.trim();
|
|
883
|
+
if (!trimmed) {
|
|
884
|
+
index += 1;
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
const fence = /^```([A-Za-z0-9_-]+)?\s*$/.exec(trimmed);
|
|
888
|
+
if (fence) {
|
|
889
|
+
const codeLines = [];
|
|
890
|
+
index += 1;
|
|
891
|
+
while (index < lines.length && !/^```\s*$/.test((lines[index] ?? "").trim())) {
|
|
892
|
+
codeLines.push(lines[index] ?? "");
|
|
893
|
+
index += 1;
|
|
894
|
+
}
|
|
895
|
+
if (index < lines.length) {
|
|
896
|
+
index += 1;
|
|
897
|
+
}
|
|
898
|
+
const languageClass = fence[1] ? ` class="language-${escapeAttribute(fence[1])}"` : "";
|
|
899
|
+
blocks.push(`<pre><code${languageClass}>${escapeHtml(codeLines.join("\n"))}</code></pre>`);
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const heading = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(trimmed);
|
|
903
|
+
if (heading) {
|
|
904
|
+
const level = heading[1].length;
|
|
905
|
+
blocks.push(`<h${level}>${formatInline(heading[2], context)}</h${level}>`);
|
|
906
|
+
index += 1;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (/^>\s?/.test(trimmed)) {
|
|
910
|
+
const quoteLines = [];
|
|
911
|
+
while (index < lines.length && /^>\s?/.test((lines[index] ?? "").trim())) {
|
|
912
|
+
quoteLines.push((lines[index] ?? "").trim().replace(/^>\s?/, ""));
|
|
913
|
+
index += 1;
|
|
914
|
+
}
|
|
915
|
+
blocks.push(`<blockquote>${formatMarkdownBlocks(quoteLines, context)}</blockquote>`);
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
if (/^[-*+]\s+/.test(trimmed)) {
|
|
919
|
+
const items = [];
|
|
920
|
+
while (index < lines.length && /^[-*+]\s+/.test((lines[index] ?? "").trim())) {
|
|
921
|
+
items.push(`<li>${formatInline((lines[index] ?? "").trim().replace(/^[-*+]\s+/, ""), context)}</li>`);
|
|
922
|
+
index += 1;
|
|
923
|
+
}
|
|
924
|
+
blocks.push(`<ul>${items.join("\n")}</ul>`);
|
|
925
|
+
continue;
|
|
926
|
+
}
|
|
927
|
+
if (/^\d+[.)]\s+/.test(trimmed)) {
|
|
928
|
+
const items = [];
|
|
929
|
+
while (index < lines.length && /^\d+[.)]\s+/.test((lines[index] ?? "").trim())) {
|
|
930
|
+
items.push(`<li>${formatInline((lines[index] ?? "").trim().replace(/^\d+[.)]\s+/, ""), context)}</li>`);
|
|
931
|
+
index += 1;
|
|
932
|
+
}
|
|
933
|
+
blocks.push(`<ol>${items.join("\n")}</ol>`);
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const paragraphLines = [];
|
|
937
|
+
while (index < lines.length) {
|
|
938
|
+
const paragraphLine = lines[index] ?? "";
|
|
939
|
+
const paragraphTrimmed = paragraphLine.trim();
|
|
940
|
+
if (!paragraphTrimmed) {
|
|
941
|
+
break;
|
|
942
|
+
}
|
|
943
|
+
if (paragraphLines.length > 0 && isMarkdownBlockStart(paragraphTrimmed)) {
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
paragraphLines.push(paragraphTrimmed);
|
|
947
|
+
index += 1;
|
|
948
|
+
}
|
|
949
|
+
blocks.push(`<p>${formatInline(paragraphLines.join(" "), context)}</p>`);
|
|
839
950
|
}
|
|
840
|
-
return
|
|
951
|
+
return blocks.join("\n");
|
|
952
|
+
}
|
|
953
|
+
function isMarkdownBlockStart(trimmedLine) {
|
|
954
|
+
return (/^```/.test(trimmedLine) ||
|
|
955
|
+
/^(#{1,6})\s+/.test(trimmedLine) ||
|
|
956
|
+
/^>\s?/.test(trimmedLine) ||
|
|
957
|
+
/^[-*+]\s+/.test(trimmedLine) ||
|
|
958
|
+
/^\d+[.)]\s+/.test(trimmedLine));
|
|
841
959
|
}
|
|
842
960
|
function formatInline(text, context) {
|
|
843
961
|
const parts = [];
|
|
@@ -855,7 +973,8 @@ function formatInline(text, context) {
|
|
|
855
973
|
if (linkMatch) {
|
|
856
974
|
const label = linkMatch[1];
|
|
857
975
|
const target = linkMatch[2];
|
|
858
|
-
|
|
976
|
+
const href = normalizeContentHref(target, context);
|
|
977
|
+
parts.push(href ? `<a href="${escapeAttribute(href)}">${escapeHtml(label)}</a>` : escapeHtml(label));
|
|
859
978
|
}
|
|
860
979
|
}
|
|
861
980
|
lastIndex = match.index + token.length;
|
|
@@ -891,9 +1010,13 @@ function sourceFileLink(file, context) {
|
|
|
891
1010
|
}
|
|
892
1011
|
function normalizeContentHref(target, context) {
|
|
893
1012
|
const trimmed = target.trim();
|
|
894
|
-
if (
|
|
1013
|
+
if (trimmed.startsWith("#")) {
|
|
895
1014
|
return trimmed;
|
|
896
1015
|
}
|
|
1016
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed)?.[1].toLowerCase();
|
|
1017
|
+
if (scheme) {
|
|
1018
|
+
return ["http", "https", "mailto"].includes(scheme) ? trimmed : undefined;
|
|
1019
|
+
}
|
|
897
1020
|
const recordHrefForTarget = context.hrefById.get(trimmed);
|
|
898
1021
|
if (recordHrefForTarget) {
|
|
899
1022
|
return hrefFrom(context.currentFile, recordHrefForTarget);
|
|
@@ -978,6 +1101,147 @@ function isDirectoryPath(root, file) {
|
|
|
978
1101
|
return false;
|
|
979
1102
|
}
|
|
980
1103
|
}
|
|
1104
|
+
function resolveSiteAssets(root, theme) {
|
|
1105
|
+
const usedOutputFileNames = new Set();
|
|
1106
|
+
const files = [];
|
|
1107
|
+
const logo = resolveImageAsset(root, "logo", theme.logo_path, usedOutputFileNames, files);
|
|
1108
|
+
const wordmark = resolveImageAsset(root, "wordmark", theme.wordmark_path, usedOutputFileNames, files);
|
|
1109
|
+
const favicon = resolveImageAsset(root, "favicon", theme.favicon_path, usedOutputFileNames, files);
|
|
1110
|
+
const faviconLight = favicon ? resolveLightFaviconAsset(favicon, usedOutputFileNames, files) : undefined;
|
|
1111
|
+
const fonts = resolveFontAssets(root, theme.fonts ?? [], usedOutputFileNames, files);
|
|
1112
|
+
return {
|
|
1113
|
+
...(logo ? { logo } : {}),
|
|
1114
|
+
...(wordmark ? { wordmark } : {}),
|
|
1115
|
+
...(favicon ? { favicon } : {}),
|
|
1116
|
+
...(faviconLight ? { faviconLight } : {}),
|
|
1117
|
+
sidebarMark: favicon ?? logo,
|
|
1118
|
+
fonts,
|
|
1119
|
+
files
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
function resolveLightFaviconAsset(favicon, usedOutputFileNames, files) {
|
|
1123
|
+
const mimeType = favicon.mimeType;
|
|
1124
|
+
if (!mimeType?.startsWith("image/")) {
|
|
1125
|
+
return undefined;
|
|
1126
|
+
}
|
|
1127
|
+
const source = fs_1.default.readFileSync(favicon.sourcePath);
|
|
1128
|
+
const outputFileName = uniqueOutputFileName("assets/favicon-light.svg", usedOutputFileNames);
|
|
1129
|
+
files.push({
|
|
1130
|
+
fileName: outputFileName,
|
|
1131
|
+
content: lightModeFaviconSvg(`data:${mimeType};base64,${source.toString("base64")}`)
|
|
1132
|
+
});
|
|
1133
|
+
return {
|
|
1134
|
+
kind: "favicon",
|
|
1135
|
+
sourcePath: favicon.sourcePath,
|
|
1136
|
+
outputFileName,
|
|
1137
|
+
mimeType: "image/svg+xml"
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
function resolveImageAsset(root, kind, configuredPath, usedOutputFileNames, files) {
|
|
1141
|
+
const sourcePath = resolveThemeAssetPath(root, configuredPath);
|
|
1142
|
+
if (!sourcePath) {
|
|
1143
|
+
return undefined;
|
|
1144
|
+
}
|
|
1145
|
+
const extension = assetExtension(sourcePath);
|
|
1146
|
+
if (!extension || !["svg", "png", "jpg", "jpeg", "webp", "ico"].includes(extension)) {
|
|
1147
|
+
return undefined;
|
|
1148
|
+
}
|
|
1149
|
+
const outputFileName = uniqueOutputFileName(`assets/${kind}.${extension}`, usedOutputFileNames);
|
|
1150
|
+
files.push({ fileName: outputFileName, content: fs_1.default.readFileSync(sourcePath) });
|
|
1151
|
+
return {
|
|
1152
|
+
kind,
|
|
1153
|
+
sourcePath,
|
|
1154
|
+
outputFileName,
|
|
1155
|
+
mimeType: mimeTypeForExtension(extension)
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function resolveFontAssets(root, fonts, usedOutputFileNames, files) {
|
|
1159
|
+
const resolved = [];
|
|
1160
|
+
for (const font of fonts) {
|
|
1161
|
+
const sourcePath = resolveThemeAssetPath(root, font.path);
|
|
1162
|
+
if (!sourcePath || !font.family.trim()) {
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
const extension = assetExtension(sourcePath);
|
|
1166
|
+
if (!extension || !["woff2", "woff", "otf", "ttf"].includes(extension)) {
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
const weight = font.weight?.trim() || "400";
|
|
1170
|
+
const style = font.style?.trim() || "normal";
|
|
1171
|
+
const baseName = safeFileName(`${font.family}-${weight}-${style}`).toLowerCase();
|
|
1172
|
+
const outputFileName = uniqueOutputFileName(`assets/fonts/${baseName}.${extension}`, usedOutputFileNames);
|
|
1173
|
+
files.push({ fileName: outputFileName, content: fs_1.default.readFileSync(sourcePath) });
|
|
1174
|
+
resolved.push({
|
|
1175
|
+
...font,
|
|
1176
|
+
weight,
|
|
1177
|
+
style,
|
|
1178
|
+
display: font.display?.trim() || "swap",
|
|
1179
|
+
sourcePath,
|
|
1180
|
+
outputFileName,
|
|
1181
|
+
cssUrl: `./fonts/${path_1.default.posix.basename(outputFileName)}`,
|
|
1182
|
+
format: fontFormatForExtension(extension)
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
return resolved;
|
|
1186
|
+
}
|
|
1187
|
+
function resolveThemeAssetPath(root, configuredPath) {
|
|
1188
|
+
const normalized = toPosixPath(configuredPath?.trim() ?? "").replace(/^\.\//, "");
|
|
1189
|
+
if (!normalized || path_1.default.isAbsolute(normalized) || normalized.startsWith("../") || normalized.includes("://") || normalized.startsWith("#")) {
|
|
1190
|
+
return undefined;
|
|
1191
|
+
}
|
|
1192
|
+
const rootPath = path_1.default.resolve(root);
|
|
1193
|
+
const fullPath = path_1.default.resolve(rootPath, normalized);
|
|
1194
|
+
if (fullPath !== rootPath && !fullPath.startsWith(`${rootPath}${path_1.default.sep}`)) {
|
|
1195
|
+
return undefined;
|
|
1196
|
+
}
|
|
1197
|
+
try {
|
|
1198
|
+
return fs_1.default.statSync(fullPath).isFile() ? fullPath : undefined;
|
|
1199
|
+
}
|
|
1200
|
+
catch {
|
|
1201
|
+
return undefined;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
function assetExtension(file) {
|
|
1205
|
+
const extension = path_1.default.extname(file).replace(/^\./, "").toLowerCase();
|
|
1206
|
+
return extension || undefined;
|
|
1207
|
+
}
|
|
1208
|
+
function uniqueOutputFileName(fileName, used) {
|
|
1209
|
+
const normalized = toPosixPath(fileName);
|
|
1210
|
+
if (!used.has(normalized)) {
|
|
1211
|
+
used.add(normalized);
|
|
1212
|
+
return normalized;
|
|
1213
|
+
}
|
|
1214
|
+
const directory = path_1.default.posix.dirname(normalized);
|
|
1215
|
+
const extension = path_1.default.posix.extname(normalized);
|
|
1216
|
+
const base = path_1.default.posix.basename(normalized, extension);
|
|
1217
|
+
let index = 2;
|
|
1218
|
+
while (used.has(`${directory}/${base}-${index}${extension}`)) {
|
|
1219
|
+
index += 1;
|
|
1220
|
+
}
|
|
1221
|
+
const result = `${directory}/${base}-${index}${extension}`;
|
|
1222
|
+
used.add(result);
|
|
1223
|
+
return result;
|
|
1224
|
+
}
|
|
1225
|
+
function mimeTypeForExtension(extension) {
|
|
1226
|
+
const types = {
|
|
1227
|
+
svg: "image/svg+xml",
|
|
1228
|
+
png: "image/png",
|
|
1229
|
+
jpg: "image/jpeg",
|
|
1230
|
+
jpeg: "image/jpeg",
|
|
1231
|
+
webp: "image/webp",
|
|
1232
|
+
ico: "image/x-icon"
|
|
1233
|
+
};
|
|
1234
|
+
return types[extension];
|
|
1235
|
+
}
|
|
1236
|
+
function fontFormatForExtension(extension) {
|
|
1237
|
+
const formats = {
|
|
1238
|
+
woff2: "woff2",
|
|
1239
|
+
woff: "woff",
|
|
1240
|
+
otf: "opentype",
|
|
1241
|
+
ttf: "truetype"
|
|
1242
|
+
};
|
|
1243
|
+
return formats[extension];
|
|
1244
|
+
}
|
|
981
1245
|
function searchIndexJs(model) {
|
|
982
1246
|
const articleEntries = model.records.article.map((article) => ({
|
|
983
1247
|
type: "article",
|
|
@@ -1047,6 +1311,7 @@ function siteManifest(model) {
|
|
|
1047
1311
|
audience: model.audience,
|
|
1048
1312
|
project_name: model.repoName,
|
|
1049
1313
|
theme_file: ".wikiwiki/site-theme.json",
|
|
1314
|
+
...(siteManifestAssets(model) ? { assets: siteManifestAssets(model) } : {}),
|
|
1050
1315
|
pages: sitePages(model),
|
|
1051
1316
|
...(integrations ? { integrations } : {}),
|
|
1052
1317
|
articles: model.records.article.map((article) => ({
|
|
@@ -1063,6 +1328,27 @@ function siteManifest(model) {
|
|
|
1063
1328
|
}))
|
|
1064
1329
|
};
|
|
1065
1330
|
}
|
|
1331
|
+
function siteManifestAssets(model) {
|
|
1332
|
+
if (!model.assets.logo && !model.assets.wordmark && !model.assets.favicon && model.assets.fonts.length === 0) {
|
|
1333
|
+
return undefined;
|
|
1334
|
+
}
|
|
1335
|
+
return {
|
|
1336
|
+
...(model.assets.logo ? { logo: model.assets.logo.outputFileName } : {}),
|
|
1337
|
+
...(model.assets.wordmark ? { wordmark: model.assets.wordmark.outputFileName } : {}),
|
|
1338
|
+
...(model.assets.favicon ? { favicon: model.assets.favicon.outputFileName } : {}),
|
|
1339
|
+
...(model.assets.faviconLight ? { favicon_light: model.assets.faviconLight.outputFileName } : {}),
|
|
1340
|
+
...(model.assets.fonts.length > 0
|
|
1341
|
+
? {
|
|
1342
|
+
fonts: model.assets.fonts.map((font) => ({
|
|
1343
|
+
family: font.family,
|
|
1344
|
+
weight: font.weight,
|
|
1345
|
+
style: font.style,
|
|
1346
|
+
url: font.outputFileName
|
|
1347
|
+
}))
|
|
1348
|
+
}
|
|
1349
|
+
: {})
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1066
1352
|
function siteManifestIntegrations(model) {
|
|
1067
1353
|
const beads = model.integrations.beads;
|
|
1068
1354
|
if (!beads || model.audience === "user") {
|
|
@@ -1446,10 +1732,14 @@ function escapeHtml(value) {
|
|
|
1446
1732
|
function escapeAttribute(value) {
|
|
1447
1733
|
return escapeHtml(value);
|
|
1448
1734
|
}
|
|
1449
|
-
function projectThemeCss(theme) {
|
|
1735
|
+
function projectThemeCss(theme, fonts = []) {
|
|
1450
1736
|
const resolved = resolveThemePalettes(theme);
|
|
1737
|
+
const fontFaces = fontFaceCss(fonts);
|
|
1451
1738
|
if (!resolved.hasTheme) {
|
|
1452
|
-
|
|
1739
|
+
const fontFamily = fonts[0]?.family
|
|
1740
|
+
? `\n:root {\n --font-family: ${cssString(fonts[0].family)}, Inter, ui-sans-serif, system-ui, sans-serif;\n}\n`
|
|
1741
|
+
: "";
|
|
1742
|
+
return `/* Optional project theme. Add .wikiwiki/site-theme.json to override CSS custom properties. */\n${fontFaces}${fontFamily}`;
|
|
1453
1743
|
}
|
|
1454
1744
|
const light = themeWithContrastGuardrails(resolved.light, "light");
|
|
1455
1745
|
const dark = themeWithContrastGuardrails(resolved.dark, "dark");
|
|
@@ -1458,6 +1748,7 @@ function projectThemeCss(theme) {
|
|
|
1458
1748
|
? `\n${comments.map((comment) => `/* ${comment} */`).join("\n")}`
|
|
1459
1749
|
: "";
|
|
1460
1750
|
return `/* Generated from .wikiwiki/site-theme.json. Edit the theme file, then run \`wk site\`. */${commentBlock}
|
|
1751
|
+
${fontFaces}
|
|
1461
1752
|
${themeCssBlock(":root,\n:root[data-theme=\"light\"]", light.theme, "light")}
|
|
1462
1753
|
|
|
1463
1754
|
@media (prefers-color-scheme: dark) {
|
|
@@ -1467,6 +1758,21 @@ ${themeCssBlock(":root:not([data-theme=\"light\"])", dark.theme, "dark", " ")}
|
|
|
1467
1758
|
${themeCssBlock(":root[data-theme=\"dark\"]", dark.theme, "dark")}
|
|
1468
1759
|
`;
|
|
1469
1760
|
}
|
|
1761
|
+
function fontFaceCss(fonts) {
|
|
1762
|
+
if (fonts.length === 0) {
|
|
1763
|
+
return "";
|
|
1764
|
+
}
|
|
1765
|
+
return `${fonts.map((font) => {
|
|
1766
|
+
const format = font.format ? ` format("${font.format}")` : "";
|
|
1767
|
+
return `@font-face {
|
|
1768
|
+
font-family: ${cssString(font.family)};
|
|
1769
|
+
src: url("${cssUrl(font.cssUrl)}")${format};
|
|
1770
|
+
font-weight: ${cssValue(font.weight ?? "400")};
|
|
1771
|
+
font-style: ${cssValue(font.style ?? "normal")};
|
|
1772
|
+
font-display: ${cssValue(font.display ?? "swap")};
|
|
1773
|
+
}`;
|
|
1774
|
+
}).join("\n\n")}\n\n`;
|
|
1775
|
+
}
|
|
1470
1776
|
function resolveThemePalettes(theme) {
|
|
1471
1777
|
const flat = themePaletteFrom(theme);
|
|
1472
1778
|
const modeLight = theme.modes?.light ?? {};
|
|
@@ -1727,12 +2033,37 @@ function rgba(color, alpha) {
|
|
|
1727
2033
|
function cssValue(value) {
|
|
1728
2034
|
return value.replace(/[;\n\r]/g, "").trim();
|
|
1729
2035
|
}
|
|
2036
|
+
function cssString(value) {
|
|
2037
|
+
return `"${value.replace(/["\\\n\r]/g, "")}"`;
|
|
2038
|
+
}
|
|
2039
|
+
function cssUrl(value) {
|
|
2040
|
+
return value.replace(/["\\\n\r]/g, "");
|
|
2041
|
+
}
|
|
1730
2042
|
function faviconSvg(model) {
|
|
1731
2043
|
const initials = escapeHtml(model.projectInitials.slice(0, 2));
|
|
1732
2044
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
1733
2045
|
<rect width="64" height="64" rx="14" fill="#111827"/>
|
|
1734
|
-
<
|
|
1735
|
-
<
|
|
2046
|
+
<path d="M15 17c0-2.2 1.8-4 4-4h12c2.2 0 4 1.8 4 4v32c0 1.1-.9 2-2 2H19c-2.2 0-4-1.8-4-4V17Z" fill="#f8fafc"/>
|
|
2047
|
+
<path d="M29 13h16c2.2 0 4 1.8 4 4v30c0 2.2-1.8 4-4 4H33c1.1 0 2-.9 2-2V17c0-2.2-1.8-4-4-4h-2Z" fill="#38bdf8"/>
|
|
2048
|
+
<path d="M22 23h6M22 30h6M39 23h5M39 30h5" stroke="#111827" stroke-width="2.4" stroke-linecap="round"/>
|
|
2049
|
+
<text x="32" y="44" text-anchor="middle" font-family="Inter, Arial, sans-serif" font-size="12" font-weight="800" fill="#111827">${initials}</text>
|
|
2050
|
+
</svg>
|
|
2051
|
+
`;
|
|
2052
|
+
}
|
|
2053
|
+
function lightModeFaviconSvg(dataUri) {
|
|
2054
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
2055
|
+
<defs>
|
|
2056
|
+
<filter id="wikiwiki-light-brand-filter" color-interpolation-filters="sRGB">
|
|
2057
|
+
<feComponentTransfer>
|
|
2058
|
+
<feFuncR type="table" tableValues="1 0"/>
|
|
2059
|
+
<feFuncG type="table" tableValues="1 0"/>
|
|
2060
|
+
<feFuncB type="table" tableValues="1 0"/>
|
|
2061
|
+
</feComponentTransfer>
|
|
2062
|
+
<feColorMatrix type="hueRotate" values="180"/>
|
|
2063
|
+
<feColorMatrix type="saturate" values="1.14"/>
|
|
2064
|
+
</filter>
|
|
2065
|
+
</defs>
|
|
2066
|
+
<image href="${escapeAttribute(dataUri)}" width="512" height="512" preserveAspectRatio="xMidYMid meet" filter="url(#wikiwiki-light-brand-filter)"/>
|
|
1736
2067
|
</svg>
|
|
1737
2068
|
`;
|
|
1738
2069
|
}
|
|
@@ -1759,6 +2090,7 @@ function siteCss() {
|
|
|
1759
2090
|
--card-gradient: linear-gradient(180deg, #fffdf7 0%, #faf9f3 100%);
|
|
1760
2091
|
--brand-gradient: linear-gradient(135deg, var(--accent), var(--accent-strong));
|
|
1761
2092
|
--brand-mark-text: #fffaf0;
|
|
2093
|
+
--brand-asset-filter: invert(1) hue-rotate(180deg) saturate(1.14);
|
|
1762
2094
|
--badge-bg: #e5f0ed;
|
|
1763
2095
|
--badge-text: var(--accent-strong);
|
|
1764
2096
|
--tag-bg: #f8e8f1;
|
|
@@ -1791,6 +2123,7 @@ function siteCss() {
|
|
|
1791
2123
|
--card-gradient: linear-gradient(180deg, #1a261f 0%, #141e18 100%);
|
|
1792
2124
|
--brand-gradient: linear-gradient(135deg, var(--accent), #2dd4bf);
|
|
1793
2125
|
--brand-mark-text: #101914;
|
|
2126
|
+
--brand-asset-filter: none;
|
|
1794
2127
|
--badge-bg: rgba(110, 231, 199, 0.16);
|
|
1795
2128
|
--badge-text: #d1fae5;
|
|
1796
2129
|
--tag-bg: rgba(240, 171, 252, 0.18);
|
|
@@ -1823,6 +2156,7 @@ function siteCss() {
|
|
|
1823
2156
|
--card-gradient: linear-gradient(180deg, #1a261f 0%, #141e18 100%);
|
|
1824
2157
|
--brand-gradient: linear-gradient(135deg, var(--accent), #2dd4bf);
|
|
1825
2158
|
--brand-mark-text: #101914;
|
|
2159
|
+
--brand-asset-filter: none;
|
|
1826
2160
|
--badge-bg: rgba(110, 231, 199, 0.16);
|
|
1827
2161
|
--badge-text: #d1fae5;
|
|
1828
2162
|
--tag-bg: rgba(240, 171, 252, 0.18);
|
|
@@ -1837,6 +2171,7 @@ function siteCss() {
|
|
|
1837
2171
|
|
|
1838
2172
|
:root[data-theme="light"] {
|
|
1839
2173
|
color-scheme: light;
|
|
2174
|
+
--brand-asset-filter: invert(1) hue-rotate(180deg) saturate(1.14);
|
|
1840
2175
|
}
|
|
1841
2176
|
|
|
1842
2177
|
* {
|
|
@@ -1935,6 +2270,21 @@ code {
|
|
|
1935
2270
|
letter-spacing: 0;
|
|
1936
2271
|
}
|
|
1937
2272
|
|
|
2273
|
+
.brand-mark.has-image {
|
|
2274
|
+
overflow: hidden;
|
|
2275
|
+
background: color-mix(in srgb, var(--panel) 86%, transparent);
|
|
2276
|
+
padding: 0.22rem;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
.brand-mark img {
|
|
2280
|
+
display: block;
|
|
2281
|
+
width: 100%;
|
|
2282
|
+
height: 100%;
|
|
2283
|
+
filter: var(--brand-asset-filter);
|
|
2284
|
+
object-fit: contain;
|
|
2285
|
+
transition: filter 160ms ease;
|
|
2286
|
+
}
|
|
2287
|
+
|
|
1938
2288
|
.brand strong,
|
|
1939
2289
|
.brand small {
|
|
1940
2290
|
display: block;
|
|
@@ -2062,12 +2412,51 @@ input[type="search"]:focus {
|
|
|
2062
2412
|
margin-bottom: 1.5rem;
|
|
2063
2413
|
}
|
|
2064
2414
|
|
|
2415
|
+
.home-brand-asset {
|
|
2416
|
+
display: flex;
|
|
2417
|
+
align-items: center;
|
|
2418
|
+
min-height: 3.5rem;
|
|
2419
|
+
margin-bottom: 0.65rem;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
.home-brand-asset img {
|
|
2423
|
+
display: block;
|
|
2424
|
+
filter: var(--brand-asset-filter);
|
|
2425
|
+
max-width: min(28rem, 100%);
|
|
2426
|
+
max-height: 5.5rem;
|
|
2427
|
+
object-fit: contain;
|
|
2428
|
+
object-position: left center;
|
|
2429
|
+
transition: filter 160ms ease;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
.home-brand-asset.logo img {
|
|
2433
|
+
width: 4.5rem;
|
|
2434
|
+
height: 4.5rem;
|
|
2435
|
+
border: 1px solid var(--border);
|
|
2436
|
+
border-radius: var(--radius);
|
|
2437
|
+
background: color-mix(in srgb, var(--panel) 82%, transparent);
|
|
2438
|
+
box-shadow: var(--shadow);
|
|
2439
|
+
padding: 0.35rem;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2065
2442
|
.page-header h1 {
|
|
2066
2443
|
margin: 0.15rem 0 0.45rem;
|
|
2067
2444
|
font-size: clamp(2rem, 4vw, 3.8rem);
|
|
2068
2445
|
line-height: 1;
|
|
2069
2446
|
}
|
|
2070
2447
|
|
|
2448
|
+
.visually-hidden {
|
|
2449
|
+
position: absolute;
|
|
2450
|
+
width: 1px;
|
|
2451
|
+
height: 1px;
|
|
2452
|
+
margin: -1px;
|
|
2453
|
+
overflow: hidden;
|
|
2454
|
+
clip: rect(0 0 0 0);
|
|
2455
|
+
white-space: nowrap;
|
|
2456
|
+
border: 0;
|
|
2457
|
+
padding: 0;
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2071
2460
|
.page-header p {
|
|
2072
2461
|
max-width: 760px;
|
|
2073
2462
|
color: var(--muted);
|
|
@@ -2322,20 +2711,77 @@ input[type="search"]:focus {
|
|
|
2322
2711
|
padding: clamp(1rem, 3vw, 1.6rem);
|
|
2323
2712
|
}
|
|
2324
2713
|
|
|
2325
|
-
.prose
|
|
2714
|
+
.prose h1,
|
|
2715
|
+
.prose h2,
|
|
2716
|
+
.prose h3,
|
|
2717
|
+
.prose h4,
|
|
2718
|
+
.prose h5,
|
|
2719
|
+
.prose h6 {
|
|
2326
2720
|
margin: 1.3rem 0 0.3rem;
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
.prose h1 {
|
|
2724
|
+
font-size: 1.55rem;
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
.prose h2 {
|
|
2327
2728
|
font-size: 1.25rem;
|
|
2328
2729
|
}
|
|
2329
2730
|
|
|
2330
|
-
.prose
|
|
2731
|
+
.prose h3 {
|
|
2732
|
+
font-size: 1.1rem;
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
.prose h4,
|
|
2736
|
+
.prose h5,
|
|
2737
|
+
.prose h6 {
|
|
2738
|
+
font-size: 1rem;
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
.prose h1:first-child,
|
|
2742
|
+
.prose h2:first-child,
|
|
2743
|
+
.prose h3:first-child,
|
|
2744
|
+
.prose h4:first-child,
|
|
2745
|
+
.prose h5:first-child,
|
|
2746
|
+
.prose h6:first-child {
|
|
2331
2747
|
margin-top: 0;
|
|
2332
2748
|
}
|
|
2333
2749
|
|
|
2334
2750
|
.prose p,
|
|
2335
|
-
.prose ul
|
|
2751
|
+
.prose ul,
|
|
2752
|
+
.prose ol,
|
|
2753
|
+
.prose blockquote,
|
|
2754
|
+
.prose pre {
|
|
2336
2755
|
color: var(--text);
|
|
2337
2756
|
}
|
|
2338
2757
|
|
|
2758
|
+
.prose ul,
|
|
2759
|
+
.prose ol {
|
|
2760
|
+
padding-left: 1.45rem;
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2763
|
+
.prose blockquote {
|
|
2764
|
+
margin: 1rem 0;
|
|
2765
|
+
border-left: 3px solid var(--accent);
|
|
2766
|
+
padding-left: 1rem;
|
|
2767
|
+
color: var(--muted);
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
.prose pre {
|
|
2771
|
+
overflow-x: auto;
|
|
2772
|
+
border-radius: var(--radius);
|
|
2773
|
+
background: var(--code-bg);
|
|
2774
|
+
padding: 0.85rem;
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
.prose pre code {
|
|
2778
|
+
display: block;
|
|
2779
|
+
overflow-wrap: normal;
|
|
2780
|
+
background: transparent;
|
|
2781
|
+
padding: 0;
|
|
2782
|
+
white-space: pre;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2339
2785
|
.record-meta {
|
|
2340
2786
|
margin-top: 1rem;
|
|
2341
2787
|
padding: 0.75rem 1rem;
|
|
@@ -2562,6 +3008,34 @@ function siteJs() {
|
|
|
2562
3008
|
return validThemeModes.includes(value) ? value : "auto";
|
|
2563
3009
|
}
|
|
2564
3010
|
|
|
3011
|
+
function effectiveThemeMode(mode) {
|
|
3012
|
+
const normalizedMode = normalizeThemeMode(mode);
|
|
3013
|
+
if (normalizedMode === "light" || normalizedMode === "dark") {
|
|
3014
|
+
return normalizedMode;
|
|
3015
|
+
}
|
|
3016
|
+
return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
function updateFavicon(mode) {
|
|
3020
|
+
const link = document.querySelector("link[data-wikiwiki-favicon]");
|
|
3021
|
+
if (!link) {
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
const effectiveMode = effectiveThemeMode(mode);
|
|
3025
|
+
const href = effectiveMode === "light"
|
|
3026
|
+
? link.getAttribute("data-favicon-light")
|
|
3027
|
+
: link.getAttribute("data-favicon-dark");
|
|
3028
|
+
const type = effectiveMode === "light"
|
|
3029
|
+
? link.getAttribute("data-favicon-light-type")
|
|
3030
|
+
: link.getAttribute("data-favicon-dark-type");
|
|
3031
|
+
if (href) {
|
|
3032
|
+
link.setAttribute("href", href);
|
|
3033
|
+
}
|
|
3034
|
+
if (type) {
|
|
3035
|
+
link.setAttribute("type", type);
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
|
|
2565
3039
|
function applyThemeMode(mode, persist) {
|
|
2566
3040
|
const normalizedMode = normalizeThemeMode(mode);
|
|
2567
3041
|
if (normalizedMode === "light" || normalizedMode === "dark") {
|
|
@@ -2570,6 +3044,7 @@ function siteJs() {
|
|
|
2570
3044
|
delete document.documentElement.dataset.theme;
|
|
2571
3045
|
}
|
|
2572
3046
|
document.documentElement.dataset.themeMode = normalizedMode;
|
|
3047
|
+
updateFavicon(normalizedMode);
|
|
2573
3048
|
document.querySelectorAll("[data-theme-choice]").forEach((button) => {
|
|
2574
3049
|
button.setAttribute("aria-pressed", button.getAttribute("data-theme-choice") === normalizedMode ? "true" : "false");
|
|
2575
3050
|
});
|
|
@@ -2678,6 +3153,19 @@ function siteJs() {
|
|
|
2678
3153
|
applyThemeMode(button.getAttribute("data-theme-choice") || "auto", true);
|
|
2679
3154
|
});
|
|
2680
3155
|
});
|
|
3156
|
+
if (window.matchMedia) {
|
|
3157
|
+
const colorSchemeQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
3158
|
+
const updateAutoFavicon = () => {
|
|
3159
|
+
if (normalizeThemeMode(document.documentElement.dataset.themeMode || "auto") === "auto") {
|
|
3160
|
+
updateFavicon("auto");
|
|
3161
|
+
}
|
|
3162
|
+
};
|
|
3163
|
+
if (colorSchemeQuery.addEventListener) {
|
|
3164
|
+
colorSchemeQuery.addEventListener("change", updateAutoFavicon);
|
|
3165
|
+
} else if (colorSchemeQuery.addListener) {
|
|
3166
|
+
colorSchemeQuery.addListener(updateAutoFavicon);
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
2681
3169
|
}());
|
|
2682
3170
|
`;
|
|
2683
3171
|
}
|