I am trying to implement spoiler and call it via inputRule
The basic diagram looks like this
const nodes: {
doc: {
content: 'block+'
} as NodeSpec,
paragraph: {
attrs: { style: { default: '' } },
content: 'inline*',
group: 'block',
parseDOM: [{ tag: 'p' }],
toDOM(node) {
return ['p', 0];
}
} as NodeSpec,
spoiler: {
attrs: {
label: {
default: ''
}
},
content: 'block+',
group: 'block',
parseDOM: [
{ tag: 'div' },
{ class: 'spoiler' },
['div', { class: 'spoiler__head', getAttrs: (dom) => getTitleAttrs(dom) }, 0],
['div', { class: 'spoiler__content' }, 0]
],
toDOM(node) {
return [
'div',
{ class: 'spoiler', ...getTitleAttrs(node) },
['div', { class: 'spoiler__head' }],
['div', { class: 'spoiler__content' }, 0]
];
}
} as NodeSpec
}
function getTitleAttrs(node) {
return {
'data-title': node.attrs.label
};
}
Editor initialization
let state = EditorState.create({
schema: main_schema,
plugins: [
inputRules({
rules: [wrappingInputRule(/^\s*=\s$/, main_schema.nodes.spoiler)]
}),
...exampleSetup({ schema: main_schema })
]
});
const editor_view = new EditorView(this.el, {
state: state,
dispatchTransaction: (transaction) => {
editor_view.updateState(editor_view.state.apply(transaction));
...
}
});
Spoiler View
function generate_spoiler(context, node, view, getPos) {
// Main spoiler node
let node_wrap = (context.dom = document.createElement('div'));
node_wrap.addClass('spoiler');
// Header wrapper
let title_wrap = document.createElement('div');
title_wrap.addClass('spoiler__head');
// Content container
let content_wrap = (context.contentDOM = document.createElement('div'));
content_wrap.addClass('spoiler__content');
// Input field
let title_input = (context.input_node = document.createElement('input'));
title_input.addClass('spoiler__input');
// Delete button
let delete_button = document.createElement('button');
delete_button.innerText = 'Удалить';
// toggle button
let toggle_button = document.createElement('button');
content_wrap.addClass('spoiler__toggle');
toggle_button.innerText = '>';
node_wrap.appendChild(title_wrap);
node_wrap.appendChild(content_wrap);
node_wrap.appendChild(delete_button);
title_wrap.appendChild(toggle_button);
title_wrap.appendChild(title_input);
delete_button.addEventListener('click', (e) => {
view.dispatch(view.state.tr.delete(getPos(), getPos() + context.nodeSize));
e.preventDefault();
});
title_input.addEventListener('input', ({ target }) => {
let tra = view.state.tr.setNodeMarkup(getPos(), null, {
label: (<HTMLInputElement>target)!.value
});
view.dispatch(tra);
});
}
export class SpoilerView {
declare dom: HTMLElement;
declare contentDOM: HTMLElement;
nodeSize: number;
input_node!: HTMLInputElement;
constructor(node, view: EditorView, getPos) {
this.nodeSize = node.nodeSize;
generate_spoiler(this, node, view, getPos);
this.input_node.value = node.attrs.label;
}
update(node) {
if (node.type.name !== 'spoiler') return false;
this.nodeSize = node.nodeSize;
this.input_node.value = node.attrs.label;
return true;
}
}
This design generally works well, include undo and redo. Exactly before trying to copy and paste back.
Initial construction and expected construction after insertion:
<spoiler>
<spoiler_head>
<input>
</spoiler_head>
<spoiler_content>
<p>Entered content</p>
</spoiler_content>
</spoiler>
Real design after pasting
<spoiler>
<spoiler_head>
<input>
</spoiler_head>
<spoiler_content>
<spoiler>
<spoiler_head>
<input>
</spoiler_head>
<spoiler_content></spoiler_content>
</spoiler>
<spoiler>
<spoiler_head>
<input>
</spoiler_head>
<spoiler_content>
<p>Entered content</p>
</spoiler_content>
</spoiler>
</spoiler_content>
</spoiler>
This also happens if you disable nodeViews
In addition, when copying, attributes are not preserved.
What could be the reason for this behavior?