@canopy-iiif/app 0.6.28 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/build/assets.js +30 -0
- package/lib/build/build.js +184 -0
- package/lib/{dev.js → build/dev.js} +5 -5
- package/lib/build/facets.js +19 -0
- package/lib/{iiif.js → build/iiif.js} +222 -373
- package/lib/{mdx.js → build/mdx.js} +13 -10
- package/lib/build/pages.js +141 -0
- package/lib/build/runtimes.js +58 -0
- package/lib/build/search-index.js +42 -0
- package/lib/build/search.js +219 -0
- package/lib/build/styles.js +99 -0
- package/lib/{thumbnail.js → iiif/thumbnail.js} +0 -1
- package/lib/index.js +2 -3
- package/lib/{search.js → search/search.js} +8 -8
- package/package.json +1 -1
- package/lib/build.js +0 -762
- package/lib/components/IIIFCard.js +0 -102
- package/lib/runtime/command-entry.jsx +0 -44
- /package/lib/{devtoast.config.json → build/devtoast.config.json} +0 -0
- /package/lib/{devtoast.css → build/devtoast.css} +0 -0
- /package/lib/{log.js → build/log.js} +0 -0
- /package/lib/{search-app.jsx → search/search-app.jsx} +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const { fs, fsp, path, ASSETS_DIR, OUT_DIR, ensureDirSync } = require('../common');
|
|
2
|
+
const { logLine } = require('./log');
|
|
3
|
+
|
|
4
|
+
async function copyAssets() {
|
|
5
|
+
try {
|
|
6
|
+
if (!fs.existsSync(ASSETS_DIR)) return;
|
|
7
|
+
} catch (_) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
async function walk(dir) {
|
|
11
|
+
const entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
12
|
+
for (const e of entries) {
|
|
13
|
+
const src = path.join(dir, e.name);
|
|
14
|
+
const rel = path.relative(ASSETS_DIR, src);
|
|
15
|
+
const dest = path.join(OUT_DIR, rel);
|
|
16
|
+
if (e.isDirectory()) { ensureDirSync(dest); await walk(src); }
|
|
17
|
+
else if (e.isFile()) {
|
|
18
|
+
ensureDirSync(path.dirname(dest));
|
|
19
|
+
await fsp.copyFile(src, dest);
|
|
20
|
+
try { logLine(`• Asset ${path.relative(process.cwd(), dest)}`, 'cyan', { dim: true }); } catch (_) {}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
try { logLine('• Copying assets...', 'blue', { bright: true }); } catch (_) {}
|
|
25
|
+
await walk(ASSETS_DIR);
|
|
26
|
+
try { logLine('✓ Assets copied', 'green'); } catch (_) {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { copyAssets };
|
|
30
|
+
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const {
|
|
2
|
+
fs,
|
|
3
|
+
path,
|
|
4
|
+
CONTENT_DIR,
|
|
5
|
+
OUT_DIR,
|
|
6
|
+
ensureDirSync,
|
|
7
|
+
cleanDir,
|
|
8
|
+
} = require("../common");
|
|
9
|
+
const mdx = require("./mdx");
|
|
10
|
+
const iiif = require("./iiif");
|
|
11
|
+
const pages = require("./pages");
|
|
12
|
+
const search = require("../search/search");
|
|
13
|
+
const searchBuild = require("./search");
|
|
14
|
+
const { buildSearchIndex } = require("./search-index");
|
|
15
|
+
const { generateFacets } = require("./facets");
|
|
16
|
+
const runtimes = require("./runtimes");
|
|
17
|
+
const { ensureStyles } = require("./styles");
|
|
18
|
+
const { copyAssets } = require("./assets");
|
|
19
|
+
const { logLine } = require("./log");
|
|
20
|
+
|
|
21
|
+
// hold records between builds if skipping IIIF
|
|
22
|
+
let iiifRecordsCache = [];
|
|
23
|
+
let pageRecords = [];
|
|
24
|
+
|
|
25
|
+
async function build(options = {}) {
|
|
26
|
+
const skipIiif = !!options?.skipIiif;
|
|
27
|
+
if (!fs.existsSync(CONTENT_DIR)) {
|
|
28
|
+
console.error("No content directory found at", CONTENT_DIR);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Clean and prepare output directory
|
|
34
|
+
*/
|
|
35
|
+
logLine("\n[1/6] Clean and prepare directories", "magenta", {
|
|
36
|
+
bright: true,
|
|
37
|
+
});
|
|
38
|
+
mdx?.resetMdxCaches();
|
|
39
|
+
if (!skipIiif) {
|
|
40
|
+
await cleanDir(OUT_DIR);
|
|
41
|
+
logLine("✓ Cleaned output directory\n", "cyan");
|
|
42
|
+
} else {
|
|
43
|
+
logLine("• Incremental rebuild\n", "blue");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build IIIF Collection content from configured source(s).
|
|
48
|
+
* This includes building IIIF manifests for works and collections,
|
|
49
|
+
* as well as collecting search records for works.
|
|
50
|
+
*/
|
|
51
|
+
logLine("\n[2/6] Build IIIF Collection content", "magenta", {
|
|
52
|
+
bright: true,
|
|
53
|
+
});
|
|
54
|
+
let searchRecords = [];
|
|
55
|
+
if (!skipIiif) {
|
|
56
|
+
const CONFIG = await iiif.loadConfig();
|
|
57
|
+
const res = await iiif.buildIiifCollectionPages(CONFIG);
|
|
58
|
+
searchRecords = Array.isArray(res && res.searchRecords)
|
|
59
|
+
? res.searchRecords
|
|
60
|
+
: [];
|
|
61
|
+
iiifRecordsCache = searchRecords;
|
|
62
|
+
} else {
|
|
63
|
+
searchRecords = Array.isArray(iiifRecordsCache) ? iiifRecordsCache : [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build contextual MDX content from the content directory.
|
|
68
|
+
* This includes collecting page metadata for sitemap and search index,
|
|
69
|
+
* as well as building all MDX pages to HTML.
|
|
70
|
+
*/
|
|
71
|
+
logLine("\n[3/6] Build contextual content from Markdown pages", "magenta", {
|
|
72
|
+
bright: true,
|
|
73
|
+
});
|
|
74
|
+
// Collect pages metadata for sitemap injection
|
|
75
|
+
pageRecords = await searchBuild.collectMdxPageRecords();
|
|
76
|
+
// Build all MDX and assets
|
|
77
|
+
logLine("\n• Building MDX pages...", "blue", { bright: true });
|
|
78
|
+
await pages.buildContentTree(CONTENT_DIR, pageRecords);
|
|
79
|
+
logLine("✓ MDX pages built\n", "green");
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build search index from IIIF and MDX records, then build or update
|
|
83
|
+
* the search.html page and search runtime bundle.
|
|
84
|
+
* This is done after all content is built so that the index is comprehensive.
|
|
85
|
+
*/
|
|
86
|
+
logLine("\n[4/6] Create search indices", "magenta", { bright: true });
|
|
87
|
+
try {
|
|
88
|
+
const searchPath = path.join(OUT_DIR, "search.html");
|
|
89
|
+
const needCreatePage = !fs.existsSync(searchPath);
|
|
90
|
+
if (needCreatePage) {
|
|
91
|
+
try {
|
|
92
|
+
logLine("• Preparing search (initial)...", "blue", { bright: true });
|
|
93
|
+
} catch (_) {}
|
|
94
|
+
// Build result item template (if present) up-front so it can be inlined
|
|
95
|
+
try {
|
|
96
|
+
await search.ensureResultTemplate();
|
|
97
|
+
} catch (_) {}
|
|
98
|
+
try {
|
|
99
|
+
logLine(" - Writing empty index...", "blue");
|
|
100
|
+
} catch (_) {}
|
|
101
|
+
await search.writeSearchIndex([]);
|
|
102
|
+
try {
|
|
103
|
+
logLine(" - Writing runtime...", "blue");
|
|
104
|
+
} catch (_) {}
|
|
105
|
+
const timeoutMs = Number(process.env.CANOPY_BUNDLE_TIMEOUT || 10000);
|
|
106
|
+
let timedOut = false;
|
|
107
|
+
await runtimes.prepareSearchRuntime(
|
|
108
|
+
process.env.CANOPY_BUNDLE_TIMEOUT || 10000,
|
|
109
|
+
"initial"
|
|
110
|
+
);
|
|
111
|
+
try {
|
|
112
|
+
logLine(" - Building search.html...", "blue");
|
|
113
|
+
} catch (_) {}
|
|
114
|
+
await search.buildSearchPage();
|
|
115
|
+
logLine("✓ Created search page", "cyan");
|
|
116
|
+
}
|
|
117
|
+
// Always (re)write the search index combining IIIF and MDX pages
|
|
118
|
+
const combined = await buildSearchIndex(searchRecords, pageRecords);
|
|
119
|
+
// Build facets for IIIF works based on configured metadata labels
|
|
120
|
+
await generateFacets(combined);
|
|
121
|
+
try {
|
|
122
|
+
logLine("• Writing search runtime (final)...", "blue", { bright: true });
|
|
123
|
+
} catch (_) {}
|
|
124
|
+
await runtimes.prepareSearchRuntime(
|
|
125
|
+
process.env.CANOPY_BUNDLE_TIMEOUT || 10000,
|
|
126
|
+
"final"
|
|
127
|
+
);
|
|
128
|
+
// Rebuild result item template after content processing to capture latest
|
|
129
|
+
try {
|
|
130
|
+
await search.ensureResultTemplate();
|
|
131
|
+
} catch (_) {}
|
|
132
|
+
// Rebuild search.html to inline the latest result template
|
|
133
|
+
try {
|
|
134
|
+
logLine("• Updating search.html...", "blue");
|
|
135
|
+
} catch (_) {}
|
|
136
|
+
await search.buildSearchPage();
|
|
137
|
+
try {
|
|
138
|
+
logLine("✓ Search page updated", "cyan");
|
|
139
|
+
} catch (_) {}
|
|
140
|
+
// Itemize counts by type for a clearer summary
|
|
141
|
+
const counts = new Map();
|
|
142
|
+
for (const r of combined) {
|
|
143
|
+
const t = String((r && r.type) || "page").toLowerCase();
|
|
144
|
+
counts.set(t, (counts.get(t) || 0) + 1);
|
|
145
|
+
}
|
|
146
|
+
const parts = Array.from(counts.entries())
|
|
147
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
148
|
+
.map(([t, n]) => `${t}: ${n}`);
|
|
149
|
+
const breakdown = parts.length ? `: ${parts.join(", ")}` : "";
|
|
150
|
+
logLine(
|
|
151
|
+
`✓ Search index: ${combined.length} total records${breakdown}`,
|
|
152
|
+
"cyan"
|
|
153
|
+
);
|
|
154
|
+
} catch (_) {}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Prepare client runtimes (e.g. search) by bundling with esbuild.
|
|
158
|
+
* This is done early so that MDX content can reference runtime assets if needed.
|
|
159
|
+
*/
|
|
160
|
+
logLine("\n[5/6] Prepare client runtimes and stylesheets", "magenta", {
|
|
161
|
+
bright: true,
|
|
162
|
+
});
|
|
163
|
+
await runtimes.prepareAllRuntimes();
|
|
164
|
+
ensureDirSync(path.join(OUT_DIR, "styles"));
|
|
165
|
+
if (!process.env.CANOPY_SKIP_STYLES) {
|
|
166
|
+
await ensureStyles();
|
|
167
|
+
logLine("✓ Wrote styles.css\n", "cyan");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Copy static assets from the assets directory to the output directory.
|
|
172
|
+
*/
|
|
173
|
+
logLine("\n[6/6] Copy static assets", "magenta", { bright: true });
|
|
174
|
+
await copyAssets();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { build };
|
|
178
|
+
|
|
179
|
+
if (require.main === module) {
|
|
180
|
+
build().catch((e) => {
|
|
181
|
+
console.error(e);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
@@ -2,11 +2,11 @@ const fs = require('fs');
|
|
|
2
2
|
const fsp = fs.promises;
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const { spawn } = require('child_process');
|
|
5
|
-
const { build } = require('
|
|
5
|
+
const { build } = require('../build/build');
|
|
6
6
|
const http = require('http');
|
|
7
7
|
const url = require('url');
|
|
8
|
-
const { CONTENT_DIR, OUT_DIR, ASSETS_DIR, ensureDirSync } = require('
|
|
9
|
-
const twHelper = (() => { try { return require('
|
|
8
|
+
const { CONTENT_DIR, OUT_DIR, ASSETS_DIR, ensureDirSync } = require('../common');
|
|
9
|
+
const twHelper = (() => { try { return require('../../helpers/build-tailwind'); } catch (_) { return null; } })();
|
|
10
10
|
function resolveTailwindCli() {
|
|
11
11
|
try {
|
|
12
12
|
const cliJs = require.resolve('tailwindcss/lib/cli.js');
|
|
@@ -23,7 +23,7 @@ let onBuildSuccess = () => {};
|
|
|
23
23
|
let onBuildStart = () => {};
|
|
24
24
|
let onCssChange = () => {};
|
|
25
25
|
let nextBuildSkipIiif = false; // hint set by watchers
|
|
26
|
-
const UI_DIST_DIR = path.resolve(path.join(__dirname, '
|
|
26
|
+
const UI_DIST_DIR = path.resolve(path.join(__dirname, '../../ui/dist'));
|
|
27
27
|
|
|
28
28
|
function prettyPath(p) {
|
|
29
29
|
try {
|
|
@@ -495,7 +495,7 @@ async function dev() {
|
|
|
495
495
|
const genDir = path.join(CACHE_DIR, 'tailwind');
|
|
496
496
|
ensureDirSync(genDir);
|
|
497
497
|
const genCfg = path.join(genDir, 'tailwind.config.js');
|
|
498
|
-
const cfg = `module.exports = {\n presets: [require('@canopy-iiif/app/ui/canopy-iiif-preset')],\n content: [\n './content/**/*.{mdx,html}',\n './site/**/*.html',\n './site/**/*.js',\n './packages/app/ui/**/*.{js,jsx,ts,tsx}',\n './packages/app/lib/components/**/*.{js,jsx}',\n ],\n theme: { extend: {} },\n plugins: [require('@canopy-iiif/app/ui/canopy-iiif-plugin')],\n};\n`;
|
|
498
|
+
const cfg = `module.exports = {\n presets: [require('@canopy-iiif/app/ui/canopy-iiif-preset')],\n content: [\n './content/**/*.{mdx,html}',\n './site/**/*.html',\n './site/**/*.js',\n './packages/app/ui/**/*.{js,jsx,ts,tsx}',\n './packages/app/lib/iiif/components/**/*.{js,jsx}',\n ],\n theme: { extend: {} },\n plugins: [require('@canopy-iiif/app/ui/canopy-iiif-plugin')],\n};\n`;
|
|
499
499
|
fs.writeFileSync(genCfg, cfg, 'utf8');
|
|
500
500
|
configPath = genCfg;
|
|
501
501
|
} catch (_) { configPath = null; }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
const { logLine } = require('./log');
|
|
3
|
+
|
|
4
|
+
async function generateFacets(combined) {
|
|
5
|
+
try {
|
|
6
|
+
const { loadConfig } = require('./iiif');
|
|
7
|
+
const searchBuild = require('./search');
|
|
8
|
+
const cfg = await loadConfig();
|
|
9
|
+
const labels = Array.isArray(cfg && cfg.metadata) ? cfg.metadata : [];
|
|
10
|
+
await searchBuild.buildFacetsForWorks(combined, labels);
|
|
11
|
+
await searchBuild.writeFacetCollections(labels, combined);
|
|
12
|
+
await searchBuild.writeFacetsSearchApi();
|
|
13
|
+
try { logLine('✓ Facets generated', 'cyan'); } catch (_) {}
|
|
14
|
+
} catch (e) {
|
|
15
|
+
try { logLine('! Facets generation skipped', 'yellow'); } catch (_) {}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { generateFacets };
|