@canopy-iiif/app 0.10.20 → 0.10.22
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 +16 -0
- package/lib/build/search-index.js +5 -0
- package/lib/build/search.js +2 -0
- package/lib/search/search-form-runtime.js +47 -30
- package/lib/search/search.js +9 -0
- package/package.json +1 -1
- package/ui/dist/index.mjs +145 -25
- 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
|
@@ -247,6 +247,21 @@ function extractPlainText(mdxSource) {
|
|
|
247
247
|
return content;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
function extractMarkdownSummary(mdxSource) {
|
|
251
|
+
let { content } = parseFrontmatter(String(mdxSource || ''));
|
|
252
|
+
if (!content) return '';
|
|
253
|
+
content = content.replace(/^import[^\n]+$/gm, ' ');
|
|
254
|
+
content = content.replace(/^export[^\n]+$/gm, ' ');
|
|
255
|
+
content = content.replace(/```[\s\S]*?```/g, ' ');
|
|
256
|
+
content = content.replace(/<[A-Za-z][^>]*?>[\s\S]*?<\/[A-Za-z][^>]*?>/g, ' ');
|
|
257
|
+
content = content.replace(/<[A-Za-z][^>]*?\/>/g, ' ');
|
|
258
|
+
content = content.replace(/\{\/[A-Za-z0-9_.-]+\}/g, ' ');
|
|
259
|
+
content = content.replace(/\{[^{}]*\}/g, ' ');
|
|
260
|
+
content = content.replace(/^#{1,6}\s+.*$/gm, ' ');
|
|
261
|
+
content = content.replace(/\s+/g, ' ').trim();
|
|
262
|
+
return content;
|
|
263
|
+
}
|
|
264
|
+
|
|
250
265
|
function extractTitle(mdxSource) {
|
|
251
266
|
const { data, content } = parseFrontmatter(String(mdxSource || ""));
|
|
252
267
|
if (data && typeof data.title === "string" && data.title.trim()) {
|
|
@@ -1006,6 +1021,7 @@ module.exports = {
|
|
|
1006
1021
|
extractTitle,
|
|
1007
1022
|
extractHeadings,
|
|
1008
1023
|
extractPlainText,
|
|
1024
|
+
extractMarkdownSummary,
|
|
1009
1025
|
isReservedFile,
|
|
1010
1026
|
parseFrontmatter,
|
|
1011
1027
|
compileMdxFile,
|
|
@@ -8,12 +8,17 @@ function pagesToRecords(pageRecords) {
|
|
|
8
8
|
.filter((p) => p && p.href && p.searchInclude)
|
|
9
9
|
.map((p) => {
|
|
10
10
|
const summary = typeof p.searchSummary === 'string' ? p.searchSummary.trim() : '';
|
|
11
|
+
const summaryMarkdown =
|
|
12
|
+
typeof p.searchSummaryMarkdown === 'string'
|
|
13
|
+
? p.searchSummaryMarkdown.trim()
|
|
14
|
+
: '';
|
|
11
15
|
const record = {
|
|
12
16
|
title: p.title || p.href,
|
|
13
17
|
href: rootRelativeHref(p.href),
|
|
14
18
|
type: p.searchType || 'page',
|
|
15
19
|
};
|
|
16
20
|
if (summary) record.summaryValue = summary;
|
|
21
|
+
if (summaryMarkdown) record.summaryMarkdown = summaryMarkdown;
|
|
17
22
|
return record;
|
|
18
23
|
});
|
|
19
24
|
}
|
package/lib/build/search.js
CHANGED
|
@@ -210,6 +210,7 @@ async function collectMdxPageRecords() {
|
|
|
210
210
|
if (base !== 'sitemap.mdx') {
|
|
211
211
|
const href = rootRelativeHref(rel.split(path.sep).join('/'));
|
|
212
212
|
const plainText = mdx.extractPlainText(src);
|
|
213
|
+
const markdownSummary = mdx.extractMarkdownSummary(src);
|
|
213
214
|
const summary = plainText || '';
|
|
214
215
|
const underSearch = /^search\//i.test(href) || href.toLowerCase() === 'search.html';
|
|
215
216
|
let include = !underSearch;
|
|
@@ -234,6 +235,7 @@ async function collectMdxPageRecords() {
|
|
|
234
235
|
searchInclude: include && !!trimmedType,
|
|
235
236
|
searchType: trimmedType || undefined,
|
|
236
237
|
searchSummary: summary,
|
|
238
|
+
searchSummaryMarkdown: markdownSummary,
|
|
237
239
|
});
|
|
238
240
|
}
|
|
239
241
|
}
|
|
@@ -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/lib/search/search.js
CHANGED
|
@@ -354,6 +354,15 @@ function sanitizeRecordForDisplay(r) {
|
|
|
354
354
|
const out = { ...base };
|
|
355
355
|
if (out.metadata) delete out.metadata;
|
|
356
356
|
if (out.summary) out.summary = toSafeString(out.summary, '');
|
|
357
|
+
const summaryMarkdown = toSafeString(
|
|
358
|
+
(r && r.summaryMarkdown) ||
|
|
359
|
+
(r && r.searchSummaryMarkdown) ||
|
|
360
|
+
(r && r.search && r.search.summaryMarkdown),
|
|
361
|
+
''
|
|
362
|
+
).trim();
|
|
363
|
+
if (summaryMarkdown) {
|
|
364
|
+
out.summaryMarkdown = summaryMarkdown;
|
|
365
|
+
}
|
|
357
366
|
const hrefRaw = toSafeString(r && r.href, '');
|
|
358
367
|
out.href = rootRelativeHref(hrefRaw);
|
|
359
368
|
const thumbnail = toSafeString(r && r.thumbnail, '');
|
package/package.json
CHANGED
package/ui/dist/index.mjs
CHANGED
|
@@ -111,41 +111,158 @@ import React3, { useMemo } from "react";
|
|
|
111
111
|
function escapeRegExp(str = "") {
|
|
112
112
|
return String(str).replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
|
|
113
113
|
}
|
|
114
|
-
function buildSnippet({ text = "", query = "", maxLength =
|
|
114
|
+
function buildSnippet({ text = "", query = "", maxLength = 360 }) {
|
|
115
115
|
const clean = String(text || "").replace(/\s+/g, " ").trim();
|
|
116
116
|
if (!clean) return "";
|
|
117
|
+
const safeMax = Math.max(60, Number(maxLength) || 360);
|
|
117
118
|
const term = String(query || "").trim();
|
|
118
119
|
if (!term)
|
|
119
|
-
return clean.length >
|
|
120
|
+
return clean.length > safeMax ? clean.slice(0, safeMax).trimEnd() + "\u2026" : clean;
|
|
120
121
|
const lower = clean.toLowerCase();
|
|
121
122
|
const termLower = term.toLowerCase();
|
|
122
123
|
const idx = lower.indexOf(termLower);
|
|
123
|
-
if (idx === -1)
|
|
124
|
-
return clean.length >
|
|
124
|
+
if (idx === -1)
|
|
125
|
+
return clean.length > safeMax ? clean.slice(0, safeMax).trimEnd() + "\u2026" : clean;
|
|
126
|
+
const padding = Math.max(0, Math.floor((safeMax - term.length) / 2));
|
|
127
|
+
let start = Math.max(0, idx - padding);
|
|
128
|
+
let end = start + safeMax;
|
|
129
|
+
if (end > clean.length) {
|
|
130
|
+
end = clean.length;
|
|
131
|
+
start = Math.max(0, end - safeMax);
|
|
125
132
|
}
|
|
126
|
-
|
|
127
|
-
const start = Math.max(0, idx - context);
|
|
128
|
-
const end = Math.min(clean.length, idx + term.length + context);
|
|
129
|
-
let snippet = clean.slice(start, end);
|
|
133
|
+
let snippet = clean.slice(start, end).trim();
|
|
130
134
|
if (start > 0) snippet = "\u2026" + snippet;
|
|
131
135
|
if (end < clean.length) snippet = snippet + "\u2026";
|
|
132
136
|
return snippet;
|
|
133
137
|
}
|
|
134
|
-
function
|
|
135
|
-
if (!query) return
|
|
138
|
+
function highlightTextNode(text, query, keyPrefix = "") {
|
|
139
|
+
if (!query) return text;
|
|
136
140
|
const term = String(query).trim();
|
|
137
|
-
if (!term) return
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
);
|
|
141
|
+
if (!term) return text;
|
|
142
|
+
const regex = new RegExp(`(${escapeRegExp(term)})`, "gi");
|
|
143
|
+
const parts = String(text).split(regex);
|
|
141
144
|
const termLower = term.toLowerCase();
|
|
142
|
-
return parts.map(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
return parts.map((part, idx) => {
|
|
146
|
+
if (!part) return null;
|
|
147
|
+
if (part.toLowerCase() === termLower) {
|
|
148
|
+
return /* @__PURE__ */ React3.createElement("mark", { key: `${keyPrefix}-${idx}` }, part);
|
|
149
|
+
}
|
|
150
|
+
return /* @__PURE__ */ React3.createElement(React3.Fragment, { key: `${keyPrefix}-${idx}` }, part);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function tokenizeInlineMarkdown(input = "") {
|
|
154
|
+
const tokens = [];
|
|
155
|
+
let text = input;
|
|
156
|
+
while (text.length) {
|
|
157
|
+
if (text.startsWith("\n")) {
|
|
158
|
+
tokens.push({ type: "break" });
|
|
159
|
+
text = text.slice(1);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (text.startsWith("**")) {
|
|
163
|
+
const closing = text.indexOf("**", 2);
|
|
164
|
+
if (closing !== -1) {
|
|
165
|
+
const inner = text.slice(2, closing);
|
|
166
|
+
tokens.push({ type: "strong", children: tokenizeInlineMarkdown(inner) });
|
|
167
|
+
text = text.slice(closing + 2);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (text.startsWith("__")) {
|
|
172
|
+
const closing = text.indexOf("__", 2);
|
|
173
|
+
if (closing !== -1) {
|
|
174
|
+
const inner = text.slice(2, closing);
|
|
175
|
+
tokens.push({ type: "strong", children: tokenizeInlineMarkdown(inner) });
|
|
176
|
+
text = text.slice(closing + 2);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (text.startsWith("*")) {
|
|
181
|
+
if (!text.startsWith("**")) {
|
|
182
|
+
const closing = text.indexOf("*", 1);
|
|
183
|
+
if (closing !== -1) {
|
|
184
|
+
const inner = text.slice(1, closing);
|
|
185
|
+
tokens.push({ type: "em", children: tokenizeInlineMarkdown(inner) });
|
|
186
|
+
text = text.slice(closing + 1);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (text.startsWith("_")) {
|
|
192
|
+
if (!text.startsWith("__")) {
|
|
193
|
+
const closing = text.indexOf("_", 1);
|
|
194
|
+
if (closing !== -1) {
|
|
195
|
+
const inner = text.slice(1, closing);
|
|
196
|
+
tokens.push({ type: "em", children: tokenizeInlineMarkdown(inner) });
|
|
197
|
+
text = text.slice(closing + 1);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (text.startsWith("`")) {
|
|
203
|
+
const closing = text.indexOf("`", 1);
|
|
204
|
+
if (closing !== -1) {
|
|
205
|
+
const inner = text.slice(1, closing);
|
|
206
|
+
tokens.push({ type: "code", value: inner });
|
|
207
|
+
text = text.slice(closing + 1);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (text.startsWith("[")) {
|
|
212
|
+
const endLabel = text.indexOf("]");
|
|
213
|
+
const startHref = endLabel !== -1 ? text.indexOf("(", endLabel) : -1;
|
|
214
|
+
const endHref = startHref !== -1 ? text.indexOf(")", startHref) : -1;
|
|
215
|
+
if (endLabel !== -1 && startHref === endLabel + 1 && endHref !== -1) {
|
|
216
|
+
const label = text.slice(1, endLabel);
|
|
217
|
+
const href = text.slice(startHref + 1, endHref);
|
|
218
|
+
tokens.push({
|
|
219
|
+
type: "link",
|
|
220
|
+
href,
|
|
221
|
+
children: tokenizeInlineMarkdown(label)
|
|
222
|
+
});
|
|
223
|
+
text = text.slice(endHref + 1);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const specials = ["**", "__", "*", "_", "`", "[", "\n"];
|
|
228
|
+
const nextIndex = specials.map(
|
|
229
|
+
(needle) => needle === "\n" ? text.indexOf("\n") : text.indexOf(needle)
|
|
230
|
+
).filter((idx) => idx > 0).reduce((min, idx) => min === -1 || idx < min ? idx : min, -1);
|
|
231
|
+
if (nextIndex === -1) {
|
|
232
|
+
tokens.push({ type: "text", value: text });
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
tokens.push({ type: "text", value: text.slice(0, nextIndex) });
|
|
236
|
+
text = text.slice(nextIndex);
|
|
237
|
+
}
|
|
238
|
+
return tokens;
|
|
239
|
+
}
|
|
240
|
+
function renderMarkdownTokens(tokens, query, keyPrefix = "token") {
|
|
241
|
+
return tokens.map((token, idx) => {
|
|
242
|
+
const key = `${keyPrefix}-${idx}`;
|
|
243
|
+
switch (token.type) {
|
|
244
|
+
case "strong":
|
|
245
|
+
return /* @__PURE__ */ React3.createElement("strong", { key }, renderMarkdownTokens(token.children || [], query, key));
|
|
246
|
+
case "em":
|
|
247
|
+
return /* @__PURE__ */ React3.createElement("em", { key }, renderMarkdownTokens(token.children || [], query, key));
|
|
248
|
+
case "code":
|
|
249
|
+
return /* @__PURE__ */ React3.createElement("code", { key }, token.value);
|
|
250
|
+
case "link":
|
|
251
|
+
return /* @__PURE__ */ React3.createElement("a", { key, href: token.href, target: "_blank", rel: "noreferrer" }, renderMarkdownTokens(token.children || [], query, key));
|
|
252
|
+
case "break":
|
|
253
|
+
return /* @__PURE__ */ React3.createElement("br", { key });
|
|
254
|
+
case "text":
|
|
255
|
+
default:
|
|
256
|
+
return /* @__PURE__ */ React3.createElement(React3.Fragment, { key }, highlightTextNode(token.value || "", query, key));
|
|
257
|
+
}
|
|
258
|
+
});
|
|
145
259
|
}
|
|
146
260
|
function formatDisplayUrl(href = "") {
|
|
147
261
|
try {
|
|
148
|
-
const url = new URL(
|
|
262
|
+
const url = new URL(
|
|
263
|
+
href,
|
|
264
|
+
href.startsWith("http") ? void 0 : "http://example.com"
|
|
265
|
+
);
|
|
149
266
|
if (!href.startsWith("http")) return href;
|
|
150
267
|
const displayPath = url.pathname.replace(/\/$/, "");
|
|
151
268
|
return `${url.host}${displayPath}${url.search}`.replace(/\/$/, "");
|
|
@@ -158,21 +275,22 @@ function ArticleCard({
|
|
|
158
275
|
title = "Untitled",
|
|
159
276
|
annotation = "",
|
|
160
277
|
summary = "",
|
|
278
|
+
summaryMarkdown = "",
|
|
161
279
|
metadata = [],
|
|
162
280
|
query = ""
|
|
163
281
|
}) {
|
|
164
|
-
const snippetSource = annotation || summary;
|
|
282
|
+
const snippetSource = summaryMarkdown || annotation || summary;
|
|
165
283
|
const snippet = useMemo(
|
|
166
284
|
() => buildSnippet({ text: snippetSource, query }),
|
|
167
285
|
[snippetSource, query]
|
|
168
286
|
);
|
|
169
|
-
const
|
|
170
|
-
() =>
|
|
171
|
-
[snippet
|
|
287
|
+
const snippetTokens = useMemo(
|
|
288
|
+
() => tokenizeInlineMarkdown(snippet),
|
|
289
|
+
[snippet]
|
|
172
290
|
);
|
|
173
291
|
const metaList = Array.isArray(metadata) ? metadata.map((m) => String(m || "")).filter(Boolean) : [];
|
|
174
292
|
const displayUrl = useMemo(() => formatDisplayUrl(href), [href]);
|
|
175
|
-
return /* @__PURE__ */ React3.createElement("a", { href, className: "canopy-article-card" }, /* @__PURE__ */ React3.createElement("article", null, displayUrl ? /* @__PURE__ */ React3.createElement("
|
|
293
|
+
return /* @__PURE__ */ React3.createElement("a", { href, 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));
|
|
176
294
|
}
|
|
177
295
|
|
|
178
296
|
// ui/src/layout/Grid.jsx
|
|
@@ -494,15 +612,16 @@ function SearchPanelForm(props = {}) {
|
|
|
494
612
|
import React10 from "react";
|
|
495
613
|
function SearchPanelTeaserResults(props = {}) {
|
|
496
614
|
const { style, className } = props || {};
|
|
497
|
-
const classes = ["canopy-search-teaser", className].filter(Boolean).join(" ");
|
|
615
|
+
const classes = ["canopy-search-teaser", "is-empty", className].filter(Boolean).join(" ");
|
|
498
616
|
return /* @__PURE__ */ React10.createElement(
|
|
499
617
|
"div",
|
|
500
618
|
{
|
|
501
619
|
"data-canopy-search-form-panel": true,
|
|
620
|
+
hidden: true,
|
|
502
621
|
className: classes || void 0,
|
|
503
622
|
style
|
|
504
623
|
},
|
|
505
|
-
/* @__PURE__ */ React10.createElement("div", { id: "cplist" })
|
|
624
|
+
/* @__PURE__ */ React10.createElement("div", { id: "cplist", className: "canopy-search-teaser__list" })
|
|
506
625
|
);
|
|
507
626
|
}
|
|
508
627
|
|
|
@@ -1235,6 +1354,7 @@ function DefaultArticleTemplate({ record, query }) {
|
|
|
1235
1354
|
title: record.title || record.href || "Untitled",
|
|
1236
1355
|
annotation: record.annotation,
|
|
1237
1356
|
summary: record.summary || record.summaryValue || "",
|
|
1357
|
+
summaryMarkdown: record.summaryMarkdown || record.summary || record.summaryValue || "",
|
|
1238
1358
|
metadata,
|
|
1239
1359
|
query
|
|
1240
1360
|
}
|