@takeshape/util 8.16.0 → 8.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/common.js +4 -1
- package/es/draftjs-templates.js +148 -0
- package/es/draftjs.js +871 -0
- package/es/get-image-url.js +8 -0
- package/es/highlight-code.js +27 -0
- package/es/templates.js +97 -0
- package/lib/common.d.ts +3 -0
- package/lib/common.d.ts.map +1 -1
- package/lib/common.js +39 -0
- package/lib/draftjs-templates.d.ts +47 -0
- package/lib/draftjs-templates.d.ts.map +1 -0
- package/lib/draftjs-templates.js +168 -0
- package/lib/draftjs.d.ts +27 -0
- package/lib/draftjs.d.ts.map +1 -0
- package/lib/draftjs.js +901 -0
- package/lib/get-image-url.d.ts +5 -0
- package/lib/get-image-url.d.ts.map +1 -0
- package/lib/get-image-url.js +16 -0
- package/lib/highlight-code.d.ts +15 -0
- package/lib/highlight-code.d.ts.map +1 -0
- package/lib/highlight-code.js +53 -0
- package/lib/templates.d.ts +30 -0
- package/lib/templates.d.ts.map +1 -0
- package/lib/templates.js +121 -0
- package/package.json +12 -2
package/es/draftjs.js
ADDED
|
@@ -0,0 +1,871 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The intention is to move all this to the client package in the end, but right now it's here
|
|
3
|
+
* to be shared between the draftjs / mdx implementations, which span the client / server differently.
|
|
4
|
+
*/
|
|
5
|
+
import { draftToMarkdown, markdownToDraft } from 'markdown-draft-js';
|
|
6
|
+
import { imageTemplate, imageTemplateMdx, oembedTemplate, oembedTemplateMdx } from './templates'; // With @types/htmlparser2 it says parseDOM is not exported; excluding types for now
|
|
7
|
+
// @ts-expect-error Untyped
|
|
8
|
+
|
|
9
|
+
import { parseDOM } from 'htmlparser2';
|
|
10
|
+
import Url from 'url-parse';
|
|
11
|
+
import render from 'dom-serializer';
|
|
12
|
+
import shortid from 'shortid';
|
|
13
|
+
import { getDraftjsImage, getDraftjsOembed, getDraftjsPullquote } from './draftjs-templates';
|
|
14
|
+
import escape from 'lodash/escape';
|
|
15
|
+
import unescape from 'lodash/unescape';
|
|
16
|
+
import pickBy from 'lodash/pickBy';
|
|
17
|
+
import { getImageUrl } from '@takeshape/routing';
|
|
18
|
+
import forEach from 'lodash/forEach';
|
|
19
|
+
const SUPERSCRIPT_MARKER = 'TEMPORARY_SUPERSCRIPT_MARKER_TLfDNyf7VYKDduyL';
|
|
20
|
+
const SUBSCRIPT_MARKER = 'TEMPORARY_SUBSCRIPT_MARKER_K5VrdPEzyQyy2RcY';
|
|
21
|
+
const INSERT_MARKER = 'TEMPORARY_INSERT_MARKER_FSYdr8m8CS7YLb8y';
|
|
22
|
+
const EXTERNAL_LINK_MARKER = 'TEMPORARY_EXTERNAL_LINK_MARKER_HP3vprmERkc9ZAss';
|
|
23
|
+
const pullquoteStyle = 'font-style: italic; margin: 2em 2.5em;';
|
|
24
|
+
|
|
25
|
+
const getDraftjsEmpty = (depth = 0) => {
|
|
26
|
+
return {
|
|
27
|
+
key: shortid.generate(),
|
|
28
|
+
depth,
|
|
29
|
+
type: 'unstyled',
|
|
30
|
+
text: '',
|
|
31
|
+
entityRanges: [],
|
|
32
|
+
inlineStyleRanges: []
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const REMOVAL_MARKER_TYPE = 'removal-marker';
|
|
37
|
+
|
|
38
|
+
const getDraftjsRemovalMarker = (depth = 0) => {
|
|
39
|
+
return {
|
|
40
|
+
key: shortid.generate(),
|
|
41
|
+
type: REMOVAL_MARKER_TYPE,
|
|
42
|
+
depth,
|
|
43
|
+
text: '',
|
|
44
|
+
entityRanges: [],
|
|
45
|
+
inlineStyleRanges: []
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const mdxShortcodePrefix = tagName => `TS${tagName}`;
|
|
50
|
+
|
|
51
|
+
const handleMultiword = (markdown, marker, markdownSyntax, markdownRegex) => {
|
|
52
|
+
const parts = markdown.replace(markdownRegex, `\\${markdownSyntax}`).split(marker);
|
|
53
|
+
let result = '';
|
|
54
|
+
let open = false;
|
|
55
|
+
|
|
56
|
+
for (const [i, part] of parts.entries()) {
|
|
57
|
+
if (open) {
|
|
58
|
+
result += part.replace(/\s+/g, match => `${markdownSyntax}${match}${markdownSyntax}`);
|
|
59
|
+
} else {
|
|
60
|
+
result += part;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (i !== parts.length - 1) {
|
|
64
|
+
result += markdownSyntax;
|
|
65
|
+
open = !open;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return result;
|
|
70
|
+
}; // Workaround inability to reasonably process child content using draftToMarkdown
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
const handleExternalLinks = markdown => {
|
|
74
|
+
const regex = /(<TSExternalLink text="TEMPORARY_EXTERNAL_LINK_MARKER_HP3vprmERkc9ZAss" href="[^"]+">)([^<]+)<\/TSExternalLink>/g;
|
|
75
|
+
return markdown.replace(regex, (match, openTag, content) => match.replace(EXTERNAL_LINK_MARKER, escape(content)));
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export function fromDraftjs(draftjs, styleItems, entityItems) {
|
|
79
|
+
let markdown = draftToMarkdown(draftjs, {
|
|
80
|
+
styleItems: {
|
|
81
|
+
'section-break': {
|
|
82
|
+
open() {
|
|
83
|
+
return '***';
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
close() {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
},
|
|
91
|
+
SUP: {
|
|
92
|
+
open() {
|
|
93
|
+
return SUPERSCRIPT_MARKER;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
close() {
|
|
97
|
+
return SUPERSCRIPT_MARKER;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
},
|
|
101
|
+
SUB: {
|
|
102
|
+
open() {
|
|
103
|
+
return SUBSCRIPT_MARKER;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
close() {
|
|
107
|
+
return SUBSCRIPT_MARKER;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
},
|
|
111
|
+
UNDERLINE: {
|
|
112
|
+
open() {
|
|
113
|
+
return INSERT_MARKER;
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
close() {
|
|
117
|
+
return INSERT_MARKER;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
},
|
|
121
|
+
'code-block': {
|
|
122
|
+
open(entity) {
|
|
123
|
+
var _entity$data;
|
|
124
|
+
|
|
125
|
+
const lang = (entity === null || entity === void 0 ? void 0 : (_entity$data = entity.data) === null || _entity$data === void 0 ? void 0 : _entity$data.lang) ?? '';
|
|
126
|
+
return `\`\`\`${lang}\n`;
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
close() {
|
|
130
|
+
return '\n```';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
},
|
|
134
|
+
...styleItems
|
|
135
|
+
},
|
|
136
|
+
entityItems: {
|
|
137
|
+
LINK: {
|
|
138
|
+
open(entity) {
|
|
139
|
+
var _entity$data2;
|
|
140
|
+
|
|
141
|
+
if ((entity === null || entity === void 0 ? void 0 : (_entity$data2 = entity.data) === null || _entity$data2 === void 0 ? void 0 : _entity$data2.target) === '_blank') {
|
|
142
|
+
var _entity$data3, _entity$data4;
|
|
143
|
+
|
|
144
|
+
const url = escape((entity === null || entity === void 0 ? void 0 : (_entity$data3 = entity.data) === null || _entity$data3 === void 0 ? void 0 : _entity$data3.url) || (entity === null || entity === void 0 ? void 0 : (_entity$data4 = entity.data) === null || _entity$data4 === void 0 ? void 0 : _entity$data4.href) || '');
|
|
145
|
+
return `<TSExternalLink text="${EXTERNAL_LINK_MARKER}" href="${url}">`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return '[';
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
close(entity) {
|
|
152
|
+
var _entity$data5, _entity$data6, _entity$data7;
|
|
153
|
+
|
|
154
|
+
if ((entity === null || entity === void 0 ? void 0 : (_entity$data5 = entity.data) === null || _entity$data5 === void 0 ? void 0 : _entity$data5.target) === '_blank') {
|
|
155
|
+
return '</TSExternalLink>';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return ']('.concat((entity === null || entity === void 0 ? void 0 : (_entity$data6 = entity.data) === null || _entity$data6 === void 0 ? void 0 : _entity$data6.url) || (entity === null || entity === void 0 ? void 0 : (_entity$data7 = entity.data) === null || _entity$data7 === void 0 ? void 0 : _entity$data7.href) || '', ')');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
},
|
|
162
|
+
...entityItems
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
markdown = handleExternalLinks(markdown);
|
|
166
|
+
markdown = handleMultiword(markdown, SUPERSCRIPT_MARKER, '^', /\^/g);
|
|
167
|
+
markdown = handleMultiword(markdown, SUBSCRIPT_MARKER, '~', /~/g); // eslint-disable-next-line security-node/non-literal-reg-expr
|
|
168
|
+
|
|
169
|
+
markdown = markdown.replace(/\+/g, '\\+').replace(new RegExp(INSERT_MARKER, 'g'), '++');
|
|
170
|
+
return markdown;
|
|
171
|
+
}
|
|
172
|
+
export function draftjsToMd(draftjs, assets) {
|
|
173
|
+
return fromDraftjs(draftjs, {
|
|
174
|
+
pullquote: {
|
|
175
|
+
open() {
|
|
176
|
+
return `<aside style="${pullquoteStyle}">`;
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
close() {
|
|
180
|
+
return '</aside>';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
}, {
|
|
185
|
+
oembed: {
|
|
186
|
+
open() {
|
|
187
|
+
return '';
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
close(entity) {
|
|
191
|
+
if (!(entity !== null && entity !== void 0 && entity.data)) {
|
|
192
|
+
return '';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const {
|
|
196
|
+
data
|
|
197
|
+
} = entity;
|
|
198
|
+
return oembedTemplate(str => str, data);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
},
|
|
202
|
+
image: {
|
|
203
|
+
open() {
|
|
204
|
+
return '';
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
close(entity) {
|
|
208
|
+
if (!(entity !== null && entity !== void 0 && entity.data)) {
|
|
209
|
+
return '';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const {
|
|
213
|
+
data
|
|
214
|
+
} = entity;
|
|
215
|
+
const asset = assets[data.id];
|
|
216
|
+
|
|
217
|
+
if (asset === undefined) {
|
|
218
|
+
return '';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const imageTemplateData = { ...data,
|
|
222
|
+
alignment: data.alignment,
|
|
223
|
+
size: data.size,
|
|
224
|
+
asset,
|
|
225
|
+
caption: getAssetField(data, asset, 'caption'),
|
|
226
|
+
credit: getAssetField(data, asset, 'credit'),
|
|
227
|
+
imageParams: {}
|
|
228
|
+
};
|
|
229
|
+
return imageTemplate(str => str, imageTemplateData);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function getTextFromDraftjs(draftjs) {
|
|
237
|
+
var _draftjs$blocks, _draftjs$blocks$;
|
|
238
|
+
|
|
239
|
+
return draftjs === null || draftjs === void 0 ? void 0 : (_draftjs$blocks = draftjs.blocks) === null || _draftjs$blocks === void 0 ? void 0 : (_draftjs$blocks$ = _draftjs$blocks[0]) === null || _draftjs$blocks$ === void 0 ? void 0 : _draftjs$blocks$.text;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function getAssetField(data, asset, field) {
|
|
243
|
+
const dataText = getTextFromDraftjs(data[field]);
|
|
244
|
+
|
|
245
|
+
if (dataText) {
|
|
246
|
+
return dataText;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (asset[field]) {
|
|
250
|
+
return getTextFromDraftjs(asset[field]);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return '';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function draftjsToMdx(draftjs, assets, prefix = mdxShortcodePrefix) {
|
|
257
|
+
return fromDraftjs(draftjs, {
|
|
258
|
+
pullquote: {
|
|
259
|
+
open() {
|
|
260
|
+
return `<${prefix('Pullquote')}>`;
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
close() {
|
|
264
|
+
return `</${prefix('Pullquote')}>`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
}
|
|
268
|
+
}, {
|
|
269
|
+
oembed: {
|
|
270
|
+
open() {
|
|
271
|
+
return '';
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
close(entity) {
|
|
275
|
+
if (!(entity !== null && entity !== void 0 && entity.data)) {
|
|
276
|
+
return '';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const {
|
|
280
|
+
data
|
|
281
|
+
} = entity;
|
|
282
|
+
return oembedTemplateMdx(prefix, data);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
},
|
|
286
|
+
image: {
|
|
287
|
+
open() {
|
|
288
|
+
return '';
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
close(entity) {
|
|
292
|
+
var _entity$data8, _data$link, _data$link2;
|
|
293
|
+
|
|
294
|
+
const asset = assets[entity === null || entity === void 0 ? void 0 : (_entity$data8 = entity.data) === null || _entity$data8 === void 0 ? void 0 : _entity$data8.id];
|
|
295
|
+
|
|
296
|
+
if (!(entity !== null && entity !== void 0 && entity.data) || asset === undefined) {
|
|
297
|
+
return '';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const {
|
|
301
|
+
data
|
|
302
|
+
} = entity;
|
|
303
|
+
const imageUrlOptions = process.env.NODE_ENV === 'production' ? {} : {
|
|
304
|
+
baseUrl: 'https://images.dev.takeshape.io'
|
|
305
|
+
};
|
|
306
|
+
return imageTemplateMdx(prefix, { ...data,
|
|
307
|
+
assetId: data.id,
|
|
308
|
+
assetPath: asset.path,
|
|
309
|
+
credit: getAssetField(data, asset, 'credit'),
|
|
310
|
+
caption: getAssetField(data, asset, 'caption'),
|
|
311
|
+
link: (_data$link = data.link) === null || _data$link === void 0 ? void 0 : _data$link.url,
|
|
312
|
+
linkisexternal: (_data$link2 = data.link) !== null && _data$link2 !== void 0 && _data$link2.external ? 'true' : 'false',
|
|
313
|
+
src: getImageUrl(asset.path, data.imageParams, imageUrlOptions)
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function getAssetIdFromImageSrc(src) {
|
|
322
|
+
return new Url(src).pathname.split('/')[3];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function parseFigureClass(className) {
|
|
326
|
+
if (className === undefined) {
|
|
327
|
+
return {
|
|
328
|
+
alignment: undefined,
|
|
329
|
+
size: undefined
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const parts = className.split(' ');
|
|
334
|
+
return {
|
|
335
|
+
alignment: parts[0],
|
|
336
|
+
size: parts[1]
|
|
337
|
+
};
|
|
338
|
+
} // eslint-disable-next-line complexity
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
function htmlDomToDraftjsImage(dom, item, entityKeyGenerator) {
|
|
342
|
+
for (const figure of dom) {
|
|
343
|
+
if (figure.type === 'tag' && figure.name === 'figure') {
|
|
344
|
+
var _figure$children, _figure$children2, _figcaption, _figcaption2, _caption, _credit, _link, _link2;
|
|
345
|
+
|
|
346
|
+
let link;
|
|
347
|
+
const figureChildOne = (_figure$children = figure.children) === null || _figure$children === void 0 ? void 0 : _figure$children[0];
|
|
348
|
+
const figureChildTwo = (_figure$children2 = figure.children) === null || _figure$children2 === void 0 ? void 0 : _figure$children2[1];
|
|
349
|
+
|
|
350
|
+
if ((figureChildOne === null || figureChildOne === void 0 ? void 0 : figureChildOne.type) === 'tag' && (figureChildOne === null || figureChildOne === void 0 ? void 0 : figureChildOne.name) === 'a') {
|
|
351
|
+
link = figureChildOne;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let figcaption;
|
|
355
|
+
|
|
356
|
+
if ((figureChildOne === null || figureChildOne === void 0 ? void 0 : figureChildOne.type) === 'tag' && (figureChildOne === null || figureChildOne === void 0 ? void 0 : figureChildOne.name) === 'figcaption') {
|
|
357
|
+
figcaption = figureChildOne;
|
|
358
|
+
} else if ((figureChildTwo === null || figureChildTwo === void 0 ? void 0 : figureChildTwo.type) === 'tag' && (figureChildTwo === null || figureChildTwo === void 0 ? void 0 : figureChildTwo.name) === 'figcaption') {
|
|
359
|
+
figcaption = figure.children[1];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const img = link ? link.children[0] : figure.children[0];
|
|
363
|
+
|
|
364
|
+
if ((img === null || img === void 0 ? void 0 : img.type) !== 'tag' || (img === null || img === void 0 ? void 0 : img.name) !== 'img') {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const figcaptionChild = (_figcaption = figcaption) === null || _figcaption === void 0 ? void 0 : _figcaption.children[0];
|
|
369
|
+
let caption;
|
|
370
|
+
|
|
371
|
+
if ((figcaptionChild === null || figcaptionChild === void 0 ? void 0 : figcaptionChild.type) === 'tag' && (figcaptionChild === null || figcaptionChild === void 0 ? void 0 : figcaptionChild.name) === 'span' && (figcaptionChild === null || figcaptionChild === void 0 ? void 0 : figcaptionChild.attribs.class) === 'caption') {
|
|
372
|
+
caption = figcaptionChild;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const captionChildThree = (_figcaption2 = figcaption) === null || _figcaption2 === void 0 ? void 0 : _figcaption2.children[2];
|
|
376
|
+
let credit;
|
|
377
|
+
|
|
378
|
+
if ((captionChildThree === null || captionChildThree === void 0 ? void 0 : captionChildThree.type) === 'tag' && (captionChildThree === null || captionChildThree === void 0 ? void 0 : captionChildThree.name) === 'span' && (captionChildThree === null || captionChildThree === void 0 ? void 0 : captionChildThree.attribs.class) === 'credit') {
|
|
379
|
+
credit = captionChildThree;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const {
|
|
383
|
+
alignment,
|
|
384
|
+
size
|
|
385
|
+
} = parseFigureClass(figure.attribs.class);
|
|
386
|
+
return getDraftjsImage({
|
|
387
|
+
captionText: ((_caption = caption) === null || _caption === void 0 ? void 0 : _caption.children[0].data) ?? '',
|
|
388
|
+
creditText: ((_credit = credit) === null || _credit === void 0 ? void 0 : _credit.children[0].data) ?? '',
|
|
389
|
+
alignment,
|
|
390
|
+
size,
|
|
391
|
+
assetId: getAssetIdFromImageSrc(img.attribs.src),
|
|
392
|
+
linkUrl: (_link = link) === null || _link === void 0 ? void 0 : _link.attribs.href,
|
|
393
|
+
linkIsExternal: ((_link2 = link) === null || _link2 === void 0 ? void 0 : _link2.attribs.target) === '_blank',
|
|
394
|
+
key: entityKeyGenerator(),
|
|
395
|
+
depth: item.level,
|
|
396
|
+
path: getImagePathFromUrl(img.attribs.src)
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function htmlDomToOembed(dom, item, entityKeyGenerator) {
|
|
403
|
+
for (const oembedDiv of dom) {
|
|
404
|
+
if (oembedDiv.type === 'tag' && oembedDiv.name === 'div' && oembedDiv.attribs.class === 'oembed') {
|
|
405
|
+
const blockquote = oembedDiv.children[0];
|
|
406
|
+
|
|
407
|
+
if ((blockquote === null || blockquote === void 0 ? void 0 : blockquote.type) !== 'tag' || (blockquote === null || blockquote === void 0 ? void 0 : blockquote.name) !== 'blockquote') {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const script = oembedDiv.children[2];
|
|
412
|
+
|
|
413
|
+
if ((script === null || script === void 0 ? void 0 : script.type) !== 'script' || (script === null || script === void 0 ? void 0 : script.name) !== 'script') {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return getDraftjsOembed({
|
|
418
|
+
html: `${render(blockquote)}\n${render(script)}\n`,
|
|
419
|
+
key: entityKeyGenerator(),
|
|
420
|
+
depth: item.level
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function mdxToDraftjsOembed(item, entityKeyGenerator) {
|
|
427
|
+
if (item.content.startsWith(`<${mdxShortcodePrefix('Oembed')}`)) {
|
|
428
|
+
const dom = parseDOM(item.content)[0];
|
|
429
|
+
return getDraftjsOembed({
|
|
430
|
+
html: unescape(dom.attribs.html),
|
|
431
|
+
width: dom.attribs.width ? Number(dom.attribs.width) : undefined,
|
|
432
|
+
height: dom.attribs.height ? Number(dom.attribs.height) : undefined,
|
|
433
|
+
url: unescape(dom.attribs.url),
|
|
434
|
+
author_name: unescape(dom.attribs.author_name),
|
|
435
|
+
author_url: unescape(dom.attribs.author_url),
|
|
436
|
+
type: unescape(dom.attribs.type),
|
|
437
|
+
cache_age: Number(dom.attribs.cache_age),
|
|
438
|
+
provider_name: unescape(dom.attribs.provider_name),
|
|
439
|
+
provider_url: unescape(dom.attribs.provider_url),
|
|
440
|
+
version: unescape(dom.attribs.version),
|
|
441
|
+
key: entityKeyGenerator(),
|
|
442
|
+
depth: item.level
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function htmlDomToPullquote(dom, item) {
|
|
448
|
+
for (const pullquote of dom) {
|
|
449
|
+
if (pullquote.type === 'tag' && pullquote.name === 'aside' && pullquote.attribs.style === pullquoteStyle) {
|
|
450
|
+
const text = pullquote.children[0];
|
|
451
|
+
|
|
452
|
+
if ((text === null || text === void 0 ? void 0 : text.type) !== 'text') {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return getDraftjsPullquote({
|
|
457
|
+
text: text.data,
|
|
458
|
+
depth: item.level
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function fromMd(md, blockEntities, blockTypes) {
|
|
465
|
+
md = md.replace(/\\\+/g, '+').replace(/\\~/g, '~').replace(/\\\^/g, '^');
|
|
466
|
+
return markdownToDraft(md, {
|
|
467
|
+
remarkablePreset: 'full',
|
|
468
|
+
remarkableOptions: {
|
|
469
|
+
html: true
|
|
470
|
+
},
|
|
471
|
+
blockStyles: {
|
|
472
|
+
ins_open: 'UNDERLINE',
|
|
473
|
+
sub: 'SUB',
|
|
474
|
+
sup: 'SUP'
|
|
475
|
+
},
|
|
476
|
+
blockEntities,
|
|
477
|
+
blockTypes: {
|
|
478
|
+
hr(item) {
|
|
479
|
+
if (!item) {
|
|
480
|
+
return getDraftjsEmpty();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
key: shortid.generate(),
|
|
485
|
+
text: ' ',
|
|
486
|
+
type: 'section-break',
|
|
487
|
+
depth: item.level,
|
|
488
|
+
inlineStyleRanges: [],
|
|
489
|
+
entityRanges: [],
|
|
490
|
+
data: {}
|
|
491
|
+
};
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
fence(item) {
|
|
495
|
+
if (!item) {
|
|
496
|
+
return getDraftjsEmpty();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
type: 'code-block',
|
|
501
|
+
data: {
|
|
502
|
+
// In markdown-to-draft this is language, we require lang
|
|
503
|
+
lang: item.params || ''
|
|
504
|
+
},
|
|
505
|
+
// Using the text handling from markdown-to-draft
|
|
506
|
+
text: (item.content || '').replace(/\n$/, ''),
|
|
507
|
+
entityRanges: [],
|
|
508
|
+
inlineStyleRanges: []
|
|
509
|
+
};
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
...blockTypes
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export function mdToDraftjs(mdx) {
|
|
518
|
+
const entities = {}; // Start really high to avoid conflicts with keys created by markdown-draft-js
|
|
519
|
+
|
|
520
|
+
let currentEntityKey = 1000000;
|
|
521
|
+
|
|
522
|
+
function entityKeyGenerator() {
|
|
523
|
+
return currentEntityKey++;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const result = fromMd(mdx, {}, {
|
|
527
|
+
htmlblock(item) {
|
|
528
|
+
if (item === undefined) {
|
|
529
|
+
return getDraftjsEmpty();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const dom = parseDOM(item.content);
|
|
533
|
+
const image = htmlDomToDraftjsImage(dom, item, entityKeyGenerator);
|
|
534
|
+
|
|
535
|
+
if (image) {
|
|
536
|
+
Object.assign(entities, image.entities);
|
|
537
|
+
return image.contentBlock;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const oembed = htmlDomToOembed(dom, item, entityKeyGenerator);
|
|
541
|
+
|
|
542
|
+
if (oembed) {
|
|
543
|
+
Object.assign(entities, oembed.entities);
|
|
544
|
+
return oembed.contentBlock;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const pullquote = htmlDomToPullquote(dom, item);
|
|
548
|
+
|
|
549
|
+
if (pullquote) {
|
|
550
|
+
Object.assign(entities, pullquote.entities);
|
|
551
|
+
return pullquote.contentBlock;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return getDraftjsEmpty(item.level);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
});
|
|
558
|
+
result.entityMap = { ...result.entityMap,
|
|
559
|
+
...entities
|
|
560
|
+
};
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
export function getImagePathFromUrl(url) {
|
|
564
|
+
return new Url(url).pathname.substr(1);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function mdxToBr(item) {
|
|
568
|
+
if (item.content.startsWith('<br/>')) {
|
|
569
|
+
return {
|
|
570
|
+
key: shortid.generate(),
|
|
571
|
+
depth: item.depth,
|
|
572
|
+
type: 'unstyled',
|
|
573
|
+
text: '',
|
|
574
|
+
entityRanges: [],
|
|
575
|
+
inlineStyleRanges: []
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function mdxToLinkData(item) {
|
|
581
|
+
if (item.content.startsWith('<TSExternalLink')) {
|
|
582
|
+
const dom = parseDOM(item.content)[0];
|
|
583
|
+
return {
|
|
584
|
+
url: dom.attribs.href,
|
|
585
|
+
target: '_blank',
|
|
586
|
+
text: dom.attribs.text
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function mdxToDraftjsImage(item, entityKeyGenerator) {
|
|
592
|
+
if (item.content.startsWith(`<${mdxShortcodePrefix('Image')}`)) {
|
|
593
|
+
const dom = parseDOM(item.content)[0];
|
|
594
|
+
return getDraftjsImage({
|
|
595
|
+
captionText: unescape(dom.attribs.caption).replace(/<\/?p>/g, ''),
|
|
596
|
+
creditText: unescape(dom.attribs.credit).replace(/<\/?p>/g, ''),
|
|
597
|
+
alignment: dom.attribs.alignment,
|
|
598
|
+
size: dom.attribs.size,
|
|
599
|
+
assetId: dom.attribs.id,
|
|
600
|
+
linkUrl: dom.attribs.link,
|
|
601
|
+
linkIsExternal: dom.attribs.linkisexternal === 'true',
|
|
602
|
+
key: entityKeyGenerator(),
|
|
603
|
+
depth: item.level,
|
|
604
|
+
path: getImagePathFromUrl(decodeURIComponent(dom.attribs.src))
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function mdxToDraftjsPullquote(item) {
|
|
610
|
+
if (item.content.startsWith(`<${mdxShortcodePrefix('Pullquote')}`)) {
|
|
611
|
+
const dom = parseDOM(item.content)[0];
|
|
612
|
+
return getDraftjsPullquote({
|
|
613
|
+
text: unescape(dom.attribs.text),
|
|
614
|
+
depth: item.level
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Mutates result to replace empty entities with links,
|
|
621
|
+
* assuming there is nothing else they could be...
|
|
622
|
+
*/
|
|
623
|
+
function addLinks(state, links) {
|
|
624
|
+
let linkNumber = 0;
|
|
625
|
+
let linkOpen = false;
|
|
626
|
+
|
|
627
|
+
for (const block of state.blocks) {
|
|
628
|
+
let blockIsCustom = false;
|
|
629
|
+
|
|
630
|
+
for (const [i, entityRange] of block.entityRanges.entries()) {
|
|
631
|
+
var _entity$data9;
|
|
632
|
+
|
|
633
|
+
const entity = state.entityMap[entityRange.key];
|
|
634
|
+
|
|
635
|
+
if (entity === undefined) {
|
|
636
|
+
throw new Error('Missing entity');
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (i === 0 && (_entity$data9 = entity.data) !== null && _entity$data9 !== void 0 && _entity$data9.marker) {
|
|
640
|
+
blockIsCustom = true;
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (blockIsCustom && i === Object.keys(block.entityRanges).length - 1) {
|
|
645
|
+
continue;
|
|
646
|
+
} // Ignore line breaks that were inserted around blocks
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
if (entity.depth === undefined) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const link = links[linkNumber];
|
|
654
|
+
|
|
655
|
+
if (link && !linkOpen) {
|
|
656
|
+
entity.type = 'LINK';
|
|
657
|
+
entity.mutability = 'MUTABLE';
|
|
658
|
+
entity.data = link;
|
|
659
|
+
entity.text = link.text;
|
|
660
|
+
entityRange.length += link.text.length;
|
|
661
|
+
linkNumber++;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
linkOpen = !linkOpen;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Mutate state to replace blocks with our custom versions
|
|
670
|
+
* Return a list of entity keys that should be removed
|
|
671
|
+
*/
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
function replaceBlocks(state, replacementBlocks) {
|
|
675
|
+
let entityKeysToExclude = [];
|
|
676
|
+
|
|
677
|
+
for (let i = 0; i < state.blocks.length; i++) {
|
|
678
|
+
const block = state.blocks[i];
|
|
679
|
+
|
|
680
|
+
for (const entityKey of Object.keys(state.entityMap)) {
|
|
681
|
+
let removeBlockEntityKeys = false;
|
|
682
|
+
const blockEntityKeys = block.entityRanges.map(range => range.key);
|
|
683
|
+
|
|
684
|
+
if (blockEntityKeys.includes(Number(entityKey))) {
|
|
685
|
+
var _entity$data10;
|
|
686
|
+
|
|
687
|
+
const entity = state.entityMap[entityKey];
|
|
688
|
+
const markerKey = (_entity$data10 = entity.data) === null || _entity$data10 === void 0 ? void 0 : _entity$data10.marker;
|
|
689
|
+
const replacementBlock = replacementBlocks[markerKey];
|
|
690
|
+
|
|
691
|
+
if (replacementBlock && !removeBlockEntityKeys) {
|
|
692
|
+
const originalBlock = state.blocks[i];
|
|
693
|
+
const originalBlockText = state.blocks[i].text;
|
|
694
|
+
replacementBlock.text = originalBlockText !== '' ? originalBlockText : replacementBlock.text;
|
|
695
|
+
replacementBlock.inlineStyleRanges = originalBlock.inlineStyleRanges;
|
|
696
|
+
const removeBlockEntities = entity.data.type === 'image' || entity.data.type === 'oembed'; // eslint-disable-next-line max-depth
|
|
697
|
+
|
|
698
|
+
if (!removeBlockEntities) {
|
|
699
|
+
replacementBlock.entityRanges = originalBlock.entityRanges.filter(entityRange => {
|
|
700
|
+
var _state$entityMap$enti;
|
|
701
|
+
|
|
702
|
+
return ((_state$entityMap$enti = state.entityMap[entityRange.key].data) === null || _state$entityMap$enti === void 0 ? void 0 : _state$entityMap$enti.marker) === undefined;
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
state.blocks[i] = replacementBlock; // eslint-disable-next-line max-depth
|
|
707
|
+
|
|
708
|
+
if (removeBlockEntities) {
|
|
709
|
+
removeBlockEntityKeys = true;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (removeBlockEntityKeys) {
|
|
715
|
+
entityKeysToExclude = entityKeysToExclude.concat(blockEntityKeys);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return entityKeysToExclude;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const blockStarts = [{
|
|
724
|
+
regex: ' <TSImage',
|
|
725
|
+
replacement: ' <TSImage'
|
|
726
|
+
}, {
|
|
727
|
+
regex: ' <TSOembed',
|
|
728
|
+
replacement: ' <TSOembed'
|
|
729
|
+
}, {
|
|
730
|
+
regex: '\\*\\*\\*',
|
|
731
|
+
replacement: '***'
|
|
732
|
+
}, {
|
|
733
|
+
regex: '```',
|
|
734
|
+
replacement: '```'
|
|
735
|
+
}];
|
|
736
|
+
const blockEnds = [{
|
|
737
|
+
regex: '(?<=<TSImage[^>]+)/>',
|
|
738
|
+
replacement: '/>'
|
|
739
|
+
}, {
|
|
740
|
+
regex: '</TSOembed>',
|
|
741
|
+
replacement: '</TSOembed>'
|
|
742
|
+
}, {
|
|
743
|
+
regex: '\\*\\*\\*',
|
|
744
|
+
replacement: '***'
|
|
745
|
+
}, {
|
|
746
|
+
regex: '```',
|
|
747
|
+
replacement: '```'
|
|
748
|
+
}];
|
|
749
|
+
/**
|
|
750
|
+
* Make sure there is a place to put the cursor around block-level items such as images and oembeds
|
|
751
|
+
*/
|
|
752
|
+
|
|
753
|
+
export function insertBreaksAroundBlocks(mdx) {
|
|
754
|
+
for (const start of blockStarts) {
|
|
755
|
+
// eslint-disable-next-line security-node/non-literal-reg-expr
|
|
756
|
+
mdx = mdx.replace(new RegExp(`^${start.regex}`, 'g'), `<br/>\n\n${start.replacement}`);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
for (const end of blockEnds) {
|
|
760
|
+
// eslint-disable-next-line security-node/non-literal-reg-expr
|
|
761
|
+
mdx = mdx.replace(new RegExp(`${end.regex}\\s*$`, 'g'), `${end.replacement}\n\n<br/>`);
|
|
762
|
+
|
|
763
|
+
for (const start of blockStarts) {
|
|
764
|
+
mdx = mdx.replace( // eslint-disable-next-line security-node/non-literal-reg-expr
|
|
765
|
+
new RegExp(`${end.regex}\\s*${start.regex}`, 'g'), `${end.replacement}\n\n<br/>\n\n${start.replacement}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return mdx;
|
|
770
|
+
}
|
|
771
|
+
export function mdxToDraftjs(mdx) {
|
|
772
|
+
const replacementBlocks = {};
|
|
773
|
+
const entities = {};
|
|
774
|
+
const oembedKeyToHtml = {};
|
|
775
|
+
let currentOembedKey; // Start really high to avoid conflicts with keys created by markdown-draft-js
|
|
776
|
+
|
|
777
|
+
let currentEntityKey = 1000000;
|
|
778
|
+
|
|
779
|
+
function entityKeyGenerator() {
|
|
780
|
+
return currentEntityKey++;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
mdx = insertBreaksAroundBlocks(mdx);
|
|
784
|
+
const links = [];
|
|
785
|
+
const result = fromMd(mdx, {
|
|
786
|
+
htmltag(item) {
|
|
787
|
+
if (item === undefined) {
|
|
788
|
+
return getDraftjsEmpty();
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const br = mdxToBr(item);
|
|
792
|
+
|
|
793
|
+
if (br) {
|
|
794
|
+
return br;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const linkData = mdxToLinkData(item);
|
|
798
|
+
|
|
799
|
+
if (linkData) {
|
|
800
|
+
links.push(linkData);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const image = mdxToDraftjsImage(item, entityKeyGenerator);
|
|
804
|
+
|
|
805
|
+
if (image) {
|
|
806
|
+
replacementBlocks[image.contentBlock.key] = image.contentBlock;
|
|
807
|
+
Object.assign(entities, image.entities);
|
|
808
|
+
return {
|
|
809
|
+
data: {
|
|
810
|
+
marker: image.contentBlock.key,
|
|
811
|
+
type: 'image'
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const oembed = mdxToDraftjsOembed(item, entityKeyGenerator);
|
|
817
|
+
|
|
818
|
+
if (oembed) {
|
|
819
|
+
currentOembedKey = Object.keys(oembed.entities)[0];
|
|
820
|
+
replacementBlocks[oembed.contentBlock.key] = oembed.contentBlock;
|
|
821
|
+
Object.assign(entities, oembed.entities);
|
|
822
|
+
return {
|
|
823
|
+
data: {
|
|
824
|
+
marker: oembed.contentBlock.key,
|
|
825
|
+
type: 'oembed'
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const pullquote = mdxToDraftjsPullquote(item);
|
|
831
|
+
|
|
832
|
+
if (pullquote) {
|
|
833
|
+
replacementBlocks[pullquote.contentBlock.key] = pullquote.contentBlock;
|
|
834
|
+
Object.assign(entities, pullquote.entities);
|
|
835
|
+
return {
|
|
836
|
+
data: {
|
|
837
|
+
marker: pullquote.contentBlock.key,
|
|
838
|
+
type: 'pullquote'
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
return getDraftjsEmpty(item.level);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
}, {
|
|
847
|
+
htmlblock(item) {
|
|
848
|
+
if (item && currentOembedKey) {
|
|
849
|
+
oembedKeyToHtml[currentOembedKey] = item.content.replace(`</${mdxShortcodePrefix('Oembed')}>\n`, '');
|
|
850
|
+
currentOembedKey = undefined;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return getDraftjsRemovalMarker();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
});
|
|
857
|
+
addLinks(result, links);
|
|
858
|
+
const entityKeysToExclude = replaceBlocks(result, replacementBlocks);
|
|
859
|
+
result.blocks = result.blocks.filter(block => block.type !== REMOVAL_MARKER_TYPE);
|
|
860
|
+
forEach(oembedKeyToHtml, (html, key) => {
|
|
861
|
+
entities[key].data.html = html;
|
|
862
|
+
});
|
|
863
|
+
result.entityMap = pickBy({ ...result.entityMap,
|
|
864
|
+
...entities
|
|
865
|
+
}, (entity, key) => {
|
|
866
|
+
var _entity$data11;
|
|
867
|
+
|
|
868
|
+
return ((_entity$data11 = entity.data) === null || _entity$data11 === void 0 ? void 0 : _entity$data11.marker) === undefined && !entityKeysToExclude.includes(Number(key));
|
|
869
|
+
});
|
|
870
|
+
return result;
|
|
871
|
+
}
|