gt-sanity 4.0.25 → 4.0.27

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 (21) hide show
  1. package/dist/index.d.ts +14 -3
  2. package/dist/index.js +249 -78
  3. package/dist/index.js.map +1 -1
  4. package/package.json +2 -2
  5. package/src/serialization/BaseDocumentMerger.ts +33 -1
  6. package/src/serialization/BaseSerializationConfig.ts +236 -46
  7. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/documentLevelDeserialization.test.ts.snap +10 -10
  8. package/src/serialization/__tests__/BaseDocumentDeserializer/__snapshots__/fieldLevelDeserialization.test.ts.snap +4 -4
  9. package/src/serialization/__tests__/BaseDocumentDeserializer/baseDeserialization.test.ts +42 -1
  10. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/documentLevelMerge.test.ts.snap +1 -1
  11. package/src/serialization/__tests__/BaseDocumentMerger/__snapshots__/fieldLevelMerge.test.ts.snap +1 -1
  12. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentInlineMarks.test.ts.snap +51 -3
  13. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/documentLevelSerialization.test.ts.snap +1 -1
  14. package/src/serialization/__tests__/BaseDocumentSerializer/__snapshots__/fieldLevelSerialization.test.ts.snap +1 -1
  15. package/src/serialization/__tests__/BaseDocumentSerializer/baseSerialization.test.ts +17 -9
  16. package/src/serialization/__tests__/roundTrip.test.ts +1083 -0
  17. package/src/serialization/data.ts +16 -3
  18. package/src/serialization/deserialize/BaseDocumentDeserializer.ts +48 -10
  19. package/src/serialization/helpers.ts +98 -1
  20. package/src/serialization/serialize/index.ts +64 -57
  21. package/src/utils/serialize.ts +2 -0
package/dist/index.d.ts CHANGED
@@ -53,7 +53,7 @@ export declare interface Adapter {
53
53
  export declare function attachGTData(
54
54
  html: string,
55
55
  data: Record<string, unknown>,
56
- type: "markDef",
56
+ type: GTDataType,
57
57
  ): string;
58
58
 
59
59
  export declare const BaseDocumentDeserializer: Deserializer;
@@ -82,7 +82,7 @@ export declare const BaseDocumentSerializer: (schemas: Schema) => {
82
82
  baseLang: string,
83
83
  ) => Record<string, unknown>;
84
84
  serializeArray: (
85
- fieldContent: Array<Record<string, unknown> | string>,
85
+ fieldContent: unknown[],
86
86
  fieldName: string,
87
87
  stopTypes: string[],
88
88
  serializers: Partial<PortableTextHtmlComponents>,
@@ -93,6 +93,7 @@ export declare const BaseDocumentSerializer: (schemas: Schema) => {
93
93
  stopTypes: string[],
94
94
  serializers: Partial<PortableTextHtmlComponents>,
95
95
  declaredFields?: ObjectField[],
96
+ isDocument?: boolean,
96
97
  ) => string;
97
98
  };
98
99
 
@@ -127,7 +128,7 @@ declare interface Deserializer {
127
128
 
128
129
  export declare function detachGTData(html: string): {
129
130
  html: string;
130
- data?: Record<"markDef", Record<string, unknown>>;
131
+ data?: GTData;
131
132
  };
132
133
 
133
134
  export { documentInternationalization };
@@ -186,6 +187,16 @@ export declare const findLatestDraft: (
186
187
 
187
188
  declare const gt: GT;
188
189
 
190
+ declare type GTData = Record<"markDef", Record<string, unknown>> &
191
+ Partial<Record<"inlineObject", Record<string, unknown>>>;
192
+
193
+ /**
194
+ * Kinds of Sanity data that can be carried on an element:
195
+ * - `markDef`: an annotation (e.g. a link) applied to the element's children
196
+ * - `inlineObject`: an inline object in a block's `children`, restored as-is
197
+ */
198
+ declare type GTDataType = "markDef" | "inlineObject";
199
+
189
200
  /**
190
201
  * Field-level localization options. `gtPlugin` forwards these to
191
202
  * `sanity-plugin-internationalized-array` — the reference Sanity plugin, which
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { GT, getLocaleProperties } from "generaltranslation";
2
2
  import { createDiagnosticMessage } from "generaltranslation/diagnostics";
3
- import { createGtApiAdapter, defaultBaseUrl, libraryDefaultLocale, decode, encode } from "generaltranslation/internal";
3
+ import { createGtApiAdapter, defaultBaseUrl, libraryDefaultLocale, encode, decode } from "generaltranslation/internal";
4
4
  import { useClient as useClient$1, useSchema, Preview, definePlugin } from "sanity";
5
5
  import { useIntentLink, Link, route } from "sanity/router";
6
6
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
@@ -13,8 +13,8 @@ import { extractWithPath, arrayToJSONMatchPath } from "@sanity/mutator";
13
13
  import { JSONPath } from "jsonpath-plus";
14
14
  import JSONPointer from "jsonpointer";
15
15
  import { htmlToBlocks } from "@portabletext/block-tools";
16
+ import { defaultComponents, toHTML } from "@portabletext/to-html";
16
17
  import { Schema } from "@sanity/schema";
17
- import { toHTML } from "@portabletext/to-html";
18
18
  import merge from "lodash.merge";
19
19
  import { Tooltip } from "@sanity/ui/tooltip";
20
20
  import { Code } from "@sanity/ui/code";
@@ -213,7 +213,22 @@ const gtRadius = [0, 4, 6, 8, 12, 16, 24], baseTheme = buildTheme(), theme = {
213
213
  !loadingSecrets && secrets && children
214
214
  ] });
215
215
  return /* @__PURE__ */ jsx(ThemeProvider, { theme, children: /* @__PURE__ */ jsx(ToastProvider, { paddingY: 7, children: showContainer ? /* @__PURE__ */ jsx(Box, { padding, children: content }) : content }) });
216
- }, isRecord$5 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), reconcileArray = (origArray, translatedArray) => {
216
+ }, isRecord$5 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), withMarkDefOrder = (translatedBlock, origBlock) => {
217
+ if (!Array.isArray(translatedBlock.markDefs) || !Array.isArray(origBlock.markDefs))
218
+ return translatedBlock;
219
+ const origOrder = origBlock.markDefs.map(
220
+ (markDef) => isRecord$5(markDef) ? markDef._key : void 0
221
+ ), position = (markDef) => {
222
+ const index = isRecord$5(markDef) ? origOrder.indexOf(markDef._key) : -1;
223
+ return index < 0 ? origOrder.length : index;
224
+ };
225
+ return {
226
+ ...translatedBlock,
227
+ markDefs: [...translatedBlock.markDefs].sort(
228
+ (a, b) => position(a) - position(b)
229
+ )
230
+ };
231
+ }, reconcileArray = (origArray, translatedArray) => {
217
232
  if (translatedArray && translatedArray.some((el) => typeof el == "string"))
218
233
  return translatedArray;
219
234
  const combined = JSON.parse(JSON.stringify(origArray));
@@ -227,7 +242,10 @@ const gtRadius = [0, 4, 6, 8, 12, 16, 24], baseTheme = buildTheme(), theme = {
227
242
  `This block no longer exists on the original document. Was it removed? ${JSON.stringify(
228
243
  translatedItem
229
244
  )}`
230
- ) : isRecord$5(origArray[foundBlockIdx]) && (origArray[foundBlockIdx]._type === "block" || origArray[foundBlockIdx]._type === "span") ? combined[foundBlockIdx] = translatedItem : isRecord$5(origArray[foundBlockIdx]) && (combined[foundBlockIdx] = reconcileObject(
245
+ ) : isRecord$5(origArray[foundBlockIdx]) && (origArray[foundBlockIdx]._type === "block" || origArray[foundBlockIdx]._type === "span") ? combined[foundBlockIdx] = withMarkDefOrder(
246
+ translatedItem,
247
+ origArray[foundBlockIdx]
248
+ ) : isRecord$5(origArray[foundBlockIdx]) && (combined[foundBlockIdx] = reconcileObject(
231
249
  origArray[foundBlockIdx],
232
250
  translatedItem
233
251
  ));
@@ -765,7 +783,34 @@ function detachGTData(html) {
765
783
  data: extractedData
766
784
  };
767
785
  }
768
- const isRecord$3 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), defaultStopTypes = [
786
+ function mergeBlocks(blocks) {
787
+ const mergedBlock = { ...blocks[0] };
788
+ mergedBlock.markDefs = mergedBlock.markDefs ?? [];
789
+ for (const [idx, block] of blocks.entries())
790
+ idx !== 0 && (mergedBlock.children.push(...block.children), mergedBlock.markDefs.push(...block.markDefs ?? []));
791
+ mergedBlock._type = "block";
792
+ const seenMarkKeys = /* @__PURE__ */ new Set();
793
+ return mergedBlock.markDefs = mergedBlock.markDefs.filter((markDef) => seenMarkKeys.has(markDef._key) ? !1 : (seenMarkKeys.add(markDef._key), !0)), mergedBlock;
794
+ }
795
+ const htmlEscapes = {
796
+ "&": "&amp;",
797
+ "<": "&lt;",
798
+ ">": "&gt;",
799
+ '"': "&quot;"
800
+ }, escapeHTML = (value) => value.replace(/[&<>"]/g, (char) => htmlEscapes[char]), SIGNIFICANT_WHITESPACE = /[\t\n\f\r]|^\s|\s$|\s\s/, STRING_ATTRIBUTE = "data-gt-string", LEADING_NEWLINES_ATTRIBUTE = "data-gt-leading-newlines", serializeString = (value, className, multiline = !1) => {
801
+ const classAttribute = className ? ` class="${className}"` : "";
802
+ if (!multiline && !SIGNIFICANT_WHITESPACE.test(value))
803
+ return `<span${classAttribute}>${escapeHTML(value)}</span>`;
804
+ const text = value.replace(/^\n+/, ""), leadingNewlines = value.length - text.length, leadingNewlinesAttribute = leadingNewlines ? ` ${LEADING_NEWLINES_ATTRIBUTE}="${leadingNewlines}"` : "";
805
+ return `<pre${classAttribute} ${STRING_ATTRIBUTE}=""${leadingNewlinesAttribute}>${escapeHTML(text)}</pre>`;
806
+ }, INLINE_OBJECT_KEY_FIELD = "__gtInlineObjectKey", restoreInlineObjectKeys = (block) => (block.children?.forEach(
807
+ (child) => {
808
+ INLINE_OBJECT_KEY_FIELD in child && (typeof child[INLINE_OBJECT_KEY_FIELD] == "string" && (child._key = child[INLINE_OBJECT_KEY_FIELD]), delete child[INLINE_OBJECT_KEY_FIELD]);
809
+ }
810
+ ), block), MARKED_LINE_BREAK = "\uFDD0", restoreMarkedLineBreaks = (block) => (block.children?.forEach((child) => {
811
+ typeof child.text == "string" && (child.text = child.text.replaceAll(MARKED_LINE_BREAK, `
812
+ `));
813
+ }), block), isRecord$3 = (value) => typeof value == "object" && value !== null && !Array.isArray(value), defaultStopTypes = [
769
814
  "reference",
770
815
  "date",
771
816
  "datetime",
@@ -779,7 +824,30 @@ const isRecord$3 = (value) => typeof value == "object" && value !== null && !Arr
779
824
  "url",
780
825
  "color",
781
826
  "code"
782
- ], defaultMarks = {}, defaultPortableTextBlockStyles = {
827
+ ], BUILTIN_ATTRIBUTE = "data-gt-builtin", annotationMark = (fallback) => (props) => {
828
+ const { value, children, markType } = props;
829
+ if (!(isRecord$3(value) && typeof value._key == "string"))
830
+ return fallback(props);
831
+ const href = markType === "link" && typeof value.href == "string" ? ` href="${escapeHTML(value.href)}"` : "", tag = markType === "link" ? "a" : "span";
832
+ return attachGTData(
833
+ `<${tag}${href} ${BUILTIN_ATTRIBUTE}="">${children}</${tag}>`,
834
+ value,
835
+ "markDef"
836
+ );
837
+ }, DECORATOR_ATTRIBUTE = "data-gt-decorator", decoratorMark = ({ children, markType }) => `<span ${DECORATOR_ATTRIBUTE}="${escapeHTML(markType)}">${children}</span>`, DECORATOR_ELEMENTS = {
838
+ sup: "sup",
839
+ sub: "sub",
840
+ u: "underline"
841
+ }, defaultMarks = {
842
+ link: annotationMark(
843
+ defaultComponents.marks.link
844
+ ),
845
+ // annotations share the mark registry, so an annotation named like one of
846
+ // these decorators keeps its markDef
847
+ sup: annotationMark(({ children }) => `<sup>${children}</sup>`),
848
+ sub: annotationMark(({ children }) => `<sub>${children}</sub>`),
849
+ underline: annotationMark(({ children }) => `<u>${children}</u>`)
850
+ }, defaultPortableTextBlockStyles = {
783
851
  normal: ({ value, children }) => `<p id="${value._key}">${children}</p>`,
784
852
  blockquote: ({ value, children }) => `<blockquote id="${value._key}">${children}</blockquote>`,
785
853
  h1: ({ value, children }) => `<h1 id="${value._key}">${children}</h1>`,
@@ -797,37 +865,102 @@ const isRecord$3 = (value) => typeof value == "object" && value !== null && !Arr
797
865
  }) => {
798
866
  const { _key, level } = value;
799
867
  return `<li id="${(_key || "").replace("-parent", "")}" data-level="${level}">${children}</li>`;
800
- }, unknownBlockFunc = ({ value, children }) => `<p id="${value._key}" data-type="unknown-block-style" data-style="${value.style}">${children}</p>`, customSerializers = {
801
- unknownType: ({ value }) => `<div class="${value._type}"></div>`,
868
+ }, unknownBlockFunc = ({ value, children }) => `<p id="${value._key}" data-type="unknown-block-style" data-style="${value.style}">${children}</p>`, INLINE_OBJECT_CONTENT = "\u200B", customSerializers = {
869
+ // Inline objects must stay inline: a <div> inside a <p> closes the
870
+ // paragraph and the rest of its text is dropped on import. The object is
871
+ // carried as data and restored by the inline-object rule below. The
872
+ // zero-width space keeps the span non-empty: HTML formatters (GT's
873
+ // included) collapse the spaces on both sides of an empty element into one.
874
+ unknownType: ({ value, isInline }) => isInline ? attachGTData(
875
+ `<span class="${escapeHTML(value._type)}">${INLINE_OBJECT_CONTENT}</span>`,
876
+ value,
877
+ "inlineObject"
878
+ ) : `<div class="${escapeHTML(value._type)}"></div>`,
879
+ unknownMark: annotationMark(decoratorMark),
802
880
  types: {},
803
881
  marks: defaultMarks,
804
882
  block: defaultPortableTextBlockStyles,
805
883
  list: defaultLists,
806
884
  listItem: defaultListItem,
807
885
  unknownBlockStyle: unknownBlockFunc
808
- }, customDeserializers = { types: {} }, customBlockDeserializers = [
809
- // handle marks with data-gt-internal
886
+ }, customDeserializers = { types: {} }, MARK_ELEMENTS = /* @__PURE__ */ new Set([
887
+ "a",
888
+ "b",
889
+ "code",
890
+ "del",
891
+ "em",
892
+ "i",
893
+ "s",
894
+ "span",
895
+ "strike",
896
+ "strong",
897
+ "sub",
898
+ "sup",
899
+ "u"
900
+ ]), markDefRule = (claims) => ({
901
+ deserialize(node, next) {
902
+ if (node.nodeType !== 1)
903
+ return;
904
+ const el = node;
905
+ if (!el.hasChildNodes() || !el.getAttribute("data-gt-internal") || !claims(el))
906
+ return;
907
+ const markDef = detachGTData(el.outerHTML).data?.markDef;
908
+ if (!(!isRecord$3(markDef) || typeof markDef._key != "string"))
909
+ return {
910
+ _type: "__annotation",
911
+ markDef,
912
+ children: next(el.childNodes)
913
+ };
914
+ }
915
+ }), decoratorRule = (decoratorName) => ({
916
+ deserialize(node, next) {
917
+ if (node.nodeType !== 1)
918
+ return;
919
+ const name = decoratorName(node);
920
+ if (name)
921
+ return {
922
+ _type: "__decorator",
923
+ name,
924
+ children: next(node.childNodes)
925
+ };
926
+ }
927
+ }), customBlockDeserializers = [
928
+ // keep whitespace-only text inside a mark (`Note:<strong> </strong>The`,
929
+ // or next to a nested mark: `<strong><em>Note:</em> </strong>The`):
930
+ // block-tools drops it and glues the words on either side together
810
931
  {
811
- deserialize(node, next) {
932
+ deserialize(node) {
933
+ const parent = node.parentElement;
934
+ if (!(node.nodeType !== 3 || !node.textContent || node.textContent.trim() || !parent || !MARK_ELEMENTS.has(parent.tagName.toLowerCase())))
935
+ return {
936
+ _type: "span",
937
+ marks: [],
938
+ text: node.textContent.replace(/[\t\n\f\r ]+/g, " ")
939
+ };
940
+ }
941
+ },
942
+ // handle inline objects with data-gt-internal
943
+ {
944
+ deserialize(node) {
812
945
  if (node.nodeType !== 1)
813
946
  return;
814
947
  const el = node;
815
- if (!el.hasChildNodes() || !el.getAttribute("data-gt-internal"))
948
+ if (!el.getAttribute("data-gt-internal"))
816
949
  return;
817
- const { html, data } = detachGTData(el.outerHTML), block = htmlToBlocks(html, blockContentType)[0], children = next(el.childNodes);
818
- let markDefs = [];
819
- return "markDefs" in block && (markDefs = block.markDefs ?? []), data?.markDef && markDefs.push(data.markDef), Array.isArray(children) && children.forEach((child) => {
820
- if (!isRecord$3(child))
821
- return;
822
- const marks = Array.isArray(child.marks) ? child.marks : [];
823
- child.marks = data?.markDef?._key ? [...marks, data.markDef._key] : marks;
824
- }), {
825
- ...block,
826
- markDefs,
827
- children
828
- };
950
+ const inlineObject = detachGTData(el.outerHTML).data?.inlineObject;
951
+ if (!(!isRecord$3(inlineObject) || typeof inlineObject._type != "string"))
952
+ return {
953
+ ...inlineObject,
954
+ _type: inlineObject._type,
955
+ [INLINE_OBJECT_KEY_FIELD]: inlineObject._key
956
+ };
829
957
  }
830
958
  },
959
+ // handle marks with data-gt-internal from a user's serializer
960
+ // (attachGTData); the built-in ones are in fallbackBlockDeserializers
961
+ markDefRule((el) => !el.hasAttribute(BUILTIN_ATTRIBUTE)),
962
+ // handle decorators, whether or not the generic import schema declares them
963
+ decoratorRule((el) => el.getAttribute(DECORATOR_ATTRIBUTE)),
831
964
  //handle undeclared styles
832
965
  {
833
966
  deserialize(node, next) {
@@ -872,7 +1005,13 @@ const isRecord$3 = (value) => typeof value == "object" && value !== null && !Arr
872
1005
  // @ts-ignore
873
1006
  style: customStyle ?? newBlock.style
874
1007
  }, customStyle))
875
- return block;
1008
+ return {
1009
+ ...block,
1010
+ level,
1011
+ _key,
1012
+ listItem,
1013
+ children: next(el.children[0].childNodes)
1014
+ };
876
1015
  }
877
1016
  return {
878
1017
  ...block,
@@ -883,21 +1022,45 @@ const isRecord$3 = (value) => typeof value == "object" && value !== null && !Arr
883
1022
  };
884
1023
  }
885
1024
  }
886
- ];
887
- function mergeBlocks(blocks) {
888
- const mergedBlock = { ...blocks[0] };
889
- mergedBlock.markDefs = mergedBlock.markDefs ?? [];
890
- for (const [idx, block] of blocks.entries())
891
- idx !== 0 && (mergedBlock.children.push(...block.children), mergedBlock.markDefs.push(...block.markDefs ?? []));
892
- return mergedBlock._type = "block", mergedBlock;
893
- }
894
- const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
1025
+ ], fallbackBlockDeserializers = [
1026
+ markDefRule((el) => el.hasAttribute(BUILTIN_ATTRIBUTE)),
1027
+ decoratorRule((el) => DECORATOR_ELEMENTS[el.tagName.toLowerCase()]),
1028
+ // a line break inside a mark (`<em>one<br>two</em>`) keeps that mark; one
1029
+ // between marks (`<em>one</em><br><em>two</em>`) stays unmarked
1030
+ {
1031
+ deserialize(node) {
1032
+ const parent = node.parentElement;
1033
+ if (!(node.nodeName !== "BR" || !parent || !MARK_ELEMENTS.has(parent.tagName.toLowerCase())))
1034
+ return {
1035
+ _type: "span",
1036
+ marks: [],
1037
+ text: MARKED_LINE_BREAK
1038
+ };
1039
+ }
1040
+ }
1041
+ ], defaultBlockDeserializers = [
1042
+ ...customBlockDeserializers,
1043
+ ...fallbackBlockDeserializers
1044
+ ], deserializeString = (element) => {
1045
+ const tagName = element.tagName?.toLowerCase();
1046
+ if (tagName === "pre" && element.hasAttribute(STRING_ATTRIBUTE)) {
1047
+ const text = (element.textContent ?? "").replace(/^\n+/, ""), leadingNewlines = Number.parseInt(
1048
+ element.getAttribute(LEADING_NEWLINES_ATTRIBUTE) ?? "",
1049
+ 10
1050
+ ) || 0;
1051
+ return `
1052
+ `.repeat(Math.max(leadingNewlines, 0)) + text;
1053
+ }
1054
+ if (tagName === "span")
1055
+ return preprocess(element.innerHTML);
1056
+ }, deserializeArray = (arrayHTML, deserializers = customDeserializers, blockDeserializers = defaultBlockDeserializers) => {
895
1057
  const output = [];
896
1058
  return Array.from(arrayHTML.children).forEach((child) => {
897
1059
  let deserializedObject;
898
1060
  try {
899
- if (child.tagName?.toLowerCase() === "span")
900
- deserializedObject = preprocess(child.innerHTML);
1061
+ const stringValue = deserializeString(child);
1062
+ if (stringValue !== void 0)
1063
+ deserializedObject = stringValue;
901
1064
  else if (child.className || child.getAttribute("data-type") === "object")
902
1065
  deserializedObject = deserializeObject(
903
1066
  child,
@@ -908,8 +1071,10 @@ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockD
908
1071
  const blocks = htmlToBlocks(child.outerHTML, blockContentType, {
909
1072
  rules: blockDeserializers
910
1073
  });
911
- deserializedObject = mergeBlocks(
912
- blocks
1074
+ deserializedObject = restoreMarkedLineBreaks(
1075
+ restoreInlineObjectKeys(
1076
+ mergeBlocks(blocks)
1077
+ )
913
1078
  ), deserializedObject._key = child.id;
914
1079
  }
915
1080
  } catch (e) {
@@ -919,14 +1084,15 @@ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockD
919
1084
  }
920
1085
  output.push(deserializedObject);
921
1086
  }), output;
922
- }, deserializeObject = (objectHTML, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
1087
+ }, deserializeObject = (objectHTML, deserializers = customDeserializers, blockDeserializers = defaultBlockDeserializers) => {
923
1088
  const deserialize = deserializers.types?.[objectHTML.className];
924
1089
  if (deserialize)
925
1090
  return deserialize(objectHTML);
926
1091
  const output = {};
927
1092
  return objectHTML.className && (output._type = objectHTML.className), Array.from(objectHTML.children).forEach((child) => {
928
- if (child.tagName?.toLowerCase() === "span")
929
- output[child.className] = preprocess(child.innerHTML);
1093
+ const stringValue = deserializeString(child);
1094
+ if (stringValue !== void 0)
1095
+ output[child.className] = stringValue;
930
1096
  else if (child.getAttribute("data-level") === "field") {
931
1097
  const deserialized = deserializeHTML(
932
1098
  child.outerHTML,
@@ -951,7 +1117,7 @@ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockD
951
1117
  return deserialize ? output = deserialize(HTMLnode) : HTMLnode.getAttribute("data-type") === "object" ? output = deserializeObject(HTMLnode, deserializers, blockDeserializers) : HTMLnode.getAttribute("data-type") === "array" ? output = deserializeArray(HTMLnode, deserializers, blockDeserializers) : (output = {}, console.debug(
952
1118
  `Tried to deserialize block ${HTMLnode.outerHTML} but failed to identify it!`
953
1119
  )), output;
954
- }, deserializeDocument$1 = (serializedDoc, deserializers = customDeserializers, blockDeserializers = customBlockDeserializers) => {
1120
+ }, deserializeDocument$1 = (serializedDoc, deserializers = customDeserializers, blockDeserializers = defaultBlockDeserializers) => {
955
1121
  const metadata = {}, head = new DOMParser().parseFromString(serializedDoc, "text/html").head;
956
1122
  return Array.from(head.children).forEach((metaTag) => {
957
1123
  const validTags = ["_id", "_rev", "_type"], metaName = metaTag.getAttribute("name");
@@ -1059,7 +1225,27 @@ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockD
1059
1225
  if (typeDef.fields) return typeDef.fields;
1060
1226
  current = typeof typeDef.type == "string" ? typeDef.type : void 0;
1061
1227
  }
1062
- }, serializeObject = (obj, stopTypes, serializers, declaredFields) => {
1228
+ }, isTextType = (typeName) => {
1229
+ const seen = /* @__PURE__ */ new Set();
1230
+ let current = typeName;
1231
+ for (; current && !seen.has(current); ) {
1232
+ if (current === "text") return !0;
1233
+ seen.add(current);
1234
+ const typeDef = getSchema(current);
1235
+ current = typeof typeDef?.type == "string" ? typeDef.type : void 0;
1236
+ }
1237
+ return !1;
1238
+ }, resolveArrayMembers = (fieldDef) => {
1239
+ if (fieldDef?.of) return fieldDef.of;
1240
+ const seen = /* @__PURE__ */ new Set();
1241
+ let current = fieldDef?.type;
1242
+ for (; current && !seen.has(current); ) {
1243
+ seen.add(current);
1244
+ const typeDef = getSchema(current);
1245
+ if (typeDef?.of) return typeDef.of;
1246
+ current = typeof typeDef?.type == "string" ? typeDef.type : void 0;
1247
+ }
1248
+ }, serializeObject = (obj, stopTypes, serializers, declaredFields, isDocument = !1) => {
1063
1249
  if (stopTypes.includes(obj._type) || isTypeExcludedByOptions(obj._type, getSchema))
1064
1250
  return "";
1065
1251
  const typeSerializers = isRecord$1(serializers.types) ? serializers.types : {};
@@ -1091,7 +1277,11 @@ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockD
1091
1277
  (field) => field.name === fieldName
1092
1278
  );
1093
1279
  if (typeof value == "string")
1094
- new RegExp(/<("[^"]*"|'[^']*'|[^'">])*>/).test(value) ? htmlField = value : htmlField = `<span class="${fieldName}">${value}</span>`;
1280
+ htmlField = serializeString(
1281
+ value,
1282
+ fieldName,
1283
+ isTextType(fieldDef?.type)
1284
+ );
1095
1285
  else if (Array.isArray(value))
1096
1286
  htmlField = serializeArray(
1097
1287
  value,
@@ -1101,12 +1291,12 @@ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockD
1101
1291
  ...serializers,
1102
1292
  types: { ...typeSerializers }
1103
1293
  },
1104
- fieldDef?.of
1294
+ resolveArrayMembers(fieldDef)
1105
1295
  );
1106
1296
  else if (isRecord$1(value)) {
1107
1297
  const embeddedObject = value, embeddedFields = resolveSchemaFields(embeddedObject._type);
1108
1298
  let toTranslate = embeddedObject;
1109
- embeddedFields && (toTranslate = fieldFilter(
1299
+ embeddedFields && !isDocument && (toTranslate = fieldFilter(
1110
1300
  toTranslate,
1111
1301
  embeddedFields,
1112
1302
  stopTypes,
@@ -1150,7 +1340,7 @@ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockD
1150
1340
  return serializedBlock;
1151
1341
  }, serializeArray = (fieldContent, fieldName, stopTypes, serializers, memberDefs) => {
1152
1342
  const validBlocks = fieldContent.filter(
1153
- (block) => typeof block == "string" || typeof block._type != "string" || !stopTypes.includes(block._type)
1343
+ (block) => typeof block == "string" || isRecord$1(block) && (typeof block._type != "string" || !stopTypes.includes(block._type))
1154
1344
  ), declaredMemberFields = (blockType) => {
1155
1345
  if (!memberDefs?.length) return;
1156
1346
  const byName = memberDefs.find(
@@ -1160,7 +1350,11 @@ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockD
1160
1350
  if (!blockType && memberDefs.length === 1) return memberDefs[0].fields;
1161
1351
  }, output = validBlocks.map((block) => {
1162
1352
  if (typeof block == "string")
1163
- return `<span>${block}</span>`;
1353
+ return serializeString(
1354
+ block,
1355
+ void 0,
1356
+ memberDefs?.some((def) => isTextType(def.type))
1357
+ );
1164
1358
  const blockType = typeof block._type == "string" ? block._type : void 0, memberFields = resolveSchemaFields(blockType), filtered = memberFields ? fieldFilter(block, memberFields, stopTypes, getSchema) : block;
1165
1359
  return serializeObject(
1166
1360
  filtered,
@@ -1181,37 +1375,13 @@ const deserializeArray = (arrayHTML, deserializers = customDeserializers, blockD
1181
1375
  stopTypes,
1182
1376
  getSchema
1183
1377
  );
1184
- const serializedFields = {};
1185
- for (const key in filteredObj) {
1186
- if (filteredObj.hasOwnProperty(key) === !1) continue;
1187
- const value = filteredObj[key], fieldDef = schema?.fields?.find((field) => field.name === key);
1188
- if (typeof value == "string")
1189
- serializedFields[key] = value;
1190
- else if (Array.isArray(value))
1191
- serializedFields[key] = serializeArray(
1192
- value.filter(
1193
- (item) => typeof item == "string" || isRecord$1(item)
1194
- ),
1195
- key,
1196
- stopTypes,
1197
- serializers,
1198
- fieldDef?.of
1199
- );
1200
- else if (value && isRecord$1(value) && !stopTypes.find((stopType) => stopType == value?._type)) {
1201
- const serialized = serializeObject(
1202
- value,
1203
- stopTypes,
1204
- serializers,
1205
- fieldDef?.fields
1206
- );
1207
- serializedFields[key] = `<div class="${key}" data-level='field'>${serialized}</div>`;
1208
- }
1209
- }
1210
1378
  const rawHTMLBody = document.createElement("body");
1211
1379
  rawHTMLBody.innerHTML = serializeObject(
1212
- serializedFields,
1380
+ filteredObj,
1213
1381
  stopTypes,
1214
- serializers
1382
+ serializers,
1383
+ void 0,
1384
+ !0
1215
1385
  );
1216
1386
  const rawHTMLHead = document.createElement("head");
1217
1387
  ["_id", "_type", "_rev"].forEach((field) => {
@@ -1255,7 +1425,8 @@ function deserializeDocument(document2) {
1255
1425
  pluginConfig.getAdditionalDeserializers()
1256
1426
  ), blockDeserializers = [
1257
1427
  ...customBlockDeserializers,
1258
- ...pluginConfig.getAdditionalBlockDeserializers()
1428
+ ...pluginConfig.getAdditionalBlockDeserializers(),
1429
+ ...fallbackBlockDeserializers
1259
1430
  ];
1260
1431
  return BaseDocumentDeserializer.deserializeDocument(
1261
1432
  document2,
@@ -2746,7 +2917,7 @@ const useTranslations = () => {
2746
2917
  ] }) })
2747
2918
  }
2748
2919
  ) : null;
2749
- var version = "4.0.25";
2920
+ var version = "4.0.27";
2750
2921
  function buildDebugInfo({
2751
2922
  secrets,
2752
2923
  preferences,