Skip to content

Commit

Permalink
commit1: add Editor component
Browse files Browse the repository at this point in the history
  • Loading branch information
ljq0226 committed Aug 9, 2023
1 parent 72733b4 commit f1a446e
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 3 deletions.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
"preview": "vite preview"
},
"dependencies": {
"prosemirror-commands": "^1.5.2",
"prosemirror-history": "^1.3.2",
"prosemirror-keymap": "^1.2.2",
"prosemirror-model": "^1.19.3",
"prosemirror-state": "^1.4.3",
"prosemirror-view": "^1.31.7",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
82 changes: 82 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 30 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
import { useState } from "react";
import Editor from "./components/Editor";

const INITIAL_HTML = `
<p style="text-align: center">He<strong>llo!</strong>__<span style="font-size: 20px;"><span style="color: orange">World</span></span></p>
<p><u><span style="font-size: 24px">你好啊</span></u><em><u>阿萨德</u></em></p>
<p><a href="https://google.com" target="_blank">Google</a></p>
<p style="text-align: right"><a href="https://github.com/ljq0226/my-prosemirror" target="_blank">GitHub</p>
`;
function App() {

const [html, setHtml] = useState(INITIAL_HTML);
return (
<div className="h-screen w-screen p-[200px] bg-slate-300">
<h1 className="text-center text-2xl">My-ProseMirror-Editor</h1>
<div className="h-[800px] bg-white w-full">

<div className="h-[800px] bg-white w-full py-10 px-8">
<Editor
initialHtml={html}
onChangeHtml={(newHtml: string) => {
setHtml(newHtml);
}}
/>
<div className="w-full h-1 bg-black" />
<div>
<h3>原生HTML</h3>
<div>{html}</div>
</div>
<div className="w-full h-1 bg-black" />
<div>
<h3>渲染后</h3>
<div
dangerouslySetInnerHTML={{
__html: html
}}
/>
</div>
</div>
</div>
)
Expand Down
81 changes: 81 additions & 0 deletions src/components/Editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useEffect, useRef } from "react";
import { Schema, Node, DOMParser } from "prosemirror-model";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { undo, redo, history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { baseKeymap, toggleMark } from "prosemirror-commands";
import { schema } from "../schema";


const createDoc = <T extends Schema>(html: string, pmSchema: T) => {
const element = document.createElement("div");
element.innerHTML = html;
return DOMParser.fromSchema(pmSchema).parse(element);
};
const createPmState = <T extends Schema>(
pmSchema: T,
options: { doc?: Node } = {}
) => {
return EditorState.create({
doc: options.doc,
schema: pmSchema,
plugins: [
history(),
keymap({
"Mod-z": undo,
"Mod-y": redo,
"Mod-Shift-z": redo
}),
keymap({
"Mod-b": toggleMark(pmSchema.marks.strong),
"Mod-i": toggleMark(pmSchema.marks.em),
"Mod-u": toggleMark(pmSchema.marks.underline)
}),
keymap({
Enter: baseKeymap["Enter"],
Backspace: baseKeymap["Backspace"]
}),
]
});
};

export type Props = {
initialHtml: string;
onChangeHtml: (html: string) => void;
};

const Editor = ({ initialHtml, onChangeHtml }: Props) => {
const elContentRef = useRef<HTMLDivElement | null>(null);
const editorViewRef = useRef<EditorView>();

useEffect(() => {
//1.创建 doc 对象
const doc = createDoc(initialHtml, schema);
//2.创建 prosemirror state
const state = createPmState(schema, { doc });
//3.创建 EditorView 视图实例
const editorView = new EditorView(elContentRef.current, {
state,
//处理编辑器中的事务(transaction),并在每次事务应用后更新编辑器的状态,并调用 onChangeHtml 回调函数通知外部编辑器内容的变化。
dispatchTransaction(transaction) {
const newState = editorView.state.apply(transaction);
editorView.updateState(newState);
onChangeHtml(editorView.dom.innerHTML);
},
});
editorViewRef.current = editorView;

return () => {
editorView.destroy();
};
}, []);
return (
<div className="relative">
<div ref={elContentRef} />
</div>
);

}

export default Editor
82 changes: 82 additions & 0 deletions src/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Schema } from "prosemirror-model";

export const schema = new Schema({
nodes: {
//顶级节点,表示整个文档。它可以包含多个 block 类型的子节点。
doc: {
content: "block+"
},
//段落节点,用于表示段落文本。它可以包含多个inline类型的子节点。
//具有align 属性,用于指定对齐方式。parseDOM定义了如何从 DOM 元素解析为节点
// ,toDOM定义了如何将节点渲染为 DOM 元素。
paragraph: {
content: "inline*",
group: "block",
attrs: {
align: { default: "left" }
},
parseDOM: [
{
tag: "p",
getAttrs(dom) {
if (typeof dom === "string") {
return false;
}
return {
align: dom.style.textAlign || "left"
};
}
}
],
toDOM(node) {
const { align } = node.attrs;
if (!align || align === "left") {
return ["p", 0];
}
return ["p", { style: `text-align: ${align}` }, 0];
}
},
//文本节点,用于表示文本内容。它是inline类型的节点。
text: {
group: "inline"
}
},
//标记:描述每个元素的解析规则和渲染规则
marks: {
em: {
parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
toDOM() {
return ["em", 0];
}
},

strong: {
parseDOM: [
{ tag: "strong" },
{
tag: "b",
getAttrs: (node: string | HTMLElement) =>
typeof node !== "string" &&
node.style.fontWeight !== "normal" &&
null
},
{
style: "font-weight",
getAttrs: (value: string | HTMLElement) =>
typeof value === "string" &&
/^(bold(er)?|[5-9]\d{2,})$/.test(value) &&
null
}
],
toDOM() {
return ["strong", 0];
}
},
underline: {
parseDOM: [{ tag: "u" }],
toDOM() {
return ["u", 0];
}
},
}
});

0 comments on commit f1a446e

Please sign in to comment.