@canopy-iiif/app 0.7.14 → 0.7.16
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/AGENTS.md +64 -0
- package/lib/build/dev.js +251 -354
- package/lib/build/iiif.js +18 -42
- package/lib/build/mdx.js +37 -74
- package/lib/build/runtimes.js +40 -103
- package/lib/build/styles.js +9 -15
- package/lib/search/command-runtime.js +370 -0
- package/lib/search/search-app.jsx +2 -2
- package/lib/search/search.js +4 -189
- package/package.json +8 -2
- package/ui/dist/index.mjs +56 -70
- package/ui/dist/index.mjs.map +4 -4
- package/ui/dist/server.mjs +26 -33
- package/ui/dist/server.mjs.map +4 -4
- package/ui/tailwind-canopy-iiif-plugin.js +7 -12
- package/ui/tailwind-canopy-iiif-preset.js +15 -0
package/lib/build/iiif.js
CHANGED
|
@@ -22,7 +22,7 @@ const IIIF_CACHE_COLLECTIONS_DIR = path.join(IIIF_CACHE_DIR, "collections");
|
|
|
22
22
|
const IIIF_CACHE_COLLECTION = path.join(IIIF_CACHE_DIR, "collection.json");
|
|
23
23
|
// Primary global index location
|
|
24
24
|
const IIIF_CACHE_INDEX = path.join(IIIF_CACHE_DIR, "index.json");
|
|
25
|
-
//
|
|
25
|
+
// Additional legacy locations kept for backward compatibility (read + optional write)
|
|
26
26
|
const IIIF_CACHE_INDEX_LEGACY = path.join(
|
|
27
27
|
IIIF_CACHE_DIR,
|
|
28
28
|
"manifest-index.json"
|
|
@@ -150,7 +150,7 @@ async function loadManifestIndex() {
|
|
|
150
150
|
return { byId, collection: idx.collection || null };
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
|
-
//
|
|
153
|
+
// Legacy index location retained for backward compatibility
|
|
154
154
|
if (fs.existsSync(IIIF_CACHE_INDEX_LEGACY)) {
|
|
155
155
|
const idx = await readJson(IIIF_CACHE_INDEX_LEGACY);
|
|
156
156
|
if (idx && typeof idx === "object") {
|
|
@@ -167,7 +167,7 @@ async function loadManifestIndex() {
|
|
|
167
167
|
return { byId, collection: idx.collection || null };
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
|
-
//
|
|
170
|
+
// Legacy manifests index retained for backward compatibility
|
|
171
171
|
if (fs.existsSync(IIIF_CACHE_INDEX_MANIFESTS)) {
|
|
172
172
|
const idx = await readJson(IIIF_CACHE_INDEX_MANIFESTS);
|
|
173
173
|
if (idx && typeof idx === "object") {
|
|
@@ -406,8 +406,9 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
406
406
|
}
|
|
407
407
|
} catch (_) {}
|
|
408
408
|
}
|
|
409
|
-
} catch (
|
|
410
|
-
|
|
409
|
+
} catch (err) {
|
|
410
|
+
const message = err && err.message ? err.message : err;
|
|
411
|
+
throw new Error(`[iiif] Failed to populate featured cache: ${message}`);
|
|
411
412
|
}
|
|
412
413
|
}
|
|
413
414
|
|
|
@@ -643,33 +644,20 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
643
644
|
1200;
|
|
644
645
|
|
|
645
646
|
// Compile the works layout component once per run
|
|
647
|
+
const worksLayoutPath = path.join(CONTENT_DIR, "works", "_layout.mdx");
|
|
648
|
+
if (!fs.existsSync(worksLayoutPath)) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
"IIIF build requires content/works/_layout.mdx. Create the layout instead of relying on generated output."
|
|
651
|
+
);
|
|
652
|
+
}
|
|
646
653
|
let WorksLayoutComp = null;
|
|
647
654
|
try {
|
|
648
|
-
const worksLayoutPath = path.join(CONTENT_DIR, "works", "_layout.mdx");
|
|
649
655
|
WorksLayoutComp = await mdx.compileMdxToComponent(worksLayoutPath);
|
|
650
|
-
} catch (
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
"div",
|
|
656
|
-
{ className: "content" },
|
|
657
|
-
React.createElement("h1", null, title || "Untitled"),
|
|
658
|
-
// Render viewer placeholder for hydration
|
|
659
|
-
React.createElement(
|
|
660
|
-
"div",
|
|
661
|
-
{ "data-canopy-viewer": "1" },
|
|
662
|
-
React.createElement("script", {
|
|
663
|
-
type: "application/json",
|
|
664
|
-
dangerouslySetInnerHTML: {
|
|
665
|
-
__html: JSON.stringify({
|
|
666
|
-
iiifContent: manifest && (manifest.id || ""),
|
|
667
|
-
}),
|
|
668
|
-
},
|
|
669
|
-
})
|
|
670
|
-
)
|
|
671
|
-
);
|
|
672
|
-
};
|
|
656
|
+
} catch (err) {
|
|
657
|
+
const message = err && err.message ? err.message : err;
|
|
658
|
+
throw new Error(
|
|
659
|
+
`Failed to compile content/works/_layout.mdx: ${message}`
|
|
660
|
+
);
|
|
673
661
|
}
|
|
674
662
|
|
|
675
663
|
for (let ci = 0; ci < chunks; ci++) {
|
|
@@ -808,20 +796,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
808
796
|
href = withBase(href);
|
|
809
797
|
return React.createElement("a", { href, ...rest }, props.children);
|
|
810
798
|
};
|
|
811
|
-
// Map exported UI components into MDX
|
|
799
|
+
// Map exported UI components into MDX and add anchor helper
|
|
812
800
|
const compMap = { ...components, a: Anchor };
|
|
813
|
-
if (!compMap.SearchPanel && compMap.CommandPalette) {
|
|
814
|
-
compMap.SearchPanel = compMap.CommandPalette;
|
|
815
|
-
}
|
|
816
|
-
if (!components.HelloWorld) {
|
|
817
|
-
components.HelloWorld = components.Fallback
|
|
818
|
-
? (props) =>
|
|
819
|
-
React.createElement(components.Fallback, {
|
|
820
|
-
name: "HelloWorld",
|
|
821
|
-
...props,
|
|
822
|
-
})
|
|
823
|
-
: () => null;
|
|
824
|
-
}
|
|
825
801
|
let MDXProvider = null;
|
|
826
802
|
try {
|
|
827
803
|
const mod = await import("@mdx-js/react");
|
package/lib/build/mdx.js
CHANGED
|
@@ -101,7 +101,7 @@ async function loadUiComponents() {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
if (!mod) {
|
|
104
|
-
// Try package subpath as a secondary resolution path
|
|
104
|
+
// Try package subpath as a secondary resolution path to avoid export-map issues
|
|
105
105
|
try {
|
|
106
106
|
mod = await import('@canopy-iiif/app/ui/server');
|
|
107
107
|
} catch (e2) {
|
|
@@ -112,7 +112,7 @@ async function loadUiComponents() {
|
|
|
112
112
|
}
|
|
113
113
|
let comp = (mod && typeof mod === 'object') ? mod : {};
|
|
114
114
|
// Hard-require core exports; do not inject fallbacks
|
|
115
|
-
const required = ['SearchPanel', 'CommandPalette', 'SearchResults', 'SearchSummary', 'SearchTabs', 'Viewer', 'Slider', 'RelatedItems'];
|
|
115
|
+
const required = ['SearchPanel', 'CommandPalette', 'SearchResults', 'SearchSummary', 'SearchTabs', 'Viewer', 'Slider', 'RelatedItems', 'Hero', 'FeaturedHero'];
|
|
116
116
|
const missing = required.filter((k) => !comp || !comp[k]);
|
|
117
117
|
if (missing.length) {
|
|
118
118
|
throw new Error('[canopy][mdx] Missing UI exports: ' + missing.join(', '));
|
|
@@ -128,41 +128,6 @@ async function loadUiComponents() {
|
|
|
128
128
|
Slider: !!comp.Slider,
|
|
129
129
|
}); } catch(_){}
|
|
130
130
|
}
|
|
131
|
-
// No stub injection beyond this point; UI package must supply these.
|
|
132
|
-
// Ensure a minimal SSR Hero exists
|
|
133
|
-
if (!comp.Hero) {
|
|
134
|
-
comp.Hero = function SimpleHero({ height = 360, item, className = '', style = {}, ...rest }){
|
|
135
|
-
const h = typeof height === 'number' ? `${height}px` : String(height || '').trim() || '360px';
|
|
136
|
-
const base = { position: 'relative', width: '100%', height: h, overflow: 'hidden', backgroundColor: 'var(--color-gray-muted)', ...style };
|
|
137
|
-
const title = (item && item.title) || '';
|
|
138
|
-
const href = (item && item.href) || '#';
|
|
139
|
-
const thumbnail = (item && item.thumbnail) || '';
|
|
140
|
-
return React.createElement('div', { className: ['canopy-hero', className].filter(Boolean).join(' '), style: base, ...rest },
|
|
141
|
-
thumbnail ? React.createElement('img', { src: thumbnail, alt: '', 'aria-hidden': 'true', style: { position:'absolute', inset:0, width:'100%', height:'100%', objectFit:'cover', objectPosition:'center', filter:'none' } }) : null,
|
|
142
|
-
React.createElement('div', { className:'canopy-hero-overlay', style: { position:'absolute', left:0, right:0, bottom:0, padding:'1rem', background:'linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.35) 55%, rgba(0,0,0,0.65) 100%)', color:'white' } },
|
|
143
|
-
React.createElement('h3', { style: { margin:0, fontSize:'1.5rem', fontWeight:600, lineHeight:1.2, textShadow:'0 1px 3px rgba(0,0,0,0.6)' } },
|
|
144
|
-
React.createElement('a', { href, style:{ color:'inherit', textDecoration:'none' }, className:'canopy-hero-link' }, title)
|
|
145
|
-
)
|
|
146
|
-
)
|
|
147
|
-
);
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
// Provide a minimal SSR FeaturedHero fallback if missing
|
|
151
|
-
if (!comp.FeaturedHero) {
|
|
152
|
-
try {
|
|
153
|
-
const helpers = require('../components/featured');
|
|
154
|
-
comp.FeaturedHero = function FeaturedHero(props) {
|
|
155
|
-
try {
|
|
156
|
-
const list = helpers && helpers.readFeaturedFromCacheSync ? helpers.readFeaturedFromCacheSync() : [];
|
|
157
|
-
if (!Array.isArray(list) || list.length === 0) return null;
|
|
158
|
-
const index = (props && typeof props.index === 'number') ? Math.max(0, Math.min(list.length - 1, Math.floor(props.index))) : null;
|
|
159
|
-
const pick = (index != null) ? index : ((props && (props.random === true || props.random === 'true')) ? Math.floor(Math.random() * list.length) : 0);
|
|
160
|
-
const item = list[pick] || list[0];
|
|
161
|
-
return React.createElement(comp.Hero, { ...props, item });
|
|
162
|
-
} catch (_) { return null; }
|
|
163
|
-
};
|
|
164
|
-
} catch (_) { /* ignore */ }
|
|
165
|
-
}
|
|
166
131
|
UI_COMPONENTS = comp;
|
|
167
132
|
UI_COMPONENTS_PATH = currentPath;
|
|
168
133
|
UI_COMPONENTS_MTIME = currentMtime;
|
|
@@ -265,26 +230,9 @@ async function loadAppWrapper() {
|
|
|
265
230
|
ok = false;
|
|
266
231
|
}
|
|
267
232
|
if (!ok) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
// Fallback to pass-through wrapper to avoid blocking builds
|
|
273
|
-
if (!App) {
|
|
274
|
-
App = function PassThrough(props) {
|
|
275
|
-
return React.createElement(React.Fragment, null, props.children);
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
try {
|
|
279
|
-
require("./log").log(
|
|
280
|
-
"! Warning: content/_app.mdx did not clearly render {children}; proceeding with best-effort wrapper\n",
|
|
281
|
-
"yellow"
|
|
282
|
-
);
|
|
283
|
-
} catch (_) {
|
|
284
|
-
console.warn(
|
|
285
|
-
"Warning: content/_app.mdx did not clearly render {children}; proceeding."
|
|
286
|
-
);
|
|
287
|
-
}
|
|
233
|
+
throw new Error(
|
|
234
|
+
"content/_app.mdx must render {children}. Update the layout so downstream pages receive their content."
|
|
235
|
+
);
|
|
288
236
|
}
|
|
289
237
|
APP_WRAPPER = { App, Head };
|
|
290
238
|
return APP_WRAPPER;
|
|
@@ -390,7 +338,7 @@ async function ensureClientRuntime() {
|
|
|
390
338
|
esbuild = require("esbuild");
|
|
391
339
|
} catch (_) {}
|
|
392
340
|
}
|
|
393
|
-
if (!esbuild)
|
|
341
|
+
if (!esbuild) throw new Error('Viewer runtime bundling requires esbuild. Install dependencies before building.');
|
|
394
342
|
ensureDirSync(OUT_DIR);
|
|
395
343
|
const scriptsDir = path.join(OUT_DIR, 'scripts');
|
|
396
344
|
ensureDirSync(scriptsDir);
|
|
@@ -509,6 +457,9 @@ async function ensureClientRuntime() {
|
|
|
509
457
|
async function ensureFacetsRuntime() {
|
|
510
458
|
let esbuild = null;
|
|
511
459
|
try { esbuild = require("../../ui/node_modules/esbuild"); } catch (_) { try { esbuild = require("esbuild"); } catch (_) {} }
|
|
460
|
+
if (!esbuild) {
|
|
461
|
+
throw new Error('RelatedItems runtime bundling requires esbuild. Install dependencies before building.');
|
|
462
|
+
}
|
|
512
463
|
ensureDirSync(OUT_DIR);
|
|
513
464
|
const scriptsDir = path.join(OUT_DIR, 'scripts');
|
|
514
465
|
ensureDirSync(scriptsDir);
|
|
@@ -589,18 +540,30 @@ async function ensureFacetsRuntime() {
|
|
|
589
540
|
});
|
|
590
541
|
`;
|
|
591
542
|
const shim = { name: 'facets-vanilla', setup(){} };
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
543
|
+
try {
|
|
544
|
+
await esbuild.build({
|
|
545
|
+
stdin: { contents: entry, resolveDir: process.cwd(), sourcefile: 'canopy-facets-entry.js', loader: 'js' },
|
|
546
|
+
outfile: outFile,
|
|
547
|
+
platform: 'browser',
|
|
548
|
+
format: 'iife',
|
|
549
|
+
bundle: true,
|
|
550
|
+
sourcemap: false,
|
|
551
|
+
target: ['es2018'],
|
|
552
|
+
logLevel: 'silent',
|
|
553
|
+
minify: true,
|
|
554
|
+
plugins: [shim]
|
|
555
|
+
});
|
|
556
|
+
} catch (e) {
|
|
557
|
+
const message = e && e.message ? e.message : e;
|
|
558
|
+
throw new Error(`RelatedItems runtime build failed: ${message}`);
|
|
603
559
|
}
|
|
560
|
+
try {
|
|
561
|
+
const { logLine } = require('./log');
|
|
562
|
+
let size = 0; try { const st = fs.statSync(outFile); size = st && st.size || 0; } catch (_) {}
|
|
563
|
+
const kb = size ? ` (${(size/1024).toFixed(1)} KB)` : '';
|
|
564
|
+
const rel = path.relative(process.cwd(), outFile).split(path.sep).join('/');
|
|
565
|
+
logLine(`✓ Wrote ${rel}${kb}`, 'cyan');
|
|
566
|
+
} catch (_) {}
|
|
604
567
|
}
|
|
605
568
|
|
|
606
569
|
// Bundle a separate client runtime for the Clover Slider to keep payloads split.
|
|
@@ -611,7 +574,7 @@ async function ensureSliderRuntime() {
|
|
|
611
574
|
} catch (_) {
|
|
612
575
|
try { esbuild = require("esbuild"); } catch (_) {}
|
|
613
576
|
}
|
|
614
|
-
if (!esbuild)
|
|
577
|
+
if (!esbuild) throw new Error('Slider runtime bundling requires esbuild. Install dependencies before building.');
|
|
615
578
|
ensureDirSync(OUT_DIR);
|
|
616
579
|
const scriptsDir = path.join(OUT_DIR, 'scripts');
|
|
617
580
|
ensureDirSync(scriptsDir);
|
|
@@ -735,8 +698,8 @@ async function ensureSliderRuntime() {
|
|
|
735
698
|
plugins: [plugin],
|
|
736
699
|
});
|
|
737
700
|
} catch (e) {
|
|
738
|
-
|
|
739
|
-
|
|
701
|
+
const message = e && e.message ? e.message : e;
|
|
702
|
+
throw new Error(`Slider runtime build failed: ${message}`);
|
|
740
703
|
}
|
|
741
704
|
try {
|
|
742
705
|
const { logLine } = require('./log');
|
|
@@ -757,7 +720,7 @@ async function ensureReactGlobals() {
|
|
|
757
720
|
esbuild = require("esbuild");
|
|
758
721
|
} catch (_) {}
|
|
759
722
|
}
|
|
760
|
-
if (!esbuild)
|
|
723
|
+
if (!esbuild) throw new Error('React globals bundling requires esbuild. Install dependencies before building.');
|
|
761
724
|
const { path } = require("../common");
|
|
762
725
|
ensureDirSync(OUT_DIR);
|
|
763
726
|
const scriptsDir = path.join(OUT_DIR, "scripts");
|
|
@@ -792,7 +755,7 @@ async function ensureReactGlobals() {
|
|
|
792
755
|
async function ensureHeroRuntime() {
|
|
793
756
|
let esbuild = null;
|
|
794
757
|
try { esbuild = require("../../ui/node_modules/esbuild"); } catch (_) { try { esbuild = require("esbuild"); } catch (_) {} }
|
|
795
|
-
if (!esbuild)
|
|
758
|
+
if (!esbuild) throw new Error('Hero runtime bundling requires esbuild. Install dependencies before building.');
|
|
796
759
|
const { path } = require("../common");
|
|
797
760
|
ensureDirSync(OUT_DIR);
|
|
798
761
|
const scriptsDir = path.join(OUT_DIR, 'scripts');
|
package/lib/build/runtimes.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
|
|
2
1
|
const { logLine } = require('./log');
|
|
3
|
-
const { fs,
|
|
2
|
+
const { fs, path, OUT_DIR, ensureDirSync } = require('../common');
|
|
4
3
|
|
|
5
4
|
async function prepareAllRuntimes() {
|
|
6
5
|
const mdx = require('./mdx');
|
|
7
6
|
try { await mdx.ensureClientRuntime(); } catch (_) {}
|
|
8
7
|
try { if (typeof mdx.ensureSliderRuntime === 'function') await mdx.ensureSliderRuntime(); } catch (_) {}
|
|
9
|
-
// Optional: Hero runtime is SSR-only by default; enable explicitly to avoid bundling Node deps in browser
|
|
10
8
|
try {
|
|
11
9
|
if (process.env.CANOPY_ENABLE_HERO_RUNTIME === '1' || process.env.CANOPY_ENABLE_HERO_RUNTIME === 'true') {
|
|
12
10
|
if (typeof mdx.ensureHeroRuntime === 'function') await mdx.ensureHeroRuntime();
|
|
@@ -14,121 +12,60 @@ async function prepareAllRuntimes() {
|
|
|
14
12
|
} catch (_) {}
|
|
15
13
|
try { if (typeof mdx.ensureFacetsRuntime === 'function') await mdx.ensureFacetsRuntime(); } catch (_) {}
|
|
16
14
|
try { if (typeof mdx.ensureReactGlobals === 'function') await mdx.ensureReactGlobals(); } catch (_) {}
|
|
17
|
-
|
|
15
|
+
await prepareCommandRuntime();
|
|
18
16
|
try { logLine('✓ Prepared client hydration runtimes', 'cyan', { dim: true }); } catch (_) {}
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
async function resolveEsbuild() {
|
|
20
|
+
try { return require('../../ui/node_modules/esbuild'); } catch (_) {
|
|
21
|
+
try { return require('esbuild'); } catch (_) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
22
26
|
|
|
23
|
-
async function
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
function rootBase(){ try { var bp = (window && window.CANOPY_BASE_PATH) ? String(window.CANOPY_BASE_PATH) : ''; return bp && bp.endsWith('/') ? bp.slice(0,-1) : bp; } catch(_) { return ''; } }
|
|
51
|
-
function isOnSearchPage(){ try{ var base=rootBase(); var p=String(location.pathname||''); if(base && p.startsWith(base)) p=p.slice(base.length); if(p.endsWith('/')) p=p.slice(0,-1); return p==='/search'; }catch(_){ return false; } }
|
|
52
|
-
async function loadRecords(){ try{ var v=''; try{ var m = await fetch(rootBase() + '/api/index.json').then(function(r){return r&&r.ok?r.json():null;}).catch(function(){return null;}); v=(m&&m.version)||''; }catch(_){} var res = await fetch(rootBase() + '/api/search-index.json' + (v?('?v='+encodeURIComponent(v)):'')).catch(function(){return null;}); var j = res && res.ok ? await res.json().catch(function(){return[];}) : []; return Array.isArray(j) ? j : (j && j.records) || []; } catch(_){ return []; } }
|
|
53
|
-
ready(async function(){ var host=document.querySelector('[data-canopy-command]'); if(!host) return; var cfg=parseProps(host)||{}; var maxResults = Number(cfg.maxResults||8)||8; var groupOrder = Array.isArray(cfg.groupOrder)?cfg.groupOrder:['work','page']; var onSearchPage = isOnSearchPage();
|
|
54
|
-
var panel = (function(){ try{ return host.querySelector('[data-canopy-command-panel]') || null; }catch(_){ return null; } })();
|
|
55
|
-
if(!panel) return; // no SSR panel present; do nothing
|
|
56
|
-
try{ var rel=host.querySelector('.relative'); if(rel && !onSearchPage) rel.setAttribute('data-canopy-panel-auto','1'); }catch(_){}
|
|
57
|
-
if(onSearchPage){ panel.style.display='none'; }
|
|
58
|
-
var list=panel.querySelector('#cplist');
|
|
59
|
-
var input = (function(){ try{ return host.querySelector('[data-canopy-command-input]') || null; }catch(_){ return null; } })();
|
|
60
|
-
if(!input) return; // require SSR input; no dynamic creation
|
|
61
|
-
// Populate from ?q= URL param if present
|
|
62
|
-
try {
|
|
63
|
-
var sp = new URLSearchParams(location.search || '');
|
|
64
|
-
var qp = sp.get('q');
|
|
65
|
-
if (qp) input.value = qp;
|
|
66
|
-
} catch(_) {}
|
|
67
|
-
// Do not inject legacy trigger buttons or inputs
|
|
68
|
-
var records = await loadRecords(); function render(items){ list.innerHTML=''; if(!items.length){ panel.style.display='none'; return; } var groups=new Map(); items.forEach(function(r){ var t=String(r.type||'page'); if(!groups.has(t)) groups.set(t, []); groups.get(t).push(r); }); function gl(t){ if(t==='work') return 'Works'; if(t==='page') return 'Pages'; return t.charAt(0).toUpperCase()+t.slice(1);} var ordered=[].concat(groupOrder.filter(function(t){return groups.has(t);})).concat(Array.from(groups.keys()).filter(function(t){return groupOrder.indexOf(t)===-1;})); ordered.forEach(function(t){ var hdr=document.createElement('div'); hdr.textContent=gl(t); hdr.style.cssText='padding:6px 12px;font-weight:600;color:#374151'; list.appendChild(hdr); groups.get(t).forEach(function(r){ var it=document.createElement('div'); it.setAttribute('data-canopy-item',''); it.tabIndex=0; it.style.cssText='display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;outline:none;'; var thumb=(String(r.type||'')==='work' && r.thumbnail)?r.thumbnail:''; if(thumb){ var img=document.createElement('img'); img.src=thumb; img.alt=''; img.style.cssText='width:40px;height:40px;object-fit:cover;border-radius:4px'; it.appendChild(img);} var span=document.createElement('span'); span.textContent=r.title||r.href; it.appendChild(span); it.onmouseenter=function(){ it.style.background='#f8fafc'; }; it.onmouseleave=function(){ it.style.background='transparent'; }; it.onfocus=function(){ it.style.background='#eef2ff'; try{ it.scrollIntoView({ block: 'nearest' }); }catch(_){} }; it.onblur=function(){ it.style.background='transparent'; }; it.onclick=function(){ try{ window.location.href = withBase(String(r.href||'')); }catch(_){} panel.style.display='none'; }; list.appendChild(it); }); }); }
|
|
69
|
-
function getItems(){ try{ return Array.prototype.slice.call(list.querySelectorAll('[data-canopy-item]')); }catch(_){ return []; } }
|
|
70
|
-
function filterAndShow(q){ try{ var qq=norm(q); if(!qq){ try{ panel.style.display='block'; list.innerHTML=''; }catch(_){} return; } var out=[]; for(var i=0;i<records.length;i++){ var r=records[i]; var title=String((r&&r.title)||''); if(!title) continue; if(norm(title).indexOf(qq)!==-1) out.push(r); if(out.length>=maxResults) break; } render(out); }catch(_){} }
|
|
71
|
-
input.addEventListener('input', function(){ if(onSearchPage){ try{ var ev = new CustomEvent('canopy:search:setQuery', { detail: { query: (input.value||'') } }); window.dispatchEvent(ev); }catch(_){ } return; } filterAndShow(input.value||''); });
|
|
72
|
-
// Keyboard navigation: ArrowDown/ArrowUp to move through items; Enter to select
|
|
73
|
-
input.addEventListener('keydown', function(e){
|
|
74
|
-
if(e.key==='ArrowDown'){ e.preventDefault(); try{ var items=getItems(); if(items.length){ panel.style.display='block'; items[0].focus(); } }catch(_){} }
|
|
75
|
-
else if(e.key==='ArrowUp'){ e.preventDefault(); try{ var items2=getItems(); if(items2.length){ panel.style.display='block'; items2[items2.length-1].focus(); } }catch(_){} }
|
|
76
|
-
});
|
|
77
|
-
list.addEventListener('keydown', function(e){
|
|
78
|
-
var cur = e.target && e.target.closest && e.target.closest('[data-canopy-item]');
|
|
79
|
-
if(!cur) return;
|
|
80
|
-
if(e.key==='ArrowDown'){
|
|
81
|
-
e.preventDefault();
|
|
82
|
-
try{ var arr=getItems(); var i=arr.indexOf(cur); var nxt=arr[Math.min(arr.length-1, i+1)]||cur; nxt.focus(); }catch(_){}
|
|
83
|
-
} else if(e.key==='ArrowUp'){
|
|
84
|
-
e.preventDefault();
|
|
85
|
-
try{ var arr2=getItems(); var i2=arr2.indexOf(cur); if(i2<=0){ input && input.focus && input.focus(); } else { var prv=arr2[i2-1]; (prv||cur).focus(); } }catch(_){}
|
|
86
|
-
} else if(e.key==='Enter'){
|
|
87
|
-
e.preventDefault(); try{ cur.click(); }catch(_){}
|
|
88
|
-
} else if(e.key==='Escape'){
|
|
89
|
-
panel.style.display='none'; try{ input && input.focus && input.focus(); }catch(_){}
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
document.addEventListener('keydown', function(e){ if(e.key==='Escape'){ panel.style.display='none'; }});
|
|
93
|
-
document.addEventListener('mousedown', function(e){ try{ if(!panel.contains(e.target) && !host.contains(e.target)){ panel.style.display='none'; } }catch(_){} });
|
|
94
|
-
// Hotkey support (e.g., mod+k)
|
|
95
|
-
document.addEventListener('keydown', function(e){
|
|
96
|
-
try {
|
|
97
|
-
var want = String((cfg && cfg.hotkey) || '').toLowerCase();
|
|
98
|
-
if (!want) return;
|
|
99
|
-
var isMod = e.metaKey || e.ctrlKey;
|
|
100
|
-
if ((want === 'mod+k' || want === 'cmd+k' || want === 'ctrl+k') && isMod && (e.key === 'k' || e.key === 'K')) {
|
|
101
|
-
e.preventDefault();
|
|
102
|
-
if(onSearchPage){ try{ var ev2 = new CustomEvent('canopy:search:setQuery', { detail: { hotkey: true } }); window.dispatchEvent(ev2); }catch(_){ } return; }
|
|
103
|
-
panel.style.display='block';
|
|
104
|
-
(input && input.focus && input.focus());
|
|
105
|
-
filterAndShow(input && input.value || '');
|
|
106
|
-
}
|
|
107
|
-
} catch(_) { }
|
|
108
|
-
});
|
|
109
|
-
function openPanel(){ if(onSearchPage){ try{ var ev3 = new CustomEvent('canopy:search:setQuery', { detail: {} }); window.dispatchEvent(ev3); }catch(_){ } return; } panel.style.display='block'; (input && input.focus && input.focus()); filterAndShow(input && input.value || ''); }
|
|
110
|
-
host.addEventListener('click', function(e){ var trg=e.target && e.target.closest && e.target.closest('[data-canopy-command-trigger]'); if(trg){ e.preventDefault(); openPanel(); }});
|
|
111
|
-
try{ var teaser2 = host.querySelector('[data-canopy-command-input]'); if(teaser2){ teaser2.addEventListener('focus', function(){ openPanel(); }); } }catch(_){}
|
|
112
|
-
});
|
|
113
|
-
})();
|
|
114
|
-
`;
|
|
115
|
-
await fsp.writeFile(cmdOut, fallback, 'utf8');
|
|
116
|
-
try { logLine(`✓ Wrote ${path.relative(process.cwd(), cmdOut)} (fallback)`, 'cyan'); } catch (_) {}
|
|
27
|
+
async function prepareCommandRuntime() {
|
|
28
|
+
const esbuild = await resolveEsbuild();
|
|
29
|
+
if (!esbuild) throw new Error('Command runtime bundling requires esbuild. Install dependencies before building.');
|
|
30
|
+
ensureDirSync(OUT_DIR);
|
|
31
|
+
const scriptsDir = path.join(OUT_DIR, 'scripts');
|
|
32
|
+
ensureDirSync(scriptsDir);
|
|
33
|
+
const entry = path.join(__dirname, '..', 'search', 'command-runtime.js');
|
|
34
|
+
const outFile = path.join(scriptsDir, 'canopy-command.js');
|
|
35
|
+
await esbuild.build({
|
|
36
|
+
entryPoints: [entry],
|
|
37
|
+
outfile: outFile,
|
|
38
|
+
platform: 'browser',
|
|
39
|
+
format: 'iife',
|
|
40
|
+
bundle: true,
|
|
41
|
+
sourcemap: false,
|
|
42
|
+
target: ['es2018'],
|
|
43
|
+
logLevel: 'silent',
|
|
44
|
+
minify: true,
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
let size = 0;
|
|
48
|
+
try { const st = fs.statSync(outFile); size = st.size || 0; } catch (_) {}
|
|
49
|
+
const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : '';
|
|
50
|
+
const rel = path.relative(process.cwd(), outFile).split(path.sep).join('/');
|
|
51
|
+
logLine(`✓ Wrote ${rel}${kb}`, 'cyan');
|
|
52
|
+
} catch (_) {}
|
|
117
53
|
}
|
|
118
54
|
|
|
119
55
|
async function prepareSearchRuntime(timeoutMs = 10000, label = '') {
|
|
120
56
|
const search = require('../search/search');
|
|
121
|
-
try { logLine(`• Writing search runtime${label ? ' ('+label+')' : ''}...`, 'blue', { bright: true }); } catch (_) {}
|
|
57
|
+
try { logLine(`• Writing search runtime${label ? ' (' + label + ')' : ''}...`, 'blue', { bright: true }); } catch (_) {}
|
|
58
|
+
|
|
122
59
|
let timedOut = false;
|
|
123
60
|
await Promise.race([
|
|
124
61
|
search.ensureSearchRuntime(),
|
|
125
62
|
new Promise((_, reject) => setTimeout(() => { timedOut = true; reject(new Error('timeout')); }, Number(timeoutMs)))
|
|
126
63
|
]).catch(() => {
|
|
127
|
-
try { console.warn(`Search: Bundling runtime timed out${label ? ' ('+label+')' : ''}, skipping`); } catch (_) {}
|
|
64
|
+
try { console.warn(`Search: Bundling runtime timed out${label ? ' (' + label + ')' : ''}, skipping`); } catch (_) {}
|
|
128
65
|
});
|
|
129
66
|
if (timedOut) {
|
|
130
|
-
try { logLine(`! Search runtime not bundled${label ? ' ('+label+')' : ''}\n`, 'yellow'); } catch (_) {}
|
|
67
|
+
try { logLine(`! Search runtime not bundled${label ? ' (' + label + ')' : ''}\n`, 'yellow'); } catch (_) {}
|
|
131
68
|
}
|
|
132
69
|
}
|
|
133
70
|
|
|
134
|
-
module.exports = { prepareAllRuntimes,
|
|
71
|
+
module.exports = { prepareAllRuntimes, prepareCommandRuntime, prepareSearchRuntime };
|
package/lib/build/styles.js
CHANGED
|
@@ -70,21 +70,15 @@ async function ensureStyles() {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function resolveTailwindCli() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
process.platform === "win32" ? "tailwindcss.cmd" : "tailwindcss"
|
|
83
|
-
);
|
|
84
|
-
if (fs.existsSync(localBin)) return { cmd: localBin, args: [] };
|
|
85
|
-
} catch (_) {}
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
73
|
+
const localBin = path.join(
|
|
74
|
+
process.cwd(),
|
|
75
|
+
"node_modules",
|
|
76
|
+
".bin",
|
|
77
|
+
process.platform === "win32" ? "tailwindcss.cmd" : "tailwindcss"
|
|
78
|
+
);
|
|
79
|
+
if (fs.existsSync(localBin)) return { cmd: localBin, args: [] };
|
|
80
|
+
return { cmd: 'tailwindcss', args: [] };
|
|
81
|
+
}
|
|
88
82
|
function buildTailwindCli({ input, output, config, minify = true }) {
|
|
89
83
|
try {
|
|
90
84
|
const cli = resolveTailwindCli();
|