@atlaskit/editor-json-transformer 9.0.1 → 9.1.0
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/CHANGELOG.md +16 -0
- package/compass.yml +41 -0
- package/dist/cjs/entry-points/main.js +12 -0
- package/dist/cjs/index.js +22 -287
- package/dist/cjs/jsonTransformer.js +268 -0
- package/dist/cjs/main.js +34 -0
- package/dist/es2019/entry-points/main.js +2 -0
- package/dist/es2019/index.js +2 -266
- package/dist/es2019/jsonTransformer.js +239 -0
- package/dist/es2019/main.js +26 -0
- package/dist/esm/entry-points/main.js +2 -0
- package/dist/esm/index.js +2 -287
- package/dist/esm/jsonTransformer.js +261 -0
- package/dist/esm/main.js +27 -0
- package/dist/types/entry-points/main.d.ts +1 -0
- package/dist/types/index.d.ts +2 -45
- package/dist/types/jsonTransformer.d.ts +39 -0
- package/dist/types/main.d.ts +8 -0
- package/editor-json-transformer.docs.tsx +3 -8
- package/jsonTransformer/package.json +8 -0
- package/main/package.json +8 -0
- package/package.json +4 -4
package/dist/cjs/main.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.isJSONDocNode = isJSONDocNode;
|
|
8
|
+
var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
|
|
9
|
+
/**
|
|
10
|
+
* Quick type guard to check if a value is a valid JSONDocNode
|
|
11
|
+
* This is only a basic structural check, does not validate the entire object nor the schema.
|
|
12
|
+
* @param value - The value to check
|
|
13
|
+
* @returns true if the value is a valid JSONDocNode structure
|
|
14
|
+
*/
|
|
15
|
+
function isJSONDocNode(value) {
|
|
16
|
+
if (!value || (0, _typeof2.default)(value) !== 'object') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
var doc = value;
|
|
20
|
+
|
|
21
|
+
// Check required properties
|
|
22
|
+
if (doc.type !== 'doc') {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(doc.content)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Version is optional but if present should be a number
|
|
30
|
+
if (doc.version !== undefined && typeof doc.version !== 'number') {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
package/dist/es2019/index.js
CHANGED
|
@@ -1,269 +1,5 @@
|
|
|
1
1
|
// Disable no-re-export rule for entry point files
|
|
2
2
|
/* eslint-disable @atlaskit/editor/no-re-export */
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { defaultSchema, getSchemaBasedOnStage } from '@atlaskit/adf-schema/schema-default';
|
|
7
|
-
import { markOverrideRuleFor } from './markOverrideRules';
|
|
8
|
-
import { sanitizeNode } from './sanitize/sanitize-node';
|
|
9
|
-
export let SchemaStage = /*#__PURE__*/function (SchemaStage) {
|
|
10
|
-
SchemaStage["FINAL"] = "final";
|
|
11
|
-
SchemaStage["STAGE_0"] = "stage0";
|
|
12
|
-
return SchemaStage;
|
|
13
|
-
}({});
|
|
14
|
-
const isType = type => node => node.type.name === type;
|
|
15
|
-
|
|
16
|
-
// Create a Set of node types that should have empty attrs removed
|
|
17
|
-
const NODES_WITH_EMPTY_ATTRS_REMOVAL = new Set(['paragraph', 'layoutSection', 'blockquote', 'nestedExpand', 'bulletList', 'listItem', 'orderedList', 'rule', 'caption', 'tableRow', 'media', 'mediaSingle', 'mediaInline']);
|
|
18
|
-
const isCodeBlock = isType('codeBlock');
|
|
19
|
-
const isMediaNode = isType('media');
|
|
20
|
-
const isMediaInline = isType('mediaInline');
|
|
21
|
-
const isMediaSingleNode = isType('mediaSingle');
|
|
22
|
-
const isMentionNode = isType('mention');
|
|
23
|
-
const isParagraph = isType('paragraph');
|
|
24
|
-
const isHeading = isType('heading');
|
|
25
|
-
const isTable = isType('table');
|
|
26
|
-
const isTableCell = isType('tableCell');
|
|
27
|
-
const isTableHeader = isType('tableHeader');
|
|
28
|
-
const isLinkMark = isType('link');
|
|
29
|
-
const isUnsupportedMark = isType('unsupportedMark');
|
|
30
|
-
const isUnsupportedNodeAttributeMark = isType('unsupportedNodeAttribute');
|
|
31
|
-
const isExpand = isType('expand');
|
|
32
|
-
const isNestedExpand = isType('nestedExpand');
|
|
33
|
-
const isUnsupportedNode = node => isType('unsupportedBlock')(node) || isType('unsupportedInline')(node);
|
|
34
|
-
const isDataConsumer = isType('dataConsumer');
|
|
35
|
-
const isFragmentMark = isType('fragment');
|
|
36
|
-
const isHardBreak = isType('hardBreak');
|
|
37
|
-
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
-
const filterNull = subject => {
|
|
40
|
-
let output = {
|
|
41
|
-
...subject
|
|
42
|
-
};
|
|
43
|
-
// eslint-disable-next-line guard-for-in
|
|
44
|
-
for (const key in output) {
|
|
45
|
-
const current = output[key];
|
|
46
|
-
if (current === null) {
|
|
47
|
-
const {
|
|
48
|
-
[key]: unusedKey,
|
|
49
|
-
...filteredObj
|
|
50
|
-
} = output;
|
|
51
|
-
output = filteredObj;
|
|
52
|
-
} else if (typeof current === 'object' && !Array.isArray(current)) {
|
|
53
|
-
output[key] = filterNull(current);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return output;
|
|
57
|
-
};
|
|
58
|
-
const createDocFromContent = content => {
|
|
59
|
-
return {
|
|
60
|
-
version: 1,
|
|
61
|
-
type: 'doc',
|
|
62
|
-
content: content || []
|
|
63
|
-
};
|
|
64
|
-
};
|
|
65
|
-
const emptyDoc = createDocFromContent([{
|
|
66
|
-
type: 'paragraph',
|
|
67
|
-
content: []
|
|
68
|
-
}]);
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Transforms a Prosemirror node into a JSON representation. Compared with
|
|
72
|
-
* native Prosemirror JSON, this representation ensures we have valid ADF attrs.
|
|
73
|
-
*
|
|
74
|
-
* @param node The Prosemirror node to transform.
|
|
75
|
-
* @param mentionMap A map of mention IDs to their display names.
|
|
76
|
-
* @returns The JSON representation of the node.
|
|
77
|
-
*/
|
|
78
|
-
const toJSON = (node, mentionMap) => {
|
|
79
|
-
const obj = {
|
|
80
|
-
type: node.type.name
|
|
81
|
-
};
|
|
82
|
-
if (isUnsupportedNode(node)) {
|
|
83
|
-
return node.attrs.originalValue;
|
|
84
|
-
} else if (isMediaNode(node)) {
|
|
85
|
-
obj.attrs = mediaToJSON(node).attrs;
|
|
86
|
-
} else if (isMediaSingleNode(node)) {
|
|
87
|
-
obj.attrs = mediaSingleToJSON(node).attrs;
|
|
88
|
-
} else if (isMediaInline(node)) {
|
|
89
|
-
obj.attrs = mediaToJSON(node).attrs;
|
|
90
|
-
} else if (isMentionNode(node)) {
|
|
91
|
-
obj.attrs = mentionToJSON(node).attrs;
|
|
92
|
-
} else if (isCodeBlock(node)) {
|
|
93
|
-
obj.attrs = codeBlockToJSON(node).attrs;
|
|
94
|
-
} else if (isTable(node)) {
|
|
95
|
-
obj.attrs = tableToJSON(node).attrs;
|
|
96
|
-
} else if (isTableCell(node)) {
|
|
97
|
-
obj.attrs = toJSONTableCell(node).attrs;
|
|
98
|
-
} else if (isTableHeader(node)) {
|
|
99
|
-
obj.attrs = toJSONTableHeader(node).attrs;
|
|
100
|
-
} else if (isExpand(node) || isNestedExpand(node)) {
|
|
101
|
-
obj.attrs = expandToJSON(node).attrs;
|
|
102
|
-
} else if (node.attrs && Object.keys(node.attrs).length) {
|
|
103
|
-
obj.attrs = node.attrs;
|
|
104
|
-
}
|
|
105
|
-
if (obj.attrs) {
|
|
106
|
-
obj.attrs = filterNull(obj.attrs);
|
|
107
|
-
}
|
|
108
|
-
if (isMentionNode(node) && mentionMap) {
|
|
109
|
-
var _text, _obj$attrs;
|
|
110
|
-
// If the mentionMap exists and has a name defined then we should use it and prepend @ to the begining of it.
|
|
111
|
-
const name = !!(mentionMap !== null && mentionMap !== void 0 && mentionMap[node.attrs.id]) ? `@${mentionMap[node.attrs.id]}` : '';
|
|
112
|
-
// Ignored via go/ees005
|
|
113
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
114
|
-
const text = (_text = (_obj$attrs = obj.attrs) === null || _obj$attrs === void 0 ? void 0 : _obj$attrs.text) !== null && _text !== void 0 ? _text : '';
|
|
115
|
-
obj.attrs = {
|
|
116
|
-
...obj.attrs,
|
|
117
|
-
// If an explicit text value is set on the mention then that should take priority over the mentionMap value.
|
|
118
|
-
text: !!text ? text : name
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
if (NODES_WITH_EMPTY_ATTRS_REMOVAL.has(node.type.name) && obj.attrs && !Object.keys(obj.attrs).length || isHardBreak(node)) {
|
|
122
|
-
delete obj.attrs;
|
|
123
|
-
}
|
|
124
|
-
if (node.isText) {
|
|
125
|
-
obj.text = node.textContent;
|
|
126
|
-
} else {
|
|
127
|
-
node.content.forEach(child => {
|
|
128
|
-
obj.content = obj.content || [];
|
|
129
|
-
obj.content.push(toJSON(child, mentionMap));
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
if (isParagraph(node) || isHeading(node)) {
|
|
133
|
-
obj.content = obj.content || [];
|
|
134
|
-
}
|
|
135
|
-
if (node.marks.length) {
|
|
136
|
-
// Run any custom mark serialisers
|
|
137
|
-
const parsedMarks = node.marks.map(mark => {
|
|
138
|
-
if (isUnsupportedMark(mark)) {
|
|
139
|
-
return canOverrideMark(mark, node.marks) ? null : mark.attrs.originalValue;
|
|
140
|
-
} else if (isUnsupportedNodeAttributeMark(mark)) {
|
|
141
|
-
return null;
|
|
142
|
-
} else if (isLinkMark(mark)) {
|
|
143
|
-
return linkToJSON(mark);
|
|
144
|
-
} else if (isDataConsumer(mark)) {
|
|
145
|
-
var _serialised$attrs$sou;
|
|
146
|
-
const serialised = dataConsumerToJSON(mark);
|
|
147
|
-
return !serialised.attrs.sources || ((_serialised$attrs$sou = serialised.attrs.sources) === null || _serialised$attrs$sou === void 0 ? void 0 : _serialised$attrs$sou.length) === 0 ? null : serialised;
|
|
148
|
-
} else if (isFragmentMark(mark)) {
|
|
149
|
-
const fragmentMark = fragmentToJSON(mark);
|
|
150
|
-
if (!fragmentMark.attrs.localId) {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
return fragmentMark;
|
|
154
|
-
} else {
|
|
155
|
-
return mark.toJSON();
|
|
156
|
-
}
|
|
157
|
-
}).filter(maybeMark => maybeMark !== null);
|
|
158
|
-
|
|
159
|
-
// Only set if we have a non-empty array, otherwise explicitly undefine it (as we only run this path if `node.marks.length`)
|
|
160
|
-
obj.marks = (parsedMarks === null || parsedMarks === void 0 ? void 0 : parsedMarks.length) > 0 ? parsedMarks : undefined;
|
|
161
|
-
const nodeAttributeMark = node.marks.find(isUnsupportedNodeAttributeMark);
|
|
162
|
-
if (nodeAttributeMark && nodeAttributeMark.attrs.type.nodeType === obj.type) {
|
|
163
|
-
obj.attrs = {
|
|
164
|
-
...getUnwrappedNodeAttributes(node, nodeAttributeMark, obj)
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return obj;
|
|
169
|
-
};
|
|
170
|
-
export { toJSON as nodeToJSON };
|
|
171
|
-
const canOverrideMark = (mark, existingMarks) => {
|
|
172
|
-
if (existingMarks.some(e => mark.attrs.originalValue.type === e.type.name)) {
|
|
173
|
-
return markOverrideRuleFor(mark.attrs.originalValue.type).canOverrideUnsupportedMark();
|
|
174
|
-
}
|
|
175
|
-
return false;
|
|
176
|
-
};
|
|
177
|
-
const getUnwrappedNodeAttributes = (node, mark, obj) => {
|
|
178
|
-
const nodeAttributes = node.type.spec.attrs;
|
|
179
|
-
const attributes = {
|
|
180
|
-
...mark.attrs.unsupported,
|
|
181
|
-
...obj.attrs
|
|
182
|
-
};
|
|
183
|
-
// Ignored via go/ees005
|
|
184
|
-
// eslint-disable-next-line no-var
|
|
185
|
-
for (var key in obj.attrs) {
|
|
186
|
-
if (obj.attrs.hasOwnProperty(key)) {
|
|
187
|
-
const attribute = nodeAttributes ? nodeAttributes[key] : null;
|
|
188
|
-
if (attribute) {
|
|
189
|
-
if (attribute.default === node.attrs[key] && mark.attrs.unsupported[key]) {
|
|
190
|
-
return {
|
|
191
|
-
...attributes,
|
|
192
|
-
[key]: mark.attrs.unsupported[key]
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return attributes;
|
|
199
|
-
};
|
|
200
|
-
export class JSONTransformer {
|
|
201
|
-
/**
|
|
202
|
-
* @param schema The current editor schema
|
|
203
|
-
* @param mentionMap An optional mapping of user IDs to mention names. This is used by the encoder to substitute empty
|
|
204
|
-
* mention node.attr.text values with mapped names.
|
|
205
|
-
*/
|
|
206
|
-
constructor(schema = defaultSchema, mentionMap) {
|
|
207
|
-
this.schema = schema;
|
|
208
|
-
this.mentionMap = mentionMap;
|
|
209
|
-
}
|
|
210
|
-
encode(node, options = {}) {
|
|
211
|
-
const content = [];
|
|
212
|
-
node.content.forEach(child => {
|
|
213
|
-
content.push(sanitizeNode(toJSON(child, this.mentionMap), options));
|
|
214
|
-
});
|
|
215
|
-
if (!content || isEqual(content, emptyDoc.content)) {
|
|
216
|
-
return createDocFromContent([]);
|
|
217
|
-
}
|
|
218
|
-
return createDocFromContent(content);
|
|
219
|
-
}
|
|
220
|
-
internalParse(content, schema) {
|
|
221
|
-
const doc = schema.nodeFromJSON(content);
|
|
222
|
-
doc.check();
|
|
223
|
-
return doc;
|
|
224
|
-
}
|
|
225
|
-
parse(content, stage) {
|
|
226
|
-
if (content.type !== 'doc') {
|
|
227
|
-
throw new Error('Expected content format to be ADF');
|
|
228
|
-
}
|
|
229
|
-
const schema = !!stage ? getSchemaBasedOnStage(stage) : this.schema;
|
|
230
|
-
if (!content.content || content.content.length === 0) {
|
|
231
|
-
return this.internalParse(emptyDoc, schema);
|
|
232
|
-
}
|
|
233
|
-
return this.internalParse(content, schema);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* This method is used to encode a single node
|
|
238
|
-
*/
|
|
239
|
-
encodeNode(node) {
|
|
240
|
-
return sanitizeNode(toJSON(node, this.mentionMap));
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Quick type guard to check if a value is a valid JSONDocNode
|
|
246
|
-
* This is only a basic structural check, does not validate the entire object nor the schema.
|
|
247
|
-
* @param value - The value to check
|
|
248
|
-
* @returns true if the value is a valid JSONDocNode structure
|
|
249
|
-
*/
|
|
250
|
-
export function isJSONDocNode(value) {
|
|
251
|
-
if (!value || typeof value !== 'object') {
|
|
252
|
-
return false;
|
|
253
|
-
}
|
|
254
|
-
const doc = value;
|
|
255
|
-
|
|
256
|
-
// Check required properties
|
|
257
|
-
if (doc.type !== 'doc') {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
if (!Array.isArray(doc.content)) {
|
|
261
|
-
return false;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Version is optional but if present should be a number
|
|
265
|
-
if (doc.version !== undefined && typeof doc.version !== 'number') {
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
return true;
|
|
269
|
-
}
|
|
4
|
+
export { JSONTransformer, SchemaStage, toJSON as nodeToJSON } from './jsonTransformer';
|
|
5
|
+
export { isJSONDocNode } from './entry-points/main';
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import isEqual from 'lodash/isEqual';
|
|
2
|
+
import { codeBlockToJSON, dataConsumerToJSON, expandToJSON, fragmentToJSON, linkToJSON, mediaSingleToJSON, mediaToJSON, mentionToJSON, tableToJSON, toJSONTableCell, toJSONTableHeader } from '@atlaskit/adf-schema';
|
|
3
|
+
import { defaultSchema, getSchemaBasedOnStage } from '@atlaskit/adf-schema/schema-default';
|
|
4
|
+
import { markOverrideRuleFor } from './markOverrideRules';
|
|
5
|
+
import { sanitizeNode } from './sanitize/sanitize-node';
|
|
6
|
+
export let SchemaStage = /*#__PURE__*/function (SchemaStage) {
|
|
7
|
+
SchemaStage["FINAL"] = "final";
|
|
8
|
+
SchemaStage["STAGE_0"] = "stage0";
|
|
9
|
+
return SchemaStage;
|
|
10
|
+
}({});
|
|
11
|
+
const isType = type => node => node.type.name === type;
|
|
12
|
+
|
|
13
|
+
// Create a Set of node types that should have empty attrs removed
|
|
14
|
+
const NODES_WITH_EMPTY_ATTRS_REMOVAL = new Set(['paragraph', 'layoutSection', 'blockquote', 'nestedExpand', 'bulletList', 'listItem', 'orderedList', 'rule', 'caption', 'tableRow', 'media', 'mediaSingle', 'mediaInline']);
|
|
15
|
+
const isCodeBlock = isType('codeBlock');
|
|
16
|
+
const isMediaNode = isType('media');
|
|
17
|
+
const isMediaInline = isType('mediaInline');
|
|
18
|
+
const isMediaSingleNode = isType('mediaSingle');
|
|
19
|
+
const isMentionNode = isType('mention');
|
|
20
|
+
const isParagraph = isType('paragraph');
|
|
21
|
+
const isHeading = isType('heading');
|
|
22
|
+
const isTable = isType('table');
|
|
23
|
+
const isTableCell = isType('tableCell');
|
|
24
|
+
const isTableHeader = isType('tableHeader');
|
|
25
|
+
const isLinkMark = isType('link');
|
|
26
|
+
const isUnsupportedMark = isType('unsupportedMark');
|
|
27
|
+
const isUnsupportedNodeAttributeMark = isType('unsupportedNodeAttribute');
|
|
28
|
+
const isExpand = isType('expand');
|
|
29
|
+
const isNestedExpand = isType('nestedExpand');
|
|
30
|
+
const isUnsupportedNode = node => isType('unsupportedBlock')(node) || isType('unsupportedInline')(node);
|
|
31
|
+
const isDataConsumer = isType('dataConsumer');
|
|
32
|
+
const isFragmentMark = isType('fragment');
|
|
33
|
+
const isHardBreak = isType('hardBreak');
|
|
34
|
+
const createDocFromContent = content => {
|
|
35
|
+
return {
|
|
36
|
+
version: 1,
|
|
37
|
+
type: 'doc',
|
|
38
|
+
content: content || []
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const emptyDoc = createDocFromContent([{
|
|
42
|
+
type: 'paragraph',
|
|
43
|
+
content: []
|
|
44
|
+
}]);
|
|
45
|
+
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
const filterNull = subject => {
|
|
48
|
+
let output = {
|
|
49
|
+
...subject
|
|
50
|
+
};
|
|
51
|
+
// eslint-disable-next-line guard-for-in
|
|
52
|
+
for (const key in output) {
|
|
53
|
+
const current = output[key];
|
|
54
|
+
if (current === null) {
|
|
55
|
+
const {
|
|
56
|
+
[key]: unusedKey,
|
|
57
|
+
...filteredObj
|
|
58
|
+
} = output;
|
|
59
|
+
output = filteredObj;
|
|
60
|
+
} else if (typeof current === 'object' && !Array.isArray(current)) {
|
|
61
|
+
output[key] = filterNull(current);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return output;
|
|
65
|
+
};
|
|
66
|
+
const canOverrideMark = (mark, existingMarks) => {
|
|
67
|
+
if (existingMarks.some(e => mark.attrs.originalValue.type === e.type.name)) {
|
|
68
|
+
return markOverrideRuleFor(mark.attrs.originalValue.type).canOverrideUnsupportedMark();
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
};
|
|
72
|
+
const getUnwrappedNodeAttributes = (node, mark, obj) => {
|
|
73
|
+
const nodeAttributes = node.type.spec.attrs;
|
|
74
|
+
const attributes = {
|
|
75
|
+
...mark.attrs.unsupported,
|
|
76
|
+
...obj.attrs
|
|
77
|
+
};
|
|
78
|
+
// Ignored via go/ees005
|
|
79
|
+
// eslint-disable-next-line no-var
|
|
80
|
+
for (var key in obj.attrs) {
|
|
81
|
+
if (obj.attrs.hasOwnProperty(key)) {
|
|
82
|
+
const attribute = nodeAttributes ? nodeAttributes[key] : null;
|
|
83
|
+
if (attribute) {
|
|
84
|
+
if (attribute.default === node.attrs[key] && mark.attrs.unsupported[key]) {
|
|
85
|
+
return {
|
|
86
|
+
...attributes,
|
|
87
|
+
[key]: mark.attrs.unsupported[key]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return attributes;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Transforms a Prosemirror node into a JSON representation. Compared with
|
|
98
|
+
* native Prosemirror JSON, this representation ensures we have valid ADF attrs.
|
|
99
|
+
*
|
|
100
|
+
* @param node The Prosemirror node to transform.
|
|
101
|
+
* @param mentionMap A map of mention IDs to their display names.
|
|
102
|
+
* @returns The JSON representation of the node.
|
|
103
|
+
*/
|
|
104
|
+
export const toJSON = (node, mentionMap) => {
|
|
105
|
+
const obj = {
|
|
106
|
+
type: node.type.name
|
|
107
|
+
};
|
|
108
|
+
if (isUnsupportedNode(node)) {
|
|
109
|
+
return node.attrs.originalValue;
|
|
110
|
+
} else if (isMediaNode(node)) {
|
|
111
|
+
obj.attrs = mediaToJSON(node).attrs;
|
|
112
|
+
} else if (isMediaSingleNode(node)) {
|
|
113
|
+
obj.attrs = mediaSingleToJSON(node).attrs;
|
|
114
|
+
} else if (isMediaInline(node)) {
|
|
115
|
+
obj.attrs = mediaToJSON(node).attrs;
|
|
116
|
+
} else if (isMentionNode(node)) {
|
|
117
|
+
obj.attrs = mentionToJSON(node).attrs;
|
|
118
|
+
} else if (isCodeBlock(node)) {
|
|
119
|
+
obj.attrs = codeBlockToJSON(node).attrs;
|
|
120
|
+
} else if (isTable(node)) {
|
|
121
|
+
obj.attrs = tableToJSON(node).attrs;
|
|
122
|
+
} else if (isTableCell(node)) {
|
|
123
|
+
obj.attrs = toJSONTableCell(node).attrs;
|
|
124
|
+
} else if (isTableHeader(node)) {
|
|
125
|
+
obj.attrs = toJSONTableHeader(node).attrs;
|
|
126
|
+
} else if (isExpand(node) || isNestedExpand(node)) {
|
|
127
|
+
obj.attrs = expandToJSON(node).attrs;
|
|
128
|
+
} else if (node.attrs && Object.keys(node.attrs).length) {
|
|
129
|
+
obj.attrs = node.attrs;
|
|
130
|
+
}
|
|
131
|
+
if (obj.attrs) {
|
|
132
|
+
obj.attrs = filterNull(obj.attrs);
|
|
133
|
+
}
|
|
134
|
+
if (isMentionNode(node) && mentionMap) {
|
|
135
|
+
var _text, _obj$attrs;
|
|
136
|
+
// If the mentionMap exists and has a name defined then we should use it and prepend @ to the begining of it.
|
|
137
|
+
const name = !!(mentionMap !== null && mentionMap !== void 0 && mentionMap[node.attrs.id]) ? `@${mentionMap[node.attrs.id]}` : '';
|
|
138
|
+
// Ignored via go/ees005
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
|
+
const text = (_text = (_obj$attrs = obj.attrs) === null || _obj$attrs === void 0 ? void 0 : _obj$attrs.text) !== null && _text !== void 0 ? _text : '';
|
|
141
|
+
obj.attrs = {
|
|
142
|
+
...obj.attrs,
|
|
143
|
+
// If an explicit text value is set on the mention then that should take priority over the mentionMap value.
|
|
144
|
+
text: !!text ? text : name
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (NODES_WITH_EMPTY_ATTRS_REMOVAL.has(node.type.name) && obj.attrs && !Object.keys(obj.attrs).length || isHardBreak(node)) {
|
|
148
|
+
delete obj.attrs;
|
|
149
|
+
}
|
|
150
|
+
if (node.isText) {
|
|
151
|
+
obj.text = node.textContent;
|
|
152
|
+
} else {
|
|
153
|
+
node.content.forEach(child => {
|
|
154
|
+
obj.content = obj.content || [];
|
|
155
|
+
obj.content.push(toJSON(child, mentionMap));
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (isParagraph(node) || isHeading(node)) {
|
|
159
|
+
obj.content = obj.content || [];
|
|
160
|
+
}
|
|
161
|
+
if (node.marks.length) {
|
|
162
|
+
// Run any custom mark serialisers
|
|
163
|
+
const parsedMarks = node.marks.map(mark => {
|
|
164
|
+
if (isUnsupportedMark(mark)) {
|
|
165
|
+
return canOverrideMark(mark, node.marks) ? null : mark.attrs.originalValue;
|
|
166
|
+
} else if (isUnsupportedNodeAttributeMark(mark)) {
|
|
167
|
+
return null;
|
|
168
|
+
} else if (isLinkMark(mark)) {
|
|
169
|
+
return linkToJSON(mark);
|
|
170
|
+
} else if (isDataConsumer(mark)) {
|
|
171
|
+
var _serialised$attrs$sou;
|
|
172
|
+
const serialised = dataConsumerToJSON(mark);
|
|
173
|
+
return !serialised.attrs.sources || ((_serialised$attrs$sou = serialised.attrs.sources) === null || _serialised$attrs$sou === void 0 ? void 0 : _serialised$attrs$sou.length) === 0 ? null : serialised;
|
|
174
|
+
} else if (isFragmentMark(mark)) {
|
|
175
|
+
const fragmentMark = fragmentToJSON(mark);
|
|
176
|
+
if (!fragmentMark.attrs.localId) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return fragmentMark;
|
|
180
|
+
} else {
|
|
181
|
+
return mark.toJSON();
|
|
182
|
+
}
|
|
183
|
+
}).filter(maybeMark => maybeMark !== null);
|
|
184
|
+
|
|
185
|
+
// Only set if we have a non-empty array, otherwise explicitly undefine it (as we only run this path if `node.marks.length`)
|
|
186
|
+
obj.marks = (parsedMarks === null || parsedMarks === void 0 ? void 0 : parsedMarks.length) > 0 ? parsedMarks : undefined;
|
|
187
|
+
const nodeAttributeMark = node.marks.find(isUnsupportedNodeAttributeMark);
|
|
188
|
+
if (nodeAttributeMark && nodeAttributeMark.attrs.type.nodeType === obj.type) {
|
|
189
|
+
obj.attrs = {
|
|
190
|
+
...getUnwrappedNodeAttributes(node, nodeAttributeMark, obj)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return obj;
|
|
195
|
+
};
|
|
196
|
+
export class JSONTransformer {
|
|
197
|
+
/**
|
|
198
|
+
* Creates a new JSONTransformer instance.
|
|
199
|
+
* @param schema The current editor schema
|
|
200
|
+
* @param mentionMap An optional mapping of user IDs to mention names. This is used by the encoder to substitute empty
|
|
201
|
+
* mention node.attr.text values with mapped names.
|
|
202
|
+
*/
|
|
203
|
+
constructor(schema = defaultSchema, mentionMap) {
|
|
204
|
+
this.schema = schema;
|
|
205
|
+
this.mentionMap = mentionMap;
|
|
206
|
+
}
|
|
207
|
+
encode(node, options = {}) {
|
|
208
|
+
const content = [];
|
|
209
|
+
node.content.forEach(child => {
|
|
210
|
+
content.push(sanitizeNode(toJSON(child, this.mentionMap), options));
|
|
211
|
+
});
|
|
212
|
+
if (!content || isEqual(content, emptyDoc.content)) {
|
|
213
|
+
return createDocFromContent([]);
|
|
214
|
+
}
|
|
215
|
+
return createDocFromContent(content);
|
|
216
|
+
}
|
|
217
|
+
internalParse(content, schema) {
|
|
218
|
+
const doc = schema.nodeFromJSON(content);
|
|
219
|
+
doc.check();
|
|
220
|
+
return doc;
|
|
221
|
+
}
|
|
222
|
+
parse(content, stage) {
|
|
223
|
+
if (content.type !== 'doc') {
|
|
224
|
+
throw new Error('Expected content format to be ADF');
|
|
225
|
+
}
|
|
226
|
+
const schema = !!stage ? getSchemaBasedOnStage(stage) : this.schema;
|
|
227
|
+
if (!content.content || content.content.length === 0) {
|
|
228
|
+
return this.internalParse(emptyDoc, schema);
|
|
229
|
+
}
|
|
230
|
+
return this.internalParse(content, schema);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* This method is used to encode a single node
|
|
235
|
+
*/
|
|
236
|
+
encodeNode(node) {
|
|
237
|
+
return sanitizeNode(toJSON(node, this.mentionMap));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quick type guard to check if a value is a valid JSONDocNode
|
|
3
|
+
* This is only a basic structural check, does not validate the entire object nor the schema.
|
|
4
|
+
* @param value - The value to check
|
|
5
|
+
* @returns true if the value is a valid JSONDocNode structure
|
|
6
|
+
*/
|
|
7
|
+
export function isJSONDocNode(value) {
|
|
8
|
+
if (!value || typeof value !== 'object') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
const doc = value;
|
|
12
|
+
|
|
13
|
+
// Check required properties
|
|
14
|
+
if (doc.type !== 'doc') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (!Array.isArray(doc.content)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Version is optional but if present should be a number
|
|
22
|
+
if (doc.version !== undefined && typeof doc.version !== 'number') {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|