inkpen 0.9.0 → 0.9.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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/inkpen/controllers/editor_controller.js +215 -148
- data/app/assets/javascripts/inkpen/export/markdown.js +42 -6
- data/app/assets/javascripts/inkpen.bundle.js +110 -107
- data/app/assets/javascripts/inkpen.bundle.js.map +4 -4
- data/docs/CHANGELOG.md +8 -0
- data/lib/inkpen/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 92089e260ac3068e3e269e864d4ec9f0294447bbb4e28764f02f5c722debc155
|
|
4
|
+
data.tar.gz: 1b824dd56c5ccd3f1c448cf841ea53db2660617f14a6b4fe5a41ee9b20f15f7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5d6ceb94d0867ab0b0a5194f6a1468454c1ef8b6782b94cd62ce469a15667ca04ad3c764681130c1e148cc823ef0b8cdd770a0822a01ffb402be632300869ce0
|
|
7
|
+
data.tar.gz: 9420e9694ce248bec30176e60e26a3521749a1e7b5463ba14423a58b87df6b18b40a82b82ff5ab9aa1f9146637f8330e32f1abb309744ca89d09346deaa28e89
|
|
@@ -1,158 +1,225 @@
|
|
|
1
1
|
import { Controller } from "@hotwired/stimulus"
|
|
2
2
|
|
|
3
3
|
// ============================================
|
|
4
|
-
// LAZY LOADING
|
|
4
|
+
// LAZY LOADING — gated by enabled extensions (spec 02 slice B, 0.9.1)
|
|
5
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
|
-
underlineExt,
|
|
36
|
-
subscriptExt,
|
|
37
|
-
superscriptExt,
|
|
38
|
-
youtubeExt,
|
|
39
|
-
characterCountExt,
|
|
40
|
-
bubbleMenuExt,
|
|
41
|
-
// Inkpen custom extensions
|
|
42
|
-
sectionMod,
|
|
43
|
-
preformattedMod,
|
|
44
|
-
slashCommandsMod,
|
|
45
|
-
blockGutterMod,
|
|
46
|
-
dragHandleMod,
|
|
47
|
-
toggleBlockMod,
|
|
48
|
-
columnsMod,
|
|
49
|
-
calloutMod,
|
|
50
|
-
blockCommandsMod,
|
|
51
|
-
enhancedImageMod,
|
|
52
|
-
fileAttachmentMod,
|
|
53
|
-
embedMod,
|
|
54
|
-
advancedTableMod,
|
|
55
|
-
tableOfContentsMod,
|
|
56
|
-
databaseMod,
|
|
57
|
-
documentSectionMod,
|
|
58
|
-
contentEmbedMod
|
|
59
|
-
] = await Promise.all([
|
|
60
|
-
import("@tiptap/core"),
|
|
61
|
-
import("@tiptap/pm/model"),
|
|
62
|
-
import("@tiptap/extension-document"),
|
|
63
|
-
import("@tiptap/starter-kit"),
|
|
64
|
-
import("@tiptap/extension-link"),
|
|
65
|
-
import("@tiptap/extension-placeholder"),
|
|
66
|
-
import("@tiptap/extension-image"),
|
|
67
|
-
import("@tiptap/extension-table"),
|
|
68
|
-
import("@tiptap/extension-table-row"),
|
|
69
|
-
import("@tiptap/extension-table-cell"),
|
|
70
|
-
import("@tiptap/extension-table-header"),
|
|
71
|
-
import("@tiptap/extension-task-list"),
|
|
72
|
-
import("@tiptap/extension-task-item"),
|
|
73
|
-
import("@tiptap/extension-mention"),
|
|
74
|
-
import("@tiptap/extension-code-block-lowlight"),
|
|
75
|
-
import("lowlight"),
|
|
76
|
-
import("@tiptap/extension-typography"),
|
|
77
|
-
import("@tiptap/extension-highlight"),
|
|
78
|
-
import("@tiptap/extension-underline"),
|
|
79
|
-
import("@tiptap/extension-subscript"),
|
|
80
|
-
import("@tiptap/extension-superscript"),
|
|
81
|
-
import("@tiptap/extension-youtube"),
|
|
82
|
-
import("@tiptap/extension-character-count"),
|
|
83
|
-
import("@tiptap/extension-bubble-menu"),
|
|
84
|
-
// Inkpen custom extensions
|
|
85
|
-
import("inkpen/extensions/section"),
|
|
86
|
-
import("inkpen/extensions/preformatted"),
|
|
87
|
-
import("inkpen/extensions/slash_commands"),
|
|
88
|
-
import("inkpen/extensions/block_gutter"),
|
|
89
|
-
import("inkpen/extensions/drag_handle"),
|
|
90
|
-
import("inkpen/extensions/toggle_block"),
|
|
91
|
-
import("inkpen/extensions/columns"),
|
|
92
|
-
import("inkpen/extensions/callout"),
|
|
93
|
-
import("inkpen/extensions/block_commands"),
|
|
94
|
-
import("inkpen/extensions/enhanced_image"),
|
|
95
|
-
import("inkpen/extensions/file_attachment"),
|
|
96
|
-
import("inkpen/extensions/embed"),
|
|
97
|
-
import("inkpen/extensions/advanced_table"),
|
|
98
|
-
import("inkpen/extensions/table_of_contents"),
|
|
99
|
-
import("inkpen/extensions/database"),
|
|
100
|
-
import("inkpen/extensions/document_section"),
|
|
101
|
-
import("inkpen/extensions/content_embed")
|
|
102
|
-
])
|
|
103
|
-
|
|
104
|
-
cachedModules = {
|
|
6
|
+
// TipTap/ProseMirror modules are dynamically imported when the editor
|
|
7
|
+
// connects. Slice A (0.9.0) dropped static-imports from index.js; this
|
|
8
|
+
// slice (B, 0.9.1) gates the dynamic-import set on the editor's
|
|
9
|
+
// `extensions-value` configuration.
|
|
10
|
+
//
|
|
11
|
+
// Module objects returned by this loader still expose every extension
|
|
12
|
+
// class name as a key — buildExtensions destructures all of them and
|
|
13
|
+
// each conditional (`if (enabledExtensions.includes("X"))`) reads the
|
|
14
|
+
// destructured local. If a gated extension is disabled, its key is
|
|
15
|
+
// simply absent (undefined), the conditional is false, and the local
|
|
16
|
+
// is never used.
|
|
17
|
+
//
|
|
18
|
+
// Real byte savings need esbuild `splitting: true` — slice C. This
|
|
19
|
+
// slice trims runtime extension-construction cost only; the bundle
|
|
20
|
+
// still inlines every module the dynamic imports can reach.
|
|
21
|
+
|
|
22
|
+
const cachedModules = new Map()
|
|
23
|
+
|
|
24
|
+
// Always loaded — required by every editor regardless of enabled set.
|
|
25
|
+
async function loadCoreModules() {
|
|
26
|
+
const [tiptapCore, tiptapPmModel, starterKit, placeholderExt, bubbleMenuExt] =
|
|
27
|
+
await Promise.all([
|
|
28
|
+
import("@tiptap/core"),
|
|
29
|
+
import("@tiptap/pm/model"),
|
|
30
|
+
import("@tiptap/starter-kit"),
|
|
31
|
+
import("@tiptap/extension-placeholder"),
|
|
32
|
+
import("@tiptap/extension-bubble-menu")
|
|
33
|
+
])
|
|
34
|
+
return {
|
|
105
35
|
Editor: tiptapCore.Editor,
|
|
106
36
|
DOMSerializer: tiptapPmModel.DOMSerializer,
|
|
107
|
-
Document: documentExt.default,
|
|
108
37
|
StarterKit: starterKit.default,
|
|
109
|
-
Link: linkExt.default,
|
|
110
38
|
Placeholder: placeholderExt.default,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
39
|
+
BubbleMenu: bubbleMenuExt.default
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// One loader per gated extension name. Each returns the subset of
|
|
44
|
+
// keys it contributes to the merged modules object. Adding a new
|
|
45
|
+
// extension means: (1) add its name + loader here, (2) destructure
|
|
46
|
+
// the new key in buildExtensions, (3) gate its usage there.
|
|
47
|
+
const EXTENSION_LOADERS = {
|
|
48
|
+
forced_document: async () => {
|
|
49
|
+
const m = await import("@tiptap/extension-document")
|
|
50
|
+
return { Document: m.default }
|
|
51
|
+
},
|
|
52
|
+
link: async () => {
|
|
53
|
+
const m = await import("@tiptap/extension-link")
|
|
54
|
+
return { Link: m.default }
|
|
55
|
+
},
|
|
56
|
+
image: async () => {
|
|
57
|
+
const m = await import("@tiptap/extension-image")
|
|
58
|
+
return { Image: m.default }
|
|
59
|
+
},
|
|
60
|
+
table: async () => {
|
|
61
|
+
const [t, tr, tc, th] = await Promise.all([
|
|
62
|
+
import("@tiptap/extension-table"),
|
|
63
|
+
import("@tiptap/extension-table-row"),
|
|
64
|
+
import("@tiptap/extension-table-cell"),
|
|
65
|
+
import("@tiptap/extension-table-header")
|
|
66
|
+
])
|
|
67
|
+
return {
|
|
68
|
+
Table: t.default,
|
|
69
|
+
TableRow: tr.default,
|
|
70
|
+
TableCell: tc.default,
|
|
71
|
+
TableHeader: th.default
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
task_list: async () => {
|
|
75
|
+
const [tl, ti] = await Promise.all([
|
|
76
|
+
import("@tiptap/extension-task-list"),
|
|
77
|
+
import("@tiptap/extension-task-item")
|
|
78
|
+
])
|
|
79
|
+
return { TaskList: tl.default, TaskItem: ti.default }
|
|
80
|
+
},
|
|
81
|
+
mention: async () => {
|
|
82
|
+
const m = await import("@tiptap/extension-mention")
|
|
83
|
+
return { Mention: m.default }
|
|
84
|
+
},
|
|
85
|
+
code_block_syntax: async () => {
|
|
86
|
+
const [cb, low] = await Promise.all([
|
|
87
|
+
import("@tiptap/extension-code-block-lowlight"),
|
|
88
|
+
import("lowlight")
|
|
89
|
+
])
|
|
90
|
+
return {
|
|
91
|
+
CodeBlockLowlight: cb.default,
|
|
92
|
+
common: low.common,
|
|
93
|
+
createLowlight: low.createLowlight
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
typography: async () => {
|
|
97
|
+
const m = await import("@tiptap/extension-typography")
|
|
98
|
+
return { Typography: m.default }
|
|
99
|
+
},
|
|
100
|
+
highlight: async () => {
|
|
101
|
+
const m = await import("@tiptap/extension-highlight")
|
|
102
|
+
return { Highlight: m.default }
|
|
103
|
+
},
|
|
104
|
+
underline: async () => {
|
|
105
|
+
const m = await import("@tiptap/extension-underline")
|
|
106
|
+
return { Underline: m.default }
|
|
107
|
+
},
|
|
108
|
+
subscript: async () => {
|
|
109
|
+
const m = await import("@tiptap/extension-subscript")
|
|
110
|
+
return { Subscript: m.default }
|
|
111
|
+
},
|
|
112
|
+
superscript: async () => {
|
|
113
|
+
const m = await import("@tiptap/extension-superscript")
|
|
114
|
+
return { Superscript: m.default }
|
|
115
|
+
},
|
|
116
|
+
youtube: async () => {
|
|
117
|
+
const m = await import("@tiptap/extension-youtube")
|
|
118
|
+
return { Youtube: m.default }
|
|
119
|
+
},
|
|
120
|
+
character_count: async () => {
|
|
121
|
+
const m = await import("@tiptap/extension-character-count")
|
|
122
|
+
return { CharacterCount: m.default }
|
|
123
|
+
},
|
|
124
|
+
section: async () => {
|
|
125
|
+
const m = await import("inkpen/extensions/section")
|
|
126
|
+
return { Section: m.Section }
|
|
127
|
+
},
|
|
128
|
+
preformatted: async () => {
|
|
129
|
+
const m = await import("inkpen/extensions/preformatted")
|
|
130
|
+
return { Preformatted: m.Preformatted }
|
|
131
|
+
},
|
|
132
|
+
slash_commands: async () => {
|
|
133
|
+
const m = await import("inkpen/extensions/slash_commands")
|
|
134
|
+
return { SlashCommands: m.SlashCommands }
|
|
135
|
+
},
|
|
136
|
+
block_gutter: async () => {
|
|
137
|
+
const m = await import("inkpen/extensions/block_gutter")
|
|
138
|
+
return { BlockGutter: m.BlockGutter }
|
|
139
|
+
},
|
|
140
|
+
drag_handle: async () => {
|
|
141
|
+
const m = await import("inkpen/extensions/drag_handle")
|
|
142
|
+
return { DragHandle: m.DragHandle }
|
|
143
|
+
},
|
|
144
|
+
toggle_block: async () => {
|
|
145
|
+
const m = await import("inkpen/extensions/toggle_block")
|
|
146
|
+
return { ToggleBlock: m.ToggleBlock, ToggleSummary: m.ToggleSummary }
|
|
147
|
+
},
|
|
148
|
+
columns: async () => {
|
|
149
|
+
const m = await import("inkpen/extensions/columns")
|
|
150
|
+
return { Columns: m.Columns, Column: m.Column }
|
|
151
|
+
},
|
|
152
|
+
callout: async () => {
|
|
153
|
+
const m = await import("inkpen/extensions/callout")
|
|
154
|
+
return { Callout: m.Callout }
|
|
155
|
+
},
|
|
156
|
+
block_commands: async () => {
|
|
157
|
+
const m = await import("inkpen/extensions/block_commands")
|
|
158
|
+
return { BlockCommands: m.BlockCommands }
|
|
159
|
+
},
|
|
160
|
+
enhanced_image: async () => {
|
|
161
|
+
const m = await import("inkpen/extensions/enhanced_image")
|
|
162
|
+
return { EnhancedImage: m.EnhancedImage }
|
|
163
|
+
},
|
|
164
|
+
file_attachment: async () => {
|
|
165
|
+
const m = await import("inkpen/extensions/file_attachment")
|
|
166
|
+
return { FileAttachment: m.FileAttachment }
|
|
167
|
+
},
|
|
168
|
+
embed: async () => {
|
|
169
|
+
const m = await import("inkpen/extensions/embed")
|
|
170
|
+
return { Embed: m.Embed }
|
|
171
|
+
},
|
|
172
|
+
advanced_table: async () => {
|
|
173
|
+
const m = await import("inkpen/extensions/advanced_table")
|
|
174
|
+
return {
|
|
175
|
+
AdvancedTable: m.AdvancedTable,
|
|
176
|
+
AdvancedTableRow: m.AdvancedTableRow,
|
|
177
|
+
AdvancedTableCell: m.AdvancedTableCell,
|
|
178
|
+
AdvancedTableHeader: m.AdvancedTableHeader
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
table_of_contents: async () => {
|
|
182
|
+
const m = await import("inkpen/extensions/table_of_contents")
|
|
183
|
+
return { TableOfContents: m.TableOfContents }
|
|
184
|
+
},
|
|
185
|
+
database: async () => {
|
|
186
|
+
const m = await import("inkpen/extensions/database")
|
|
187
|
+
return { Database: m.Database }
|
|
188
|
+
},
|
|
189
|
+
document_section: async () => {
|
|
190
|
+
const m = await import("inkpen/extensions/document_section")
|
|
191
|
+
return { DocumentSection: m.DocumentSection }
|
|
192
|
+
},
|
|
193
|
+
content_embed: async () => {
|
|
194
|
+
const m = await import("inkpen/extensions/content_embed")
|
|
195
|
+
return { ContentEmbed: m.ContentEmbed }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Exported for tests so they can mock loaders or assert the registry's
|
|
200
|
+
// surface. Not part of the public gem API; the controller is the only
|
|
201
|
+
// production consumer.
|
|
202
|
+
export const __EXTENSION_LOADER_NAMES__ = Object.keys(EXTENSION_LOADERS)
|
|
203
|
+
export const __loadEditorModulesForTest = loadEditorModules
|
|
204
|
+
|
|
205
|
+
async function loadEditorModules(enabledExtensions = []) {
|
|
206
|
+
const enabledSet = new Set(enabledExtensions)
|
|
207
|
+
const cacheKey = [...enabledSet].sort().join(",") || "(empty)"
|
|
208
|
+
if (cachedModules.has(cacheKey)) return cachedModules.get(cacheKey)
|
|
209
|
+
|
|
210
|
+
const core = await loadCoreModules()
|
|
211
|
+
|
|
212
|
+
const gatedPromises = []
|
|
213
|
+
for (const [name, loader] of Object.entries(EXTENSION_LOADERS)) {
|
|
214
|
+
if (enabledSet.has(name)) {
|
|
215
|
+
gatedPromises.push(loader())
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const gatedResults = await Promise.all(gatedPromises)
|
|
219
|
+
|
|
220
|
+
const modules = Object.assign({}, core, ...gatedResults)
|
|
221
|
+
cachedModules.set(cacheKey, modules)
|
|
222
|
+
return modules
|
|
156
223
|
}
|
|
157
224
|
|
|
158
225
|
// Extensions loaded lazily to prevent import failures from breaking the editor
|
|
@@ -332,7 +399,7 @@ export default class extends Controller {
|
|
|
332
399
|
|
|
333
400
|
async initializeEditor() {
|
|
334
401
|
// Lazy-load all TipTap modules
|
|
335
|
-
this.modules = await loadEditorModules()
|
|
402
|
+
this.modules = await loadEditorModules(this.extensionsValue)
|
|
336
403
|
const { Editor } = this.modules
|
|
337
404
|
|
|
338
405
|
const extensions = await this.buildExtensions()
|
|
@@ -291,11 +291,11 @@ function serializeInlineContent(node) {
|
|
|
291
291
|
const iterate = (items) => {
|
|
292
292
|
if (Array.isArray(items)) {
|
|
293
293
|
for (const item of items) {
|
|
294
|
-
result +=
|
|
294
|
+
result += serializeInlineNode(item)
|
|
295
295
|
}
|
|
296
296
|
} else if (typeof items.forEach === "function") {
|
|
297
297
|
items.forEach(item => {
|
|
298
|
-
result +=
|
|
298
|
+
result += serializeInlineNode(item)
|
|
299
299
|
})
|
|
300
300
|
}
|
|
301
301
|
}
|
|
@@ -304,6 +304,16 @@ function serializeInlineContent(node) {
|
|
|
304
304
|
return result
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
function serializeInlineNode(node) {
|
|
308
|
+
if (!node) return ""
|
|
309
|
+
|
|
310
|
+
const type = node.type?.name || node.type
|
|
311
|
+
if (type === "text") return serializeTextWithMarks(node)
|
|
312
|
+
if (type === "hardBreak") return "\n"
|
|
313
|
+
|
|
314
|
+
return serializeNode(node).trim()
|
|
315
|
+
}
|
|
316
|
+
|
|
307
317
|
/**
|
|
308
318
|
* Serialize text node with marks
|
|
309
319
|
*/
|
|
@@ -316,8 +326,24 @@ function serializeTextWithMarks(node) {
|
|
|
316
326
|
return text
|
|
317
327
|
}
|
|
318
328
|
|
|
319
|
-
|
|
320
|
-
|
|
329
|
+
const markName = mark => mark.type?.name || mark.type
|
|
330
|
+
const hasBold = node.marks.some(mark => ["bold", "strong"].includes(markName(mark)))
|
|
331
|
+
const hasItalic = node.marks.some(mark => ["italic", "em"].includes(markName(mark)))
|
|
332
|
+
|
|
333
|
+
if (hasBold && hasItalic) {
|
|
334
|
+
text = `***${text}***`
|
|
335
|
+
} else if (hasBold) {
|
|
336
|
+
text = MARK_SERIALIZERS.bold(text)
|
|
337
|
+
} else if (hasItalic) {
|
|
338
|
+
text = MARK_SERIALIZERS.italic(text)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const remainingMarks = node.marks.filter(mark => {
|
|
342
|
+
const name = markName(mark)
|
|
343
|
+
return !["bold", "strong", "italic", "em"].includes(name)
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
for (const mark of remainingMarks) {
|
|
321
347
|
const serializer = MARK_SERIALIZERS[mark.type?.name || mark.type]
|
|
322
348
|
if (serializer) {
|
|
323
349
|
text = serializer(text, mark)
|
|
@@ -425,7 +451,8 @@ function serializeBlockquote(node, options) {
|
|
|
425
451
|
*/
|
|
426
452
|
function serializeCodeBlock(node) {
|
|
427
453
|
const language = node.attrs?.language || ""
|
|
428
|
-
|
|
454
|
+
const content = (node.textContent || "").replace(/\n$/, "")
|
|
455
|
+
return "```" + language + "\n" + content + "\n```"
|
|
429
456
|
}
|
|
430
457
|
|
|
431
458
|
/**
|
|
@@ -470,7 +497,7 @@ function serializeTable(node, options) {
|
|
|
470
497
|
const rowChildren = row.content?.content || row.content || []
|
|
471
498
|
|
|
472
499
|
iterate(rowChildren, (cell, cellIndex) => {
|
|
473
|
-
const content =
|
|
500
|
+
const content = serializeTableCell(cell, options)
|
|
474
501
|
cells.push(content)
|
|
475
502
|
|
|
476
503
|
// Track alignment from first row cells
|
|
@@ -500,6 +527,15 @@ function serializeTable(node, options) {
|
|
|
500
527
|
return rows.join("\n") + "\n"
|
|
501
528
|
}
|
|
502
529
|
|
|
530
|
+
function serializeTableCell(cell, options) {
|
|
531
|
+
const content = serializeChildren(cell, options)
|
|
532
|
+
.trim()
|
|
533
|
+
.replace(/\n+/g, "<br>")
|
|
534
|
+
.replace(/\s*\|\s*/g, "\\|")
|
|
535
|
+
|
|
536
|
+
return content || " "
|
|
537
|
+
}
|
|
538
|
+
|
|
503
539
|
/**
|
|
504
540
|
* Serialize callout to GFM-style alert
|
|
505
541
|
*/
|