1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
import { TextSelection } from 'prosemirror-state';
import { Fragment, Node, Slice } from 'prosemirror-model';
import * as ReactDOM from 'react-dom/client';
import * as React from 'react';
interface ISummaryView {}
// currently nothing needs to be rendered for the internal view of a summary.
// eslint-disable-next-line react/prefer-stateless-function
export class SummaryViewInternal extends React.Component<ISummaryView> {
render() {
return null;
}
}
// an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked.
// this node actively edits prosemirror (as opposed to just changing how things are rendered) and thus doesn't
// really need a react view. However, it would be cleaner to figure out how to do this just as a react rendering
// method instead of changing prosemirror's text when the expand/elide buttons are clicked.
export class SummaryView {
dom: HTMLSpanElement; // container for label and value
root: any;
constructor(node: any, view: any, getPos: any) {
this.dom = document.createElement('span');
this.dom.className = this.className(node.attrs.visibility);
this.dom.onpointerdown = (e: any) => {
this.onPointerDown(e, node, view, getPos);
};
this.dom.onkeypress = function (e: any) {
e.stopPropagation();
};
this.dom.onkeydown = function (e: any) {
e.stopPropagation();
};
this.dom.onkeyup = function (e: any) {
e.stopPropagation();
};
this.dom.onmousedown = function (e: any) {
e.stopPropagation();
};
const js = node.toJSON;
node.toJSON = function (...args: any[]) {
return js.apply(this, args);
};
this.root = ReactDOM.createRoot(this.dom);
this.root.render(<SummaryViewInternal />);
}
className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed');
destroy() {
this.root.unmount();
}
selectNode() {}
updateSummarizedText(start: any, view: any) {
const mtype = view.state.schema.marks.summarize;
const mtypeInc = view.state.schema.marks.summarizeInclusive;
let endPos = start;
const visited = new Set();
for (let i: number = start + 1; i < view.state.doc.nodeSize - 1; i++) {
let skip = false;
// eslint-disable-next-line no-loop-func
view.state.doc.nodesBetween(start, i, (node: Node /* , pos: number, parent: Node, index: number */) => {
if (node.isLeaf && !visited.has(node) && !skip) {
if (node.marks.find((m: any) => m.type === mtype || m.type === mtypeInc)) {
visited.add(node);
endPos = i + node.nodeSize - 1;
} else skip = true;
}
});
}
return TextSelection.create(view.state.doc, start, endPos);
}
onPointerDown = (e: any, node: any, view: any, getPos: any) => {
const visible = !node.attrs.visibility;
const attrs = { ...node.attrs, visibility: visible };
let textSelection = TextSelection.create(view.state.doc, getPos() + 1);
if (!visible) {
// update summarized text and save in attrs
textSelection = this.updateSummarizedText(getPos() + 1, view);
attrs.text = textSelection.content();
attrs.textslice = attrs.text.toJSON();
}
view.dispatch(
view.state.tr
.setSelection(textSelection) // select the current summarized text (or where it will be if its collapsed)
.replaceSelection(!visible ? new Slice(Fragment.fromArray([]), 0, 0) : node.attrs.text) // collapse/expand it
.setNodeMarkup(getPos(), undefined, attrs)
); // update the attrs
e.preventDefault();
e.stopPropagation();
this.dom.className = this.className(visible);
};
}
|