]> BookStack Code Mirror - bookstack/blob - resources/js/editor/util.js
Shared link mark update logic with color controls
[bookstack] / resources / js / editor / util.js
1 import schema from "./schema";
2 import {DOMParser, DOMSerializer} from "prosemirror-model";
3
4 /**
5  * @param {String} html
6  * @return {PmNode}
7  */
8 export function htmlToDoc(html) {
9     const renderDoc = document.implementation.createHTMLDocument();
10     renderDoc.body.innerHTML = html;
11     return DOMParser.fromSchema(schema).parse(renderDoc.body);
12 }
13
14 /**
15  * @param {PmNode} doc
16  * @return {string}
17  */
18 export function docToHtml(doc) {
19     const fragment = DOMSerializer.fromSchema(schema).serializeFragment(doc.content);
20     const renderDoc = document.implementation.createHTMLDocument();
21     renderDoc.body.appendChild(fragment);
22     return renderDoc.body.innerHTML;
23 }
24
25 /**
26  * @param {PmEditorState} state
27  * @return {String}
28  */
29 export function stateToHtml(state) {
30     const fragment = DOMSerializer.fromSchema(schema).serializeFragment(state.doc.content);
31     const renderDoc = document.implementation.createHTMLDocument();
32     renderDoc.body.appendChild(fragment);
33     return renderDoc.body.innerHTML;
34 }
35
36 /**
37  * @param {Object} object
38  * @return {{}}
39  */
40 export function nullifyEmptyValues(object) {
41     const clean = {};
42     for (const [key, value] of Object.entries(object)) {
43         clean[key] = (value === "") ? null : value;
44     }
45     return clean;
46 }
47
48 /**
49  * @param {PmEditorState} state
50  * @param {PmSelection} selection
51  * @param {PmMarkType} markType
52  * @return {{from: Number, to: Number}}
53  */
54 export function expandSelectionToMark(state, selection, markType) {
55     let {from, to} = selection;
56     const noRange = (from === to);
57     if (noRange) {
58         const markRange = markRangeAtPosition(state, markType, from);
59         if (markRange.from !== -1) {
60             from = markRange.from;
61             to = markRange.to;
62         }
63     }
64     return {from, to};
65 }
66
67 /**
68  * @param {PmEditorState} state
69  * @param {PmMarkType} markType
70  * @param {Number} pos
71  * @return {{from: Number, to: Number}}
72  */
73 export function markRangeAtPosition(state, markType, pos) {
74     const $pos = state.doc.resolve(pos);
75
76     const {parent, parentOffset} = $pos;
77     const start = parent.childAfter(parentOffset);
78     if (!start.node) return {from: -1, to: -1};
79
80     const mark = start.node.marks.find((mark) => mark.type === markType);
81     if (!mark) return {from: -1, to: -1};
82
83     let startIndex = $pos.index();
84     let startPos = $pos.start() + start.offset;
85     let endIndex = startIndex + 1;
86     let endPos = startPos + start.node.nodeSize;
87     while (startIndex > 0 && mark.isInSet(parent.child(startIndex - 1).marks)) {
88         startIndex -= 1;
89         startPos -= parent.child(startIndex).nodeSize;
90     }
91     while (endIndex < parent.childCount && mark.isInSet(parent.child(endIndex).marks)) {
92         endPos += parent.child(endIndex).nodeSize;
93         endIndex += 1;
94     }
95     return {from: startPos, to: endPos};
96 }
97
98 /**
99  * @class KeyedMultiStack
100  * Holds many stacks, seperated via a key, with a simple
101  * interface to pop and push values to the stacks.
102  */
103 export class KeyedMultiStack {
104
105     constructor() {
106         this.stack = {};
107     }
108
109     /**
110      * @param {String} key
111      * @return {undefined|*}
112      */
113     pop(key) {
114         if (Array.isArray(this.stack[key])) {
115             return this.stack[key].pop();
116         }
117         return undefined;
118     }
119
120     /**
121      * @param {String} key
122      * @param {*} value
123      */
124     push(key, value) {
125         if (this.stack[key] === undefined) {
126             this.stack[key] = [];
127         }
128
129         this.stack[key].push(value);
130     }
131 }