triiiceratops 0.10.1 → 0.10.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.
@@ -1,4 +1,4 @@
1
- import { a as t } from "./X-BUzsFa3u.js";
1
+ import { a as t } from "./X-Dgb3I7Ob.js";
2
2
  const a = (
3
3
  /** @type {(inputs: {}) => LocalizedString} */
4
4
  () => (
@@ -33,8 +33,8 @@
33
33
  }
34
34
  }
35
35
 
36
- function navigate(result: any) {
37
- const canvas = viewerState.canvases[result.canvasIndex];
36
+ function navigate(canvasIndex: number) {
37
+ const canvas = viewerState.canvases[canvasIndex];
38
38
  if (canvas) {
39
39
  viewerState.setCanvas(canvas.id);
40
40
  }
@@ -121,32 +121,44 @@
121
121
  })}
122
122
  </div>
123
123
 
124
- {#each viewerState.searchResults as result, i}
124
+ {#each viewerState.searchResults as group}
125
125
  <button
126
- class="w-full text-left card bg-base-100 shadow hover:shadow-md transition-all p-4 text-sm group border border-transparent hover:border-primary focus:outline-none focus:ring-2 focus:ring-primary"
127
- onclick={() => navigate(result)}
126
+ class="w-full text-left bg-base-100 shadow-sm border border-base-200 rounded-box cursor-pointer hover:shadow-md transition-all block p-0 select-none"
127
+ onclick={() => navigate(group.canvasIndex)}
128
128
  >
129
- <div class="flex justify-between items-baseline mb-1">
130
- <span
131
- class="font-bold text-xs opacity-70 bg-base-200 px-1.5 py-0.5 rounded"
132
- >{result.canvasLabel}</span
129
+ <div
130
+ class="text-sm font-bold opacity-80 bg-base-200/50 flex items-center justify-between py-2 px-3 border-b border-base-200"
131
+ >
132
+ <span>{group.canvasLabel}</span>
133
+ <span class="badge badge-sm badge-ghost"
134
+ >{group.hits.length}
135
+ {group.hits.length === 1
136
+ ? 'match'
137
+ : 'matches'}</span
133
138
  >
134
139
  </div>
135
-
136
- {#if result.type === 'hit'}
137
- <div class="leading-relaxed">
138
- <span>{@html result.before}</span>
139
- <span
140
- class="bg-yellow-200 text-yellow-900 font-bold px-0.5 rounded"
141
- >{@html result.match}</span
140
+ <div class="p-0">
141
+ {#each group.hits.slice(0, 1) as result}
142
+ <div
143
+ class="p-3 text-sm border-b border-base-200 last:border-none hover:bg-base-200/30 transition-colors"
142
144
  >
143
- <span>{@html result.after}</span>
144
- </div>
145
- {:else}
146
- <div class="leading-relaxed">
147
- {result.match}
148
- </div>
149
- {/if}
145
+ {#if result.type === 'hit'}
146
+ <div class="leading-relaxed">
147
+ <span>{@html result.before}</span>
148
+ <span
149
+ class="bg-yellow-200 text-yellow-900 font-bold px-0.5 rounded"
150
+ >{@html result.match}</span
151
+ >
152
+ <span>{@html result.after}</span>
153
+ </div>
154
+ {:else}
155
+ <div class="leading-relaxed">
156
+ {@html result.match}
157
+ </div>
158
+ {/if}
159
+ </div>
160
+ {/each}
161
+ </div>
150
162
  </button>
151
163
  {/each}
152
164
  {/if}
@@ -356,6 +356,27 @@
356
356
  }
357
357
  });
358
358
 
359
+ // Auto-scroll active thumbnail into view
360
+ $effect(() => {
361
+ if (!galleryElement || !viewerState.canvasId) return;
362
+ // Use a slight timeout to ensure DOM is ready/layout is stable if needed,
363
+ // though usually effect runs after render.
364
+ const id = viewerState.canvasId;
365
+
366
+ // requestAnimationFrame to ensure we are in a good painting frame?
367
+ // Or just direct. Svelte 5 effects are post-dom-update.
368
+ const activeEl = galleryElement.querySelector(
369
+ `[data-id="${CSS.escape(id)}"]`,
370
+ );
371
+ if (activeEl) {
372
+ activeEl.scrollIntoView({
373
+ behavior: 'smooth',
374
+ block: 'nearest',
375
+ inline: 'center',
376
+ });
377
+ }
378
+ });
379
+
359
380
  // Switch to horizontal layout if height is small or docked to top/bottom
360
381
  let isHorizontal = $derived(
361
382
  dockSide === 'top' ||
@@ -483,7 +504,7 @@
483
504
 
484
505
  <!-- Content (Grid or Horizontal Scroll) -->
485
506
  <div
486
- class="flex-1 px-1 bg-base-100 {isHorizontal
507
+ class="flex-1 p-1 bg-base-100 {isHorizontal
487
508
  ? 'overflow-x-auto overflow-y-hidden h-full'
488
509
  : 'overflow-y-auto overflow-x-hidden'}"
489
510
  >
@@ -503,6 +524,7 @@
503
524
  ? 'ring-2 ring-primary bg-primary/5'
504
525
  : ''}"
505
526
  onclick={() => selectCanvas(thumb.id)}
527
+ data-id={thumb.id}
506
528
  aria-label="Select canvas {thumb.label}"
507
529
  >
508
530
  <div
@@ -1,4 +1,4 @@
1
- import { a as t } from "./X-BUzsFa3u.js";
1
+ import { a as t } from "./X-Dgb3I7Ob.js";
2
2
  const s = (
3
3
  /** @type {(inputs: {}) => LocalizedString} */
4
4
  () => (
@@ -4,10 +4,10 @@ var nt = (s, e, t) => Mf(s, typeof e != "symbol" ? e + "" : e, t);
4
4
  import "svelte/internal/disclose-version";
5
5
  import * as d from "svelte/internal/client";
6
6
  import { getContext as Rf, onMount as If, onDestroy as Cf } from "svelte";
7
- import { m as xl, g as as, l as Pf, s as Df, X as bl, c as Lf, V as Of } from "../X-BUzsFa3u.js";
7
+ import { m as xl, g as as, l as Pf, s as Df, X as bl, c as Lf, V as Of } from "../X-Dgb3I7Ob.js";
8
8
  import Hs from "openseadragon";
9
- import { A as Bf } from "../ArrowCounterClockwise-C6n_F9YZ.js";
10
- import { q as Nf, h as Ff, c as kf, j as Uf, k as $f, t as Gf, u as Hf, v as Vf, r as Xf, s as zf, m as jf, i as Yf, g as Wf, a as Zf, n as qf, f as Kf, e as Jf, b as Qf, d as ep, o as tp, l as sp, p as ip } from "../annotation_tool_point-CCJi2I8J.js";
9
+ import { A as Bf } from "../ArrowCounterClockwise-CvTUOlYp.js";
10
+ import { q as Nf, h as Ff, c as kf, j as Uf, k as $f, t as Gf, u as Hf, v as Vf, r as Xf, s as zf, m as jf, i as Yf, g as Wf, a as Zf, n as qf, f as Kf, e as Jf, b as Qf, d as ep, o as tp, l as sp, p as ip } from "../annotation_tool_point-CKkqbVDq.js";
11
11
  import "manifesto.js";
12
12
  var np = Object.defineProperty, rp = (s, e, t) => e in s ? np(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t, El = (s, e, t) => rp(s, typeof e != "symbol" ? e + "" : e, t), wl = Object.prototype.hasOwnProperty;
13
13
  function Vs(s, e) {
@@ -1,9 +1,9 @@
1
1
  import "svelte/internal/disclose-version";
2
2
  import * as e from "svelte/internal/client";
3
3
  import { getContext as s0 } from "svelte";
4
- import { l as l0, s as i0, X as n0, c as o0, V as c0, g as v0 } from "../X-BUzsFa3u.js";
5
- import { A as d0 } from "../ArrowCounterClockwise-C6n_F9YZ.js";
6
- import { i as _0, a as g0, b as f0, g as u0, c as h0, e as m0, d as p0, f as b0 } from "../image_filters_reset-CWe7vTJU.js";
4
+ import { l as l0, s as i0, X as n0, c as o0, V as c0, g as v0 } from "../X-Dgb3I7Ob.js";
5
+ import { A as d0 } from "../ArrowCounterClockwise-CvTUOlYp.js";
6
+ import { i as _0, a as g0, b as f0, g as u0, c as h0, e as m0, d as p0, f as b0 } from "../image_filters_reset-DZrbHhqM.js";
7
7
  const G = {
8
8
  brightness: 100,
9
9
  contrast: 100,
@@ -292,7 +292,8 @@ export class ViewerState {
292
292
  throw new Error('Search request failed');
293
293
  const data = await response.json();
294
294
  const resources = data.resources || [];
295
- const processedResults = [];
295
+ // Group results by canvas index
296
+ const resultsByCanvas = new Map();
296
297
  // Helper to parse xywh
297
298
  const parseSelector = (onVal) => {
298
299
  const val = typeof onVal === 'string'
@@ -308,73 +309,84 @@ export class ViewerState {
308
309
  return coords; // [x, y, w, h]
309
310
  return null;
310
311
  };
312
+ // Helper to unescape mark tags
313
+ const decodeMark = (str) => {
314
+ if (!str)
315
+ return '';
316
+ return str
317
+ .replace(/&lt;mark&gt;/g, '<mark>')
318
+ .replace(/&lt;\/mark&gt;/g, '</mark>');
319
+ };
311
320
  if (data.hits) {
312
321
  for (const hit of data.hits) {
313
322
  // hits have property 'annotations' which is array of ids
314
- // Collapse all annotations for this hit into a single result per canvas
315
323
  const annotations = hit.annotations || [];
316
- const hitBoundsByCanvas = new Map();
324
+ // We need to determine which canvas this hit belongs to.
325
+ // A hit might technically span annotations on multiple canvases (unlikely for IIIF Content Search),
326
+ // but usually it's associated with specific annotations on one canvas.
327
+ // We will take the first valid canvas we find for the annotations.
328
+ let canvasIndex = -1;
329
+ let bounds = null;
330
+ let allBounds = [];
317
331
  for (const annoId of annotations) {
318
332
  const annotation = resources.find((r) => r['@id'] === annoId || r.id === annoId);
319
333
  if (annotation && annotation.on) {
320
- // annotation.on can be "canvas-id" or "canvas-id#xywh=..."
321
334
  const onVal = typeof annotation.on === 'string'
322
335
  ? annotation.on
323
336
  : annotation.on['@id'] || annotation.on.id;
324
337
  const cleanOn = onVal.split('#')[0];
325
- const bounds = parseSelector(onVal);
326
- const canvasIndex = this.canvases.findIndex((c) => c.id === cleanOn);
327
- if (canvasIndex >= 0) {
328
- if (!hitBoundsByCanvas.has(canvasIndex)) {
329
- const canvas = this.canvases[canvasIndex];
330
- // Try to get a label
331
- let label = 'Canvas ' + (canvasIndex + 1);
332
- try {
333
- if (canvas.getLabel) {
334
- const l = canvas.getLabel();
335
- if (Array.isArray(l) &&
336
- l.length > 0)
337
- label = l[0].value;
338
- else if (typeof l === 'string')
339
- label = l;
340
- }
341
- else if (canvas.label) {
342
- // Fallback if raw object
343
- if (typeof canvas.label === 'string')
344
- label = canvas.label;
345
- else if (Array.isArray(canvas.label))
346
- label = canvas.label[0]?.value;
347
- }
348
- }
349
- catch (e) {
350
- /* ignore */
351
- }
352
- hitBoundsByCanvas.set(canvasIndex, {
353
- label: String(label),
354
- bounds: [],
355
- });
338
+ const b = parseSelector(onVal);
339
+ const cIndex = this.canvases.findIndex((c) => c.id === cleanOn);
340
+ if (cIndex >= 0) {
341
+ // If we haven't set a canvas yet, set it
342
+ if (canvasIndex === -1) {
343
+ canvasIndex = cIndex;
356
344
  }
357
- if (bounds) {
358
- hitBoundsByCanvas
359
- .get(canvasIndex)
360
- .bounds.push(bounds);
345
+ // If we found bounds, add them
346
+ if (b) {
347
+ allBounds.push(b);
348
+ if (!bounds)
349
+ bounds = b;
361
350
  }
362
351
  }
363
352
  }
364
353
  }
365
- // Create one result per canvas for this hit
366
- for (const [canvasIndex, data] of hitBoundsByCanvas) {
367
- processedResults.push({
354
+ if (canvasIndex >= 0) {
355
+ if (!resultsByCanvas.has(canvasIndex)) {
356
+ const canvas = this.canvases[canvasIndex];
357
+ let label = 'Canvas ' + (canvasIndex + 1);
358
+ try {
359
+ if (canvas.getLabel) {
360
+ const l = canvas.getLabel();
361
+ if (Array.isArray(l) && l.length > 0)
362
+ label = l[0].value;
363
+ else if (typeof l === 'string')
364
+ label = l;
365
+ }
366
+ else if (canvas.label) {
367
+ // Fallback if raw object
368
+ if (typeof canvas.label === 'string')
369
+ label = canvas.label;
370
+ else if (Array.isArray(canvas.label))
371
+ label = canvas.label[0]?.value;
372
+ }
373
+ }
374
+ catch (e) {
375
+ /* ignore */
376
+ }
377
+ resultsByCanvas.set(canvasIndex, {
378
+ canvasIndex,
379
+ canvasLabel: String(label),
380
+ hits: [],
381
+ });
382
+ }
383
+ resultsByCanvas.get(canvasIndex).hits.push({
368
384
  type: 'hit',
369
- before: hit.before,
370
- match: hit.match,
371
- after: hit.after,
372
- canvasIndex,
373
- canvasLabel: data.label,
374
- // Store all bounds for this hit on this canvas
375
- allBounds: data.bounds,
376
- // Keep first bounds for backwards compatibility
377
- bounds: data.bounds.length > 0 ? data.bounds[0] : null,
385
+ before: decodeMark(hit.before),
386
+ match: decodeMark(hit.match),
387
+ after: decodeMark(hit.after),
388
+ bounds,
389
+ allBounds,
378
390
  });
379
391
  }
380
392
  }
@@ -410,46 +422,54 @@ export class ViewerState {
410
422
  catch (e) {
411
423
  /* ignore */
412
424
  }
413
- processedResults.push({
425
+ if (!resultsByCanvas.has(canvasIndex)) {
426
+ resultsByCanvas.set(canvasIndex, {
427
+ canvasIndex,
428
+ canvasLabel: String(label),
429
+ hits: [],
430
+ });
431
+ }
432
+ resultsByCanvas.get(canvasIndex).hits.push({
414
433
  type: 'resource',
415
- match: res.resource && res.resource.chars
434
+ match: decodeMark(res.resource && res.resource.chars
416
435
  ? res.resource.chars
417
- : res.chars || '',
418
- canvasIndex,
419
- canvasLabel: String(label),
436
+ : res.chars || ''),
420
437
  bounds,
438
+ allBounds: bounds ? [bounds] : [],
421
439
  });
422
440
  }
423
441
  }
424
442
  }
425
- this.searchResults = processedResults;
443
+ // Convert Map to Array and Sort
444
+ this.searchResults = Array.from(resultsByCanvas.values()).sort((a, b) => a.canvasIndex - b.canvasIndex);
426
445
  // Generate ephemeral search annotations
427
- // Create one annotation per bound location (flatten allBounds)
446
+ // We need to flatten our grouped structure to generate the annotations list
428
447
  let annotationIndex = 0;
429
- this.searchAnnotations = processedResults.flatMap((r) => {
430
- const canvas = this.canvases[r.canvasIndex];
431
- // Use allBounds if available, otherwise fall back to single bounds
432
- const boundsArray = r.allBounds && r.allBounds.length > 0
433
- ? r.allBounds
434
- : r.bounds
435
- ? [r.bounds]
436
- : [];
437
- return boundsArray.map((bounds) => {
438
- const on = `${canvas.id}#xywh=${bounds.join(',')}`;
439
- return {
440
- '@id': `urn:search-hit:${annotationIndex++}`,
441
- '@type': 'oa:Annotation',
442
- motivation: 'sc:painting',
443
- on: on,
444
- canvasId: canvas.id,
445
- resource: {
446
- '@type': 'cnt:ContentAsText',
447
- chars: r.match,
448
- },
449
- // Flag to identify styling in Overlay?
450
- // Or just standard rendering.
451
- isSearchHit: true,
452
- };
448
+ this.searchAnnotations = this.searchResults.flatMap((group) => {
449
+ const canvas = this.canvases[group.canvasIndex];
450
+ return group.hits.flatMap((hit) => {
451
+ const boundsArray = hit.allBounds && hit.allBounds.length > 0
452
+ ? hit.allBounds
453
+ : hit.bounds
454
+ ? [hit.bounds]
455
+ : [];
456
+ return boundsArray.map((bounds) => {
457
+ const on = `${canvas.id}#xywh=${bounds.join(',')}`;
458
+ return {
459
+ '@id': `urn:search-hit:${annotationIndex++}`,
460
+ '@type': 'oa:Annotation',
461
+ motivation: 'sc:painting',
462
+ on: on,
463
+ canvasId: canvas.id,
464
+ resource: {
465
+ '@type': 'cnt:ContentAsText',
466
+ chars: hit.match,
467
+ },
468
+ // Flag to identify styling in Overlay?
469
+ // Or just standard rendering.
470
+ isSearchHit: true,
471
+ };
472
+ });
453
473
  });
454
474
  });
455
475
  }