mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 05:26:24 +09:00
serializing/deserializing content editable
This commit is contained in:
1 parent
5c7da8feeb
commit
f0b370cb50
7 files changed
+55
-27
No files matched your search
+11
-11
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { TouchBackend } from "react-dnd-touch-backend";
|
||||
import store from "immerhin";
|
||||
@@ -58,25 +58,25 @@ const useElementsTree = () => {
|
||||
const [rootInstance] = useRootInstance();
|
||||
const [breakpoints] = useBreakpoints();
|
||||
|
||||
const onChangeChildren: OnChangeChildren = useCallback((change) => {
|
||||
store.createTransaction([rootInstanceContainer], (rootInstance) => {
|
||||
if (rootInstance === undefined) return;
|
||||
|
||||
const { instanceId, updates } = change;
|
||||
setInstanceChildrenMutable(instanceId, updates, rootInstance);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return useMemo(() => {
|
||||
if (rootInstance === undefined) return;
|
||||
|
||||
const onChangeChildren: OnChangeChildren = (change) => {
|
||||
store.createTransaction([rootInstanceContainer], (rootInstance) => {
|
||||
if (rootInstance === undefined) return;
|
||||
|
||||
const { instanceId, updates } = change;
|
||||
setInstanceChildrenMutable(instanceId, updates, rootInstance);
|
||||
});
|
||||
};
|
||||
|
||||
return createElementsTree({
|
||||
instance: rootInstance,
|
||||
breakpoints,
|
||||
Component: WrapperComponentDev,
|
||||
onChangeChildren,
|
||||
});
|
||||
}, [rootInstance, breakpoints]);
|
||||
}, [rootInstance, breakpoints, onChangeChildren]);
|
||||
};
|
||||
|
||||
const useSubscribePreviewMode = () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, MouseEvent, FormEvent, useRef, useMemo } from "react";
|
||||
import { useCallback, MouseEvent, FormEvent, useMemo } from "react";
|
||||
import type { OnChangeChildren } from "~/shared/tree-utils";
|
||||
import {
|
||||
type Instance,
|
||||
@@ -96,6 +96,7 @@ export const WrapperComponentDev = ({
|
||||
{instance.children}
|
||||
</Editor>
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isEditing]);
|
||||
|
||||
if (editor) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { type Publish } from "@webstudio-is/sdk";
|
||||
import { useMemo, useState, type MouseEventHandler } from "react";
|
||||
import { useSelectionRect } from "~/designer/shared/nano-states";
|
||||
import {
|
||||
useSelectedInstanceData,
|
||||
useSelectionRect,
|
||||
} from "~/designer/shared/nano-states";
|
||||
import { ToggleGroup, type CSS } from "~/shared/design-system";
|
||||
import { FontBoldIcon, FontItalicIcon, Link2Icon } from "~/shared/icons";
|
||||
import { createInstance } from "~/shared/tree-utils";
|
||||
@@ -85,6 +88,7 @@ type TextToolbarProps = {
|
||||
|
||||
export const TextToolbar = ({ publish }: TextToolbarProps) => {
|
||||
const [selectionRect] = useSelectionRect();
|
||||
const [selectedIntsanceData] = useSelectedInstanceData();
|
||||
const [element, setElement] = useState<HTMLElement | null>(null);
|
||||
const placement = useMemo(() => {
|
||||
if (selectionRect === undefined || element === null) return;
|
||||
@@ -92,7 +96,9 @@ export const TextToolbar = ({ publish }: TextToolbarProps) => {
|
||||
return getPlacement({ toolbarRect, selectionRect });
|
||||
}, [selectionRect, element]);
|
||||
|
||||
if (selectionRect === undefined) return null;
|
||||
if (selectionRect === undefined || selectedIntsanceData === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Toolbar
|
||||
|
||||
@@ -10,6 +10,7 @@ export type {
|
||||
SerializedElementNode,
|
||||
SerializedTextNode,
|
||||
SerializedRootNode,
|
||||
ParagraphNode,
|
||||
} from "lexical";
|
||||
export {
|
||||
ElementNode,
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
import { type Instance } from "@webstudio-is/sdk";
|
||||
import { $createInstanceNode } from "../nodes/node-instance";
|
||||
import { $createTextNode, $createParagraphNode } from "../lexical";
|
||||
import {
|
||||
$createTextNode,
|
||||
$createParagraphNode,
|
||||
type ParagraphNode,
|
||||
} from "../lexical";
|
||||
|
||||
export const toLexicalNodes = (children: Instance["children"]) => {
|
||||
const nodes = [];
|
||||
const nodes: Array<ParagraphNode> = [];
|
||||
|
||||
let p = $createParagraphNode();
|
||||
nodes.push(p);
|
||||
|
||||
for (const child of children) {
|
||||
if (child === "\n") {
|
||||
p = $createParagraphNode();
|
||||
nodes.push(p);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof child === "string") {
|
||||
const paragraph = $createParagraphNode();
|
||||
const textNode = $createTextNode(child);
|
||||
paragraph.append(textNode);
|
||||
nodes.push(paragraph);
|
||||
p.append(textNode);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("toUpdates", () => {
|
||||
},
|
||||
} as const;
|
||||
const updates = toUpdates(state.root);
|
||||
expect(updates).toStrictEqual(["a\nb"]);
|
||||
expect(updates).toStrictEqual(["a", "\n", "b"]);
|
||||
});
|
||||
|
||||
test("text and instance", () => {
|
||||
@@ -83,6 +83,15 @@ describe("toUpdates", () => {
|
||||
children: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: "normal",
|
||||
style: "",
|
||||
text: "c",
|
||||
type: "text",
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
direction: "ltr",
|
||||
format: "",
|
||||
@@ -106,6 +115,7 @@ describe("toUpdates", () => {
|
||||
id: "62bcc02160a439686c7eabde",
|
||||
text: "b",
|
||||
},
|
||||
"c",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -19,13 +19,7 @@ export const toUpdates = (
|
||||
updates: ChildrenUpdates = []
|
||||
): ChildrenUpdates => {
|
||||
if (node.type === "text" && "text" in node) {
|
||||
const last = updates[updates.length - 1];
|
||||
if (typeof last === "string") {
|
||||
// Every new Paragraph node is basically just a new line.
|
||||
updates[updates.length - 1] = `${last}\n${node.text}`;
|
||||
} else {
|
||||
updates.push(node.text);
|
||||
}
|
||||
updates.push(node.text);
|
||||
}
|
||||
|
||||
if (node.type === "instance" && "instance" in node) {
|
||||
@@ -46,8 +40,13 @@ export const toUpdates = (
|
||||
|
||||
if ("children" in node) {
|
||||
for (const child of node.children) {
|
||||
if (child.type === "paragraph" && updates.length !== 0) {
|
||||
updates.push("\n");
|
||||
}
|
||||
|
||||
toUpdates(child, updates);
|
||||
}
|
||||
}
|
||||
|
||||
return updates;
|
||||
};
|
||||
Reference in new issue
Block a user