Compare commits

...

4 Commits

Author SHA1 Message Date
Tom Moor 4a6da11380 sp 2025-03-16 09:45:52 -04:00
Tom Moor d7565ffc1c tests 2025-03-16 09:31:24 -04:00
Tom Moor f2362cddba Merge branch 'main' into perf/read-only-editor 2025-03-15 22:24:13 -04:00
Tom Moor bd75caec22 stash 2025-03-07 20:30:16 -07:00
4 changed files with 192 additions and 18 deletions
+39 -13
View File
@@ -6,7 +6,9 @@ import * as React from "react";
import { mergeRefs } from "react-merge-refs";
import { Optional } from "utility-types";
import insertFiles from "@shared/editor/commands/insertFiles";
import EditorContainer from "@shared/editor/components/Styles";
import { AttachmentPreset } from "@shared/types";
import { ProsemirrorHelper } from "@shared/utils/ProsemirrorHelper";
import { getDataTransferFiles } from "@shared/utils/files";
import { AttachmentValidation } from "@shared/validations";
import ClickablePadding from "~/components/ClickablePadding";
@@ -183,22 +185,46 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
[updateComments]
);
const paragraphs = React.useMemo(() => {
if (props.readOnly && typeof props.value === "object") {
return ProsemirrorHelper.getPlainParagraphs(props.value);
}
return undefined;
}, [props.readOnly, props.value]);
return (
<ErrorBoundary component="div" reloadOnChunkMissing>
<>
<LazyLoadedEditor
key={props.extensions?.length || 0}
ref={mergeRefs([ref, localRef, handleRefChanged])}
uploadFile={handleUploadFile}
embeds={embeds}
userPreferences={preferences}
dictionary={dictionary}
{...props}
onClickLink={handleClickLink}
onChange={handleChange}
placeholder={props.placeholder || ""}
defaultValue={props.defaultValue || ""}
/>
{paragraphs ? (
<EditorContainer
rtl={props.dir === "rtl"}
grow={props.grow}
style={props.style}
editorStyle={props.editorStyle}
>
<div className="ProseMirror">
{paragraphs.map((paragraph, index) => (
<p key={index} dir="auto">
{paragraph.content.map((content) => content.text)}
</p>
))}
</div>
</EditorContainer>
) : (
<LazyLoadedEditor
key={props.extensions?.length || 0}
ref={mergeRefs([ref, localRef, handleRefChanged])}
uploadFile={handleUploadFile}
embeds={embeds}
userPreferences={preferences}
dictionary={dictionary}
{...props}
onClickLink={handleClickLink}
onChange={handleChange}
placeholder={props.placeholder || ""}
defaultValue={props.defaultValue || ""}
/>
)}
{props.editorStyle?.paddingBottom && !props.readOnly && (
<ClickablePadding
onClick={props.readOnly ? undefined : focusAtEnd}
-5
View File
@@ -339,11 +339,6 @@ width: 100%;
padding: ${props.editorStyle?.padding ?? "initial"};
margin: ${props.editorStyle?.margin ?? "initial"};
.ProseMirror {
padding: 0;
margin: 0;
}
& > .ProseMirror-yjs-cursor {
display: none;
}
+128
View File
@@ -1,3 +1,4 @@
import { ProsemirrorData } from "../types";
import { CommentMark, ProsemirrorHelper } from "./ProsemirrorHelper";
describe("ProsemirrorHelper", () => {
@@ -87,4 +88,131 @@ describe("ProsemirrorHelper", () => {
expect(returnedAnchorText).toBeUndefined();
});
});
describe("getPlainParagraphs", () => {
it("should return an array of plain paragraphs", async () => {
const data = {
type: "doc",
content: [
{
type: "paragraph",
content: [
{
type: "text",
text: "some content in a paragraph",
},
],
},
{
type: "paragraph",
content: [
{
type: "text",
text: "some content in another paragraph",
},
],
},
],
} as ProsemirrorData;
const paragraphs = ProsemirrorHelper.getPlainParagraphs(data);
expect(paragraphs).toEqual([
{
type: "paragraph",
content: [
{
type: "text",
text: "some content in a paragraph",
},
],
},
{
type: "paragraph",
content: [
{
type: "text",
text: "some content in another paragraph",
},
],
},
]);
});
it("should return undefined when data contains inline nodes", async () => {
const data = {
type: "doc",
content: [
{
type: "paragraph",
content: [
{
type: "text",
text: "some content in a paragraph",
},
{
type: "emoji",
attrs: {
"data-name": "😆",
},
},
],
},
],
} as ProsemirrorData;
const paragraphs = ProsemirrorHelper.getPlainParagraphs(data);
expect(paragraphs).toBeUndefined();
});
it("should return undefined when data contains block nodes", async () => {
const data = {
type: "doc",
content: [
{
type: "blockquote",
content: [
{
type: "paragraph",
content: [
{
type: "text",
text: "some content in a paragraph",
},
],
},
],
},
],
} as ProsemirrorData;
const paragraphs = ProsemirrorHelper.getPlainParagraphs(data);
expect(paragraphs).toBeUndefined();
});
it("should return undefined when data contains marks", async () => {
const data = {
type: "doc",
content: [
{
type: "paragraph",
content: [
{
type: "text",
text: "some content in a paragraph",
marks: [
{
type: "bold",
},
],
},
],
},
],
} as ProsemirrorData;
const paragraphs = ProsemirrorHelper.getPlainParagraphs(data);
expect(paragraphs).toBeUndefined();
});
});
});
+25
View File
@@ -346,4 +346,29 @@ export class ProsemirrorHelper {
return replace(data);
}
/**
* Returns the paragraphs from the data if there are only plain paragraphs
* without any formatting. Otherwise returns undefined.
*
* @param data The ProsemirrorData object
* @returns An array of paragraph nodes or undefined
*/
static getPlainParagraphs(data: ProsemirrorData) {
const paragraphs = [];
for (const node of data.content) {
if (
node.type === "paragraph" &&
!node.content.some(
(item) =>
item.type !== "text" || (item.marks && item.marks.length > 0)
)
) {
paragraphs.push(node);
} else {
return undefined;
}
}
return paragraphs;
}
}