Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 83 additions & 2 deletions packages/react-tei/src/SidePanel/unitex/UnitexAnnotation.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
import { describe, expect, it, vi } from "vitest";
import { render } from "vitest-browser-react";
import { I18nProvider } from "../../i18n/I18nProvider";
import type { DocumentNavigationContextValue } from "../../navigation/DocumentNavigationContext";
import { TestDocumentNavigationContextProvider } from "../../navigation/TestDocumentNavigationContextProvider";
import { UnitexAnnotation } from "./UnitexAnnotation";

function TestWrapper({ children }: { children: React.ReactNode }) {
return <I18nProvider>{children}</I18nProvider>;
function TestWrapper({
navigateToBodyTargetSelector,
children,
}: {
navigateToBodyTargetSelector?: DocumentNavigationContextValue["navigateToBodyTargetSelector"];
children: React.ReactNode;
}) {
return (
<TestDocumentNavigationContextProvider
value={{
navigateToBodyTargetSelector,
}}
>
<I18nProvider>{children}</I18nProvider>
</TestDocumentNavigationContextProvider>
);
}

describe("UnitexAnnotation", () => {
Expand Down Expand Up @@ -46,4 +62,69 @@ describe("UnitexAnnotation", () => {

expect(onToggle).toHaveBeenCalledTimes(1);
});

it("should disable navigation buttons when annotation is not displayed", async () => {
const screen = await render(
<UnitexAnnotation
annotation={{ term: "example", frequency: 5, displayed: false }}
color="blue"
onToggle={() => {}}
/>,
{
wrapper: TestWrapper,
},
);

const previousButton = screen.getByRole("button", {
name: "Aller au précédent",
});
const nextButton = screen.getByRole("button", {
name: "Aller au suivant",
});

await expect.element(previousButton).toBeDisabled();
await expect.element(nextButton).toBeDisabled();
});

it("should call navigateToBodyTargetSelector when navigation buttons are clicked", async () => {
const navigateToBodyTargetSelector = vi.fn();
const screen = await render(
<UnitexAnnotation
annotation={{ term: "example", frequency: 5, displayed: true }}
color="blue"
onToggle={() => {}}
/>,
{
wrapper: ({ children }) => (
<TestWrapper
navigateToBodyTargetSelector={navigateToBodyTargetSelector}
>
{children}
</TestWrapper>
),
},
);

const previousButton = screen.getByRole("button", {
name: "Aller au précédent",
});
const nextButton = screen.getByRole("button", {
name: "Aller au suivant",
});

await expect.element(previousButton).toBeEnabled();
await expect.element(nextButton).toBeEnabled();

await previousButton.click();
expect(navigateToBodyTargetSelector).toHaveBeenCalledWith(
"[data-term=example]",
-1,
);

await nextButton.click();
expect(navigateToBodyTargetSelector).toHaveBeenCalledWith(
"[data-term=example]",
1,
);
});
});
39 changes: 37 additions & 2 deletions packages/react-tei/src/SidePanel/unitex/UnitexAnnotation.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import ArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import ArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
import Box from "@mui/material/Box";
import Checkbox from "@mui/material/Checkbox";
import Chip from "@mui/material/Chip";
import { grey } from "@mui/material/colors";
import IconButton from "@mui/material/IconButton";
import Stack from "@mui/material/Stack";
import Tooltip from "@mui/material/Tooltip";
import { useTranslation } from "react-i18next";
import { kebabCasify } from "../../helper/kebabCasify";
import { useDocumentNavigation } from "../../navigation/useNavigateToSection";
import type { TermStatistic } from "../../unitex/parseUnitexEnrichment";

export function UnitexAnnotation({
Expand All @@ -12,12 +18,23 @@ export function UnitexAnnotation({
onToggle,
}: UnitexAnnotationProps) {
const { t } = useTranslation();
const { navigateToBodyTargetSelector } = useDocumentNavigation();

const checkBoxLabel = t("unitex.toggleTerm", {
context: annotation.displayed ? "hide" : "show",
term: annotation.term,
});

const selector = `[data-term=${kebabCasify(annotation.term)}]`;
Comment thread
jonathanarnault marked this conversation as resolved.
Outdated

const handleGoToPrevious = () => {
navigateToBodyTargetSelector(selector, -1);
};

const handleGoToNext = () => {
navigateToBodyTargetSelector(selector, 1);
};

return (
<>
<Tooltip title={checkBoxLabel} placement="left">
Expand Down Expand Up @@ -57,14 +74,32 @@ export function UnitexAnnotation({
slotProps={{
root: {
sx: {
backgroundColor: annotation.displayed ? color : grey[600],
color: "white",
backgroundColor: annotation.displayed ? color : grey[100],
color: "black",
},
},
}}
/>
</Tooltip>
</Box>
<Stack gap={0.5} direction="row">
<IconButton
size="small"
disabled={!annotation.displayed}
onClick={handleGoToPrevious}
aria-label={t("unitex.previous")}
>
<ArrowUpIcon />
</IconButton>
<IconButton
size="small"
disabled={!annotation.displayed}
onClick={handleGoToNext}
aria-label={t("unitex.next")}
>
<ArrowDownIcon />
</IconButton>
</Stack>
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { render } from "vitest-browser-react";

import { DocumentContextProvider } from "../../DocumentContextProvider";
import { I18nProvider } from "../../i18n/I18nProvider";
import { TestDocumentNavigationContextProvider } from "../../navigation/TestDocumentNavigationContextProvider";
import type { TermStatistic } from "../../unitex/parseUnitexEnrichment";
import { UnitexAnnotationBlock } from "./UnitexAnnotationBlock";
import type { UnitexAnnotationBlockType } from "./unitexAnnotationBlocks";
Expand All @@ -22,7 +23,9 @@ function TestWrapper({ children }: { children: React.ReactNode }) {
jsonDocument={[]}
jsonUnitexEnrichment={enrichments}
>
{children}
<TestDocumentNavigationContextProvider>
{children}
</TestDocumentNavigationContextProvider>
</DocumentContextProvider>
</I18nProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function UnitexAnnotationBlock({ block }: UnitexAnnotationBlockProps) {
<Box
sx={{
display: "grid",
gridTemplateColumns: "max-content 1fr",
gridTemplateColumns: "max-content 1fr max-content",
gridTemplateRows: "auto",
columnGap: 0.5,
rowGap: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { render } from "vitest-browser-react";

import { DocumentContextProvider } from "../../DocumentContextProvider";
import { I18nProvider } from "../../i18n/I18nProvider";
import { TestDocumentNavigationContextProvider } from "../../navigation/TestDocumentNavigationContextProvider";
import type { TermStatistic } from "../../unitex/parseUnitexEnrichment";
import { UnitexSection } from "./UnitexSection";
import type { UnitexAnnotationBlockType } from "./unitexAnnotationBlocks";
Expand All @@ -22,7 +23,9 @@ function TestWrapper({ children }: { children: React.ReactNode }) {
jsonDocument={[]}
jsonUnitexEnrichment={enrichments}
>
{children}
<TestDocumentNavigationContextProvider>
{children}
</TestDocumentNavigationContextProvider>
</DocumentContextProvider>
</I18nProvider>
);
Expand Down
2 changes: 2 additions & 0 deletions packages/react-tei/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export const en: Translation = {
toggleBlock_hide: "Deactivate underlining of words in the text",
toggleTerm_show: 'Activate underlining for the term "{{term}}"',
toggleTerm_hide: 'Deactivate underlining for the term "{{term}}"',
previous: "Go to previous",
next: "Go to next",

date_one: "Date ({{count}})",
date_other: "Dates ({{count}})",
Expand Down
2 changes: 2 additions & 0 deletions packages/react-tei/src/i18n/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const fr = {
toggleBlock_hide: "Désactiver le soulignement des mots dans le texte",
toggleTerm_show: 'Activer le soulignement pour le terme "{{term}}"',
toggleTerm_hide: 'Désactiver le soulignement pour le terme "{{term}}"',
previous: "Aller au précédent",
next: "Aller au suivant",

date_one: "Date ({{count}})",
date_other: "Dates ({{count}})",
Expand Down
13 changes: 8 additions & 5 deletions packages/react-tei/src/navigation/DocumentNavigationContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createContext, useCallback, useEffect, useMemo, useRef } from "react";
import { useDocumentContext } from "../DocumentContextProvider";

export type DocumentNavigationContextValue = {
navigateToBodyTargetSelector(querySelector: string): void;
navigateToBodyTargetSelector(querySelector: string, order?: number): void;
navigateToHeading(headingId: string): void;
navigateToFootnote(footnoteId: string): void;
navigateToFootnoteRef(id: string): void;
Expand Down Expand Up @@ -91,7 +91,7 @@ export function DocumentNavigationContextProvider({
const currentSelectorRef = useRef<CurrentSelectorRef | null>(null);

const navigateToTargetLoop = useCallback(
(wrapperElement: HTMLElement, querySelector: string) => {
(wrapperElement: HTMLElement, querySelector: string, direction = 1) => {
const targetElements =
wrapperElement.querySelectorAll<HTMLElement>(querySelector);

Expand All @@ -111,7 +111,10 @@ export function DocumentNavigationContextProvider({
};
} else {
currentSelectorRef.current.index =
(currentSelectorRef.current.index + 1) % targetElements.length;
(currentSelectorRef.current.index +
direction +
targetElements.length) %
targetElements.length;
}

const index = currentSelectorRef.current.index;
Expand All @@ -133,15 +136,15 @@ export function DocumentNavigationContextProvider({
);

const navigateToBodyTargetSelector = useCallback(
Comment thread
jonathanarnault marked this conversation as resolved.
(querySelector: string) => {
(querySelector: string, direction = 1) => {
const documentElement = documentRef.current;

if (!documentElement) {
console.error("Document element not found");
return;
}

navigateToTargetLoop(documentElement, querySelector);
navigateToTargetLoop(documentElement, querySelector, direction);
},
[documentRef, navigateToTargetLoop],
);
Expand Down
48 changes: 46 additions & 2 deletions packages/react-tei/src/tags/Highlight.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ function TestWrapper({ children }: { children: React.ReactNode }) {
jsonDocument={[]}
jsonUnitexEnrichment={{
persName: [
{ term: "Paris", displayed: true, frequency: 1 },
{ term: "Albert Einstein", displayed: true, frequency: 1 },
{ term: "Einstein", displayed: true, frequency: 2 },
{ term: "Nancy", displayed: true, frequency: 5 },
],
placeName: [
{ term: "Paris", displayed: true, frequency: 1 },
{
term: "London",
displayed: false,
Expand Down Expand Up @@ -92,4 +91,49 @@ describe("Highlight", () => {
await expect.element(element).toHaveAttribute("data-term", "nancy");
await expect.element(element).toHaveAttribute("data-group", "persName");
});

it("should support nested highlights", async () => {
const screen = await render(
<Highlight
data={{
tag: "highlight",
attributes: { groups: "persName", term: "albert-einstein" },
value: [
{
tag: "#text",
value: "Albert ",
},
{
tag: "highlight",
attributes: { groups: "persName", term: "einstein" },
value: "Einstein",
},
],
}}
/>,
{
wrapper: TestWrapper,
},
);

const outerElement = screen.getByRole("mark").filter({
hasText: "Albert Einstein",
exact: true,
});
await expect.element(outerElement).toBeInTheDocument();
await expect.element(outerElement).toHaveTextContent("Albert Einstein");
await expect
.element(outerElement)
.toHaveAttribute("data-term", "albert-einstein");
await expect
.element(outerElement)
.toHaveAttribute("data-group", "persName");

const innerElement = screen.getByText("Einstein", { exact: true });
await expect.element(innerElement).toBeInTheDocument();
await expect.element(innerElement).toHaveAttribute("data-term", "einstein");
await expect
.element(innerElement)
.toHaveAttribute("data-group", "persName");
});
});
7 changes: 4 additions & 3 deletions packages/react-tei/src/tags/Highlight.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Box } from "@mui/material";
import { useMemo } from "react";
import { useDocumentContext } from "../DocumentContextProvider";
import { kebabCasify } from "../helper/kebabCasify";
import { chipColors } from "../SidePanel/unitex/unitexAnnotationBlocks";
import type { ComponentProps } from "./type";
import { Value } from "./Value";
Expand All @@ -10,17 +11,17 @@ export const Highlight = ({ data }: HighlightProps) => {
const { unitexEnrichment } = useDocumentContext();

const groups = useMemo(() => {
if (!attributes?.groups || !unitexEnrichment || typeof value !== "string") {
if (!attributes?.groups || !unitexEnrichment) {
return [];
}
return ([] as string[]).concat(attributes?.groups).filter((group) => {
const term = unitexEnrichment?.document?.[group]?.find(
(term) => term.term === value,
({ term }) => kebabCasify(term) === attributes?.term,
);

return term?.displayed ?? false;
});
}, [unitexEnrichment, attributes?.groups, value]);
}, [unitexEnrichment, attributes?.groups, attributes?.term]);

if (groups.length === 0) {
return <Value data={value} />;
Expand Down