]> BookStack Code Mirror - bookstack/blob - resources/js/editor/markdown-serializer.js
Started work on details/summary blocks
[bookstack] / resources / js / editor / markdown-serializer.js
1 import {MarkdownSerializer, defaultMarkdownSerializer, MarkdownSerializerState} from "prosemirror-markdown";
2 import {docToHtml} from "./util";
3
4 const nodes = defaultMarkdownSerializer.nodes;
5 const marks = defaultMarkdownSerializer.marks;
6
7
8 nodes.callout = function (state, node) {
9     writeNodeAsHtml(state, node);
10 };
11
12 nodes.table = function (state, node) {
13     writeNodeAsHtml(state, node);
14 };
15
16 nodes.iframe = function (state, node) {
17     writeNodeAsHtml(state, node);
18 };
19
20 nodes.details = function (state, node) {
21     wrapNodeWithHtml(state, node, '<details>', '</details>');
22 };
23
24 nodes.details_summary = function(state, node) {
25     writeNodeAsHtml(state, node);
26 };
27
28 function isPlainURL(link, parent, index, side) {
29     if (link.attrs.title || !/^\w+:/.test(link.attrs.href)) {
30         return false
31     }
32     const content = parent.child(index + (side < 0 ? -1 : 0));
33     if (!content.isText || content.text != link.attrs.href || content.marks[content.marks.length - 1] != link) {
34         return false
35     }
36     if (index == (side < 0 ? 1 : parent.childCount - 1)) {
37         return true
38     }
39     const next = parent.child(index + (side < 0 ? -2 : 1));
40     return !link.isInSet(next.marks)
41 }
42
43 marks.link = {
44     open(state, mark, parent, index) {
45         const attrs = mark.attrs;
46         if (attrs.target) {
47             return `<a href="${attrs.target}" ${attrs.title ? `title="${attrs.title}"` : ''} target="${attrs.target}">`
48         }
49         return isPlainURL(mark, parent, index, 1) ? "<" : "["
50     },
51     close(state, mark, parent, index) {
52         if (mark.attrs.target) {
53             return `</a>`;
54         }
55         return isPlainURL(mark, parent, index, -1) ? ">"
56             : "](" + state.esc(mark.attrs.href) + (mark.attrs.title ? " " + state.quote(mark.attrs.title) : "") + ")"
57     }
58 };
59
60 marks.underline = {
61     open: '<span style="text-decoration: underline;">',
62     close: '</span>',
63 };
64
65 marks.strike = {
66     open: '<span style="text-decoration: line-through;">',
67     close: '</span>',
68 };
69
70 marks.superscript = {
71     open: '<sup>',
72     close: '</sup>',
73 };
74
75 marks.subscript = {
76     open: '<sub>',
77     close: '</sub>',
78 };
79
80 marks.text_color = {
81     open(state, mark, parent, index) {
82         return `<span style="color: ${mark.attrs.color};">`
83     },
84     close: '</span>',
85 };
86
87 marks.background_color = {
88     open(state, mark, parent, index) {
89         return `<span style="background-color: ${mark.attrs.color};">`
90     },
91     close: '</span>',
92 };
93
94 /**
95  * @param {MarkdownSerializerState} state
96  * @param {PmNode} node
97  */
98 function writeNodeAsHtml(state, node) {
99     const html = docToHtml({content: [node]});
100     state.write(html);
101     state.ensureNewLine();
102     state.write('\n');
103     state.closeBlock();
104 }
105
106 /**
107  * @param {MarkdownSerializerState} state
108  * @param {PmNode} node
109  * @param {String} openTag
110  * @param {String} closeTag
111  */
112 function wrapNodeWithHtml(state, node, openTag, closeTag) {
113     state.write(openTag);
114     state.ensureNewLine();
115     state.renderContent(node);
116     state.write(closeTag);
117     state.closeBlock();
118     state.ensureNewLine();
119     state.write('\n');
120 }
121
122 // Update serializers to just write out as HTML if we have an attribute
123 // or element that cannot be represented in commonmark without losing
124 // formatting or content.
125 for (const [nodeType, serializerFunction] of Object.entries(nodes)) {
126     nodes[nodeType] = function (state, node, parent, index) {
127         if (node.attrs.align || node.attrs.height || node.attrs.width) {
128             writeNodeAsHtml(state, node);
129         } else {
130             serializerFunction(state, node, parent, index);
131         }
132     }
133 }
134
135
136 const serializer = new MarkdownSerializer(nodes, marks);
137
138 export default serializer;