@canopy-iiif/app 0.8.4 → 0.8.5
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/iiif.js +359 -83
- package/lib/build/styles.js +53 -1
- package/lib/common.js +28 -6
- package/lib/search/search-app.jsx +177 -25
- package/lib/search/search-form-runtime.js +126 -19
- package/lib/search/search.js +130 -18
- package/package.json +4 -1
- package/ui/dist/index.mjs +201 -97
- package/ui/dist/index.mjs.map +4 -4
- package/ui/dist/server.mjs +44 -25
- package/ui/dist/server.mjs.map +2 -2
- package/ui/styles/_variables.scss +1 -0
- package/ui/styles/base/_common.scss +27 -5
- package/ui/styles/base/_heading.scss +2 -4
- package/ui/styles/base/index.scss +1 -0
- package/ui/styles/components/_card.scss +47 -4
- package/ui/styles/components/_sub-navigation.scss +14 -14
- package/ui/styles/components/header/_header.scss +1 -4
- package/ui/styles/components/header/_logo.scss +33 -10
- package/ui/styles/components/search/_filters.scss +5 -7
- package/ui/styles/components/search/_form.scss +55 -17
- package/ui/styles/components/search/_results.scss +13 -15
- package/ui/styles/index.css +250 -72
- package/ui/styles/index.scss +2 -4
- package/ui/tailwind-canopy-iiif-plugin.js +10 -2
- package/ui/tailwind-canopy-iiif-preset.js +21 -19
- package/ui/theme.js +303 -0
- package/ui/styles/variables.emit.scss +0 -72
- package/ui/styles/variables.scss +0 -76
package/ui/dist/index.mjs
CHANGED
|
@@ -104,11 +104,76 @@ function Card({
|
|
|
104
104
|
);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// ui/src/layout/AnnotationCard.jsx
|
|
108
|
+
import React3, { useMemo } from "react";
|
|
109
|
+
function escapeRegExp(str = "") {
|
|
110
|
+
return String(str).replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
111
|
+
}
|
|
112
|
+
function buildSnippet({ text = "", query = "", maxLength = 240 }) {
|
|
113
|
+
const clean = String(text || "").replace(/\s+/g, " ").trim();
|
|
114
|
+
if (!clean) return "";
|
|
115
|
+
const term = String(query || "").trim();
|
|
116
|
+
if (!term)
|
|
117
|
+
return clean.length > maxLength ? clean.slice(0, maxLength) + "\u2026" : clean;
|
|
118
|
+
const lower = clean.toLowerCase();
|
|
119
|
+
const termLower = term.toLowerCase();
|
|
120
|
+
const idx = lower.indexOf(termLower);
|
|
121
|
+
if (idx === -1) {
|
|
122
|
+
return clean.length > maxLength ? clean.slice(0, maxLength) + "\u2026" : clean;
|
|
123
|
+
}
|
|
124
|
+
const context = Math.max(0, maxLength / 2);
|
|
125
|
+
const start = Math.max(0, idx - context);
|
|
126
|
+
const end = Math.min(clean.length, idx + term.length + context);
|
|
127
|
+
let snippet = clean.slice(start, end);
|
|
128
|
+
if (start > 0) snippet = "\u2026" + snippet;
|
|
129
|
+
if (end < clean.length) snippet = snippet + "\u2026";
|
|
130
|
+
return snippet;
|
|
131
|
+
}
|
|
132
|
+
function highlightSnippet(snippet, query) {
|
|
133
|
+
if (!query) return snippet;
|
|
134
|
+
const term = String(query).trim();
|
|
135
|
+
if (!term) return snippet;
|
|
136
|
+
const parts = String(snippet).split(
|
|
137
|
+
new RegExp(`(${escapeRegExp(term)})`, "gi")
|
|
138
|
+
);
|
|
139
|
+
const termLower = term.toLowerCase();
|
|
140
|
+
return parts.map(
|
|
141
|
+
(part, idx) => part.toLowerCase() === termLower ? /* @__PURE__ */ React3.createElement("mark", { key: idx }, part) : /* @__PURE__ */ React3.createElement(React3.Fragment, { key: idx }, part)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
function AnnotationCard({
|
|
145
|
+
href = "#",
|
|
146
|
+
title = "Untitled",
|
|
147
|
+
annotation = "",
|
|
148
|
+
summary = "",
|
|
149
|
+
metadata = [],
|
|
150
|
+
query = ""
|
|
151
|
+
}) {
|
|
152
|
+
const snippetSource = annotation || summary;
|
|
153
|
+
const snippet = useMemo(
|
|
154
|
+
() => buildSnippet({ text: snippetSource, query }),
|
|
155
|
+
[snippetSource, query]
|
|
156
|
+
);
|
|
157
|
+
const highlighted = useMemo(
|
|
158
|
+
() => highlightSnippet(snippet, query),
|
|
159
|
+
[snippet, query]
|
|
160
|
+
);
|
|
161
|
+
const metaList = Array.isArray(metadata) ? metadata.map((m) => String(m || "")).filter(Boolean) : [];
|
|
162
|
+
return /* @__PURE__ */ React3.createElement("a", { href }, /* @__PURE__ */ React3.createElement("article", { className: "canopy-annotation-card" }, /* @__PURE__ */ React3.createElement("h3", null, title), snippet ? /* @__PURE__ */ React3.createElement("p", { className: "mt-2 text-sm leading-relaxed text-slate-700" }, highlighted) : null, metaList.length ? /* @__PURE__ */ React3.createElement("ul", { className: "mt-3 flex flex-wrap gap-2 text-xs text-slate-500" }, metaList.slice(0, 4).map((item, idx) => /* @__PURE__ */ React3.createElement(
|
|
163
|
+
"li",
|
|
164
|
+
{
|
|
165
|
+
key: `${item}-${idx}`,
|
|
166
|
+
className: "rounded-full border border-slate-200 bg-slate-50 px-2 py-1"
|
|
167
|
+
},
|
|
168
|
+
item
|
|
169
|
+
))) : null));
|
|
170
|
+
}
|
|
171
|
+
|
|
107
172
|
// ui/src/layout/Grid.jsx
|
|
108
173
|
import Masonry from "react-masonry-css";
|
|
109
|
-
import
|
|
174
|
+
import React4 from "react";
|
|
110
175
|
function GridItem({ children, className = "", style = {}, ...rest }) {
|
|
111
|
-
return /* @__PURE__ */
|
|
176
|
+
return /* @__PURE__ */ React4.createElement(
|
|
112
177
|
"div",
|
|
113
178
|
{
|
|
114
179
|
className: `canopy-grid-item ${className}`.trim(),
|
|
@@ -136,7 +201,7 @@ function Grid({
|
|
|
136
201
|
640: 2
|
|
137
202
|
};
|
|
138
203
|
const vars = { "--grid-gap": gap, "--grid-padding-y": paddingY };
|
|
139
|
-
return /* @__PURE__ */
|
|
204
|
+
return /* @__PURE__ */ React4.createElement("div", { className: "canopy-grid-wrap" }, /* @__PURE__ */ React4.createElement(
|
|
140
205
|
"style",
|
|
141
206
|
{
|
|
142
207
|
dangerouslySetInnerHTML: {
|
|
@@ -148,7 +213,7 @@ function Grid({
|
|
|
148
213
|
`
|
|
149
214
|
}
|
|
150
215
|
}
|
|
151
|
-
), /* @__PURE__ */
|
|
216
|
+
), /* @__PURE__ */ React4.createElement(
|
|
152
217
|
Masonry,
|
|
153
218
|
{
|
|
154
219
|
breakpointCols: cols,
|
|
@@ -162,7 +227,7 @@ function Grid({
|
|
|
162
227
|
}
|
|
163
228
|
|
|
164
229
|
// ui/src/iiif/Viewer.jsx
|
|
165
|
-
import
|
|
230
|
+
import React5, { useEffect as useEffect2, useState as useState2 } from "react";
|
|
166
231
|
var DEFAULT_VIEWER_OPTIONS = {
|
|
167
232
|
showDownload: false,
|
|
168
233
|
showIIIFBadge: false,
|
|
@@ -218,7 +283,7 @@ var Viewer = (props) => {
|
|
|
218
283
|
} catch (_) {
|
|
219
284
|
json = "{}";
|
|
220
285
|
}
|
|
221
|
-
return /* @__PURE__ */
|
|
286
|
+
return /* @__PURE__ */ React5.createElement("div", { "data-canopy-viewer": "1", className: "not-prose" }, /* @__PURE__ */ React5.createElement(
|
|
222
287
|
"script",
|
|
223
288
|
{
|
|
224
289
|
type: "application/json",
|
|
@@ -226,11 +291,11 @@ var Viewer = (props) => {
|
|
|
226
291
|
}
|
|
227
292
|
));
|
|
228
293
|
}
|
|
229
|
-
return /* @__PURE__ */
|
|
294
|
+
return /* @__PURE__ */ React5.createElement(CloverViewer, { ...props, options: mergedOptions });
|
|
230
295
|
};
|
|
231
296
|
|
|
232
297
|
// ui/src/iiif/Slider.jsx
|
|
233
|
-
import
|
|
298
|
+
import React6, { useEffect as useEffect3, useState as useState3 } from "react";
|
|
234
299
|
var Slider = (props) => {
|
|
235
300
|
const [CloverSlider, setCloverSlider] = useState3(null);
|
|
236
301
|
useEffect3(() => {
|
|
@@ -256,7 +321,7 @@ var Slider = (props) => {
|
|
|
256
321
|
} catch (_) {
|
|
257
322
|
json = "{}";
|
|
258
323
|
}
|
|
259
|
-
return /* @__PURE__ */
|
|
324
|
+
return /* @__PURE__ */ React6.createElement("div", { "data-canopy-slider": "1", className: "not-prose" }, /* @__PURE__ */ React6.createElement(
|
|
260
325
|
"script",
|
|
261
326
|
{
|
|
262
327
|
type: "application/json",
|
|
@@ -264,11 +329,11 @@ var Slider = (props) => {
|
|
|
264
329
|
}
|
|
265
330
|
));
|
|
266
331
|
}
|
|
267
|
-
return /* @__PURE__ */
|
|
332
|
+
return /* @__PURE__ */ React6.createElement(CloverSlider, { ...props });
|
|
268
333
|
};
|
|
269
334
|
|
|
270
335
|
// ui/src/iiif/MdxRelatedItems.jsx
|
|
271
|
-
import
|
|
336
|
+
import React7 from "react";
|
|
272
337
|
function MdxRelatedItems(props) {
|
|
273
338
|
let json = "{}";
|
|
274
339
|
try {
|
|
@@ -276,11 +341,11 @@ function MdxRelatedItems(props) {
|
|
|
276
341
|
} catch (_) {
|
|
277
342
|
json = "{}";
|
|
278
343
|
}
|
|
279
|
-
return /* @__PURE__ */
|
|
344
|
+
return /* @__PURE__ */ React7.createElement("div", { "data-canopy-related-items": "1", className: "not-prose" }, /* @__PURE__ */ React7.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
|
|
280
345
|
}
|
|
281
346
|
|
|
282
347
|
// ui/src/search/MdxSearchResults.jsx
|
|
283
|
-
import
|
|
348
|
+
import React8 from "react";
|
|
284
349
|
function MdxSearchResults(props) {
|
|
285
350
|
let json = "{}";
|
|
286
351
|
try {
|
|
@@ -288,11 +353,11 @@ function MdxSearchResults(props) {
|
|
|
288
353
|
} catch (_) {
|
|
289
354
|
json = "{}";
|
|
290
355
|
}
|
|
291
|
-
return /* @__PURE__ */
|
|
356
|
+
return /* @__PURE__ */ React8.createElement("div", { "data-canopy-search-results": "1" }, /* @__PURE__ */ React8.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
|
|
292
357
|
}
|
|
293
358
|
|
|
294
359
|
// ui/src/search/SearchSummary.jsx
|
|
295
|
-
import
|
|
360
|
+
import React9 from "react";
|
|
296
361
|
function SearchSummary(props) {
|
|
297
362
|
let json = "{}";
|
|
298
363
|
try {
|
|
@@ -300,11 +365,11 @@ function SearchSummary(props) {
|
|
|
300
365
|
} catch (_) {
|
|
301
366
|
json = "{}";
|
|
302
367
|
}
|
|
303
|
-
return /* @__PURE__ */
|
|
368
|
+
return /* @__PURE__ */ React9.createElement("div", { "data-canopy-search-summary": "1" }, /* @__PURE__ */ React9.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
|
|
304
369
|
}
|
|
305
370
|
|
|
306
371
|
// ui/src/search/MdxSearchTabs.jsx
|
|
307
|
-
import
|
|
372
|
+
import React10 from "react";
|
|
308
373
|
function MdxSearchTabs(props) {
|
|
309
374
|
let json = "{}";
|
|
310
375
|
try {
|
|
@@ -312,31 +377,50 @@ function MdxSearchTabs(props) {
|
|
|
312
377
|
} catch (_) {
|
|
313
378
|
json = "{}";
|
|
314
379
|
}
|
|
315
|
-
return /* @__PURE__ */
|
|
380
|
+
return /* @__PURE__ */ React10.createElement("div", { "data-canopy-search-tabs": "1" }, /* @__PURE__ */ React10.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: json } }));
|
|
316
381
|
}
|
|
317
382
|
|
|
318
383
|
// ui/src/search/SearchResults.jsx
|
|
319
|
-
import
|
|
384
|
+
import React11 from "react";
|
|
320
385
|
function SearchResults({
|
|
321
386
|
results = [],
|
|
322
387
|
type = "all",
|
|
323
|
-
layout = "grid"
|
|
388
|
+
layout = "grid",
|
|
389
|
+
query = ""
|
|
324
390
|
}) {
|
|
325
391
|
if (!results.length) {
|
|
326
|
-
return /* @__PURE__ */
|
|
392
|
+
return /* @__PURE__ */ React11.createElement("div", { className: "text-slate-600" }, /* @__PURE__ */ React11.createElement("em", null, "No results"));
|
|
393
|
+
}
|
|
394
|
+
const isAnnotationView = String(type).toLowerCase() === "annotation";
|
|
395
|
+
if (isAnnotationView) {
|
|
396
|
+
return /* @__PURE__ */ React11.createElement("div", { id: "search-results", className: "space-y-4" }, results.map((r, i) => {
|
|
397
|
+
if (!r || !r.annotation) return null;
|
|
398
|
+
return /* @__PURE__ */ React11.createElement(
|
|
399
|
+
AnnotationCard,
|
|
400
|
+
{
|
|
401
|
+
key: r.id || i,
|
|
402
|
+
href: r.href,
|
|
403
|
+
title: r.title || r.href || "Untitled",
|
|
404
|
+
annotation: r.annotation,
|
|
405
|
+
summary: r.summary,
|
|
406
|
+
metadata: Array.isArray(r.metadata) ? r.metadata : [],
|
|
407
|
+
query
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
}));
|
|
327
411
|
}
|
|
328
412
|
if (layout === "list") {
|
|
329
|
-
return /* @__PURE__ */
|
|
413
|
+
return /* @__PURE__ */ React11.createElement("ul", { id: "search-results", className: "space-y-3" }, results.map((r, i) => {
|
|
330
414
|
const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
|
|
331
415
|
const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
|
|
332
|
-
return /* @__PURE__ */
|
|
416
|
+
return /* @__PURE__ */ React11.createElement(
|
|
333
417
|
"li",
|
|
334
418
|
{
|
|
335
419
|
key: i,
|
|
336
420
|
className: `search-result ${r.type}`,
|
|
337
421
|
"data-thumbnail-aspect-ratio": aspect
|
|
338
422
|
},
|
|
339
|
-
/* @__PURE__ */
|
|
423
|
+
/* @__PURE__ */ React11.createElement(
|
|
340
424
|
Card,
|
|
341
425
|
{
|
|
342
426
|
href: r.href,
|
|
@@ -350,17 +434,17 @@ function SearchResults({
|
|
|
350
434
|
);
|
|
351
435
|
}));
|
|
352
436
|
}
|
|
353
|
-
return /* @__PURE__ */
|
|
437
|
+
return /* @__PURE__ */ React11.createElement("div", { id: "search-results" }, /* @__PURE__ */ React11.createElement(Grid, null, results.map((r, i) => {
|
|
354
438
|
const hasDims = Number.isFinite(Number(r.thumbnailWidth)) && Number(r.thumbnailWidth) > 0 && Number.isFinite(Number(r.thumbnailHeight)) && Number(r.thumbnailHeight) > 0;
|
|
355
439
|
const aspect = hasDims ? Number(r.thumbnailWidth) / Number(r.thumbnailHeight) : void 0;
|
|
356
|
-
return /* @__PURE__ */
|
|
440
|
+
return /* @__PURE__ */ React11.createElement(
|
|
357
441
|
GridItem,
|
|
358
442
|
{
|
|
359
443
|
key: i,
|
|
360
444
|
className: `search-result ${r.type}`,
|
|
361
445
|
"data-thumbnail-aspect-ratio": aspect
|
|
362
446
|
},
|
|
363
|
-
/* @__PURE__ */
|
|
447
|
+
/* @__PURE__ */ React11.createElement(
|
|
364
448
|
Card,
|
|
365
449
|
{
|
|
366
450
|
href: r.href,
|
|
@@ -376,7 +460,7 @@ function SearchResults({
|
|
|
376
460
|
}
|
|
377
461
|
|
|
378
462
|
// ui/src/search/SearchTabs.jsx
|
|
379
|
-
import
|
|
463
|
+
import React12 from "react";
|
|
380
464
|
function SearchTabs({
|
|
381
465
|
type = "all",
|
|
382
466
|
onTypeChange,
|
|
@@ -391,7 +475,7 @@ function SearchTabs({
|
|
|
391
475
|
const toLabel = (t) => t && t.length ? t.charAt(0).toUpperCase() + t.slice(1) : "";
|
|
392
476
|
const hasFilters = typeof onOpenFilters === "function";
|
|
393
477
|
const filterBadge = activeFilterCount > 0 ? ` (${activeFilterCount})` : "";
|
|
394
|
-
return /* @__PURE__ */
|
|
478
|
+
return /* @__PURE__ */ React12.createElement("div", { className: "canopy-search-tabs-wrapper" }, /* @__PURE__ */ React12.createElement(
|
|
395
479
|
"div",
|
|
396
480
|
{
|
|
397
481
|
role: "tablist",
|
|
@@ -402,7 +486,7 @@ function SearchTabs({
|
|
|
402
486
|
const active = String(type).toLowerCase() === String(t).toLowerCase();
|
|
403
487
|
const cRaw = counts && Object.prototype.hasOwnProperty.call(counts, t) ? counts[t] : void 0;
|
|
404
488
|
const c = Number.isFinite(Number(cRaw)) ? Number(cRaw) : 0;
|
|
405
|
-
return /* @__PURE__ */
|
|
489
|
+
return /* @__PURE__ */ React12.createElement(
|
|
406
490
|
"button",
|
|
407
491
|
{
|
|
408
492
|
key: t,
|
|
@@ -417,7 +501,7 @@ function SearchTabs({
|
|
|
417
501
|
")"
|
|
418
502
|
);
|
|
419
503
|
})
|
|
420
|
-
), hasFilters ? /* @__PURE__ */
|
|
504
|
+
), hasFilters ? /* @__PURE__ */ React12.createElement(
|
|
421
505
|
"button",
|
|
422
506
|
{
|
|
423
507
|
type: "button",
|
|
@@ -425,12 +509,12 @@ function SearchTabs({
|
|
|
425
509
|
"aria-expanded": filtersOpen ? "true" : "false",
|
|
426
510
|
className: "inline-flex items-center gap-2 rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm font-medium text-slate-700 shadow-sm transition hover:border-brand-200 hover:bg-brand-50 hover:text-brand-700"
|
|
427
511
|
},
|
|
428
|
-
/* @__PURE__ */
|
|
512
|
+
/* @__PURE__ */ React12.createElement("span", null, filtersLabel, filterBadge)
|
|
429
513
|
) : null);
|
|
430
514
|
}
|
|
431
515
|
|
|
432
516
|
// ui/src/search/SearchFiltersDialog.jsx
|
|
433
|
-
import
|
|
517
|
+
import React13 from "react";
|
|
434
518
|
function toArray(input) {
|
|
435
519
|
if (!input) return [];
|
|
436
520
|
if (Array.isArray(input)) return input;
|
|
@@ -469,20 +553,20 @@ function FacetSection({ facet, selected, onToggle }) {
|
|
|
469
553
|
const selectedValues = selected.get(String(slug)) || /* @__PURE__ */ new Set();
|
|
470
554
|
const checkboxId = (valueSlug) => `filter-${slug}-${valueSlug}`;
|
|
471
555
|
const hasSelection = selectedValues.size > 0;
|
|
472
|
-
const [quickQuery, setQuickQuery] =
|
|
556
|
+
const [quickQuery, setQuickQuery] = React13.useState("");
|
|
473
557
|
const hasQuery = quickQuery.trim().length > 0;
|
|
474
|
-
const filteredValues =
|
|
558
|
+
const filteredValues = React13.useMemo(
|
|
475
559
|
() => facetMatches(values, quickQuery),
|
|
476
560
|
[values, quickQuery]
|
|
477
561
|
);
|
|
478
|
-
return /* @__PURE__ */
|
|
562
|
+
return /* @__PURE__ */ React13.createElement(
|
|
479
563
|
"details",
|
|
480
564
|
{
|
|
481
565
|
className: "canopy-search-filters__facet",
|
|
482
566
|
open: hasSelection
|
|
483
567
|
},
|
|
484
|
-
/* @__PURE__ */
|
|
485
|
-
/* @__PURE__ */
|
|
568
|
+
/* @__PURE__ */ React13.createElement("summary", { className: "canopy-search-filters__facet-summary" }, /* @__PURE__ */ React13.createElement("span", null, label), /* @__PURE__ */ React13.createElement("span", { className: "canopy-search-filters__facet-count" }, values.length)),
|
|
569
|
+
/* @__PURE__ */ React13.createElement("div", { className: "canopy-search-filters__facet-content" }, /* @__PURE__ */ React13.createElement("div", { className: "canopy-search-filters__quick" }, /* @__PURE__ */ React13.createElement(
|
|
486
570
|
"input",
|
|
487
571
|
{
|
|
488
572
|
type: "search",
|
|
@@ -492,7 +576,7 @@ function FacetSection({ facet, selected, onToggle }) {
|
|
|
492
576
|
className: "canopy-search-filters__quick-input",
|
|
493
577
|
"aria-label": `Filter ${label} values`
|
|
494
578
|
}
|
|
495
|
-
), quickQuery ? /* @__PURE__ */
|
|
579
|
+
), quickQuery ? /* @__PURE__ */ React13.createElement(
|
|
496
580
|
"button",
|
|
497
581
|
{
|
|
498
582
|
type: "button",
|
|
@@ -500,11 +584,11 @@ function FacetSection({ facet, selected, onToggle }) {
|
|
|
500
584
|
className: "canopy-search-filters__quick-clear"
|
|
501
585
|
},
|
|
502
586
|
"Clear"
|
|
503
|
-
) : null), hasQuery && !filteredValues.length ? /* @__PURE__ */
|
|
587
|
+
) : null), hasQuery && !filteredValues.length ? /* @__PURE__ */ React13.createElement("p", { className: "canopy-search-filters__facet-notice" }, "No matches found.") : null, /* @__PURE__ */ React13.createElement("ul", { className: "canopy-search-filters__facet-list" }, filteredValues.map((entry) => {
|
|
504
588
|
const valueSlug = String(entry.slug || entry.value || "");
|
|
505
589
|
const isChecked = selectedValues.has(valueSlug);
|
|
506
590
|
const inputId = checkboxId(valueSlug);
|
|
507
|
-
return /* @__PURE__ */
|
|
591
|
+
return /* @__PURE__ */ React13.createElement("li", { key: valueSlug, className: "canopy-search-filters__facet-item" }, /* @__PURE__ */ React13.createElement(
|
|
508
592
|
"input",
|
|
509
593
|
{
|
|
510
594
|
id: inputId,
|
|
@@ -516,15 +600,15 @@ function FacetSection({ facet, selected, onToggle }) {
|
|
|
516
600
|
if (onToggle) onToggle(slug, valueSlug, nextChecked);
|
|
517
601
|
}
|
|
518
602
|
}
|
|
519
|
-
), /* @__PURE__ */
|
|
603
|
+
), /* @__PURE__ */ React13.createElement(
|
|
520
604
|
"label",
|
|
521
605
|
{
|
|
522
606
|
htmlFor: inputId,
|
|
523
607
|
className: "canopy-search-filters__facet-label"
|
|
524
608
|
},
|
|
525
|
-
/* @__PURE__ */
|
|
609
|
+
/* @__PURE__ */ React13.createElement("span", null, entry.value, " ", Number.isFinite(entry.doc_count) ? /* @__PURE__ */ React13.createElement("span", { className: "canopy-search-filters__facet-count" }, "(", entry.doc_count, ")") : null)
|
|
526
610
|
));
|
|
527
|
-
}), !filteredValues.length && !hasQuery ? /* @__PURE__ */
|
|
611
|
+
}), !filteredValues.length && !hasQuery ? /* @__PURE__ */ React13.createElement("li", { className: "canopy-search-filters__facet-empty" }, "No values available.") : null))
|
|
528
612
|
);
|
|
529
613
|
}
|
|
530
614
|
function SearchFiltersDialog(props = {}) {
|
|
@@ -544,7 +628,7 @@ function SearchFiltersDialog(props = {}) {
|
|
|
544
628
|
0
|
|
545
629
|
);
|
|
546
630
|
if (!open) return null;
|
|
547
|
-
return /* @__PURE__ */
|
|
631
|
+
return /* @__PURE__ */ React13.createElement(
|
|
548
632
|
"div",
|
|
549
633
|
{
|
|
550
634
|
role: "dialog",
|
|
@@ -555,7 +639,7 @@ function SearchFiltersDialog(props = {}) {
|
|
|
555
639
|
onOpenChange(false);
|
|
556
640
|
}
|
|
557
641
|
},
|
|
558
|
-
/* @__PURE__ */
|
|
642
|
+
/* @__PURE__ */ React13.createElement("div", { className: "canopy-search-filters" }, /* @__PURE__ */ React13.createElement("header", { className: "canopy-search-filters__header" }, /* @__PURE__ */ React13.createElement("div", null, /* @__PURE__ */ React13.createElement("h2", { className: "canopy-search-filters__title" }, title), /* @__PURE__ */ React13.createElement("p", { className: "canopy-search-filters__subtitle" }, subtitle)), /* @__PURE__ */ React13.createElement(
|
|
559
643
|
"button",
|
|
560
644
|
{
|
|
561
645
|
type: "button",
|
|
@@ -563,7 +647,7 @@ function SearchFiltersDialog(props = {}) {
|
|
|
563
647
|
className: "canopy-search-filters__close"
|
|
564
648
|
},
|
|
565
649
|
"Close"
|
|
566
|
-
)), /* @__PURE__ */
|
|
650
|
+
)), /* @__PURE__ */ React13.createElement("div", { className: "canopy-search-filters__body" }, Array.isArray(facets) && facets.length ? /* @__PURE__ */ React13.createElement("div", { className: "canopy-search-filters__facets" }, facets.map((facet) => /* @__PURE__ */ React13.createElement(
|
|
567
651
|
FacetSection,
|
|
568
652
|
{
|
|
569
653
|
key: facet.slug || facet.label,
|
|
@@ -571,7 +655,7 @@ function SearchFiltersDialog(props = {}) {
|
|
|
571
655
|
selected: selectedMap,
|
|
572
656
|
onToggle
|
|
573
657
|
}
|
|
574
|
-
))) : /* @__PURE__ */
|
|
658
|
+
))) : /* @__PURE__ */ React13.createElement("p", { className: "canopy-search-filters__empty" }, "No filters are available for this collection.")), /* @__PURE__ */ React13.createElement("footer", { className: "canopy-search-filters__footer" }, /* @__PURE__ */ React13.createElement("div", null, activeCount ? `${activeCount} filter${activeCount === 1 ? "" : "s"} applied` : "No filters applied"), /* @__PURE__ */ React13.createElement("div", { className: "canopy-search-filters__footer-actions" }, /* @__PURE__ */ React13.createElement(
|
|
575
659
|
"button",
|
|
576
660
|
{
|
|
577
661
|
type: "button",
|
|
@@ -582,7 +666,7 @@ function SearchFiltersDialog(props = {}) {
|
|
|
582
666
|
className: "canopy-search-filters__button canopy-search-filters__button--secondary"
|
|
583
667
|
},
|
|
584
668
|
"Clear all"
|
|
585
|
-
), /* @__PURE__ */
|
|
669
|
+
), /* @__PURE__ */ React13.createElement(
|
|
586
670
|
"button",
|
|
587
671
|
{
|
|
588
672
|
type: "button",
|
|
@@ -595,14 +679,14 @@ function SearchFiltersDialog(props = {}) {
|
|
|
595
679
|
}
|
|
596
680
|
|
|
597
681
|
// ui/src/search-form/MdxSearchFormModal.jsx
|
|
598
|
-
import
|
|
682
|
+
import React17 from "react";
|
|
599
683
|
|
|
600
684
|
// ui/src/Icons.jsx
|
|
601
|
-
import
|
|
602
|
-
var MagnifyingGlassIcon = (props) => /* @__PURE__ */
|
|
685
|
+
import React14 from "react";
|
|
686
|
+
var MagnifyingGlassIcon = (props) => /* @__PURE__ */ React14.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 512 512", ...props }, /* @__PURE__ */ React14.createElement("path", { d: "M456.69 421.39L362.6 327.3a173.81 173.81 0 0034.84-104.58C397.44 126.38 319.06 48 222.72 48S48 126.38 48 222.72s78.38 174.72 174.72 174.72A173.81 173.81 0 00327.3 362.6l94.09 94.09a25 25 0 0035.3-35.3zM97.92 222.72a124.8 124.8 0 11124.8 124.8 124.95 124.95 0 01-124.8-124.8z" }));
|
|
603
687
|
|
|
604
688
|
// ui/src/search/SearchPanelForm.jsx
|
|
605
|
-
import
|
|
689
|
+
import React15 from "react";
|
|
606
690
|
function readBasePath() {
|
|
607
691
|
const normalize = (val) => {
|
|
608
692
|
const raw = typeof val === "string" ? val.trim() : "";
|
|
@@ -661,21 +745,22 @@ function SearchPanelForm(props = {}) {
|
|
|
661
745
|
buttonLabel = "Search",
|
|
662
746
|
label,
|
|
663
747
|
searchPath = "/search",
|
|
664
|
-
inputId: inputIdProp
|
|
748
|
+
inputId: inputIdProp,
|
|
749
|
+
clearLabel = "Clear search"
|
|
665
750
|
} = props || {};
|
|
666
751
|
const text = typeof label === "string" && label.trim() ? label.trim() : buttonLabel;
|
|
667
|
-
const action =
|
|
752
|
+
const action = React15.useMemo(
|
|
668
753
|
() => resolveSearchPath(searchPath),
|
|
669
754
|
[searchPath]
|
|
670
755
|
);
|
|
671
|
-
const autoId = typeof
|
|
672
|
-
const [fallbackId] =
|
|
756
|
+
const autoId = typeof React15.useId === "function" ? React15.useId() : void 0;
|
|
757
|
+
const [fallbackId] = React15.useState(
|
|
673
758
|
() => `canopy-search-form-${Math.random().toString(36).slice(2, 10)}`
|
|
674
759
|
);
|
|
675
760
|
const inputId = inputIdProp || autoId || fallbackId;
|
|
676
|
-
const inputRef =
|
|
677
|
-
const [hasValue, setHasValue] =
|
|
678
|
-
const focusInput =
|
|
761
|
+
const inputRef = React15.useRef(null);
|
|
762
|
+
const [hasValue, setHasValue] = React15.useState(false);
|
|
763
|
+
const focusInput = React15.useCallback(() => {
|
|
679
764
|
const el = inputRef.current;
|
|
680
765
|
if (!el) return;
|
|
681
766
|
if (document.activeElement === el) return;
|
|
@@ -688,30 +773,44 @@ function SearchPanelForm(props = {}) {
|
|
|
688
773
|
}
|
|
689
774
|
}
|
|
690
775
|
}, []);
|
|
691
|
-
const handlePointerDown =
|
|
776
|
+
const handlePointerDown = React15.useCallback(
|
|
692
777
|
(event) => {
|
|
693
778
|
const target = event.target;
|
|
694
779
|
if (target && typeof target.closest === "function") {
|
|
695
780
|
if (target.closest("[data-canopy-search-form-trigger]")) return;
|
|
781
|
+
if (target.closest("[data-canopy-search-form-clear]")) return;
|
|
696
782
|
}
|
|
697
783
|
event.preventDefault();
|
|
698
784
|
focusInput();
|
|
699
785
|
},
|
|
700
786
|
[focusInput]
|
|
701
787
|
);
|
|
702
|
-
|
|
788
|
+
React15.useEffect(() => {
|
|
703
789
|
const el = inputRef.current;
|
|
704
790
|
if (!el) return;
|
|
705
791
|
if (el.value && el.value.trim()) {
|
|
706
792
|
setHasValue(true);
|
|
707
793
|
}
|
|
708
794
|
}, []);
|
|
709
|
-
const handleInputChange =
|
|
795
|
+
const handleInputChange = React15.useCallback((event) => {
|
|
710
796
|
var _a;
|
|
711
|
-
const nextHasValue = Boolean(
|
|
797
|
+
const nextHasValue = Boolean(
|
|
798
|
+
((_a = event == null ? void 0 : event.target) == null ? void 0 : _a.value) && event.target.value.trim()
|
|
799
|
+
);
|
|
712
800
|
setHasValue(nextHasValue);
|
|
713
801
|
}, []);
|
|
714
|
-
|
|
802
|
+
const handleClear = React15.useCallback((event) => {
|
|
803
|
+
}, []);
|
|
804
|
+
const handleClearKey = React15.useCallback(
|
|
805
|
+
(event) => {
|
|
806
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
807
|
+
event.preventDefault();
|
|
808
|
+
handleClear(event);
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
[handleClear]
|
|
812
|
+
);
|
|
813
|
+
return /* @__PURE__ */ React15.createElement(
|
|
715
814
|
"form",
|
|
716
815
|
{
|
|
717
816
|
action,
|
|
@@ -721,59 +820,63 @@ function SearchPanelForm(props = {}) {
|
|
|
721
820
|
spellCheck: "false",
|
|
722
821
|
className: "canopy-search-form canopy-search-form-shell",
|
|
723
822
|
onPointerDown: handlePointerDown,
|
|
724
|
-
"data-placeholder": placeholder || "",
|
|
725
823
|
"data-has-value": hasValue ? "1" : "0"
|
|
726
824
|
},
|
|
727
|
-
/* @__PURE__ */
|
|
728
|
-
"
|
|
825
|
+
/* @__PURE__ */ React15.createElement("label", { htmlFor: inputId, className: "canopy-search-form__label" }, /* @__PURE__ */ React15.createElement(MagnifyingGlassIcon, { className: "canopy-search-form__icon" }), /* @__PURE__ */ React15.createElement(
|
|
826
|
+
"input",
|
|
729
827
|
{
|
|
730
|
-
|
|
731
|
-
|
|
828
|
+
id: inputId,
|
|
829
|
+
type: "search",
|
|
830
|
+
name: "q",
|
|
831
|
+
inputMode: "search",
|
|
832
|
+
"data-canopy-search-form-input": true,
|
|
833
|
+
placeholder,
|
|
834
|
+
className: "canopy-search-form__input",
|
|
835
|
+
"aria-label": "Search",
|
|
836
|
+
ref: inputRef,
|
|
837
|
+
onChange: handleInputChange,
|
|
838
|
+
onInput: handleInputChange
|
|
839
|
+
}
|
|
840
|
+
)),
|
|
841
|
+
hasValue ? /* @__PURE__ */ React15.createElement(
|
|
842
|
+
"button",
|
|
843
|
+
{
|
|
844
|
+
type: "button",
|
|
845
|
+
className: "canopy-search-form__clear",
|
|
846
|
+
onClick: handleClear,
|
|
847
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
848
|
+
onKeyDown: handleClearKey,
|
|
849
|
+
"aria-label": clearLabel,
|
|
850
|
+
"data-canopy-search-form-clear": true
|
|
732
851
|
},
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
{
|
|
737
|
-
id: inputId,
|
|
738
|
-
type: "search",
|
|
739
|
-
name: "q",
|
|
740
|
-
inputMode: "search",
|
|
741
|
-
"data-canopy-search-form-input": true,
|
|
742
|
-
placeholder,
|
|
743
|
-
className: "canopy-search-form__input",
|
|
744
|
-
"aria-label": "Search",
|
|
745
|
-
ref: inputRef,
|
|
746
|
-
onChange: handleInputChange,
|
|
747
|
-
onInput: handleInputChange
|
|
748
|
-
}
|
|
749
|
-
)
|
|
750
|
-
),
|
|
751
|
-
/* @__PURE__ */ React14.createElement(
|
|
852
|
+
"\xD7"
|
|
853
|
+
) : null,
|
|
854
|
+
/* @__PURE__ */ React15.createElement(
|
|
752
855
|
"button",
|
|
753
856
|
{
|
|
754
857
|
type: "submit",
|
|
755
858
|
"data-canopy-search-form-trigger": "submit",
|
|
756
859
|
className: "canopy-search-form__submit"
|
|
757
860
|
},
|
|
758
|
-
/* @__PURE__ */
|
|
759
|
-
/* @__PURE__ */
|
|
861
|
+
/* @__PURE__ */ React15.createElement("span", null, text),
|
|
862
|
+
/* @__PURE__ */ React15.createElement("span", { "aria-hidden": true, className: "canopy-search-form__shortcut" }, /* @__PURE__ */ React15.createElement("span", null, "\u2318"), /* @__PURE__ */ React15.createElement("span", null, "K"))
|
|
760
863
|
)
|
|
761
864
|
);
|
|
762
865
|
}
|
|
763
866
|
|
|
764
867
|
// ui/src/search/SearchPanelTeaserResults.jsx
|
|
765
|
-
import
|
|
868
|
+
import React16 from "react";
|
|
766
869
|
function SearchPanelTeaserResults(props = {}) {
|
|
767
870
|
const { style, className } = props || {};
|
|
768
871
|
const classes = ["canopy-search-teaser", className].filter(Boolean).join(" ");
|
|
769
|
-
return /* @__PURE__ */
|
|
872
|
+
return /* @__PURE__ */ React16.createElement(
|
|
770
873
|
"div",
|
|
771
874
|
{
|
|
772
875
|
"data-canopy-search-form-panel": true,
|
|
773
876
|
className: classes || void 0,
|
|
774
877
|
style
|
|
775
878
|
},
|
|
776
|
-
/* @__PURE__ */
|
|
879
|
+
/* @__PURE__ */ React16.createElement("div", { id: "cplist" })
|
|
777
880
|
);
|
|
778
881
|
}
|
|
779
882
|
|
|
@@ -793,11 +896,11 @@ function MdxSearchFormModal(props = {}) {
|
|
|
793
896
|
const text = typeof label === "string" && label.trim() ? label.trim() : buttonLabel;
|
|
794
897
|
const resolvedSearchPath = resolveSearchPath(searchPath);
|
|
795
898
|
const data = { placeholder, hotkey, maxResults, groupOrder, label: text, searchPath: resolvedSearchPath };
|
|
796
|
-
return /* @__PURE__ */
|
|
899
|
+
return /* @__PURE__ */ React17.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React17.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React17.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React17.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React17.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
|
|
797
900
|
}
|
|
798
901
|
|
|
799
902
|
// ui/src/search/SearchPanel.jsx
|
|
800
|
-
import
|
|
903
|
+
import React18 from "react";
|
|
801
904
|
function SearchPanel(props = {}) {
|
|
802
905
|
const {
|
|
803
906
|
placeholder = "Search\u2026",
|
|
@@ -814,9 +917,10 @@ function SearchPanel(props = {}) {
|
|
|
814
917
|
const text = typeof label === "string" && label.trim() ? label.trim() : buttonLabel;
|
|
815
918
|
const resolvedSearchPath = resolveSearchPath(searchPath);
|
|
816
919
|
const data = { placeholder, hotkey, maxResults, groupOrder, label: text, searchPath: resolvedSearchPath };
|
|
817
|
-
return /* @__PURE__ */
|
|
920
|
+
return /* @__PURE__ */ React18.createElement("div", { "data-canopy-search-form": true, className: "flex-1 min-w-0" }, /* @__PURE__ */ React18.createElement("div", { className: "relative w-full" }, /* @__PURE__ */ React18.createElement(SearchPanelForm, { placeholder, buttonLabel, label, searchPath: resolvedSearchPath }), /* @__PURE__ */ React18.createElement(SearchPanelTeaserResults, null)), /* @__PURE__ */ React18.createElement("script", { type: "application/json", dangerouslySetInnerHTML: { __html: JSON.stringify(data) } }));
|
|
818
921
|
}
|
|
819
922
|
export {
|
|
923
|
+
AnnotationCard,
|
|
820
924
|
Card,
|
|
821
925
|
Grid,
|
|
822
926
|
GridItem,
|