@semiont/react-ui 0.2.36 → 0.2.37

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.
Files changed (33) hide show
  1. package/dist/index.d.mts +8 -0
  2. package/dist/index.mjs +252 -166
  3. package/dist/index.mjs.map +1 -1
  4. package/package.json +1 -1
  5. package/src/components/CodeMirrorRenderer.tsx +71 -203
  6. package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +142 -0
  7. package/src/components/__tests__/LiveRegion.hooks.test.tsx +79 -0
  8. package/src/components/__tests__/ResizeHandle.test.tsx +165 -0
  9. package/src/components/__tests__/SessionExpiryBanner.test.tsx +123 -0
  10. package/src/components/__tests__/StatusDisplay.test.tsx +160 -0
  11. package/src/components/__tests__/Toolbar.test.tsx +110 -0
  12. package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +285 -0
  13. package/src/components/annotation-popups/__tests__/SharedPopupElements.test.tsx +273 -0
  14. package/src/components/modals/__tests__/KeyboardShortcutsHelpModal.test.tsx +90 -0
  15. package/src/components/modals/__tests__/ProposeEntitiesModal.test.tsx +129 -0
  16. package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +180 -0
  17. package/src/components/navigation/__tests__/ObservableLink.test.tsx +90 -0
  18. package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +169 -0
  19. package/src/components/navigation/__tests__/SortableResourceTab.test.tsx +371 -0
  20. package/src/components/resource/AnnotateView.tsx +27 -153
  21. package/src/components/resource/__tests__/AnnotationHistory.test.tsx +349 -0
  22. package/src/components/resource/__tests__/HistoryEvent.test.tsx +492 -0
  23. package/src/components/resource/__tests__/event-formatting.test.ts +273 -0
  24. package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +226 -0
  25. package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +188 -0
  26. package/src/components/resource/panels/__tests__/PanelHeader.test.tsx +69 -0
  27. package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +445 -0
  28. package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +271 -0
  29. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +210 -0
  30. package/src/components/settings/__tests__/SettingsPanel.test.tsx +190 -0
  31. package/src/components/viewers/__tests__/ImageViewer.test.tsx +63 -0
  32. package/src/integrations/__tests__/css-modules-helper.test.tsx +225 -0
  33. package/src/integrations/__tests__/styled-components-theme.test.ts +179 -0
package/dist/index.d.mts CHANGED
@@ -1724,6 +1724,13 @@ declare function usePreloadTranslations(): {
1724
1724
  isLoaded: (locale: string) => boolean;
1725
1725
  };
1726
1726
 
1727
+ /**
1728
+ * Pure logic extracted from CodeMirrorRenderer
1729
+ *
1730
+ * These functions have zero dependency on CodeMirror's DOM or React.
1731
+ * They handle position conversion, tooltip generation, and decoration metadata.
1732
+ */
1733
+
1727
1734
  type Annotation$g = components['schemas']['Annotation'];
1728
1735
  interface TextSegment {
1729
1736
  exact: string;
@@ -1731,6 +1738,7 @@ interface TextSegment {
1731
1738
  start: number;
1732
1739
  end: number;
1733
1740
  }
1741
+
1734
1742
  interface Props$b {
1735
1743
  content: string;
1736
1744
  segments?: TextSegment[];
package/dist/index.mjs CHANGED
@@ -26141,10 +26141,10 @@ function scrollAnnotationIntoView(annotationId, rootElement, options = {}) {
26141
26141
  }
26142
26142
 
26143
26143
  // src/components/CodeMirrorRenderer.tsx
26144
+ import { isReference as isReference3 } from "@semiont/api-client";
26145
+
26146
+ // src/lib/codemirror-logic.ts
26144
26147
  import { isHighlight as isHighlight2, isReference as isReference2, isResolvedReference as isResolvedReference2, isComment as isComment2, isAssessment, isTag as isTag3, getBodySource as getBodySource3 } from "@semiont/api-client";
26145
- import { jsx as jsx8 } from "react/jsx-runtime";
26146
- var updateAnnotationsEffect = StateEffect.define();
26147
- var updateWidgetsEffect = StateEffect.define();
26148
26148
  function convertSegmentPositions(segments, content4) {
26149
26149
  if (!content4.includes("\r\n")) {
26150
26150
  return segments;
@@ -26193,34 +26193,148 @@ function getAnnotationTooltip(annotation) {
26193
26193
  }
26194
26194
  return "Annotation";
26195
26195
  }
26196
+ function getAnnotationDecorationMeta(annotation, isNew) {
26197
+ const baseClassName = Object.values(ANNOTATORS).find((a15) => a15.matchesAnnotation(annotation))?.className || "annotation-highlight";
26198
+ const className = isNew ? `${baseClassName} annotation-sparkle` : baseClassName;
26199
+ const isHighlightAnn = isHighlight2(annotation);
26200
+ const isReferenceAnn = isReference2(annotation);
26201
+ const isCommentAnn = isComment2(annotation);
26202
+ const isAssessmentAnn = isAssessment(annotation);
26203
+ const isTagAnn = isTag3(annotation);
26204
+ let annotationType = "highlight";
26205
+ if (isCommentAnn) annotationType = "comment";
26206
+ else if (isReferenceAnn) annotationType = "reference";
26207
+ else if (isAssessmentAnn) annotationType = "assessment";
26208
+ else if (isTagAnn) annotationType = "tag";
26209
+ else if (isHighlightAnn) annotationType = "highlight";
26210
+ return {
26211
+ className,
26212
+ annotationType,
26213
+ annotationId: annotation.id,
26214
+ tooltip: getAnnotationTooltip(annotation)
26215
+ };
26216
+ }
26217
+ function computeAnnotationDecorations(segments, newAnnotationIds) {
26218
+ return segments.filter((s11) => s11.annotation).sort((a15, b8) => a15.start - b8.start).map((segment) => {
26219
+ const annotation = segment.annotation;
26220
+ const isNew = newAnnotationIds?.has(annotation.id) || false;
26221
+ return {
26222
+ start: segment.start,
26223
+ end: segment.end,
26224
+ meta: getAnnotationDecorationMeta(annotation, isNew)
26225
+ };
26226
+ });
26227
+ }
26228
+ function computeWidgetDecorations(segments, generatingReferenceId, getTargetDocumentName) {
26229
+ return segments.filter((s11) => s11.annotation && isReference2(s11.annotation)).sort((a15, b8) => a15.end - b8.end).map((segment) => {
26230
+ const annotation = segment.annotation;
26231
+ const bodySource = getBodySource3(annotation.body);
26232
+ const targetName = bodySource ? getTargetDocumentName?.(bodySource) : void 0;
26233
+ const isGenerating = generatingReferenceId ? annotation.id === generatingReferenceId : false;
26234
+ return {
26235
+ annotationId: annotation.id,
26236
+ position: segment.end,
26237
+ targetName,
26238
+ isGenerating,
26239
+ bodySource: bodySource ?? void 0
26240
+ };
26241
+ });
26242
+ }
26243
+
26244
+ // src/lib/codemirror-handlers.ts
26245
+ function handleAnnotationClick(target, segmentsById, eventBus) {
26246
+ const annotationElement = target.closest("[data-annotation-id]");
26247
+ const annotationId = annotationElement?.getAttribute("data-annotation-id");
26248
+ if (!annotationId) return false;
26249
+ const segment = segmentsById.get(annotationId);
26250
+ if (!segment?.annotation) return false;
26251
+ eventBus.get("browse:click").next({
26252
+ annotationId,
26253
+ motivation: segment.annotation.motivation
26254
+ });
26255
+ return true;
26256
+ }
26257
+ function handleWidgetClick(target) {
26258
+ const widget = target.closest(".reference-preview-widget");
26259
+ if (!widget || widget.dataset.widgetGenerating === "true") {
26260
+ return { handled: false };
26261
+ }
26262
+ const annotationId = widget.dataset.widgetAnnotationId;
26263
+ const bodySource = widget.dataset.widgetBodySource;
26264
+ const isResolved = widget.dataset.widgetResolved === "true";
26265
+ if (!annotationId) return { handled: false };
26266
+ if (isResolved && bodySource) {
26267
+ return {
26268
+ handled: true,
26269
+ action: "navigate",
26270
+ documentId: bodySource,
26271
+ annotationId
26272
+ };
26273
+ }
26274
+ return {
26275
+ handled: true,
26276
+ action: "browse-click",
26277
+ annotationId,
26278
+ motivation: widget.dataset.widgetMotivation || "linking"
26279
+ };
26280
+ }
26281
+ function dispatchWidgetClick(result, eventBus) {
26282
+ if (!result.handled) return;
26283
+ if (result.action === "navigate" && result.documentId) {
26284
+ eventBus.get("browse:reference-navigate").next({ documentId: result.documentId });
26285
+ } else if (result.action === "browse-click" && result.annotationId) {
26286
+ eventBus.get("browse:click").next({
26287
+ annotationId: result.annotationId,
26288
+ motivation: result.motivation || "linking"
26289
+ });
26290
+ }
26291
+ }
26292
+ function handleWidgetMouseEnter(target) {
26293
+ const widget = target.closest(".reference-preview-widget");
26294
+ if (!widget || widget.dataset.widgetGenerating === "true") {
26295
+ return { showPreview: false, widget: null };
26296
+ }
26297
+ const indicator = widget.querySelector(".reference-indicator");
26298
+ if (indicator) indicator.style.opacity = "1";
26299
+ if (widget.dataset.widgetResolved === "true" && widget.dataset.widgetTargetName) {
26300
+ return {
26301
+ showPreview: true,
26302
+ targetName: widget.dataset.widgetTargetName,
26303
+ widget
26304
+ };
26305
+ }
26306
+ return { showPreview: false, widget };
26307
+ }
26308
+ function handleWidgetMouseLeave(target) {
26309
+ const widget = target.closest(".reference-preview-widget");
26310
+ if (!widget) {
26311
+ return { hidePreview: false, widget: null };
26312
+ }
26313
+ const indicator = widget.querySelector(".reference-indicator");
26314
+ if (indicator) indicator.style.opacity = "0.6";
26315
+ if (widget.dataset.widgetResolved === "true") {
26316
+ return { hidePreview: true, widget };
26317
+ }
26318
+ return { hidePreview: false, widget };
26319
+ }
26320
+
26321
+ // src/components/CodeMirrorRenderer.tsx
26322
+ import { jsx as jsx8 } from "react/jsx-runtime";
26323
+ var updateAnnotationsEffect = StateEffect.define();
26324
+ var updateWidgetsEffect = StateEffect.define();
26196
26325
  function buildAnnotationDecorations(segments, newAnnotationIds) {
26197
26326
  const builder = new RangeSetBuilder();
26198
- const annotatedSegments = segments.filter((s11) => s11.annotation).sort((a15, b8) => a15.start - b8.start);
26199
- for (const segment of annotatedSegments) {
26200
- if (!segment.annotation) continue;
26201
- const isNew = newAnnotationIds?.has(segment.annotation.id) || false;
26202
- const baseClassName = Object.values(ANNOTATORS).find((a15) => a15.matchesAnnotation(segment.annotation))?.className || "annotation-highlight";
26203
- const className = isNew ? `${baseClassName} annotation-sparkle` : baseClassName;
26204
- const isHighlightAnn = isHighlight2(segment.annotation);
26205
- const isReferenceAnn = isReference2(segment.annotation);
26206
- const isCommentAnn = isComment2(segment.annotation);
26207
- const isAssessmentAnn = isAssessment(segment.annotation);
26208
- const isTagAnn = isTag3(segment.annotation);
26209
- let annotationType = "highlight";
26210
- if (isCommentAnn) annotationType = "comment";
26211
- else if (isReferenceAnn) annotationType = "reference";
26212
- else if (isAssessmentAnn) annotationType = "assessment";
26213
- else if (isTagAnn) annotationType = "tag";
26214
- else if (isHighlightAnn) annotationType = "highlight";
26327
+ const entries = computeAnnotationDecorations(segments, newAnnotationIds);
26328
+ for (const { start: start2, end, meta: meta2 } of entries) {
26215
26329
  const decoration = Decoration.mark({
26216
- class: className,
26330
+ class: meta2.className,
26217
26331
  attributes: {
26218
- "data-annotation-id": segment.annotation.id,
26219
- "data-annotation-type": annotationType,
26220
- title: getAnnotationTooltip(segment.annotation)
26332
+ "data-annotation-id": meta2.annotationId,
26333
+ "data-annotation-type": meta2.annotationType,
26334
+ title: meta2.tooltip
26221
26335
  }
26222
26336
  });
26223
- builder.add(segment.start, segment.end, decoration);
26337
+ builder.add(start2, end, decoration);
26224
26338
  }
26225
26339
  return builder.finish();
26226
26340
  }
@@ -26243,26 +26357,23 @@ function createAnnotationDecorationsField() {
26243
26357
  }
26244
26358
  function buildWidgetDecorations(_content, segments, generatingReferenceId, getTargetDocumentName) {
26245
26359
  const builder = new RangeSetBuilder();
26246
- const allAnnotatedSegments = segments.filter((s11) => s11.annotation).sort((a15, b8) => a15.end - b8.end);
26247
- for (const segment of allAnnotatedSegments) {
26248
- if (!segment.annotation) continue;
26249
- const annotation = segment.annotation;
26250
- if (isReference2(annotation)) {
26251
- const bodySource = getBodySource3(annotation.body);
26252
- const targetName = bodySource ? getTargetDocumentName?.(bodySource) : void 0;
26253
- const isGenerating = generatingReferenceId ? annotation.id === generatingReferenceId : false;
26254
- const widget = new ReferenceResolutionWidget(
26255
- annotation,
26256
- targetName,
26257
- isGenerating
26258
- );
26259
- builder.add(
26260
- segment.end,
26261
- segment.end,
26262
- Decoration.widget({ widget, side: 1 })
26263
- );
26360
+ const widgetMetas = computeWidgetDecorations(segments, generatingReferenceId, getTargetDocumentName);
26361
+ const annotationsByEnd = /* @__PURE__ */ new Map();
26362
+ for (const s11 of segments) {
26363
+ if (s11.annotation && isReference3(s11.annotation)) {
26364
+ annotationsByEnd.set(s11.end, s11);
26264
26365
  }
26265
26366
  }
26367
+ for (const meta2 of widgetMetas) {
26368
+ const segment = annotationsByEnd.get(meta2.position);
26369
+ if (!segment?.annotation) continue;
26370
+ const widget = new ReferenceResolutionWidget(
26371
+ segment.annotation,
26372
+ meta2.targetName,
26373
+ meta2.isGenerating
26374
+ );
26375
+ builder.add(meta2.position, meta2.position, Decoration.widget({ widget, side: 1 }));
26376
+ }
26266
26377
  return builder.finish();
26267
26378
  }
26268
26379
  var widgetDecorationsField = StateField.define({
@@ -26338,27 +26449,18 @@ function CodeMirrorRenderer({
26338
26449
  onChange(newContent);
26339
26450
  }
26340
26451
  }),
26341
- // Handle clicks on annotations
26452
+ // Handle clicks on annotations — delegates to extracted handler
26342
26453
  EditorView.domEventHandlers({
26343
26454
  click: (event, _view) => {
26344
26455
  const target = event.target;
26345
- const annotationElement = target.closest("[data-annotation-id]");
26346
- const annotationId = annotationElement?.getAttribute("data-annotation-id");
26347
- if (annotationId && eventBusRef.current) {
26348
- const segment = segmentsByIdRef.current.get(annotationId);
26349
- if (segment?.annotation) {
26350
- event.preventDefault();
26351
- eventBusRef.current.get("browse:click").next({
26352
- annotationId,
26353
- motivation: segment.annotation.motivation
26354
- });
26355
- return true;
26356
- }
26456
+ if (eventBusRef.current && handleAnnotationClick(target, segmentsByIdRef.current, eventBusRef.current)) {
26457
+ event.preventDefault();
26458
+ return true;
26357
26459
  }
26358
26460
  return false;
26359
26461
  }
26360
26462
  }),
26361
- // Style the editor - use CSS string to inject !important rules
26463
+ // Style the editor
26362
26464
  EditorView.baseTheme({
26363
26465
  "&.cm-editor": {
26364
26466
  height: "100%",
@@ -26369,7 +26471,6 @@ function CodeMirrorRenderer({
26369
26471
  },
26370
26472
  ".cm-scroller": {
26371
26473
  overflow: "visible !important",
26372
- // Let parent container handle scrolling
26373
26474
  height: "auto !important"
26374
26475
  },
26375
26476
  ".cm-content, .cm-gutters": {
@@ -26423,54 +26524,41 @@ function CodeMirrorRenderer({
26423
26524
  const annotationElement = target.closest("[data-annotation-id]");
26424
26525
  if (annotationElement) handleMouseLeave();
26425
26526
  };
26426
- const handleWidgetClick = (e6) => {
26527
+ const onWidgetClick = (e6) => {
26427
26528
  const target = e6.target;
26428
- const widget = target.closest(".reference-preview-widget");
26429
- if (!widget || widget.dataset.widgetGenerating === "true") return;
26529
+ const result = handleWidgetClick(target);
26530
+ if (!result.handled) return;
26430
26531
  e6.preventDefault();
26431
26532
  e6.stopPropagation();
26432
- const annotationId = widget.dataset.widgetAnnotationId;
26433
- const bodySource = widget.dataset.widgetBodySource;
26434
- const isResolved = widget.dataset.widgetResolved === "true";
26435
- if (!annotationId || !eventBusRef.current) return;
26436
- if (isResolved && bodySource) {
26437
- eventBusRef.current.get("browse:reference-navigate").next({ documentId: bodySource });
26438
- } else {
26439
- const motivation = widget.dataset.widgetMotivation || "linking";
26440
- eventBusRef.current.get("browse:click").next({ annotationId, motivation });
26533
+ if (eventBusRef.current) {
26534
+ dispatchWidgetClick(result, eventBusRef.current);
26441
26535
  }
26442
26536
  };
26443
- const handleWidgetMouseEnter = (e6) => {
26537
+ const onWidgetMouseEnter = (e6) => {
26444
26538
  const target = e6.target;
26445
- const widget = target.closest(".reference-preview-widget");
26446
- if (!widget || widget.dataset.widgetGenerating === "true") return;
26447
- const indicator = widget.querySelector(".reference-indicator");
26448
- if (indicator) indicator.style.opacity = "1";
26449
- if (widget.dataset.widgetResolved === "true" && widget.dataset.widgetTargetName) {
26450
- showWidgetPreview(widget, widget.dataset.widgetTargetName);
26539
+ const result = handleWidgetMouseEnter(target);
26540
+ if (result.showPreview && result.targetName && result.widget) {
26541
+ showWidgetPreview(result.widget, result.targetName);
26451
26542
  }
26452
26543
  };
26453
- const handleWidgetMouseLeave = (e6) => {
26544
+ const onWidgetMouseLeave = (e6) => {
26454
26545
  const target = e6.target;
26455
- const widget = target.closest(".reference-preview-widget");
26456
- if (!widget) return;
26457
- const indicator = widget.querySelector(".reference-indicator");
26458
- if (indicator) indicator.style.opacity = "0.6";
26459
- if (widget.dataset.widgetResolved === "true") {
26460
- hideWidgetPreview(widget);
26546
+ const result = handleWidgetMouseLeave(target);
26547
+ if (result.hidePreview && result.widget) {
26548
+ hideWidgetPreview(result.widget);
26461
26549
  }
26462
26550
  };
26463
26551
  container.addEventListener("mouseover", handleMouseOver);
26464
26552
  container.addEventListener("mouseout", handleMouseOut);
26465
- container.addEventListener("click", handleWidgetClick);
26466
- container.addEventListener("mouseenter", handleWidgetMouseEnter, true);
26467
- container.addEventListener("mouseleave", handleWidgetMouseLeave, true);
26553
+ container.addEventListener("click", onWidgetClick);
26554
+ container.addEventListener("mouseenter", onWidgetMouseEnter, true);
26555
+ container.addEventListener("mouseleave", onWidgetMouseLeave, true);
26468
26556
  return () => {
26469
26557
  container.removeEventListener("mouseover", handleMouseOver);
26470
26558
  container.removeEventListener("mouseout", handleMouseOut);
26471
- container.removeEventListener("click", handleWidgetClick);
26472
- container.removeEventListener("mouseenter", handleWidgetMouseEnter, true);
26473
- container.removeEventListener("mouseleave", handleWidgetMouseLeave, true);
26559
+ container.removeEventListener("click", onWidgetClick);
26560
+ container.removeEventListener("mouseenter", onWidgetMouseEnter, true);
26561
+ container.removeEventListener("mouseleave", onWidgetMouseLeave, true);
26474
26562
  cleanupHover();
26475
26563
  view.destroy();
26476
26564
  viewRef.current = null;
@@ -29271,13 +29359,13 @@ function PopupContainer({ children, position: position3, onClose, isOpen, wide =
29271
29359
 
29272
29360
  // src/components/image-annotation/AnnotationOverlay.tsx
29273
29361
  import { useMemo as useMemo2 } from "react";
29274
- import { getSvgSelector, isHighlight as isHighlight3, isReference as isReference3, isAssessment as isAssessment2, isComment as isComment3, isTag as isTag4, isBodyResolved, isResolvedReference as isResolvedReference3 } from "@semiont/api-client";
29362
+ import { getSvgSelector, isHighlight as isHighlight3, isReference as isReference4, isAssessment as isAssessment2, isComment as isComment3, isTag as isTag4, isBodyResolved, isResolvedReference as isResolvedReference3 } from "@semiont/api-client";
29275
29363
  import { parseSvgSelector } from "@semiont/api-client";
29276
29364
  import { jsx as jsx18, jsxs as jsxs9 } from "react/jsx-runtime";
29277
29365
  function getAnnotationColor(annotation) {
29278
29366
  if (isHighlight3(annotation)) {
29279
29367
  return { stroke: "rgb(250, 204, 21)", fill: "rgba(250, 204, 21, 0.2)" };
29280
- } else if (isReference3(annotation)) {
29368
+ } else if (isReference4(annotation)) {
29281
29369
  return { stroke: "rgb(59, 130, 246)", fill: "rgba(59, 130, 246, 0.2)" };
29282
29370
  } else if (isAssessment2(annotation)) {
29283
29371
  return { stroke: "rgb(239, 68, 68)", fill: "rgba(239, 68, 68, 0.2)" };
@@ -29299,7 +29387,7 @@ function getAnnotationTooltip2(annotation) {
29299
29387
  return "Tag";
29300
29388
  } else if (isResolvedReference3(annotation)) {
29301
29389
  return "Resolved Reference";
29302
- } else if (isReference3(annotation)) {
29390
+ } else if (isReference4(annotation)) {
29303
29391
  return "Unresolved Reference";
29304
29392
  }
29305
29393
  return "Annotation";
@@ -29335,7 +29423,7 @@ function AnnotationOverlay({
29335
29423
  const isHovered = annotation.id === hoveredAnnotationId;
29336
29424
  const isSelected = annotation.id === selectedAnnotationId;
29337
29425
  const colors = getAnnotationColor(annotation);
29338
- const isRef = isReference3(annotation);
29426
+ const isRef = isReference4(annotation);
29339
29427
  const isResolved = isRef && isBodyResolved(annotation.body);
29340
29428
  const statusEmoji = isRef ? isResolved ? "\u{1F517}" : "\u2753" : null;
29341
29429
  switch (parsed.type) {
@@ -30102,9 +30190,10 @@ function ProposeEntitiesModal({
30102
30190
  // src/components/resource/AnnotateView.tsx
30103
30191
  import { useRef as useRef12, useEffect as useEffect18, useCallback as useCallback13, lazy, Suspense } from "react";
30104
30192
  import { resourceUri as toResourceUri } from "@semiont/core";
30105
- import { getTextPositionSelector as getTextPositionSelector2, getTextQuoteSelector, getTargetSelector as getTargetSelector2, getMimeCategory, isPdfMimeType as isPdfMimeType2, extractContext, findTextWithContext, buildContentCache } from "@semiont/api-client";
30106
- import { jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
30107
- var PdfAnnotationCanvas = lazy(() => import("./PdfAnnotationCanvas.client-COQREPXU.mjs").then((mod) => ({ default: mod.PdfAnnotationCanvas })));
30193
+ import { getMimeCategory, isPdfMimeType as isPdfMimeType2 } from "@semiont/api-client";
30194
+
30195
+ // src/lib/text-segmentation.ts
30196
+ import { getTextPositionSelector as getTextPositionSelector2, getTextQuoteSelector, getTargetSelector as getTargetSelector2, findTextWithContext, buildContentCache } from "@semiont/api-client";
30108
30197
  function segmentTextWithAnnotations(content4, annotations) {
30109
30198
  if (!content4) {
30110
30199
  return [{ exact: "", start: 0, end: 0 }];
@@ -30164,6 +30253,37 @@ function segmentTextWithAnnotations(content4, annotations) {
30164
30253
  }
30165
30254
  return segments;
30166
30255
  }
30256
+
30257
+ // src/lib/text-selection-handler.ts
30258
+ import { extractContext } from "@semiont/api-client";
30259
+ function buildTextSelectors(content4, selectedText, start2, end) {
30260
+ if (!selectedText || start2 < 0 || end <= start2 || end > content4.length) {
30261
+ return null;
30262
+ }
30263
+ const context = extractContext(content4, start2, end);
30264
+ return [
30265
+ {
30266
+ type: "TextPositionSelector",
30267
+ start: start2,
30268
+ end
30269
+ },
30270
+ {
30271
+ type: "TextQuoteSelector",
30272
+ exact: selectedText,
30273
+ ...context.prefix && { prefix: context.prefix },
30274
+ ...context.suffix && { suffix: context.suffix }
30275
+ }
30276
+ ];
30277
+ }
30278
+ function fallbackTextPosition(content4, selectedText) {
30279
+ const start2 = content4.indexOf(selectedText);
30280
+ if (start2 === -1) return null;
30281
+ return { start: start2, end: start2 + selectedText.length };
30282
+ }
30283
+
30284
+ // src/components/resource/AnnotateView.tsx
30285
+ import { jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
30286
+ var PdfAnnotationCanvas = lazy(() => import("./PdfAnnotationCanvas.client-COQREPXU.mjs").then((mod) => ({ default: mod.PdfAnnotationCanvas })));
30167
30287
  function AnnotateView({
30168
30288
  content: content4,
30169
30289
  mimeType = "text/plain",
@@ -30235,59 +30355,25 @@ function AnnotateView({
30235
30355
  const text7 = selection2.toString();
30236
30356
  const cmContainer = container.querySelector(".codemirror-renderer");
30237
30357
  const view = cmContainer?.__cmView;
30358
+ let start2;
30359
+ let end;
30238
30360
  if (!view || !view.posAtDOM) {
30239
- const start3 = content4.indexOf(text7);
30240
- if (start3 === -1) {
30241
- return;
30242
- }
30243
- const end2 = start3 + text7.length;
30244
- const context = extractContext(content4, start3, end2);
30245
- if (selectedMotivation) {
30246
- eventBus.get("mark:requested").next({
30247
- selector: [
30248
- {
30249
- type: "TextPositionSelector",
30250
- start: start3,
30251
- end: end2
30252
- },
30253
- {
30254
- type: "TextQuoteSelector",
30255
- exact: text7,
30256
- ...context.prefix && { prefix: context.prefix },
30257
- ...context.suffix && { suffix: context.suffix }
30258
- }
30259
- ],
30260
- motivation: selectedMotivation
30261
- });
30262
- selection2.removeAllRanges();
30263
- return;
30264
- }
30265
- return;
30266
- }
30267
- const start2 = view.posAtDOM(range.startContainer, range.startOffset);
30268
- const end = start2 + text7.length;
30269
- if (start2 >= 0) {
30270
- const context = extractContext(content4, start2, end);
30271
- if (selectedMotivation) {
30272
- eventBus.get("mark:requested").next({
30273
- selector: [
30274
- {
30275
- type: "TextPositionSelector",
30276
- start: start2,
30277
- end
30278
- },
30279
- {
30280
- type: "TextQuoteSelector",
30281
- exact: text7,
30282
- ...context.prefix && { prefix: context.prefix },
30283
- ...context.suffix && { suffix: context.suffix }
30284
- }
30285
- ],
30286
- motivation: selectedMotivation
30287
- });
30288
- selection2.removeAllRanges();
30289
- return;
30290
- }
30361
+ const pos = fallbackTextPosition(content4, text7);
30362
+ if (!pos) return;
30363
+ start2 = pos.start;
30364
+ end = pos.end;
30365
+ } else {
30366
+ start2 = view.posAtDOM(range.startContainer, range.startOffset);
30367
+ end = start2 + text7.length;
30368
+ }
30369
+ if (start2 >= 0 && selectedMotivation) {
30370
+ const selectors = buildTextSelectors(content4, text7, start2, end);
30371
+ if (!selectors) return;
30372
+ eventBus.get("mark:requested").next({
30373
+ selector: selectors,
30374
+ motivation: selectedMotivation
30375
+ });
30376
+ selection2.removeAllRanges();
30291
30377
  }
30292
30378
  };
30293
30379
  container.addEventListener("mouseup", handleMouseUp);
@@ -45562,7 +45648,7 @@ var BrowseView = memo(function BrowseView2({
45562
45648
  // src/components/resource/ResourceViewer.tsx
45563
45649
  import { useState as useState17, useEffect as useEffect23, useCallback as useCallback16, useRef as useRef16, useMemo as useMemo5 } from "react";
45564
45650
  import { resourceUri } from "@semiont/core";
45565
- import { getExactText as getExactText3, getTargetSelector as getTargetSelector4, isHighlight as isHighlight4, isAssessment as isAssessment3, isReference as isReference4, isComment as isComment4, isTag as isTag5, getBodySource as getBodySource4 } from "@semiont/api-client";
45651
+ import { getExactText as getExactText3, getTargetSelector as getTargetSelector4, isHighlight as isHighlight4, isAssessment as isAssessment3, isReference as isReference5, isComment as isComment4, isTag as isTag5, getBodySource as getBodySource4 } from "@semiont/api-client";
45566
45652
  import { jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
45567
45653
  function ResourceViewer({
45568
45654
  resource,
@@ -45694,7 +45780,7 @@ function ResourceViewer({
45694
45780
  const handleDeleteAnnotation = useCallback16((id2) => {
45695
45781
  eventBus.get("mark:delete").next({ annotationId: id2 });
45696
45782
  }, []);
45697
- const handleAnnotationClick = useCallback16((annotation, event) => {
45783
+ const handleAnnotationClick2 = useCallback16((annotation, event) => {
45698
45784
  const metadata = Object.values(ANNOTATORS).find((a15) => a15.matchesAnnotation(annotation));
45699
45785
  if (metadata?.hasSidePanel) {
45700
45786
  if (selectedClick === "detail") {
@@ -45705,8 +45791,8 @@ function ResourceViewer({
45705
45791
  return;
45706
45792
  }
45707
45793
  }
45708
- const isSimpleAnnotation = isHighlight4(annotation) || isAssessment3(annotation) || isComment4(annotation) || isReference4(annotation) || isTag5(annotation);
45709
- if (selectedClick === "follow" && isReference4(annotation)) {
45794
+ const isSimpleAnnotation = isHighlight4(annotation) || isAssessment3(annotation) || isComment4(annotation) || isReference5(annotation) || isTag5(annotation);
45795
+ if (selectedClick === "follow" && isReference5(annotation)) {
45710
45796
  const bodySource = getBodySource4(annotation.body);
45711
45797
  if (bodySource) {
45712
45798
  const resourceId = bodySource.split("/resources/")[1];
@@ -45734,7 +45820,7 @@ function ResourceViewer({
45734
45820
  const allAnnotations = [...highlights, ...references, ...assessments, ...comments, ...tags3];
45735
45821
  const annotation = allAnnotations.find((a15) => a15.id === annotationId);
45736
45822
  if (annotation) {
45737
- handleAnnotationClick(annotation);
45823
+ handleAnnotationClick2(annotation);
45738
45824
  }
45739
45825
  return;
45740
45826
  }
@@ -45742,12 +45828,12 @@ function ResourceViewer({
45742
45828
  const allAnnotations = [...highlights, ...references, ...assessments, ...comments, ...tags3];
45743
45829
  const annotation = allAnnotations.find((a15) => a15.id === annotationId);
45744
45830
  if (annotation) {
45745
- handleAnnotationClick(annotation);
45831
+ handleAnnotationClick2(annotation);
45746
45832
  }
45747
45833
  return;
45748
45834
  }
45749
45835
  eventBus.get("browse:panel-open").next({ panel: "annotations", scrollToAnnotationId: annotationId, motivation });
45750
- }, [highlights, references, assessments, comments, tags3, handleAnnotationClick, selectedClick]);
45836
+ }, [highlights, references, assessments, comments, tags3, handleAnnotationClick2, selectedClick]);
45751
45837
  useEventSubscriptions({
45752
45838
  // View mode
45753
45839
  "mark:mode-toggled": handleViewModeToggle,
@@ -46272,12 +46358,12 @@ function AssessmentPanel({
46272
46358
  document.addEventListener("keydown", handleEscape);
46273
46359
  return () => document.removeEventListener("keydown", handleEscape);
46274
46360
  }, [pendingAnnotation]);
46275
- const handleAnnotationClick = useCallback18(({ annotationId }) => {
46361
+ const handleAnnotationClick2 = useCallback18(({ annotationId }) => {
46276
46362
  setFocusedAnnotationId(annotationId);
46277
46363
  setTimeout(() => setFocusedAnnotationId(null), 3e3);
46278
46364
  }, []);
46279
46365
  useEventSubscriptions({
46280
- "browse:click": handleAnnotationClick
46366
+ "browse:click": handleAnnotationClick2
46281
46367
  });
46282
46368
  return /* @__PURE__ */ jsxs22("div", { className: "semiont-panel", children: [
46283
46369
  /* @__PURE__ */ jsx32(PanelHeader, { annotationType: "assessment", count: annotations.length, title: t12("title") }),
@@ -46645,12 +46731,12 @@ function CommentsPanel({
46645
46731
  container.scrollTo({ top: scrollTo, behavior: "smooth" });
46646
46732
  }
46647
46733
  }, [hoveredAnnotationId]);
46648
- const handleAnnotationClick = useCallback19(({ annotationId }) => {
46734
+ const handleAnnotationClick2 = useCallback19(({ annotationId }) => {
46649
46735
  setFocusedAnnotationId(annotationId);
46650
46736
  setTimeout(() => setFocusedAnnotationId(null), 3e3);
46651
46737
  }, []);
46652
46738
  useEventSubscriptions({
46653
- "browse:click": handleAnnotationClick
46739
+ "browse:click": handleAnnotationClick2
46654
46740
  });
46655
46741
  const handleSaveNewComment = () => {
46656
46742
  if (newCommentText.trim() && pendingAnnotation) {
@@ -46872,12 +46958,12 @@ function HighlightPanel({
46872
46958
  container.scrollTo({ top: scrollTo, behavior: "smooth" });
46873
46959
  }
46874
46960
  }, [hoveredAnnotationId]);
46875
- const handleAnnotationClick = useCallback20(({ annotationId }) => {
46961
+ const handleAnnotationClick2 = useCallback20(({ annotationId }) => {
46876
46962
  setFocusedAnnotationId(annotationId);
46877
46963
  setTimeout(() => setFocusedAnnotationId(null), 3e3);
46878
46964
  }, []);
46879
46965
  useEventSubscriptions({
46880
- "browse:click": handleAnnotationClick
46966
+ "browse:click": handleAnnotationClick2
46881
46967
  });
46882
46968
  useEffect28(() => {
46883
46969
  if (pendingAnnotation && pendingAnnotation.motivation === "highlighting") {
@@ -47252,12 +47338,12 @@ function ReferencesPanel({
47252
47338
  container.scrollTo({ top: scrollTo, behavior: "smooth" });
47253
47339
  }
47254
47340
  }, [hoveredAnnotationId]);
47255
- const handleAnnotationClick = useCallback21(({ annotationId }) => {
47341
+ const handleAnnotationClick2 = useCallback21(({ annotationId }) => {
47256
47342
  setFocusedAnnotationId(annotationId);
47257
47343
  setTimeout(() => setFocusedAnnotationId(null), 3e3);
47258
47344
  }, []);
47259
47345
  useEventSubscriptions({
47260
- "browse:click": handleAnnotationClick
47346
+ "browse:click": handleAnnotationClick2
47261
47347
  });
47262
47348
  const handleAssist = () => {
47263
47349
  setLastDetectionLog(null);
@@ -47751,12 +47837,12 @@ function TaggingPanel({
47751
47837
  if (typeof window === "undefined") return;
47752
47838
  localStorage.setItem("assist-section-expanded-tag", String(isAssistExpanded));
47753
47839
  }, [isAssistExpanded]);
47754
- const handleAnnotationClick = useCallback22(({ annotationId }) => {
47840
+ const handleAnnotationClick2 = useCallback22(({ annotationId }) => {
47755
47841
  setFocusedAnnotationId(annotationId);
47756
47842
  setTimeout(() => setFocusedAnnotationId(null), 3e3);
47757
47843
  }, []);
47758
47844
  useEventSubscriptions({
47759
- "browse:click": handleAnnotationClick
47845
+ "browse:click": handleAnnotationClick2
47760
47846
  });
47761
47847
  const entryRefs = useRef23(/* @__PURE__ */ new Map());
47762
47848
  const sortedAnnotations = useMemo10(() => {