Now I have a Node named live_data_2 which have a textNode as childNode. I want to select this node and the textNode which is around this node together , but I find that it did not meet expectations. * The selection will bounce back
Here is the code
import React, { useState, useEffect, useRef } from 'react';
import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Plugin } from 'prosemirror-state';
import LiveData2View from './components/liveDataView';
import rjSchema, { addLiveData2 } from './components/rjSchema';
import { undo, redo, history } from 'prosemirror-history';
import { keymap } from 'prosemirror-keymap';
import 'prosemirror-view/style/prosemirror.css';
const Pm: React.FC = () => {
const editorRef = useRef<HTMLDivElement>(null);
const [editorView, setEditorView] = useState<EditorView>();
const [button, setButton] = useState(false);
const [button2, setButton2] = useState(false);
const [preview, setPreview] = useState(true);
useEffect(() => {
const state = EditorState.create({
schema: rjSchema,
plugins: [
history(),
...(preview ?
[
new Plugin({
props: {
editable(): boolean {
return false;
},
},
}),
] :
[]),
keymap({ 'Mod-z': undo, 'Mod-y': redo }),
],
});
console.log(state);
if (editorRef && editorRef.current && editorRef.current.childNodes.length === 0) {
setEditorView(
new EditorView(editorRef.current, {
state,
nodeViews: {
['live_data_2'](node, view, getPos) {
return new LiveData2View(node, view, getPos);
},
},
})
);
}
}, [editorRef]);
useEffect(() => {
if (editorView) {
addLiveData2(editorView);
}
}, [button]);
useEffect(() => {
if (editorView) {
const textNode = editorView.state.schema.text('text');
editorView.dispatch(editorView.state.tr.insert(editorView.state.selection.from, textNode));
}
}, [button2]);
return (
<>
<div
onClick={() => {
setButton(prev => !prev);
}}
>
button
</div>
<div
onClick={() => {
setButton2(prev => !prev);
}}
>
button2
</div>
<div
onClick={() => {
setPreview(preview => !preview);
}}
>
preview
</div>
<section ref={editorRef} style={{ height: '1000px', width: '1000px' }} />
</>
);
};
export default Pm;
import OrderedMap from 'orderedmap';
import { Schema, DOMOutputSpec, MarkSpec } from 'prosemirror-model';
import { nodes, marks } from 'prosemirror-schema-basic';
import { addListNodes } from 'prosemirror-schema-list';
const pDOM = ['p', 0] as DOMOutputSpec;
/**
* group为block组成的doc, 用于summary和content
*/
export const blockDoc = {
content: 'block+',
};
/**
* group为inline组成的doc, 用于基础信息模块
*/
export const inlineDoc = {
content: 'inline*',
};
export const text = {
group: 'inline',
};
/**
* 段落
*/
export const paragraph = {
content: 'inline*',
group: 'block',
selectable: false,
parseDOM: [{ tag: 'p' }],
toDOM(): DOMOutputSpec {
return pDOM;
},
};
const underline: MarkSpec = {
parseDOM: [
{
style: 'text-decoration',
getAttrs(value: string | Node): false | null {
return /underline/.test(value as string) && null;
},
},
{
style: 'text-decoration-line',
getAttrs(value: string | Node): false | null {
return /underline/.test(value as string) && null;
},
},
],
toDOM(): DOMOutputSpec {
return ['span', { style: 'text-decoration-line:underline' }, 0];
},
};
const clear: MarkSpec = {
toDOM(): DOMOutputSpec {
return ['span', { style: '' }];
},
};
const coustomMarks = {
underline,
clear,
bold: marks.strong,
italic: marks.em,
};
const liveData2 = {
content: 'text*',
group: 'inline',
inline: true,
atom: true,
marks: '',
selectable: true,
leaf: false,
attrs: {
locator: {}, // 引用全局第几个参考资料
description: {
// live data描述,用于hover展示
default: '',
},
nodeId: {
default: '',
},
},
parseDOM: [
{
tag: 'span[data-type=live_data_2]',
getAttrs(dom: Node | string): false | Record<string, unknown> {
const locator = (dom as HTMLElement).getAttribute('data-locator');
const description = (dom as HTMLElement).getAttribute('data-description');
if (!locator) {
return false;
}
return {
locator,
description,
};
},
},
],
toDOM(node: Node): DOMOutputSpec {
const { locator, description } = node.attrs;
return [
'span',
{
'data-type': 'live_data_2',
'data-locator': locator,
'data-description': description,
},
0,
];
},
};
export const addLiveData2 = view => {
const textNode = view.state.schema.text('livedata');
const node = view.state.schema.node(
'live_data_2',
{
locator: '243134',
description: '123',
},
textNode
);
view.dispatch(view.state.tr.insert(view.state.selection.from, node));
};
export default new Schema({
nodes: {
doc: blockDoc,
text,
inlineDoc,
paragraph,
live_data_2: liveData2,
},
});
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { Node } from 'prosemirror-model';
import { EditorView } from 'prosemirror-view';
function LiveDataComp(props): JSX.Element {
return <span>{props.text}</span>;
}
export default class LiveData2View {
dom: HTMLSpanElement;
node: Node;
view: EditorView;
getPos: () => number;
selected: boolean;
firstChild: Node | null;
constructor(node: Node, view: EditorView, getPos: () => number) {
this.dom = document.createElement('span');
this.node = node;
this.view = view;
this.firstChild = node.firstChild;
this.selected = false;
// this.dom.classList.add(styles.wrapper);
this.getPos = getPos;
this._renderView();
}
destroy(): void {
// 手动销毁组件
ReactDOM.unmountComponentAtNode(this.dom);
}
// 改变nodeView选中样式
selectNode(): void {
this.selected = true;
this._renderView();
}
deselectNode(): void {
this.selected = false;
this._renderView();
}
// setSelection(anchor: number, head: number) {}
stopEvent(): boolean {
return false;
}
private _getProps = () => {
const props = {
text: this.firstChild?.textContent,
};
return props;
};
private _renderView = () => {
const props = this._getProps();
console.log(props.text);
ReactDOM.render(<LiveDataComp text={props.text} />, this.dom);
};
}