gt-sanity 0.0.5 → 1.0.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.
Files changed (108) hide show
  1. package/LICENSE.md +1 -8
  2. package/README.md +5 -5
  3. package/dist/index.d.mts +122 -95
  4. package/dist/index.d.ts +122 -95
  5. package/dist/index.js +9089 -1119
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +9099 -1100
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +11 -4
  10. package/src/adapter/core.ts +111 -9
  11. package/src/adapter/createTask.ts +1 -1
  12. package/src/adapter/getLocales.ts +2 -2
  13. package/src/adapter/types.ts +9 -0
  14. package/src/components/TranslationsProvider.tsx +942 -0
  15. package/src/components/page/BatchProgress.tsx +27 -0
  16. package/src/components/page/ImportAllDialog.tsx +51 -0
  17. package/src/components/page/ImportMissingDialog.tsx +55 -0
  18. package/src/components/page/TranslateAllDialog.tsx +55 -0
  19. package/src/components/page/TranslationsTable.tsx +81 -0
  20. package/src/components/page/TranslationsTool.tsx +338 -0
  21. package/src/components/shared/BaseTranslationWrapper.tsx +82 -0
  22. package/src/components/{LanguageStatus.tsx → shared/LanguageStatus.tsx} +2 -0
  23. package/src/components/shared/LocaleCheckbox.tsx +47 -0
  24. package/src/components/{ProgressBar.tsx → shared/ProgressBar.tsx} +2 -0
  25. package/src/components/shared/SingleDocumentView.tsx +108 -0
  26. package/src/components/tab/TranslationView.tsx +379 -0
  27. package/src/components/tab/TranslationsTab.tsx +25 -0
  28. package/src/configuration/baseDocumentLevelConfig/documentLevelPatch.ts +21 -11
  29. package/src/configuration/baseDocumentLevelConfig/helpers/createI18nDocAndPatchMetadata.ts +57 -23
  30. package/src/configuration/baseDocumentLevelConfig/helpers/createTranslationMetadata.ts +2 -0
  31. package/src/configuration/baseDocumentLevelConfig/helpers/getOrCreateTranslationMetadata.ts +2 -0
  32. package/src/configuration/baseDocumentLevelConfig/helpers/getTranslationMetadata.ts +2 -0
  33. package/src/configuration/baseDocumentLevelConfig/helpers/patchI18nDoc.ts +31 -8
  34. package/src/configuration/baseDocumentLevelConfig/index.ts +18 -101
  35. package/src/configuration/baseFieldLevelConfig.ts +19 -51
  36. package/src/configuration/utils/checkSerializationVersion.ts +2 -0
  37. package/src/configuration/utils/findDocumentAtRevision.ts +2 -0
  38. package/src/configuration/utils/findLatestDraft.ts +2 -0
  39. package/src/hooks/useClient.ts +3 -1
  40. package/src/hooks/useSecrets.ts +2 -0
  41. package/src/index.ts +91 -67
  42. package/src/sanity-api/findDocuments.ts +44 -0
  43. package/src/sanity-api/publishDocuments.ts +49 -0
  44. package/src/sanity-api/resolveRefs.ts +146 -0
  45. package/src/serialization/BaseDocumentMerger.ts +138 -0
  46. package/src/serialization/BaseSerializationConfig.ts +220 -0
  47. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/documentLevelDeserialization.test.ts.snap +189 -0
  48. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/fieldLevelDeserialization.test.ts.snap +107 -0
  49. package/src/serialization/__tests__/BaseDocumentDeserializer/baseDeserialization.test.ts +397 -0
  50. package/src/serialization/__tests__/BaseDocumentDeserializer/documentLevelDeserialization.test.ts +107 -0
  51. package/src/serialization/__tests__/BaseDocumentDeserializer/fieldLevelDeserialization.test.ts +107 -0
  52. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/documentLevelMerge.test.ts.snap +193 -0
  53. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/fieldLevelMerge.test.ts.snap +97 -0
  54. package/src/serialization/__tests__/BaseDocumentMerger/baseMerge.test.ts +36 -0
  55. package/src/serialization/__tests__/BaseDocumentMerger/documentLevelMerge.test.ts +96 -0
  56. package/src/serialization/__tests__/BaseDocumentMerger/fieldLevelMerge.test.ts +142 -0
  57. package/src/serialization/__tests__/BaseDocumentMerger/utils.ts +52 -0
  58. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentInlineMarks.test.ts.snap +39 -0
  59. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentLevelSerialization.test.ts.snap +8 -0
  60. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/fieldLevelSerialization.test.ts.snap +8 -0
  61. package/src/serialization/__tests__/BaseDocumentSerializer/baseSerialization.test.ts +345 -0
  62. package/src/serialization/__tests__/BaseDocumentSerializer/documentInlineMarks.test.ts +53 -0
  63. package/src/serialization/__tests__/BaseDocumentSerializer/documentLevelSerialization.test.ts +120 -0
  64. package/src/serialization/__tests__/BaseDocumentSerializer/fieldLevelSerialization.test.ts +153 -0
  65. package/src/serialization/__tests__/BaseDocumentSerializer/utils.ts +27 -0
  66. package/src/serialization/__tests__/README +2 -0
  67. package/src/serialization/__tests__/__fixtures__/annotationAndInlineBlocks.json +140 -0
  68. package/src/serialization/__tests__/__fixtures__/customStyles.json +62 -0
  69. package/src/serialization/__tests__/__fixtures__/documentInlineMarks.json +70 -0
  70. package/src/serialization/__tests__/__fixtures__/documentLevelArticle.json +185 -0
  71. package/src/serialization/__tests__/__fixtures__/fieldLevelArticle.json +107 -0
  72. package/src/serialization/__tests__/__fixtures__/inlineDocumentLevelArticle.json +134 -0
  73. package/src/serialization/__tests__/__fixtures__/inlineSchema.ts +270 -0
  74. package/src/serialization/__tests__/__fixtures__/messy-html.html +26 -0
  75. package/src/serialization/__tests__/__fixtures__/nestedLanguageFields.json +54 -0
  76. package/src/serialization/__tests__/__fixtures__/schema.ts +310 -0
  77. package/src/serialization/__tests__/global.setup.ts +40 -0
  78. package/src/serialization/__tests__/helpers.ts +132 -0
  79. package/src/serialization/data.ts +82 -0
  80. package/src/serialization/deserialize/BaseDocumentDeserializer.ts +171 -0
  81. package/src/serialization/deserialize/helpers.ts +42 -0
  82. package/src/serialization/helpers.ts +18 -0
  83. package/src/serialization/index.ts +11 -0
  84. package/src/serialization/serialize/fieldFilters.ts +124 -0
  85. package/src/serialization/serialize/index.ts +284 -0
  86. package/src/serialization/types.ts +41 -0
  87. package/src/translation/checkTranslationStatus.ts +42 -0
  88. package/src/translation/createJobs.ts +16 -0
  89. package/src/translation/downloadTranslations.ts +68 -0
  90. package/src/translation/importDocument.ts +23 -0
  91. package/src/translation/initProject.ts +61 -0
  92. package/src/translation/uploadFiles.ts +32 -0
  93. package/src/types.ts +7 -20
  94. package/src/utils/applyDocuments.ts +72 -0
  95. package/src/utils/batchProcessor.ts +111 -0
  96. package/src/utils/importUtils.ts +95 -0
  97. package/src/utils/serialize.ts +52 -0
  98. package/src/utils/shared.ts +1 -0
  99. package/src/adapter/index.ts +0 -13
  100. package/src/components/NewTask.tsx +0 -249
  101. package/src/components/TaskView.tsx +0 -255
  102. package/src/components/TranslationContext.tsx +0 -19
  103. package/src/components/TranslationView.tsx +0 -82
  104. package/src/components/TranslationsTab.tsx +0 -177
  105. package/src/configuration/baseDocumentLevelConfig/helpers/index.ts +0 -5
  106. package/src/configuration/baseDocumentLevelConfig/legacyDocumentLevelPatch.ts +0 -69
  107. package/src/configuration/index.ts +0 -18
  108. package/src/configuration/utils/index.ts +0 -3
@@ -0,0 +1,26 @@
1
+ <html>
2
+ <head>
3
+ <meta name="_id" content="xxx-xxxxxxx-xxxxxx-xxxxx" />
4
+ <meta name="_type" content="article" />
5
+ <meta name="_rev" content="5KevdrlthkOcXnmdCMJjkO" />
6
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
7
+ </head>
8
+ <body>
9
+ <div class="documentLevelArticle" id="mzeqi4" data-type="object">
10
+ <span class="title">Här är artikel titeln</span>
11
+ <div class="content" data-type="array">
12
+ <p id="e6977d1d9f16">Här är lite content</p>
13
+ <div class="objectField" id="9e505b3b2ab4" data-type="object">
14
+ <div class="nestedArrayField" data-type="array">
15
+ <div class="childObjectField" id="8b2f375c2e7c" data-type="object">
16
+ <span class="title">Det här är en dragspels titeln</span>
17
+ <div class="content" data-type="array">
18
+ <p id="7ac862f38e89">Lite content<br />i vår accordion</p>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,54 @@
1
+ {
2
+ "slices": [
3
+ {
4
+ "_key": "6b9d0b28810f",
5
+ "_type": "localeBlock",
6
+ "en": [
7
+ {
8
+ "_key": "cebb6c538ce0",
9
+ "_type": "block",
10
+ "children": [
11
+ {
12
+ "_key": "48cc6e3b0754",
13
+ "_type": "span",
14
+ "marks": [],
15
+ "text": "This is block text in my slice."
16
+ }
17
+ ],
18
+ "markDefs": [],
19
+ "style": "normal"
20
+ }
21
+ ],
22
+ "fr_FR": [
23
+ {
24
+ "_key": "8bf012dc2eda",
25
+ "_type": "block",
26
+ "children": [
27
+ {
28
+ "_key": "44954c1912e1",
29
+ "_type": "span",
30
+ "marks": [],
31
+ "text": "Ceci est une texte du bloque en mon slice"
32
+ }
33
+ ],
34
+ "markDefs": [],
35
+ "style": "normal"
36
+ }
37
+ ]
38
+ },
39
+ {
40
+ "_type": "reference",
41
+ "_key": "13ed4b8013e4",
42
+ "_ref": "66dd1434-23c3-4d94-9e5a-d117775be828"
43
+ }
44
+ ],
45
+ "pageFields": {
46
+ "_type": "pageFields",
47
+ "name": {
48
+ "_type": "localeString",
49
+ "en": "Hello, this is my page name in my page field.",
50
+ "fr_FR": "C'est une field en frances"
51
+ },
52
+ "slug": "current slug"
53
+ }
54
+ }
@@ -0,0 +1,310 @@
1
+ import { Schema } from '@sanity/schema';
2
+
3
+ const arrayField = {
4
+ name: 'arrayField',
5
+ title: 'Array Field',
6
+ type: 'array',
7
+ of: [
8
+ {
9
+ type: 'block',
10
+ marks: {
11
+ annotations: [{ type: 'linkField' }],
12
+ },
13
+ },
14
+ { type: 'objectField' },
15
+ { type: 'linkField' },
16
+ ],
17
+ };
18
+ const linkField = {
19
+ name: 'linkField',
20
+ title: 'Link Field',
21
+ type: 'object',
22
+ fields: [
23
+ {
24
+ name: 'label',
25
+ title: 'Label',
26
+ type: 'string',
27
+ },
28
+ {
29
+ name: 'linkType',
30
+ title: 'Link Type',
31
+ type: 'string',
32
+ options: {
33
+ list: [
34
+ { title: 'None', value: 'none' },
35
+ { title: 'URL', value: 'href' },
36
+ { title: 'Page', value: 'page' },
37
+ { title: 'Simple Page', value: 'simplePage' },
38
+ { title: 'Post', value: 'post' },
39
+ { title: 'File', value: 'file' },
40
+ ],
41
+ },
42
+ },
43
+ {
44
+ name: 'href',
45
+ title: 'URL',
46
+ type: 'url',
47
+ hidden: ({ parent }: { parent: any }) => parent?.linkType !== 'href',
48
+ },
49
+ {
50
+ name: 'page',
51
+ title: 'Page',
52
+ type: 'reference',
53
+ to: [{ type: 'page' }],
54
+ weak: true,
55
+ hidden: ({ parent }: { parent: any }) => parent?.linkType !== 'page',
56
+ },
57
+ {
58
+ name: 'simplePage',
59
+ title: 'Simple Page',
60
+ type: 'reference',
61
+ to: [{ type: 'simplePage' }],
62
+ weak: true,
63
+ hidden: ({ parent }: { parent: any }) =>
64
+ parent?.linkType !== 'simplePage',
65
+ },
66
+ {
67
+ name: 'post',
68
+ title: 'Post',
69
+ type: 'reference',
70
+ to: [{ type: 'post' }],
71
+ weak: true,
72
+ hidden: ({ parent }: { parent: any }) => parent?.linkType !== 'post',
73
+ },
74
+ {
75
+ name: 'file',
76
+ title: 'File',
77
+ type: 'file',
78
+ hidden: ({ parent }: { parent: any }) => parent?.linkType !== 'file',
79
+ },
80
+ {
81
+ name: 'openInNewTab',
82
+ title: 'Open in New Tab',
83
+ type: 'boolean',
84
+ },
85
+ ],
86
+ };
87
+
88
+ const childObjectField = {
89
+ name: 'childObjectField',
90
+ title: 'Child Object Field',
91
+ type: 'object',
92
+ fields: [
93
+ {
94
+ name: 'title',
95
+ title: 'Title',
96
+ type: 'string',
97
+ },
98
+ {
99
+ name: 'content',
100
+ title: 'Content',
101
+ type: 'array',
102
+ of: [
103
+ {
104
+ type: 'block',
105
+ marks: {
106
+ annotations: [{ type: 'linkField' }],
107
+ },
108
+ },
109
+ { type: 'linkField' },
110
+ ],
111
+ },
112
+ ],
113
+ };
114
+
115
+ const objectField = {
116
+ name: 'objectField',
117
+ title: 'Object Field',
118
+ type: 'object',
119
+ fields: [
120
+ {
121
+ name: 'title',
122
+ title: 'Title',
123
+ type: 'string',
124
+ },
125
+ {
126
+ name: 'objectAsField',
127
+ title: 'Object As Field',
128
+ type: 'childObjectField',
129
+ },
130
+ {
131
+ name: 'nestedArrayField',
132
+ title: 'Nested Array Field',
133
+ type: 'array',
134
+ of: [
135
+ {
136
+ type: 'block',
137
+ marks: {
138
+ annotations: [{ type: 'linkField' }],
139
+ },
140
+ },
141
+ { type: 'childObjectField' },
142
+ ],
143
+ },
144
+ ],
145
+ };
146
+
147
+ const documentLevelArticle = {
148
+ name: 'documentLevelArticle',
149
+ title: 'Document Level Article',
150
+ type: 'document',
151
+ fields: [
152
+ {
153
+ name: 'title',
154
+ title: 'Title',
155
+ type: 'string',
156
+ },
157
+ {
158
+ name: 'meta',
159
+ title: 'Meta',
160
+ type: 'string',
161
+ localize: false,
162
+ },
163
+ {
164
+ name: 'snippet',
165
+ title: 'Snippet',
166
+ type: 'text',
167
+ },
168
+ {
169
+ name: 'tags',
170
+ title: 'Tags',
171
+ type: 'array',
172
+ of: [{ type: 'string' }],
173
+ },
174
+ {
175
+ name: 'hidden',
176
+ title: 'Hidden',
177
+ type: 'boolean',
178
+ },
179
+ {
180
+ name: 'config',
181
+ title: 'Config',
182
+ type: 'objectField',
183
+ },
184
+ {
185
+ name: 'content',
186
+ title: 'Content',
187
+ type: 'arrayField',
188
+ },
189
+ ],
190
+ };
191
+
192
+ function createLocaleFields(locales: string[], fieldType: Record<string, any>) {
193
+ return locales.map((locale) => ({
194
+ ...{ name: locale },
195
+ ...fieldType,
196
+ }));
197
+ }
198
+
199
+ const fieldLevelArticle = {
200
+ name: 'fieldLevelArticle',
201
+ title: 'Field Level Article',
202
+ type: 'document',
203
+ fields: [
204
+ {
205
+ name: 'title',
206
+ title: 'Title',
207
+ type: 'localeString',
208
+ },
209
+ {
210
+ name: 'meta',
211
+ title: 'Meta',
212
+ type: 'string',
213
+ localize: false,
214
+ },
215
+ {
216
+ name: 'snippet',
217
+ title: 'Snippet',
218
+ type: 'object',
219
+ fields: createLocaleFields(['en', 'fr', 'de'], { type: 'text' }),
220
+ },
221
+ {
222
+ name: 'tags',
223
+ title: 'Tags',
224
+ type: 'object',
225
+ fields: createLocaleFields(['en', 'fr', 'de'], {
226
+ type: 'array',
227
+ of: [{ type: 'string' }],
228
+ }),
229
+ },
230
+ {
231
+ name: 'hidden',
232
+ title: 'Hidden',
233
+ type: 'boolean',
234
+ },
235
+ {
236
+ name: 'config',
237
+ title: 'Config',
238
+ type: 'object',
239
+ fields: createLocaleFields(['en', 'fr', 'de'], { type: 'objectField' }),
240
+ },
241
+ {
242
+ name: 'content',
243
+ title: 'Content',
244
+ type: 'object',
245
+ fields: createLocaleFields(['en', 'fr', 'de'], { type: 'arrayField' }),
246
+ },
247
+ {
248
+ name: 'slices',
249
+ title: 'Slices',
250
+ type: 'array',
251
+ of: [
252
+ { type: 'localeBlock' },
253
+ { type: 'reference', to: [{ type: 'marketText' }] },
254
+ ],
255
+ },
256
+ {
257
+ name: 'pageFields',
258
+ title: 'Page Fields',
259
+ type: 'pageFields',
260
+ },
261
+ ],
262
+ };
263
+
264
+ const localeBlock = {
265
+ name: 'localeBlock',
266
+ title: 'Locale Block',
267
+ type: 'object',
268
+ fields: createLocaleFields(['en', 'fr_FR', 'de_DE'], { type: 'arrayField' }),
269
+ };
270
+
271
+ const localeString = {
272
+ name: 'localeString',
273
+ title: 'Locale String',
274
+ type: 'object',
275
+ fields: createLocaleFields(['en', 'fr_FR', 'de_DE'], { type: 'string' }),
276
+ };
277
+
278
+ const pageFields = {
279
+ name: 'pageFields',
280
+ title: 'Page Fields',
281
+ type: 'object',
282
+ fields: [
283
+ {
284
+ title: 'Page Name',
285
+ name: 'name',
286
+ type: 'localeString',
287
+ },
288
+ {
289
+ name: 'slug',
290
+ type: 'string',
291
+ },
292
+ ],
293
+ };
294
+
295
+ const types = [
296
+ arrayField,
297
+ linkField,
298
+ childObjectField,
299
+ objectField,
300
+ documentLevelArticle,
301
+ fieldLevelArticle,
302
+ pageFields,
303
+ localeBlock,
304
+ localeString,
305
+ ];
306
+
307
+ export default new Schema({
308
+ name: 'test',
309
+ types,
310
+ });
@@ -0,0 +1,40 @@
1
+ import {
2
+ PortableTextTextBlock,
3
+ PortableTextSpan,
4
+ PortableTextObject,
5
+ } from 'sanity';
6
+ import { vi } from 'vitest';
7
+
8
+ let mockTestKey = 0;
9
+
10
+ vi.mock('@portabletext/block-tools', async () => {
11
+ const originalModule = await vi.importActual<
12
+ typeof import('@portabletext/block-tools')
13
+ >('@portabletext/block-tools');
14
+ return {
15
+ ...originalModule,
16
+ //not ideal but vi.mock('@sanity/block-tools/src/util/randomKey.ts' is not working
17
+ htmlToBlocks: (html: string, blockContentType: any, options: any) => {
18
+ const blocks = originalModule.htmlToBlocks(
19
+ html,
20
+ blockContentType,
21
+ options
22
+ );
23
+ const newBlocks = blocks.map((block) => {
24
+ const newChildren = (
25
+ block as unknown as PortableTextTextBlock<
26
+ PortableTextSpan | PortableTextObject
27
+ >
28
+ ).children.map((child) => {
29
+ return { ...child, _key: `randomKey-${mockTestKey++}` };
30
+ });
31
+ return {
32
+ ...block,
33
+ children: newChildren,
34
+ _key: `randomKey-${mockTestKey++}`,
35
+ };
36
+ });
37
+ return newBlocks;
38
+ },
39
+ };
40
+ });
@@ -0,0 +1,132 @@
1
+ import { BaseDocumentSerializer, BaseDocumentDeserializer } from '../index';
2
+ import {
3
+ customSerializers,
4
+ customDeserializers,
5
+ customBlockDeserializers,
6
+ } from '../BaseSerializationConfig';
7
+ import { PortableTextBlock, SanityDocument, TypedObject } from 'sanity';
8
+ import { SerializedDocument, TranslationLevel } from '../types';
9
+ import schema from './__fixtures__/schema';
10
+ import clone from 'just-clone';
11
+
12
+ export const getSerialized = (
13
+ document: SanityDocument,
14
+ level: TranslationLevel
15
+ ): SerializedDocument => {
16
+ const serializer = BaseDocumentSerializer(schema);
17
+ return serializer.serializeDocument(document, level);
18
+ };
19
+
20
+ export const getDeserialized = (
21
+ document: SanityDocument,
22
+ level: TranslationLevel
23
+ ): Record<string, any> => {
24
+ const serialized = getSerialized(document, level);
25
+ const deserializer = BaseDocumentDeserializer;
26
+ return deserializer.deserializeDocument(serialized.content);
27
+ };
28
+
29
+ export const getValidFields = (
30
+ field: Record<string, any>
31
+ ): Record<string, any> => {
32
+ const invalidFields = ['_type', '_key'];
33
+ return Object.keys(field).filter((key) => !invalidFields.includes(key));
34
+ };
35
+
36
+ export const toPlainText = (blocks: PortableTextBlock[]): string => {
37
+ return blocks
38
+ .map((block) => {
39
+ if (block._type !== 'block' || !block.children) {
40
+ return '';
41
+ }
42
+ return (block.children as Array<any>).map((child) => child.text).join('');
43
+ })
44
+ .join('\n\n');
45
+ };
46
+
47
+ export const createCustomInnerHTML = (title: string): string =>
48
+ `Custom serializer works and includes title: '${title}'`;
49
+
50
+ const additionalSerializerTypes = {
51
+ //block and top-level tests
52
+ objectField: ({ value }: { value: TypedObject }) => {
53
+ const innerText = createCustomInnerHTML(value.title as string);
54
+ const html = `<div class="${value._type}" id="${value._key ?? value._id}">${innerText}</div>`;
55
+ return html;
56
+ },
57
+ //inline-level tests
58
+ childObjectField: ({ value }: { value: TypedObject }) => {
59
+ const innerText = createCustomInnerHTML(value.title as string);
60
+ const html = `<span class="${value._type}" id="${value._key ?? value._id}">${innerText}</span>`;
61
+ return html;
62
+ },
63
+ };
64
+
65
+ const tempSerializers = clone(customSerializers);
66
+ tempSerializers.types = {
67
+ ...tempSerializers.types,
68
+ ...additionalSerializerTypes,
69
+ };
70
+ tempSerializers.marks = {
71
+ annotation: ({ value, markType, children }) => {
72
+ return `<span class="${markType}" id="${value._key}">${children}</span>`;
73
+ },
74
+ };
75
+
76
+ export const addedCustomSerializers = tempSerializers;
77
+
78
+ export const addedDeserializerTypes = {
79
+ objectField: (html: HTMLElement): TypedObject => {
80
+ const title = html.innerHTML.split(':')[1].replace(/'/g, '').trim();
81
+ const _type = html.className;
82
+ const _key = html.id;
83
+ return { title, _type, _key };
84
+ },
85
+ };
86
+
87
+ const tempDeserializers = clone(customDeserializers);
88
+ tempDeserializers.types = {
89
+ ...tempDeserializers.types,
90
+ ...addedDeserializerTypes,
91
+ };
92
+
93
+ export const addedCustomDeserializers = tempDeserializers;
94
+
95
+ export const addedBlockDeserializers = [
96
+ ...customBlockDeserializers,
97
+ {
98
+ deserialize(el: HTMLElement): TypedObject | undefined {
99
+ if (!el.className || el.className.toLowerCase() !== 'childobjectfield') {
100
+ return undefined;
101
+ }
102
+
103
+ const title = el.innerHTML.split(':')[1].replace(/'/g, '').trim();
104
+ const _type = el.className;
105
+ const _key = el.id;
106
+
107
+ return { title, _type, _key };
108
+ },
109
+ },
110
+ {
111
+ deserialize(
112
+ el: HTMLElement,
113
+ //eslint-disable-next-line no-undef -- not picking up NodeListOf/ChildNode
114
+ next: (nodes: NodeListOf<ChildNode>) => any
115
+ ): TypedObject | undefined {
116
+ if (!el.className || el.className?.toLowerCase() !== 'annotation') {
117
+ return undefined;
118
+ }
119
+
120
+ const markDef = {
121
+ _key: el.id,
122
+ _type: 'annotation',
123
+ };
124
+
125
+ return {
126
+ _type: '__annotation',
127
+ markDef: markDef,
128
+ children: next(el.childNodes),
129
+ };
130
+ },
131
+ },
132
+ ];
@@ -0,0 +1,82 @@
1
+ export function attachGTData(
2
+ html: string,
3
+ data: Record<string, any>,
4
+ type: 'markDef'
5
+ ): string {
6
+ // Parse the HTML string to find the first element
7
+ const parser = new DOMParser();
8
+ const doc = parser.parseFromString(html, 'text/html');
9
+ const firstElement = doc.body.firstElementChild;
10
+
11
+ if (!firstElement) {
12
+ // If no element found, return original HTML
13
+ return html;
14
+ }
15
+
16
+ // Encode the data as base64 JSON
17
+ const encodedData = encode(JSON.stringify({ [type]: data }));
18
+
19
+ // Add the data-gt-internal attribute
20
+ firstElement.setAttribute('data-gt-internal', encodedData);
21
+
22
+ return firstElement.outerHTML;
23
+ }
24
+
25
+ export function detachGTData(html: string): {
26
+ html: string;
27
+ data?: Record<'markDef', Record<string, any>>;
28
+ } {
29
+ // Parse the HTML string to find the first element
30
+ const parser = new DOMParser();
31
+ const doc = parser.parseFromString(html, 'text/html');
32
+ const firstElement = doc.body.firstElementChild;
33
+
34
+ if (!firstElement) {
35
+ // If no element found, return original HTML with no data
36
+ return { html };
37
+ }
38
+
39
+ // Get the encoded data
40
+ const encodedData = firstElement.getAttribute('data-gt-internal');
41
+
42
+ let extractedData: Record<'markDef', Record<string, any>> | undefined;
43
+ if (encodedData) {
44
+ try {
45
+ // Decode and parse the data
46
+ const decodedData = decode(encodedData);
47
+ extractedData = JSON.parse(decodedData);
48
+
49
+ // Remove the data attribute to clean up the HTML
50
+ firstElement.removeAttribute('data-gt-internal');
51
+ } catch (error) {
52
+ console.warn('Failed to decode GT internal data:', error);
53
+ }
54
+ }
55
+
56
+ return {
57
+ html: firstElement.outerHTML,
58
+ data: extractedData,
59
+ };
60
+ }
61
+
62
+ // Encode a string to base64
63
+ export function encode(data: string): string {
64
+ // Browser path
65
+ const bytes = new TextEncoder().encode(data);
66
+ let binary = '';
67
+ for (let i = 0; i < bytes.length; i++) {
68
+ binary += String.fromCharCode(bytes[i]);
69
+ }
70
+ return btoa(binary);
71
+ }
72
+
73
+ // Decode a base64 string to a string
74
+ export function decode(base64: string): string {
75
+ // Browser path
76
+ const binary = atob(base64);
77
+ const bytes = new Uint8Array(binary.length);
78
+ for (let i = 0; i < binary.length; i++) {
79
+ bytes[i] = binary.charCodeAt(i);
80
+ }
81
+ return new TextDecoder().decode(bytes);
82
+ }