@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 CHANGED
@@ -601,6 +601,8 @@ async function ensureClientRuntime() {
601
601
  } catch (_) { /* no-op */ }
602
602
  }
603
603
 
604
+ function seedScrollSearchInput() {}
605
+
604
606
  ready(function() {
605
607
  mountAll('[data-canopy-viewer]', CloverViewer);
606
608
  mountAll('[data-canopy-scroll]', CloverScroll);
@@ -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
- if (!records.length) return;
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.style.cssText = 'flex:0 0 48px;height:48px;border-radius:6px;overflow:hidden;background:#f1f5f9;display:flex;align-items:center;justify-content:center;';
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.style.cssText = 'width:100%;height:100%;object-fit:cover;';
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.style.cssText = 'display:flex;flex-direction:column;gap:2px;min-width:0;';
253
+ textWrap.className = 'canopy-search-teaser__text';
238
254
  const title = document.createElement('span');
239
255
  title.textContent = record.title || record.href || '';
240
- title.style.cssText = 'font-weight:600;font-size:0.95rem;line-height:1.3;color:#111827;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
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.style.cssText = 'font-size:0.8rem;color:#475569;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;';
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
- panel.style.display = 'block';
298
+ if (!ensure()) return;
283
299
  focusFirst(list);
284
300
  } else if (event.key === 'ArrowUp') {
285
301
  event.preventDefault();
286
- panel.style.display = 'block';
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.style.display = 'none';
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.style.display = 'none';
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.style.display = onSearchPage ? 'none' : 'block';
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({ input, list, panel });
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.style.display = 'none';
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.style.display = 'none';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "0.10.21",
3
+ "version": "0.10.23",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
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((needle) => needle === "\n" ? text.indexOf("\n") : text.indexOf(needle)).filter((idx) => idx > 0).reduce((min, idx) => min === -1 || idx < min ? idx : min, -1);
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(href, href.startsWith("http") ? void 0 : "http://example.com");
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
- return /* @__PURE__ */ React3.createElement("a", { href, className: "canopy-article-card" }, /* @__PURE__ */ React3.createElement("article", null, displayUrl ? /* @__PURE__ */ React3.createElement("p", { className: "canopy-article-card__url" }, displayUrl) : null, /* @__PURE__ */ React3.createElement("h3", null, title), 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));
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
  }