Skip to content

Commit

Permalink
Merge branch 'main' into recogito#103-redraw-export
Browse files Browse the repository at this point in the history
  • Loading branch information
oleksandr-danylchenko committed Dec 16, 2024
2 parents 32b04a5 + 5b9abad commit b2a67c3
Show file tree
Hide file tree
Showing 31 changed files with 2,135 additions and 924 deletions.
2,564 changes: 1,823 additions & 741 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@recogito/text-annotator-monorepo",
"version": "3.0.0-rc.50",
"version": "3.0.0-rc.53",
"description": "Recogito Text Annotator monorepo",
"author": "Rainer Simon",
"repository": {
Expand Down
10 changes: 5 additions & 5 deletions packages/extension-tei/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@recogito/text-annotator-tei",
"version": "3.0.0-rc.50",
"version": "3.0.0-rc.53",
"description": "Recogito Text Annotator TEI extension",
"author": "Rainer Simon",
"license": "BSD-3-Clause",
Expand All @@ -27,12 +27,12 @@
},
"devDependencies": {
"CETEIcean": "^1.9.3",
"typescript": "5.6.3",
"vite": "^5.4.10",
"typescript": "5.7.2",
"vite": "^6.0.3",
"vite-plugin-dts": "^4.3.0"
},
"peerDependencies": {
"@annotorious/core": "^3.0.11",
"@recogito/text-annotator": "3.0.0-rc.50"
"@annotorious/core": "^3.0.14",
"@recogito/text-annotator": "3.0.0-rc.53"
}
}
6 changes: 3 additions & 3 deletions packages/extension-tei/src/crosswalk/forward.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,17 @@ export const reviveTarget = (t: TextAnnotationTarget, container: HTMLElement) =>

// Helper
const reanchorIfNeeded = (parent: Node, offset: number) => {
if (parent.firstChild.toString().length >= offset) {
if (parent.firstChild instanceof Text && parent.firstChild.length >= offset) {
return { node: parent.firstChild, offset };
} else {
return reanchor(parent.firstChild, parent, offset);
}
}

const reanchoredStart = reanchorIfNeeded(startNode, startOffset);
const reanchoredEnd = reanchorIfNeeded(endNode, endOffset);

range.setStart(reanchoredStart.node, reanchoredStart.offset);

const reanchoredEnd = reanchorIfNeeded(endNode, endOffset);
range.setEnd(reanchoredEnd.node, reanchoredEnd.offset);

const textSelector = rangeToSelector(range, container);
Expand Down
22 changes: 11 additions & 11 deletions packages/text-annotator-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@recogito/react-text-annotator",
"version": "3.0.0-rc.50",
"version": "3.0.0-rc.53",
"description": "Recogito Text Annotator React bindings",
"author": "Rainer Simon",
"license": "BSD-3-Clause",
Expand All @@ -24,14 +24,14 @@
"module": "./dist/react-text-annotator.es.js",
"types": "./dist/index.d.ts",
"devDependencies": {
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "5.6.3",
"vite": "^5.4.10",
"typescript": "5.7.2",
"vite": "^6.0.3",
"vite-plugin-dts": "^4.3.0",
"vite-tsconfig-paths": "^5.0.1"
"vite-tsconfig-paths": "^5.1.4"
},
"peerDependencies": {
"openseadragon": "^3.0.0 || ^4.0.0 || ^5.0.0",
Expand All @@ -44,11 +44,11 @@
}
},
"dependencies": {
"@annotorious/core": "^3.0.11",
"@annotorious/react": "^3.0.11",
"@floating-ui/react": "^0.26.27",
"@recogito/text-annotator": "3.0.0-rc.50",
"@recogito/text-annotator-tei": "3.0.0-rc.50",
"@annotorious/core": "^3.0.14",
"@annotorious/react": "^3.0.14",
"@floating-ui/react": "^0.27.1",
"@recogito/text-annotator": "3.0.0-rc.53",
"@recogito/text-annotator-tei": "3.0.0-rc.53",
"CETEIcean": "^1.9.3"
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { PointerEvent, ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { useAnnotator, useSelection } from '@annotorious/react';
import { isRevived, TextAnnotation, TextAnnotator } from '@recogito/text-annotator';
import {
NOT_ANNOTATABLE_CLASS,
toDomRectList,
type TextAnnotation,
type TextAnnotator,
} from '@recogito/text-annotator';

import { isMobile } from './isMobile';
import {
arrow,
autoUpdate,
flip,
FloatingArrow,
FloatingArrowProps,
FloatingFocusManager,
FloatingPortal,
inline,
Expand All @@ -16,12 +25,16 @@ import {
useRole
} from '@floating-ui/react';

import './TextAnnotatorPopup.css';
import './TextAnnotationPopup.css';

interface TextAnnotationPopupProps {

ariaCloseWarning?: string;

arrow?: boolean;

arrowProps?: Omit<FloatingArrowProps, 'context' | 'ref'>;

popup(props: TextAnnotationPopupContentProps): ReactNode;

}
Expand All @@ -32,11 +45,17 @@ export interface TextAnnotationPopupContentProps {

editable?: boolean;

event?: PointerEvent;
event?: PointerEvent | KeyboardEvent;

}

export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
const toViewportBounds = (annotationBounds: DOMRect, container: HTMLElement): DOMRect => {
const { left, top, right, bottom } = annotationBounds;
const containerBounds = container.getBoundingClientRect();
return new DOMRect(left + containerBounds.left, top + containerBounds.top, right - left, bottom - top);
}

export const TextAnnotationPopup = (props: TextAnnotationPopupProps) => {

const r = useAnnotator<TextAnnotator>();

Expand All @@ -46,6 +65,8 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {

const [isOpen, setOpen] = useState(selected?.length > 0);

const arrowRef = useRef(null);

const { refs, floatingStyles, update, context } = useFloating({
placement: isMobile() ? 'bottom' : 'top',
open: isOpen,
Expand All @@ -56,10 +77,11 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
}
},
middleware: [
offset(10),
inline(),
flip(),
shift({ mainAxis: false, crossAxis: true, padding: 10 })
offset(10),
flip({ crossAxis: true }),
shift({ crossAxis: true, padding: 10 }),
arrow({ element: arrowRef })
],
whileElementsMounted: autoUpdate
});
Expand All @@ -71,26 +93,36 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
const { getFloatingProps } = useInteractions([dismiss, role]);

useEffect(() => {
const annotationSelector = annotation?.target.selector;
setOpen(annotationSelector?.length > 0 ? isRevived(annotationSelector) : false);
}, [annotation]);
if (annotation?.id) {
const bounds = r?.state.store.getAnnotationBounds(annotation.id);
setOpen(Boolean(bounds));
} else {
setOpen(false);
}
}, [annotation?.id, r?.state.store]);

useEffect(() => {
if (isOpen && annotation) {
const {
target: {
selector: [{ range }]
}
} = annotation;
if (!r) return;

if (isOpen && annotation?.id) {
refs.setPositionReference({
getBoundingClientRect: () => range.getBoundingClientRect(),
getClientRects: () => range.getClientRects()
getBoundingClientRect: () => {
// Annotation bounds are relative to the document element
const bounds = r.state.store.getAnnotationBounds(annotation.id);
return bounds
? toViewportBounds(bounds, r.element)
: new DOMRect();
},
getClientRects: () => {
const rects = r.state.store.getAnnotationRects(annotation.id);
const viewportRects = rects.map(rect => toViewportBounds(rect, r.element));
return toDomRectList(viewportRects);
}
});
} else {
refs.setPositionReference(null);
}
}, [isOpen, annotation, refs]);
}, [isOpen, annotation?.id, annotation?.target, r]);

useEffect(() => {
const config: MutationObserverInit = { attributes: true, childList: true, subtree: true };
Expand All @@ -106,12 +138,6 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
};
}, [update]);

// Prevent text-annotator from handling the irrelevant events triggered from the popup
const getStopEventsPropagationProps = useCallback(
() => ({ onPointerUp: (event: PointerEvent<HTMLDivElement>) => event.stopPropagation() }),
[]
);

// Don't shift focus to the floating element if selected via keyboard or on mobile.
const initialFocus = useMemo(() => {
return (event?.type === 'keyup' || event?.type === 'contextmenu' || isMobile()) ? -1 : 0;
Expand All @@ -128,17 +154,23 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
returnFocus={false}
initialFocus={initialFocus}>
<div
className="a9s-popup r6o-popup annotation-popup r6o-text-popup not-annotatable"
className={`a9s-popup r6o-popup annotation-popup r6o-text-popup ${NOT_ANNOTATABLE_CLASS}`}
ref={refs.setFloating}
style={floatingStyles}
{...getFloatingProps()}
{...getStopEventsPropagationProps()}>
{...getFloatingProps(getStopEventsPropagationProps())}>
{props.popup({
annotation: selected[0].annotation,
editable: selected[0].editable,
event
})}

{props.arrow && (
<FloatingArrow
ref={arrowRef}
context={context}
{...(props.arrowProps || {})} />
)}

<button className="r6o-popup-sr-only" aria-live="assertive" onClick={onClose}>
{props.ariaCloseWarning || 'Click or leave this dialog to close it.'}
</button>
Expand All @@ -148,3 +180,25 @@ export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {
) : null;

}

/**
* Prevent text-annotator from handling the irrelevant events
* triggered from the popup/toolbar/dialog
*/
const getStopEventsPropagationProps = <T extends HTMLElement = HTMLElement>() => ({
onPointerUp: (event: React.PointerEvent<T>) => event.stopPropagation(),
onPointerDown: (event: React.PointerEvent<T>) => event.stopPropagation(),
onMouseDown: (event: React.MouseEvent<T>) => event.stopPropagation(),
onMouseUp: (event: React.MouseEvent<T>) => event.stopPropagation()
});

/** For backwards compatibility **/
/** @deprecated Use TextAnnotationPopup instead */
export const TextAnnotatorPopup = (props: TextAnnotationPopupProps) => {

useEffect(() => {
console.warn('TextAnnotatorPopup is deprecated and will be removed in a future version. Please use TextAnnotationPopup instead.');
}, []);

return <TextAnnotationPopup {...props} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './TextAnnotationPopup';
10 changes: 6 additions & 4 deletions packages/text-annotator-react/src/TextAnnotator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,23 @@ import { createTextAnnotator } from '@recogito/text-annotator';

import '@recogito/text-annotator/dist/text-annotator.css';

export interface TextAnnotatorProps<E extends unknown> extends Omit<TextAnnotatorOptions<TextAnnotation, E>, 'adapter'> {
export interface TextAnnotatorProps<I extends TextAnnotation = TextAnnotation, E extends unknown = TextAnnotation> extends Omit<TextAnnotatorOptions<I, E>, 'adapter'> {

children?: ReactNode | JSX.Element;

adapter?: FormatAdapter<TextAnnotation, E> | ((container: HTMLElement) => FormatAdapter<TextAnnotation, E>) | null;
adapter?: FormatAdapter<I, E> | ((container: HTMLElement) => FormatAdapter<I, E>) | null;

filter?: Filter;
filter?: Filter<I>;

style?: HighlightStyleExpression;

className?: string;

}

export const TextAnnotator = <E extends unknown>(props: TextAnnotatorProps<E>) => {
export const TextAnnotator = <I extends TextAnnotation = TextAnnotation, E extends unknown = TextAnnotation>(
props: TextAnnotatorProps<I, E>
) => {

const el = useRef<HTMLDivElement>(null);

Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion packages/text-annotator-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export * from './tei';
export * from './TextAnnotator';
export * from './TextAnnotatorPopup';
export * from './TextAnnotationPopup';
export * from './TextAnnotatorPlugin';

// Essential re-exports from @annotorious/core
Expand Down
10 changes: 8 additions & 2 deletions packages/text-annotator-react/src/tei/CETEIcean.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import CETEI from 'CETEIcean';

interface CETEIceanProps {

initArgs?: any;

tei?: string;

onLoad?(element: Element): void;
Expand Down Expand Up @@ -55,7 +57,7 @@ export const CETEIcean = (props: CETEIceanProps) => {

useEffect(() => {
if (props.tei) {
const ceteicean = new CETEI();
const ceteicean = new CETEI(props.initArgs);

ceteicean.addBehaviors({
...PRESET_BEHAVIORS,
Expand All @@ -71,7 +73,11 @@ export const CETEIcean = (props: CETEIceanProps) => {
props.onLoad(el.current);
});
}
}, [props.tei]);

return () => {
el.current.innerHTML = '';
}
}, [props.tei, JSON.stringify(props.initArgs), props.onLoad]);

return (
<div
Expand Down
6 changes: 3 additions & 3 deletions packages/text-annotator-react/src/tei/TEIAnnotator.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Children, ReactElement, ReactNode, cloneElement, useContext, useEffect } from 'react';
import { Children, ReactElement, ReactNode, cloneElement, useCallback, useContext, useEffect } from 'react';
import { AnnotoriousContext, Filter } from '@annotorious/react';
import { TEIPlugin } from '@recogito/text-annotator-tei';
import { createTextAnnotator, HighlightStyleExpression } from '@recogito/text-annotator';
Expand All @@ -22,10 +22,10 @@ export const TEIAnnotator = (props: TEIAnnotatorProps) => {

const { anno, setAnno } = useContext(AnnotoriousContext);

const onLoad = (element: HTMLElement) => {
const onLoad = useCallback((element: HTMLElement) => {
const anno = TEIPlugin(createTextAnnotator(element, opts));
setAnno(anno);
}
}, []);

useEffect(() => {
if (!anno)
Expand Down
Loading

0 comments on commit b2a67c3

Please sign in to comment.