]> BookStack Code Mirror - bookstack/blob - resources/js/editor/schema-nodes.js
Added support for iframe node blocks
[bookstack] / resources / js / editor / schema-nodes.js
1 import {orderedList, bulletList, listItem} from "prosemirror-schema-list";
2 import {tableNodes} from "prosemirror-tables";
3
4 /**
5  * @param {HTMLElement} node
6  * @return {string|null}
7  */
8 function getAlignAttrFromDomNode(node) {
9     const classList = node.classList;
10     const styles = node.style || {};
11     const alignments = ['right', 'left', 'center', 'justify'];
12     for (const alignment of alignments) {
13         if (classList.contains('align-' + alignment) || styles.textAlign === alignment) {
14             return alignment;
15         }
16     }
17     return null;
18 }
19
20 /**
21  * @param node
22  * @param {Object} attrs
23  * @return {Object}
24  */
25 function addAlignmentAttr(node, attrs) {
26     const positions = ['right', 'left', 'center', 'justify'];
27     for (const position of positions) {
28         if (node.attrs.align === position) {
29             return addClassToAttrs('align-' + position, attrs);
30         }
31     }
32     return attrs;
33 }
34
35 function getAttrsParserForAlignment(node) {
36     return {
37         align: getAlignAttrFromDomNode(node),
38     };
39 }
40
41 /**
42  * @param {String} className
43  * @param {Object} attrs
44  * @return {Object}
45  */
46 function addClassToAttrs(className, attrs) {
47     return Object.assign({}, attrs, {
48         class: attrs.class ? attrs.class + ' ' + className : className,
49     });
50 }
51
52 /**
53  * @param {String[]} attrNames
54  * @return {function(Element): {}}
55  */
56 function domAttrsToAttrsParser(attrNames) {
57     return function (node) {
58         const attrs = {};
59         for (const attr of attrNames) {
60             attrs[attr] = node.hasAttribute(attr) ? node.getAttribute(attr) : null;
61         }
62         return attrs;
63     };
64 }
65
66 /**
67  * @param {PmNode} node
68  * @param {String[]} attrNames
69  */
70 function extractAttrsForDom(node, attrNames) {
71     const domAttrs = {};
72     for (const attr of attrNames) {
73         if (node.attrs[attr]) {
74             domAttrs[attr] = node.attrs[attr];
75         }
76     }
77     return domAttrs;
78 }
79
80 const doc = {
81     content: "block+",
82 };
83
84 const paragraph = {
85     content: "inline*",
86     group: "block",
87     parseDOM: [
88         {
89             tag: "p",
90             getAttrs: getAttrsParserForAlignment,
91         }
92     ],
93     attrs: {
94         align: {
95             default: null,
96         }
97     },
98     toDOM(node) {
99         return ["p", addAlignmentAttr(node, {}), 0];
100     }
101 };
102
103 const blockquote = {
104     content: "block+",
105     group: "block",
106     defining: true,
107     parseDOM: [{tag: "blockquote", getAttrs: getAttrsParserForAlignment}],
108     attrs: {
109         align: {
110             default: null,
111         }
112     },
113     toDOM(node) {
114         return ["blockquote", addAlignmentAttr(node, {}), 0];
115     }
116 };
117
118 const horizontal_rule = {
119     group: "block",
120     parseDOM: [{tag: "hr"}],
121     toDOM() {
122         return ["hr"];
123     }
124 };
125
126
127 const headingParseGetAttrs = (level) => {
128     return function (node) {
129         return {level, align: getAlignAttrFromDomNode(node)};
130     };
131 };
132 const heading = {
133     attrs: {level: {default: 1}, align: {default: null}},
134     content: "inline*",
135     group: "block",
136     defining: true,
137     parseDOM: [
138         {tag: "h1", getAttrs: headingParseGetAttrs(1)},
139         {tag: "h2", getAttrs: headingParseGetAttrs(2)},
140         {tag: "h3", getAttrs: headingParseGetAttrs(3)},
141         {tag: "h4", getAttrs: headingParseGetAttrs(4)},
142         {tag: "h5", getAttrs: headingParseGetAttrs(5)},
143         {tag: "h6", getAttrs: headingParseGetAttrs(6)},
144     ],
145     toDOM(node) {
146         return ["h" + node.attrs.level, addAlignmentAttr(node, {}), 0]
147     }
148 };
149
150 const code_block = {
151     content: "text*",
152     marks: "",
153     group: "block",
154     code: true,
155     defining: true,
156     parseDOM: [{tag: "pre", preserveWhitespace: "full"}],
157     toDOM() {
158         return ["pre", ["code", 0]];
159     }
160 };
161
162 const text = {
163     group: "inline"
164 };
165
166 const image = {
167     inline: true,
168     attrs: {
169         src: {},
170         alt: {default: null},
171         title: {default: null},
172         height: {default: null},
173         width: {default: null},
174     },
175     group: "inline",
176     draggable: true,
177     parseDOM: [{
178         tag: "img[src]", getAttrs: function getAttrs(dom) {
179             return {
180                 src: dom.getAttribute("src"),
181                 title: dom.getAttribute("title"),
182                 alt: dom.getAttribute("alt"),
183                 height: dom.getAttribute("height"),
184                 width: dom.getAttribute("width"),
185             }
186         }
187     }],
188     toDOM: function toDOM(node) {
189         const ref = node.attrs;
190         const src = ref.src;
191         const alt = ref.alt;
192         const title = ref.title;
193         const width = ref.width;
194         const height = ref.height;
195         return ["img", {src, alt, title, width, height}]
196     }
197 };
198
199 const iframe = {
200     attrs: {
201         src: {},
202         height: {default: null},
203         width: {default: null},
204         title: {default: null},
205         allow: {default: null},
206         sandbox: {default: null},
207     },
208     group: "block",
209     draggable: true,
210     parseDOM: [{
211         tag: "iframe",
212         getAttrs: domAttrsToAttrsParser(["src", "width", "height", "title", "allow", "sandbox"]),
213     }],
214     toDOM(node) {
215         const attrs = extractAttrsForDom(node, ["src", "width", "height", "title", "allow", "sandbox"])
216         return ["iframe", attrs];
217     }
218 };
219
220 const hard_break = {
221     inline: true,
222     group: "inline",
223     selectable: false,
224     parseDOM: [{tag: "br"}],
225     toDOM() {
226         return ["br"];
227     }
228 };
229
230
231 const calloutParseGetAttrs = (type) => {
232     return function (node) {
233         return {type, align: getAlignAttrFromDomNode(node)};
234     };
235 };
236 const callout = {
237     attrs: {
238         type: {default: 'info'},
239         align: {default: null},
240     },
241     content: "inline*",
242     group: "block",
243     defining: true,
244     parseDOM: [
245         {tag: 'p.callout.info', getAttrs: calloutParseGetAttrs('info'), priority: 75},
246         {tag: 'p.callout.success', getAttrs: calloutParseGetAttrs('success'), priority: 75},
247         {tag: 'p.callout.danger', getAttrs: calloutParseGetAttrs('danger'), priority: 75},
248         {tag: 'p.callout.warning', getAttrs: calloutParseGetAttrs('warning'), priority: 75},
249         {tag: 'p.callout', getAttrs: calloutParseGetAttrs('info'), priority: 75},
250     ],
251     toDOM(node) {
252         const type = node.attrs.type || 'info';
253         return ['p', addAlignmentAttr(node, {class: 'callout ' + type}), 0];
254     }
255 };
256
257 const ordered_list = Object.assign({}, orderedList, {content: "list_item+", group: "block"});
258 const bullet_list = Object.assign({}, bulletList, {content: "list_item+", group: "block"});
259 const list_item = Object.assign({}, listItem, {content: 'paragraph block*'});
260
261 const {
262     table_row,
263     table_cell,
264     table_header,
265 } = tableNodes({
266     tableGroup: "block",
267     cellContent: "block+"
268 });
269
270 const table = {
271     content: "table_row+",
272     attrs: {
273         style: {default: null},
274     },
275     tableRole: "table",
276     isolating: true,
277     group: "block",
278     parseDOM: [{tag: "table", getAttrs: domAttrsToAttrsParser(['style'])}],
279     toDOM(node) {
280         console.log(extractAttrsForDom(node, ['style']));
281         return ["table", extractAttrsForDom(node, ['style']), ["tbody", 0]]
282     }
283 };
284
285 const nodes = {
286     doc,
287     paragraph,
288     blockquote,
289     horizontal_rule,
290     heading,
291     code_block,
292     text,
293     image,
294     iframe,
295     hard_break,
296     callout,
297     ordered_list,
298     bullet_list,
299     list_item,
300     table,
301     table_row,
302     table_cell,
303     table_header,
304 };
305
306 export default nodes;