Skip to content
Draft
12 changes: 6 additions & 6 deletions e2e/specs/math.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test("contains text from slate editor", async ({ page }) => {
const textContent = await mathEditor.locator(".wrs_container").textContent();
expect(textContent?.slice(1)).toEqual("111+1");
await page.getByTestId("save-math").click();
await expect(page.getByRole("dialog")).not.toBeVisible();
await page.locator("[data-trigger][data-state='closed']").waitFor();
});

test("can change text and save", async ({ page }) => {
Expand All @@ -44,8 +44,8 @@ test("can change text and save", async ({ page }) => {
await expect(mathInput).toBeFocused();
await page.keyboard.type("=112");
await page.getByTestId("save-math").click();
await expect(page.getByRole("dialog")).not.toBeVisible();
expect(await page.getByTestId("math").textContent()).toEqual("111+1 =112");
await page.locator("[data-trigger][data-state='closed']").waitFor();
expect(await page.getByTestId("math").textContent()).toEqual("111+1=112");
});

test("can change preview when preview button pressed", async ({ page }) => {
Expand All @@ -59,9 +59,9 @@ test("can change preview when preview button pressed", async ({ page }) => {
await expect(mathInput).toBeFocused();
await page.keyboard.type("=112");
await page.getByTestId("preview-math").click();
expect(await page.getByTestId("math-preview").textContent()).toEqual("111+1 =112");
expect(await page.getByTestId("math-preview").textContent()).toEqual("111+1=112");
await page.getByTestId("save-math").click();
await expect(page.getByRole("dialog")).not.toBeVisible();
await page.locator("[data-trigger][data-state='closed']").waitFor();
});

test("can provide modal when leaving unchecked edits", async ({ page }) => {
Expand All @@ -78,5 +78,5 @@ test("can provide modal when leaving unchecked edits", async ({ page }) => {
await alertDialog.waitFor({ state: "visible" });
await expect(alertDialog).toBeVisible();
await alertDialog.getByRole("button", { name: "Fortsett" }).click();
await expect(page.getByRole("dialog")).not.toBeVisible();
await page.locator("[data-trigger][data-state='closed']").waitFor();
});
3 changes: 2 additions & 1 deletion e2e/specs/symbol.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ test("can insert symbol from toolbar", async ({ page }) => {
const symbol = page.getByTestId("button-half");
await symbol.waitFor({ state: "visible" });
await symbol.click();
await expect(page.getByRole("dialog")).not.toBeVisible();
const toolbar = page.locator("[data-toolbar]");
await expect(toolbar).toBeHidden();
await expect(page.getByTestId("slate-editor")).toHaveText(/½.+/);
});

Expand Down
72 changes: 35 additions & 37 deletions src/components/SlateEditor/plugins/aside/SlateAside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,48 @@
*
*/

import { ReactNode } from "react";
import { Editor, Node, Transforms } from "slate";
import { ReactEditor, RenderElementProps } from "slate-react";
import { AsideElement, ASIDE_ELEMENT_TYPE } from "./asideTypes";
import SlateFactAside from "./SlateFactAside";
import { styled } from "@ndla/styled-system/jsx";
import { EmbedWrapper, FactBox } from "@ndla/ui";
import { useTranslation } from "react-i18next";
import { Editor } from "slate";
import { RenderElementProps } from "slate-react";
import { DeleteButton } from "../../../DeleteButton";
import { MoveContentButton } from "../../../MoveContentButton";
import { useEditableElement } from "../../utils/useEditableElement";
import { AsideElement } from "./asideTypes";

interface Props {
interface Props extends RenderElementProps {
element: AsideElement;
editor: Editor;
children: ReactNode;
attributes: RenderElementProps["attributes"];
}

const SlateAside = (props: Props) => {
const { element, editor } = props;
const ButtonContainer = styled("div", {
base: {
position: "absolute",
top: "xsmall",
right: "xsmall",
display: "flex",
justifyContent: "flex-end",
zIndex: "docked",
gap: "3xsmall",
},
});

const onRemoveClick = () => {
const path = ReactEditor.findPath(editor, element);
Transforms.removeNodes(editor, {
at: path,
match: (node) => Node.isElement(node) && node.type === ASIDE_ELEMENT_TYPE,
});
setTimeout(() => {
ReactEditor.focus(editor);
Transforms.select(editor, path);
Transforms.collapse(editor);
}, 0);
};
const SlateAside = ({ element, editor, attributes, children }: Props) => {
const { t } = useTranslation();
const { handleRemove, handleUnwrap } = useEditableElement(element, editor);

const onMoveContent = () => {
const path = ReactEditor.findPath(editor, element);
Transforms.unwrapNodes(editor, {
at: path,
match: (node) => Node.isElement(node) && node.type === ASIDE_ELEMENT_TYPE,
voids: true,
});
setTimeout(() => {
ReactEditor.focus(editor);
Transforms.select(editor, path);
Transforms.collapse(editor, { edge: "start" });
}, 0);
};

return <SlateFactAside onRemoveClick={onRemoveClick} onMoveContent={onMoveContent} {...props} />;
return (
<EmbedWrapper>
<ButtonContainer contentEditable={false}>
<MoveContentButton onClick={handleUnwrap} aria-label={t("form.moveContent")} />
<DeleteButton aria-label={t("form.remove")} onMouseDown={handleRemove} data-testid="remove-fact-aside" />
</ButtonContainer>
<FactBox defaultOpen {...attributes}>
{children}
</FactBox>
</EmbedWrapper>
);
};

export default SlateAside;
52 changes: 0 additions & 52 deletions src/components/SlateEditor/plugins/aside/SlateFactAside.tsx

This file was deleted.

62 changes: 13 additions & 49 deletions src/components/SlateEditor/plugins/audio/SlateAudio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import { PencilFill, DeleteBinLine, LinkMedium } from "@ndla/icons";
import { DialogContent, DialogRoot, DialogTrigger, IconButton, Spinner } from "@ndla/primitives";
import { SafeLinkIconButton } from "@ndla/safelink";
import { styled } from "@ndla/styled-system/jsx";
import { AudioEmbedData, AudioMetaData } from "@ndla/types-embed";
import { AudioMetaData } from "@ndla/types-embed";
import { AudioEmbed, EmbedWrapper } from "@ndla/ui";
import { useCallback, useMemo, useState } from "react";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Editor, Path, Transforms } from "slate";
import { ReactEditor, RenderElementProps, useSelected } from "slate-react";
import { Editor } from "slate";
import { RenderElementProps, useSelected } from "slate-react";
import { useAudioMeta } from "../../../../modules/embed/queries";
import { useArticleLanguage } from "../../ArticleLanguageProvider";
import { useEditableElement } from "../../utils/useEditableElement";
import AudioEmbedForm from "./AudioEmbedForm";
import { AudioElement } from "./audioTypes";

Expand Down Expand Up @@ -51,9 +52,9 @@ const ButtonContainer = styled("div", {

const SlateAudio = ({ element, editor, attributes, children }: Props) => {
const { t } = useTranslation();
const [isEditing, setIsEditing] = useState(false);
const isSelected = useSelected();
const language = useArticleLanguage();
const { handleRemove, handleEditingChange, handleSave, dialogProps } = useEditableElement(element, editor);

const audioMetaQuery = useAudioMeta(element.data?.resourceId ?? "", language, {
enabled: !!parseInt(element.data?.resourceId ?? ""),
Expand All @@ -79,50 +80,8 @@ const SlateAudio = ({ element, editor, attributes, children }: Props) => {
[audioMetaQuery.data, audioMetaQuery.error, element.data],
);

const handleRemove = () => {
const path = ReactEditor.findPath(editor, element);
Transforms.removeNodes(editor, {
at: path,
voids: true,
});
setTimeout(() => {
ReactEditor.focus(editor);
Transforms.select(editor, path);
Transforms.collapse(editor);
}, 0);
};

const onClose = () => {
setIsEditing(false);
ReactEditor.focus(editor);
const path = ReactEditor.findPath(editor, element);
if (Editor.hasPath(editor, Path.next(path))) {
setTimeout(() => {
Transforms.select(editor, Path.next(path));
}, 0);
}
};

const onSave = useCallback(
(data: AudioEmbedData) => {
setIsEditing(false);
const properties = {
data,
};
ReactEditor.focus(editor);
const path = ReactEditor.findPath(editor, element);
Transforms.setNodes(editor, properties, { at: path });
if (Editor.hasPath(editor, Path.next(path))) {
setTimeout(() => {
Transforms.select(editor, Path.next(path));
}, 0);
}
},
[editor, element],
);

return (
<DialogRoot open={isEditing} onOpenChange={({ open }) => setIsEditing(open)}>
<DialogRoot {...dialogProps}>
<StyledEmbedWrapper
{...attributes}
contentEditable={false}
Expand Down Expand Up @@ -180,7 +139,12 @@ const SlateAudio = ({ element, editor, attributes, children }: Props) => {
<Portal>
<DialogContent>
{!!element.data && !!audioMetaQuery.data && (
<AudioEmbedForm audio={audioMetaQuery.data} onSave={onSave} onCancel={onClose} embed={element.data} />
<AudioEmbedForm
audio={audioMetaQuery.data}
onSave={(data) => handleSave({ data })}
onCancel={() => handleEditingChange(false)}
embed={element.data}
/>
)}
</DialogContent>
</Portal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import {
IconButton,
} from "@ndla/primitives";
import { ImageMetaInformationV3DTO } from "@ndla/types-backend/image-api";
import { CampaignBlockEmbedData } from "@ndla/types-embed";
import { CampaignBlock, EmbedWrapper } from "@ndla/ui";
import { useCallback, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Editor, Path, Transforms } from "slate";
import { ReactEditor, RenderElementProps } from "slate-react";
import { Editor } from "slate";
import { RenderElementProps } from "slate-react";
import { fetchImage } from "../../../../modules/image/imageApi";
import { DialogCloseButton } from "../../../DialogCloseButton";
import { useEditableElement } from "../../utils/useEditableElement";
import { StyledFigureButtons } from "../embed/FigureButtons";
import CampaignBlockForm from "./CampaignBlockForm";
import { CampaignBlockElement } from "./types";
Expand All @@ -37,73 +37,18 @@ interface Props extends RenderElementProps {

const SlateCampaignBlock = ({ element, editor, attributes, children }: Props) => {
const { t } = useTranslation();
const [isEditing, setIsEditing] = useState(!!element.isFirstEdit);
const campaignBlock = element.data;
const [image, setImage] = useState<ImageMetaInformationV3DTO | undefined>(undefined);

const onOpenChange = useCallback(
(open: boolean) => {
setIsEditing(open);
if (open) return;
ReactEditor.focus(editor);
if (element.isFirstEdit) {
Transforms.removeNodes(editor, {
at: ReactEditor.findPath(editor, element),
voids: true,
});
}
const path = ReactEditor.findPath(editor, element);
if (Editor.hasPath(editor, Path.next(path))) {
setTimeout(() => {
Transforms.select(editor, Path.next(path));
}, 0);
}
},
[editor, element],
);

const onSave = useCallback(
(data: CampaignBlockEmbedData) => {
setIsEditing(false);
const properties = {
data: data,
isFirstEdit: false,
};

ReactEditor.focus(editor);
const path = ReactEditor.findPath(editor, element);
Transforms.setNodes(editor, properties, { at: path });

if (Editor.hasPath(editor, Path.next(path))) {
setTimeout(() => {
Transforms.select(editor, Path.next(path));
}, 0);
}
},
[setIsEditing, editor, element],
);
const { handleRemove, handleSave, dialogProps } = useEditableElement(element, editor);

useEffect(() => {
if (campaignBlock?.imageId) {
fetchImage(campaignBlock.imageId).then((img) => setImage(img));
}
}, [campaignBlock?.imageId]);

const handleRemove = useCallback(() => {
const path = ReactEditor.findPath(editor, element);
Transforms.removeNodes(editor, {
at: path,
voids: true,
});
setTimeout(() => {
ReactEditor.focus(editor);
Transforms.select(editor, path);
Transforms.collapse(editor);
}, 0);
}, [editor, element]);

return (
<DialogRoot size="large" open={isEditing} onOpenChange={(details) => onOpenChange(details.open)}>
<DialogRoot size="large" {...dialogProps}>
<EmbedWrapper {...attributes} data-testid="slate-campaign-block" contentEditable={false}>
{!!campaignBlock && (
<>
Expand All @@ -114,7 +59,6 @@ const SlateCampaignBlock = ({ element, editor, attributes, children }: Props) =>
variant="secondary"
aria-label={t("campaignBlockForm.title")}
title={t("campaignBlockForm.title")}
onClick={() => setIsEditing(true)}
>
<PencilFill />
</IconButton>
Expand Down Expand Up @@ -155,7 +99,7 @@ const SlateCampaignBlock = ({ element, editor, attributes, children }: Props) =>
<DialogCloseButton />
</DialogHeader>
<DialogBody>
<CampaignBlockForm initialData={campaignBlock} onSave={onSave} />
<CampaignBlockForm initialData={campaignBlock} onSave={(data) => handleSave({ data })} />
</DialogBody>
</DialogContent>
</Portal>
Expand Down
Loading
Loading