@canopy-iiif/app 0.10.21 → 0.10.23
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 +2 -0
- package/lib/search/search-form-runtime.js +47 -30
- package/package.json +1 -1
- package/ui/dist/index.mjs +36 -7
- package/ui/dist/index.mjs.map +2 -2
- package/ui/dist/server.mjs +6 -5
- package/ui/dist/server.mjs.map +2 -2
- package/ui/styles/components/_article-card.scss +11 -10
- package/ui/styles/components/index.scss +1 -0
- package/ui/styles/components/search/_panel.scss +7 -0
- package/ui/styles/components/search/_results.scss +74 -1
- package/ui/styles/index.css +90 -11
package/lib/build/mdx.js
CHANGED
|
@@ -186,6 +186,18 @@ function groupLabel(type) {
|
|
|
186
186
|
return type.charAt(0).toUpperCase() + type.slice(1);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
function hidePanel(panel) {
|
|
190
|
+
if (!panel) return;
|
|
191
|
+
panel.classList.add('is-empty');
|
|
192
|
+
panel.setAttribute('hidden', 'hidden');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function showPanel(panel) {
|
|
196
|
+
if (!panel) return;
|
|
197
|
+
panel.classList.remove('is-empty');
|
|
198
|
+
panel.removeAttribute('hidden');
|
|
199
|
+
}
|
|
200
|
+
|
|
189
201
|
function getItems(list) {
|
|
190
202
|
try {
|
|
191
203
|
return Array.prototype.slice.call(list.querySelectorAll('[data-canopy-item]'));
|
|
@@ -196,7 +208,12 @@ function getItems(list) {
|
|
|
196
208
|
|
|
197
209
|
function renderList(list, records, groupOrder) {
|
|
198
210
|
list.innerHTML = '';
|
|
199
|
-
|
|
211
|
+
const panel = list.closest('[data-canopy-search-form-panel]');
|
|
212
|
+
if (!records.length) {
|
|
213
|
+
hidePanel(panel);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
showPanel(panel);
|
|
200
217
|
const groups = new Map();
|
|
201
218
|
records.forEach((record) => {
|
|
202
219
|
const type = String(record && record.type || 'page');
|
|
@@ -207,8 +224,8 @@ function renderList(list, records, groupOrder) {
|
|
|
207
224
|
const orderedKeys = [...desiredOrder.filter((key) => groups.has(key)), ...Array.from(groups.keys()).filter((key) => !desiredOrder.includes(key))];
|
|
208
225
|
orderedKeys.forEach((key) => {
|
|
209
226
|
const header = document.createElement('div');
|
|
227
|
+
header.className = 'canopy-search-teaser__label';
|
|
210
228
|
header.textContent = groupLabel(key);
|
|
211
|
-
header.style.cssText = 'padding:6px 12px;font-weight:600;color:#374151';
|
|
212
229
|
list.appendChild(header);
|
|
213
230
|
const entries = groups.get(key) || [];
|
|
214
231
|
entries.forEach((record) => {
|
|
@@ -217,44 +234,39 @@ function renderList(list, records, groupOrder) {
|
|
|
217
234
|
item.setAttribute('data-canopy-item', '');
|
|
218
235
|
item.href = href;
|
|
219
236
|
item.tabIndex = 0;
|
|
220
|
-
item.className = 'canopy-card canopy-card--teaser';
|
|
221
|
-
item.style.cssText = 'display:flex;gap:12px;padding:8px 12px;text-decoration:none;color:#030712;border-radius:8px;align-items:center;outline:none;';
|
|
237
|
+
item.className = 'canopy-card canopy-card--teaser canopy-search-teaser__item';
|
|
222
238
|
|
|
223
239
|
const showThumb = String(record && record.type || '') === 'work' && record && record.thumbnail;
|
|
224
240
|
if (showThumb) {
|
|
225
241
|
const media = document.createElement('div');
|
|
226
|
-
media.
|
|
242
|
+
media.className = 'canopy-search-teaser__thumb';
|
|
227
243
|
const img = document.createElement('img');
|
|
228
244
|
img.src = record.thumbnail;
|
|
229
245
|
img.alt = '';
|
|
230
246
|
img.loading = 'lazy';
|
|
231
|
-
img.
|
|
247
|
+
img.className = 'canopy-search-teaser__thumb-img';
|
|
232
248
|
media.appendChild(img);
|
|
233
249
|
item.appendChild(media);
|
|
234
250
|
}
|
|
235
251
|
|
|
236
252
|
const textWrap = document.createElement('div');
|
|
237
|
-
textWrap.
|
|
253
|
+
textWrap.className = 'canopy-search-teaser__text';
|
|
238
254
|
const title = document.createElement('span');
|
|
239
255
|
title.textContent = record.title || record.href || '';
|
|
240
|
-
title.
|
|
256
|
+
title.className = 'canopy-search-teaser__title';
|
|
241
257
|
textWrap.appendChild(title);
|
|
242
258
|
const meta = Array.isArray(record && record.metadata) ? record.metadata : [];
|
|
243
259
|
if (meta.length) {
|
|
244
260
|
const metaLine = document.createElement('span');
|
|
245
261
|
metaLine.textContent = meta.slice(0, 2).join(' • ');
|
|
246
|
-
metaLine.
|
|
262
|
+
metaLine.className = 'canopy-search-teaser__meta';
|
|
247
263
|
textWrap.appendChild(metaLine);
|
|
248
264
|
}
|
|
249
265
|
item.appendChild(textWrap);
|
|
250
266
|
|
|
251
|
-
item.onmouseenter = () => { item.style.background = '#f8fafc'; };
|
|
252
|
-
item.onmouseleave = () => { item.style.background = 'transparent'; };
|
|
253
267
|
item.onfocus = () => {
|
|
254
|
-
item.style.background = '#eef2ff';
|
|
255
268
|
try { item.scrollIntoView({ block: 'nearest' }); } catch (_) {}
|
|
256
269
|
};
|
|
257
|
-
item.onblur = () => { item.style.background = 'transparent'; };
|
|
258
270
|
list.appendChild(item);
|
|
259
271
|
});
|
|
260
272
|
});
|
|
@@ -275,15 +287,19 @@ function focusLast(list, resetTarget) {
|
|
|
275
287
|
items[items.length - 1].focus();
|
|
276
288
|
}
|
|
277
289
|
|
|
278
|
-
function bindKeyboardNavigation({ input, list, panel }) {
|
|
290
|
+
function bindKeyboardNavigation({ input, list, panel, ensureResults }) {
|
|
291
|
+
const ensure = () => {
|
|
292
|
+
if (typeof ensureResults === 'function') ensureResults();
|
|
293
|
+
return getItems(list).length > 0;
|
|
294
|
+
};
|
|
279
295
|
input.addEventListener('keydown', (event) => {
|
|
280
296
|
if (event.key === 'ArrowDown') {
|
|
281
297
|
event.preventDefault();
|
|
282
|
-
|
|
298
|
+
if (!ensure()) return;
|
|
283
299
|
focusFirst(list);
|
|
284
300
|
} else if (event.key === 'ArrowUp') {
|
|
285
301
|
event.preventDefault();
|
|
286
|
-
|
|
302
|
+
if (!ensure()) return;
|
|
287
303
|
focusLast(list, input);
|
|
288
304
|
}
|
|
289
305
|
});
|
|
@@ -311,7 +327,7 @@ function bindKeyboardNavigation({ input, list, panel }) {
|
|
|
311
327
|
event.preventDefault();
|
|
312
328
|
try { current.click(); } catch (_) {}
|
|
313
329
|
} else if (event.key === 'Escape') {
|
|
314
|
-
panel
|
|
330
|
+
hidePanel(panel);
|
|
315
331
|
try { input && input.focus && input.focus(); } catch (_) {}
|
|
316
332
|
}
|
|
317
333
|
});
|
|
@@ -336,7 +352,7 @@ async function attachSearchForm(host) {
|
|
|
336
352
|
} catch (_) {}
|
|
337
353
|
}
|
|
338
354
|
|
|
339
|
-
if (onSearchPage) panel
|
|
355
|
+
if (onSearchPage) hidePanel(panel);
|
|
340
356
|
|
|
341
357
|
const list = (() => {
|
|
342
358
|
try { return panel.querySelector('#cplist'); } catch (_) { return null; }
|
|
@@ -357,13 +373,7 @@ async function attachSearchForm(host) {
|
|
|
357
373
|
const records = await loadRecords();
|
|
358
374
|
|
|
359
375
|
function render(items) {
|
|
360
|
-
list.innerHTML = '';
|
|
361
|
-
if (!items.length) {
|
|
362
|
-
panel.style.display = onSearchPage ? 'none' : 'block';
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
376
|
renderList(list, items, groupOrder);
|
|
366
|
-
panel.style.display = 'block';
|
|
367
377
|
}
|
|
368
378
|
|
|
369
379
|
function filterAndShow(query) {
|
|
@@ -371,7 +381,7 @@ async function attachSearchForm(host) {
|
|
|
371
381
|
const q = toLower(query);
|
|
372
382
|
if (!q) {
|
|
373
383
|
list.innerHTML = '';
|
|
374
|
-
panel
|
|
384
|
+
hidePanel(panel);
|
|
375
385
|
return;
|
|
376
386
|
}
|
|
377
387
|
const out = [];
|
|
@@ -404,18 +414,27 @@ async function attachSearchForm(host) {
|
|
|
404
414
|
filterAndShow(input.value || '');
|
|
405
415
|
});
|
|
406
416
|
|
|
407
|
-
bindKeyboardNavigation({
|
|
417
|
+
bindKeyboardNavigation({
|
|
418
|
+
input,
|
|
419
|
+
list,
|
|
420
|
+
panel,
|
|
421
|
+
ensureResults: () => {
|
|
422
|
+
if (!getItems(list).length) {
|
|
423
|
+
filterAndShow(input.value || '');
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
});
|
|
408
427
|
|
|
409
428
|
document.addEventListener('keydown', (event) => {
|
|
410
429
|
if (event.key === 'Escape') {
|
|
411
|
-
panel
|
|
430
|
+
hidePanel(panel);
|
|
412
431
|
}
|
|
413
432
|
});
|
|
414
433
|
|
|
415
434
|
document.addEventListener('mousedown', (event) => {
|
|
416
435
|
try {
|
|
417
436
|
if (!panel.contains(event.target) && !host.contains(event.target)) {
|
|
418
|
-
panel
|
|
437
|
+
hidePanel(panel);
|
|
419
438
|
}
|
|
420
439
|
} catch (_) {}
|
|
421
440
|
});
|
|
@@ -433,7 +452,6 @@ async function attachSearchForm(host) {
|
|
|
433
452
|
try { window.dispatchEvent(new CustomEvent('canopy:search:setQuery', { detail: { hotkey: true } })); } catch (_) {}
|
|
434
453
|
return;
|
|
435
454
|
}
|
|
436
|
-
panel.style.display = 'block';
|
|
437
455
|
if (input && input.focus) input.focus();
|
|
438
456
|
filterAndShow(input && input.value || '');
|
|
439
457
|
}
|
|
@@ -446,7 +464,6 @@ async function attachSearchForm(host) {
|
|
|
446
464
|
try { window.dispatchEvent(new CustomEvent('canopy:search:setQuery', { detail: {} })); } catch (_) {}
|
|
447
465
|
return;
|
|
448
466
|
}
|
|
449
|
-
panel.style.display = 'block';
|
|
450
467
|
if (input && input.focus) input.focus();
|
|
451
468
|
filterAndShow(input && input.value || '');
|
|
452
469
|
}
|
package/package.json
CHANGED
package/ui/dist/index.mjs
CHANGED
|
@@ -108,6 +108,21 @@ function Card({
|
|
|
108
108
|
|
|
109
109
|
// ui/src/layout/ArticleCard.jsx
|
|
110
110
|
import React3, { useMemo } from "react";
|
|
111
|
+
var DUMMY_ORIGIN = "https://canopy.local";
|
|
112
|
+
function appendQueryParam(href = "", key, value) {
|
|
113
|
+
try {
|
|
114
|
+
const absolute = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(href);
|
|
115
|
+
const baseHref = absolute ? href : `${DUMMY_ORIGIN}${href.startsWith("/") ? "" : "/"}${href}`;
|
|
116
|
+
const url = new URL(baseHref);
|
|
117
|
+
url.searchParams.set(key, value);
|
|
118
|
+
if (absolute) return url.toString();
|
|
119
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
120
|
+
} catch (_) {
|
|
121
|
+
if (!href) return href;
|
|
122
|
+
const sep = href.includes("?") ? "&" : "?";
|
|
123
|
+
return `${href}${sep}${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
111
126
|
function escapeRegExp(str = "") {
|
|
112
127
|
return String(str).replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
113
128
|
}
|
|
@@ -225,7 +240,9 @@ function tokenizeInlineMarkdown(input = "") {
|
|
|
225
240
|
}
|
|
226
241
|
}
|
|
227
242
|
const specials = ["**", "__", "*", "_", "`", "[", "\n"];
|
|
228
|
-
const nextIndex = specials.map(
|
|
243
|
+
const nextIndex = specials.map(
|
|
244
|
+
(needle) => needle === "\n" ? text.indexOf("\n") : text.indexOf(needle)
|
|
245
|
+
).filter((idx) => idx > 0).reduce((min, idx) => min === -1 || idx < min ? idx : min, -1);
|
|
229
246
|
if (nextIndex === -1) {
|
|
230
247
|
tokens.push({ type: "text", value: text });
|
|
231
248
|
break;
|
|
@@ -257,7 +274,10 @@ function renderMarkdownTokens(tokens, query, keyPrefix = "token") {
|
|
|
257
274
|
}
|
|
258
275
|
function formatDisplayUrl(href = "") {
|
|
259
276
|
try {
|
|
260
|
-
const url = new URL(
|
|
277
|
+
const url = new URL(
|
|
278
|
+
href,
|
|
279
|
+
href.startsWith("http") ? void 0 : "http://example.com"
|
|
280
|
+
);
|
|
261
281
|
if (!href.startsWith("http")) return href;
|
|
262
282
|
const displayPath = url.pathname.replace(/\/$/, "");
|
|
263
283
|
return `${url.host}${displayPath}${url.search}`.replace(/\/$/, "");
|
|
@@ -272,7 +292,8 @@ function ArticleCard({
|
|
|
272
292
|
summary = "",
|
|
273
293
|
summaryMarkdown = "",
|
|
274
294
|
metadata = [],
|
|
275
|
-
query = ""
|
|
295
|
+
query = "",
|
|
296
|
+
recordType = ""
|
|
276
297
|
}) {
|
|
277
298
|
const snippetSource = summaryMarkdown || annotation || summary;
|
|
278
299
|
const snippet = useMemo(
|
|
@@ -285,7 +306,13 @@ function ArticleCard({
|
|
|
285
306
|
);
|
|
286
307
|
const metaList = Array.isArray(metadata) ? metadata.map((m) => String(m || "")).filter(Boolean) : [];
|
|
287
308
|
const displayUrl = useMemo(() => formatDisplayUrl(href), [href]);
|
|
288
|
-
|
|
309
|
+
const resolvedHref = useMemo(() => {
|
|
310
|
+
if (recordType === "annotation" && query && href) {
|
|
311
|
+
return appendQueryParam(href, "q", query);
|
|
312
|
+
}
|
|
313
|
+
return href;
|
|
314
|
+
}, [href, recordType, query]);
|
|
315
|
+
return /* @__PURE__ */ React3.createElement("a", { href: resolvedHref, className: "canopy-article-card" }, /* @__PURE__ */ React3.createElement("article", null, /* @__PURE__ */ React3.createElement("h3", null, title), displayUrl ? /* @__PURE__ */ React3.createElement("span", { className: "canopy-article-card__url" }, displayUrl) : null, snippet ? /* @__PURE__ */ React3.createElement("p", { className: "canopy-article-card__snippet" }, renderMarkdownTokens(snippetTokens, query)) : null, metaList.length ? /* @__PURE__ */ React3.createElement("ul", { className: "canopy-article-card__meta" }, metaList.slice(0, 3).map((item, idx) => /* @__PURE__ */ React3.createElement("li", { key: `${item}-${idx}` }, item))) : null));
|
|
289
316
|
}
|
|
290
317
|
|
|
291
318
|
// ui/src/layout/Grid.jsx
|
|
@@ -607,15 +634,16 @@ function SearchPanelForm(props = {}) {
|
|
|
607
634
|
import React10 from "react";
|
|
608
635
|
function SearchPanelTeaserResults(props = {}) {
|
|
609
636
|
const { style, className } = props || {};
|
|
610
|
-
const classes = ["canopy-search-teaser", className].filter(Boolean).join(" ");
|
|
637
|
+
const classes = ["canopy-search-teaser", "is-empty", className].filter(Boolean).join(" ");
|
|
611
638
|
return /* @__PURE__ */ React10.createElement(
|
|
612
639
|
"div",
|
|
613
640
|
{
|
|
614
641
|
"data-canopy-search-form-panel": true,
|
|
642
|
+
hidden: true,
|
|
615
643
|
className: classes || void 0,
|
|
616
644
|
style
|
|
617
645
|
},
|
|
618
|
-
/* @__PURE__ */ React10.createElement("div", { id: "cplist" })
|
|
646
|
+
/* @__PURE__ */ React10.createElement("div", { id: "cplist", className: "canopy-search-teaser__list" })
|
|
619
647
|
);
|
|
620
648
|
}
|
|
621
649
|
|
|
@@ -1350,7 +1378,8 @@ function DefaultArticleTemplate({ record, query }) {
|
|
|
1350
1378
|
summary: record.summary || record.summaryValue || "",
|
|
1351
1379
|
summaryMarkdown: record.summaryMarkdown || record.summary || record.summaryValue || "",
|
|
1352
1380
|
metadata,
|
|
1353
|
-
query
|
|
1381
|
+
query,
|
|
1382
|
+
recordType: record.type
|
|
1354
1383
|
}
|
|
1355
1384
|
);
|
|
1356
1385
|
}
|