@canopy-iiif/app 0.9.0 → 0.9.2
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/mdx.js +142 -1
- package/lib/build/pages.js +6 -0
- package/lib/build/search-index.js +10 -5
- package/lib/build/search.js +9 -1
- package/lib/search/search.js +1 -2
- package/package.json +2 -1
- package/ui/dist/index.mjs +30 -17
- package/ui/dist/index.mjs.map +3 -3
- package/ui/dist/server.mjs +369 -38
- package/ui/dist/server.mjs.map +4 -4
- package/ui/styles/components/_sub-navigation.scss +0 -1
- package/ui/styles/index.css +0 -1
package/lib/build/mdx.js
CHANGED
|
@@ -139,6 +139,67 @@ async function loadUiComponents() {
|
|
|
139
139
|
return UI_COMPONENTS;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
function slugifyHeading(text) {
|
|
143
|
+
try {
|
|
144
|
+
return String(text || '')
|
|
145
|
+
.toLowerCase()
|
|
146
|
+
.trim()
|
|
147
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
148
|
+
.replace(/\s+/g, '-');
|
|
149
|
+
} catch (_) {
|
|
150
|
+
return '';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function extractHeadings(mdxSource) {
|
|
155
|
+
const { content } = parseFrontmatter(String(mdxSource || ''));
|
|
156
|
+
const cleaned = content.replace(/```[\s\S]*?```/g, '');
|
|
157
|
+
const headingRegex = /^ {0,3}(#{1,6})\s+(.+?)\s*$/gm;
|
|
158
|
+
const seen = new Map();
|
|
159
|
+
const headings = [];
|
|
160
|
+
let match;
|
|
161
|
+
while ((match = headingRegex.exec(cleaned))) {
|
|
162
|
+
const hashes = match[1] || '';
|
|
163
|
+
const depth = hashes.length;
|
|
164
|
+
let raw = match[2] || '';
|
|
165
|
+
let explicitId = null;
|
|
166
|
+
const idMatch = raw.match(/\s*\{#([A-Za-z0-9_-]+)\}\s*$/);
|
|
167
|
+
if (idMatch) {
|
|
168
|
+
explicitId = idMatch[1];
|
|
169
|
+
raw = raw.slice(0, raw.length - idMatch[0].length);
|
|
170
|
+
}
|
|
171
|
+
const title = raw.replace(/\<[^>]*\>/g, '').replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').trim();
|
|
172
|
+
if (!title) continue;
|
|
173
|
+
const baseId = explicitId || slugifyHeading(title) || `section-${headings.length + 1}`;
|
|
174
|
+
const count = seen.get(baseId) || 0;
|
|
175
|
+
seen.set(baseId, count + 1);
|
|
176
|
+
const id = count === 0 ? baseId : `${baseId}-${count + 1}`;
|
|
177
|
+
headings.push({
|
|
178
|
+
id,
|
|
179
|
+
title,
|
|
180
|
+
depth,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return headings;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function extractPlainText(mdxSource) {
|
|
187
|
+
let { content } = parseFrontmatter(String(mdxSource || ''));
|
|
188
|
+
if (!content) return '';
|
|
189
|
+
content = content.replace(/```[\s\S]*?```/g, ' ');
|
|
190
|
+
content = content.replace(/`{1,3}([^`]+)`{1,3}/g, '$1');
|
|
191
|
+
content = content.replace(/!\[[^\]]*\]\([^)]*\)/g, ' ');
|
|
192
|
+
content = content.replace(/\[[^\]]*\]\([^)]*\)/g, '$1');
|
|
193
|
+
content = content.replace(/<[^>]+>/g, ' ');
|
|
194
|
+
content = content.replace(/\{#([^}]+)\}/g, ' ');
|
|
195
|
+
content = content.replace(/\{\/[A-Za-z0-9_.-]+\}/g, ' ');
|
|
196
|
+
content = content.replace(/\{[^{}]*\}/g, ' ');
|
|
197
|
+
content = content.replace(/[#>*~_\-]+/g, ' ');
|
|
198
|
+
content = content.replace(/\n+/g, ' ');
|
|
199
|
+
content = content.replace(/\s+/g, ' ').trim();
|
|
200
|
+
return content;
|
|
201
|
+
}
|
|
202
|
+
|
|
142
203
|
function extractTitle(mdxSource) {
|
|
143
204
|
const { data, content } = parseFrontmatter(String(mdxSource || ""));
|
|
144
205
|
if (data && typeof data.title === "string" && data.title.trim()) {
|
|
@@ -271,6 +332,84 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
|
|
|
271
332
|
const mod = await import(pathToFileURL(tmpFile).href + bust);
|
|
272
333
|
const MDXContent = mod.default || mod.MDXContent || mod;
|
|
273
334
|
const components = await loadUiComponents();
|
|
335
|
+
const rawHeadings = Array.isArray(extraProps && extraProps.page && extraProps.page.headings)
|
|
336
|
+
? extraProps.page.headings
|
|
337
|
+
.map((heading) => (heading ? { ...heading } : heading))
|
|
338
|
+
.filter(Boolean)
|
|
339
|
+
: [];
|
|
340
|
+
const headingQueue = rawHeadings.slice();
|
|
341
|
+
const headingIdCounts = new Map();
|
|
342
|
+
headingQueue.forEach((heading) => {
|
|
343
|
+
if (heading && heading.id) {
|
|
344
|
+
const key = String(heading.id);
|
|
345
|
+
headingIdCounts.set(key, (headingIdCounts.get(key) || 0) + 1);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
function reserveHeadingId(base) {
|
|
350
|
+
const fallback = base || 'section';
|
|
351
|
+
let candidate = fallback;
|
|
352
|
+
let attempt = 1;
|
|
353
|
+
while (headingIdCounts.has(candidate)) {
|
|
354
|
+
attempt += 1;
|
|
355
|
+
candidate = `${fallback}-${attempt}`;
|
|
356
|
+
}
|
|
357
|
+
headingIdCounts.set(candidate, 1);
|
|
358
|
+
return candidate;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function extractTextFromChildren(children) {
|
|
362
|
+
if (children == null) return '';
|
|
363
|
+
if (typeof children === 'string' || typeof children === 'number') return String(children);
|
|
364
|
+
if (Array.isArray(children)) return children.map((child) => extractTextFromChildren(child)).join('');
|
|
365
|
+
if (React.isValidElement(children)) return extractTextFromChildren(children.props && children.props.children);
|
|
366
|
+
return '';
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function takeHeading(level, children) {
|
|
370
|
+
if (!headingQueue.length) return null;
|
|
371
|
+
const idx = headingQueue.findIndex((item) => {
|
|
372
|
+
if (!item || typeof item !== 'object') return false;
|
|
373
|
+
const depth = typeof item.depth === 'number' ? item.depth : item.level;
|
|
374
|
+
return depth === level;
|
|
375
|
+
});
|
|
376
|
+
if (idx === -1) return null;
|
|
377
|
+
const [heading] = headingQueue.splice(idx, 1);
|
|
378
|
+
if (!heading.id) {
|
|
379
|
+
const text = heading.title || extractTextFromChildren(children);
|
|
380
|
+
const baseId = slugifyHeading(text);
|
|
381
|
+
heading.id = reserveHeadingId(baseId);
|
|
382
|
+
}
|
|
383
|
+
if (!heading.title) {
|
|
384
|
+
heading.title = extractTextFromChildren(children);
|
|
385
|
+
}
|
|
386
|
+
return heading;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function createHeadingComponent(level) {
|
|
390
|
+
const tag = `h${level}`;
|
|
391
|
+
const Base = components && components[tag] ? components[tag] : tag;
|
|
392
|
+
return function HeadingComponent(props) {
|
|
393
|
+
const heading = takeHeading(level, props && props.children);
|
|
394
|
+
const id = props && props.id ? props.id : heading && heading.id;
|
|
395
|
+
const finalProps = id ? { ...props, id } : props;
|
|
396
|
+
return React.createElement(Base, finalProps, props && props.children);
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const levelsPresent = Array.from(
|
|
401
|
+
new Set(
|
|
402
|
+
headingQueue
|
|
403
|
+
.map((heading) => (heading ? heading.depth || heading.level : null))
|
|
404
|
+
.filter((level) => typeof level === 'number' && level >= 1 && level <= 6)
|
|
405
|
+
)
|
|
406
|
+
);
|
|
407
|
+
const headingComponents = levelsPresent.length
|
|
408
|
+
? levelsPresent.reduce((acc, level) => {
|
|
409
|
+
acc[`h${level}`] = createHeadingComponent(level);
|
|
410
|
+
return acc;
|
|
411
|
+
}, {})
|
|
412
|
+
: {};
|
|
274
413
|
const MDXProvider = await getMdxProvider();
|
|
275
414
|
// Base path support for anchors
|
|
276
415
|
const Anchor = function A(props) {
|
|
@@ -294,7 +433,7 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
|
|
|
294
433
|
? React.createElement(PageContext.Provider, { value: contextValue }, withLayout)
|
|
295
434
|
: withLayout;
|
|
296
435
|
const withApp = React.createElement(app.App, null, withContext);
|
|
297
|
-
const compMap = { ...components, a: Anchor };
|
|
436
|
+
const compMap = { ...components, ...headingComponents, a: Anchor };
|
|
298
437
|
const page = MDXProvider
|
|
299
438
|
? React.createElement(MDXProvider, { components: compMap }, withApp)
|
|
300
439
|
: withApp;
|
|
@@ -811,6 +950,8 @@ async function ensureHeroRuntime() {
|
|
|
811
950
|
|
|
812
951
|
module.exports = {
|
|
813
952
|
extractTitle,
|
|
953
|
+
extractHeadings,
|
|
954
|
+
extractPlainText,
|
|
814
955
|
isReservedFile,
|
|
815
956
|
parseFrontmatter,
|
|
816
957
|
compileMdxFile,
|
package/lib/build/pages.js
CHANGED
|
@@ -50,6 +50,7 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}) {
|
|
|
50
50
|
const pageInfo = navigation.getPageInfo(normalizedRel);
|
|
51
51
|
const navData = navigation.buildNavigationForFile(normalizedRel);
|
|
52
52
|
const mergedProps = { ...(extraProps || {}) };
|
|
53
|
+
const headings = mdx.extractHeadings(source);
|
|
53
54
|
if (pageInfo) {
|
|
54
55
|
mergedProps.page = mergedProps.page
|
|
55
56
|
? { ...pageInfo, ...mergedProps.page }
|
|
@@ -58,6 +59,11 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}) {
|
|
|
58
59
|
if (navData && !mergedProps.navigation) {
|
|
59
60
|
mergedProps.navigation = navData;
|
|
60
61
|
}
|
|
62
|
+
if (headings && headings.length) {
|
|
63
|
+
mergedProps.page = mergedProps.page
|
|
64
|
+
? { ...mergedProps.page, headings }
|
|
65
|
+
: { headings };
|
|
66
|
+
}
|
|
61
67
|
const { body, head } = await mdx.compileMdxFile(filePath, outPath, null, mergedProps);
|
|
62
68
|
const needsHydrateViewer =
|
|
63
69
|
body.includes('data-canopy-viewer') || body.includes('data-canopy-scroll');
|
|
@@ -6,11 +6,16 @@ function pagesToRecords(pageRecords) {
|
|
|
6
6
|
const list = Array.isArray(pageRecords) ? pageRecords : [];
|
|
7
7
|
return list
|
|
8
8
|
.filter((p) => p && p.href && p.searchInclude)
|
|
9
|
-
.map((p) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
.map((p) => {
|
|
10
|
+
const summary = typeof p.searchSummary === 'string' ? p.searchSummary.trim() : '';
|
|
11
|
+
const record = {
|
|
12
|
+
title: p.title || p.href,
|
|
13
|
+
href: rootRelativeHref(p.href),
|
|
14
|
+
type: p.searchType || 'page',
|
|
15
|
+
};
|
|
16
|
+
if (summary) record.summaryValue = summary;
|
|
17
|
+
return record;
|
|
18
|
+
});
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
function maybeMockRecords() {
|
package/lib/build/search.js
CHANGED
|
@@ -184,6 +184,8 @@ async function collectMdxPageRecords() {
|
|
|
184
184
|
const rel = path.relative(CONTENT_DIR, p).replace(/\.mdx$/i, '.html');
|
|
185
185
|
if (base !== 'sitemap.mdx') {
|
|
186
186
|
const href = rootRelativeHref(rel.split(path.sep).join('/'));
|
|
187
|
+
const plainText = mdx.extractPlainText(src);
|
|
188
|
+
const summary = plainText || '';
|
|
187
189
|
const underSearch = /^search\//i.test(href) || href.toLowerCase() === 'search.html';
|
|
188
190
|
let include = !underSearch;
|
|
189
191
|
let resolvedType = null;
|
|
@@ -201,7 +203,13 @@ async function collectMdxPageRecords() {
|
|
|
201
203
|
resolvedType = 'page';
|
|
202
204
|
}
|
|
203
205
|
const trimmedType = resolvedType && String(resolvedType).trim();
|
|
204
|
-
pages.push({
|
|
206
|
+
pages.push({
|
|
207
|
+
title,
|
|
208
|
+
href,
|
|
209
|
+
searchInclude: include && !!trimmedType,
|
|
210
|
+
searchType: trimmedType || undefined,
|
|
211
|
+
searchSummary: summary,
|
|
212
|
+
});
|
|
205
213
|
}
|
|
206
214
|
}
|
|
207
215
|
}
|
package/lib/search/search.js
CHANGED
|
@@ -231,8 +231,7 @@ function sanitizeRecordForIndex(r) {
|
|
|
231
231
|
''
|
|
232
232
|
).trim();
|
|
233
233
|
if (summaryVal) {
|
|
234
|
-
|
|
235
|
-
out.summary = clipped;
|
|
234
|
+
out.summary = summaryVal;
|
|
236
235
|
}
|
|
237
236
|
return out;
|
|
238
237
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canopy-iiif/app",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Mat Jordan <mat@northwestern.edu>",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"./head": "./lib/head.js",
|
|
20
20
|
"./orchestrator": {
|
|
21
21
|
"types": "./types/orchestrator.d.ts",
|
|
22
|
+
"import": "./lib/orchestrator.js",
|
|
22
23
|
"require": "./lib/orchestrator.js",
|
|
23
24
|
"default": "./lib/orchestrator.js"
|
|
24
25
|
}
|
package/ui/dist/index.mjs
CHANGED
|
@@ -104,7 +104,7 @@ function Card({
|
|
|
104
104
|
);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
// ui/src/layout/
|
|
107
|
+
// ui/src/layout/TextCard.jsx
|
|
108
108
|
import React3, { useMemo } from "react";
|
|
109
109
|
function escapeRegExp(str = "") {
|
|
110
110
|
return String(str).replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
@@ -141,7 +141,7 @@ function highlightSnippet(snippet, query) {
|
|
|
141
141
|
(part, idx) => part.toLowerCase() === termLower ? /* @__PURE__ */ React3.createElement("mark", { key: idx }, part) : /* @__PURE__ */ React3.createElement(React3.Fragment, { key: idx }, part)
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
|
-
function
|
|
144
|
+
function TextCard({
|
|
145
145
|
href = "#",
|
|
146
146
|
title = "Untitled",
|
|
147
147
|
annotation = "",
|
|
@@ -428,26 +428,36 @@ function SearchResults({
|
|
|
428
428
|
if (!results.length) {
|
|
429
429
|
return /* @__PURE__ */ React12.createElement("div", { className: "text-slate-600" }, /* @__PURE__ */ React12.createElement("em", null, "No results"));
|
|
430
430
|
}
|
|
431
|
-
const
|
|
431
|
+
const normalizedType = String(type || "all").toLowerCase();
|
|
432
|
+
const isAnnotationView = normalizedType === "annotation";
|
|
432
433
|
if (isAnnotationView) {
|
|
433
434
|
return /* @__PURE__ */ React12.createElement("div", { id: "search-results", className: "space-y-4" }, results.map((r, i) => {
|
|
434
|
-
if (!r
|
|
435
|
-
return
|
|
436
|
-
AnnotationCard,
|
|
437
|
-
{
|
|
438
|
-
key: r.id || i,
|
|
439
|
-
href: r.href,
|
|
440
|
-
title: r.title || r.href || "Untitled",
|
|
441
|
-
annotation: r.annotation,
|
|
442
|
-
summary: r.summary,
|
|
443
|
-
metadata: Array.isArray(r.metadata) ? r.metadata : [],
|
|
444
|
-
query
|
|
445
|
-
}
|
|
446
|
-
);
|
|
435
|
+
if (!r) return null;
|
|
436
|
+
return renderTextCard(r, r.id || i);
|
|
447
437
|
}));
|
|
448
438
|
}
|
|
439
|
+
const renderTextCard = (record, key) => {
|
|
440
|
+
if (!record) return null;
|
|
441
|
+
return /* @__PURE__ */ React12.createElement(
|
|
442
|
+
TextCard,
|
|
443
|
+
{
|
|
444
|
+
key,
|
|
445
|
+
href: record.href,
|
|
446
|
+
title: record.title || record.href || "Untitled",
|
|
447
|
+
annotation: record.annotation,
|
|
448
|
+
summary: record.summary || record.summaryValue || "",
|
|
449
|
+
metadata: Array.isArray(record.metadata) ? record.metadata : [],
|
|
450
|
+
query
|
|
451
|
+
}
|
|
452
|
+
);
|
|
453
|
+
};
|
|
454
|
+
const isWorkRecord = (record) => String(record && record.type).toLowerCase() === "work";
|
|
455
|
+
const shouldRenderAsTextCard = (record) => !isWorkRecord(record) || normalizedType !== "work";
|
|
449
456
|
if (layout === "list") {
|
|
450
457
|
return /* @__PURE__ */ React12.createElement("ul", { id: "search-results", className: "space-y-3" }, results.map((r, i) => {
|
|
458
|
+
if (shouldRenderAsTextCard(r)) {
|
|
459
|
+
return /* @__PURE__ */ React12.createElement("li", { key: i, className: `search-result ${r && r.type}` }, renderTextCard(r, i));
|
|
460
|
+
}
|
|
451
461
|
const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
|
|
452
462
|
const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
|
|
453
463
|
return /* @__PURE__ */ React12.createElement(
|
|
@@ -472,6 +482,9 @@ function SearchResults({
|
|
|
472
482
|
}));
|
|
473
483
|
}
|
|
474
484
|
return /* @__PURE__ */ React12.createElement("div", { id: "search-results" }, /* @__PURE__ */ React12.createElement(Grid, null, results.map((r, i) => {
|
|
485
|
+
if (shouldRenderAsTextCard(r)) {
|
|
486
|
+
return /* @__PURE__ */ React12.createElement(GridItem, { key: i, className: `search-result ${r && r.type}` }, renderTextCard(r, i));
|
|
487
|
+
}
|
|
475
488
|
const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
|
|
476
489
|
const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
|
|
477
490
|
return /* @__PURE__ */ React12.createElement(
|
|
@@ -957,7 +970,6 @@ function SearchPanel(props = {}) {
|
|
|
957
970
|
return /* @__PURE__ */ React19.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React19.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React19.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React19.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React19.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
|
|
958
971
|
}
|
|
959
972
|
export {
|
|
960
|
-
AnnotationCard,
|
|
961
973
|
Card,
|
|
962
974
|
Grid,
|
|
963
975
|
GridItem,
|
|
@@ -975,6 +987,7 @@ export {
|
|
|
975
987
|
MdxSearchTabs as SearchTabs,
|
|
976
988
|
SearchTabs as SearchTabsUI,
|
|
977
989
|
Slider,
|
|
990
|
+
TextCard,
|
|
978
991
|
Viewer
|
|
979
992
|
};
|
|
980
993
|
//# sourceMappingURL=index.mjs.map
|