@multiplatform.one/rich-text 7.7.6 → 7.8.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/dist/cjs/RichEditor.native.js +18 -7
- package/dist/cjs/RichEditor.native.js.map +1 -1
- package/dist/cjs/RichTextEditor/index.cjs +2 -0
- package/dist/cjs/RichTextEditor/index.native.js +2 -0
- package/dist/cjs/RichTextEditor/index.native.js.map +1 -1
- package/dist/cjs/editor/TentapEditor.native.js +29 -39
- package/dist/cjs/editor/TentapEditor.native.js.map +1 -1
- package/dist/cjs/editor/TiptapEditor.cjs +6 -2
- package/dist/cjs/editor/TiptapEditor.native.js +8 -4
- package/dist/cjs/editor/TiptapEditor.native.js.map +1 -1
- package/dist/cjs/editor/blockAnchor.cjs +3 -2
- package/dist/cjs/editor/blockAnchor.native.js +4 -2
- package/dist/cjs/editor/blockAnchor.native.js.map +1 -1
- package/dist/cjs/editor/nativeEditorCSS.cjs +30 -0
- package/dist/cjs/editor/nativeEditorCSS.native.js +33 -0
- package/dist/cjs/editor/nativeEditorCSS.native.js.map +1 -0
- package/dist/cjs/toolbar/HeadingPicker.cjs +7 -0
- package/dist/cjs/toolbar/HeadingPicker.native.js +13 -17
- package/dist/cjs/toolbar/HeadingPicker.native.js.map +1 -1
- package/dist/cjs/toolbar/ListPicker.cjs +7 -0
- package/dist/cjs/toolbar/ListPicker.native.js +12 -11
- package/dist/cjs/toolbar/ListPicker.native.js.map +1 -1
- package/dist/cjs/toolbar/MoreDropdown.cjs +7 -0
- package/dist/cjs/toolbar/MoreDropdown.native.js +12 -11
- package/dist/cjs/toolbar/MoreDropdown.native.js.map +1 -1
- package/dist/esm/RichEditor.native.js +19 -8
- package/dist/esm/RichEditor.native.js.map +1 -1
- package/dist/esm/RichTextEditor/index.mjs +2 -0
- package/dist/esm/RichTextEditor/index.mjs.map +1 -1
- package/dist/esm/RichTextEditor/index.native.js +2 -0
- package/dist/esm/RichTextEditor/index.native.js.map +1 -1
- package/dist/esm/editor/TentapEditor.native.js +29 -39
- package/dist/esm/editor/TentapEditor.native.js.map +1 -1
- package/dist/esm/editor/TiptapEditor.mjs +6 -2
- package/dist/esm/editor/TiptapEditor.mjs.map +1 -1
- package/dist/esm/editor/TiptapEditor.native.js +8 -4
- package/dist/esm/editor/TiptapEditor.native.js.map +1 -1
- package/dist/esm/editor/blockAnchor.mjs +3 -2
- package/dist/esm/editor/blockAnchor.mjs.map +1 -1
- package/dist/esm/editor/blockAnchor.native.js +4 -2
- package/dist/esm/editor/blockAnchor.native.js.map +1 -1
- package/dist/esm/editor/nativeEditorCSS.mjs +5 -0
- package/dist/esm/editor/nativeEditorCSS.mjs.map +1 -0
- package/dist/esm/editor/nativeEditorCSS.native.js +5 -0
- package/dist/esm/editor/nativeEditorCSS.native.js.map +1 -0
- package/dist/esm/toolbar/HeadingPicker.mjs +7 -0
- package/dist/esm/toolbar/HeadingPicker.mjs.map +1 -1
- package/dist/esm/toolbar/HeadingPicker.native.js +13 -17
- package/dist/esm/toolbar/HeadingPicker.native.js.map +1 -1
- package/dist/esm/toolbar/ListPicker.mjs +7 -0
- package/dist/esm/toolbar/ListPicker.mjs.map +1 -1
- package/dist/esm/toolbar/ListPicker.native.js +12 -11
- package/dist/esm/toolbar/ListPicker.native.js.map +1 -1
- package/dist/esm/toolbar/MoreDropdown.mjs +7 -0
- package/dist/esm/toolbar/MoreDropdown.mjs.map +1 -1
- package/dist/esm/toolbar/MoreDropdown.native.js +12 -11
- package/dist/esm/toolbar/MoreDropdown.native.js.map +1 -1
- package/dist/jsx/RichEditor.native.js +18 -7
- package/dist/jsx/RichEditor.native.js.map +1 -1
- package/dist/jsx/RichTextEditor/index.mjs +2 -0
- package/dist/jsx/RichTextEditor/index.mjs.map +1 -1
- package/dist/jsx/RichTextEditor/index.native.js +2 -0
- package/dist/jsx/RichTextEditor/index.native.js.map +1 -1
- package/dist/jsx/editor/TentapEditor.native.js +29 -39
- package/dist/jsx/editor/TentapEditor.native.js.map +1 -1
- package/dist/jsx/editor/TiptapEditor.mjs +6 -2
- package/dist/jsx/editor/TiptapEditor.mjs.map +1 -1
- package/dist/jsx/editor/TiptapEditor.native.js +8 -4
- package/dist/jsx/editor/TiptapEditor.native.js.map +1 -1
- package/dist/jsx/editor/blockAnchor.mjs +3 -2
- package/dist/jsx/editor/blockAnchor.mjs.map +1 -1
- package/dist/jsx/editor/blockAnchor.native.js +4 -2
- package/dist/jsx/editor/blockAnchor.native.js.map +1 -1
- package/dist/jsx/editor/nativeEditorCSS.mjs +5 -0
- package/dist/jsx/editor/nativeEditorCSS.mjs.map +1 -0
- package/dist/jsx/editor/nativeEditorCSS.native.js +33 -0
- package/dist/jsx/editor/nativeEditorCSS.native.js.map +1 -0
- package/dist/jsx/toolbar/HeadingPicker.mjs +7 -0
- package/dist/jsx/toolbar/HeadingPicker.mjs.map +1 -1
- package/dist/jsx/toolbar/HeadingPicker.native.js +13 -17
- package/dist/jsx/toolbar/HeadingPicker.native.js.map +1 -1
- package/dist/jsx/toolbar/ListPicker.mjs +7 -0
- package/dist/jsx/toolbar/ListPicker.mjs.map +1 -1
- package/dist/jsx/toolbar/ListPicker.native.js +12 -11
- package/dist/jsx/toolbar/ListPicker.native.js.map +1 -1
- package/dist/jsx/toolbar/MoreDropdown.mjs +7 -0
- package/dist/jsx/toolbar/MoreDropdown.mjs.map +1 -1
- package/dist/jsx/toolbar/MoreDropdown.native.js +12 -11
- package/dist/jsx/toolbar/MoreDropdown.native.js.map +1 -1
- package/package.json +10 -8
- package/src/RichEditor.native.spec.tsx +158 -0
- package/src/RichEditor.native.tsx +28 -7
- package/src/RichTextEditor/index.tsx +4 -2
- package/src/RichTextEditor/richTextEditor.spec.tsx +40 -6
- package/src/editor/SlashCommands.spec.tsx +3 -1
- package/src/editor/TentapEditor.native.spec.tsx +120 -0
- package/src/editor/TentapEditor.native.tsx +40 -54
- package/src/editor/TiptapEditor.tsx +2 -2
- package/src/editor/blockAnchor.ts +3 -2
- package/src/editor/nativeEditorCSS.spec.ts +75 -0
- package/src/editor/nativeEditorCSS.ts +4 -0
- package/src/integration.spec.tsx +0 -23
- package/src/richEditor.spec.tsx +0 -23
- package/src/toolbar/HeadingPicker.tsx +14 -15
- package/src/toolbar/ListPicker.tsx +13 -9
- package/src/toolbar/MoreDropdown.tsx +13 -9
- package/src/toolbar/bubbleToolbar.spec.tsx +2 -1
- package/src/toolbar/fixedToolbarStories.spec.tsx +4 -2
- package/src/toolbar/statusBarStories.spec.tsx +4 -2
- package/src/toolbar/toolbarButtonStories.spec.tsx +4 -2
- package/src/toolbar/toolbarGroupStories.spec.tsx +4 -2
- package/types/RichEditor.native.d.ts.map +1 -1
- package/types/RichTextEditor/index.d.ts +1 -1
- package/types/RichTextEditor/index.d.ts.map +1 -1
- package/types/editor/TentapEditor.native.d.ts.map +1 -1
- package/types/editor/blockAnchor.d.ts.map +1 -1
- package/types/editor/nativeEditorCSS.d.ts +3 -0
- package/types/editor/nativeEditorCSS.d.ts.map +1 -0
- package/types/toolbar/HeadingPicker.d.ts.map +1 -1
- package/types/toolbar/ListPicker.d.ts.map +1 -1
- package/types/toolbar/MoreDropdown.d.ts.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","MoreDropdown_exports","MoreDropdown","module","exports","import_jsx_runtime","require","import_react","import_react2","import_components","import_capabilities","import_ToolbarButton","import_icons","import_shortcuts","import_t","INLINE_ITEMS","label","SHORTCUTS","subscript","command","icon","SubscriptIcon","isActive","s","isSubscript","label2","superscript","SuperscriptIcon","isActive2","isSuperscript","ALIGN_ITEMS","label3","alignLeft","AlignLeftIcon","isActive3","textAlign","label4","alignCenter","AlignCenterIcon","isActive4","label5","alignRight","AlignRightIcon","isActive5","label6","alignJustify","AlignJustifyIcon","isActive6","BLOCK_ITEMS","label7","horizontalRule","HorizontalRuleIcon","label8","clearFormatting","ClearFormattingIcon","MoreIcon","jsx","DotsThreeIcon","size","weight","ItemSection","param","items","state","onSelect","Fragment","children","map","item","_item_isActive","Icon","label9","DropdownMenu","Item","closeOnSelect","backgroundColor","onSelect1","payload"],"sources":["../../../src/toolbar/MoreDropdown.tsx"],"sourcesContent":[null],"mappings":"AAMA,YAAS;;AACT,IAAAA,SAAS,GAAAC,MAAQ,CAAAC,cAAgB;AACjC,IAAAC,gBAAS,GAAAF,MAAoB,CAAAG,wBAAA;AAE7B,IAAAC,iBAAS,GAAAJ,MAAA,CAAAK,mBAA+B;AACxC,IAAAC,YAAS,GAAAN,MAAA,CAAAO,SAAqB,CAAAC,cAAA;AAC9B,IAAAC,QAAA,GAAAA,CAAAC,MAAA,EAAAC,GAAA;EACE,SAAAC,IAAA,IAAAD,GAAA,EACAZ,SAAA,CAAAW,MAAA,EAAAE,IAAA;IAAAC,GAAA,EAAAF,GAAA,CAAAC,IAAA;IAAAE,UAAA;EAAA;AAAA;AACA,IACAC,WAAA,GAAAA,CAAAC,EAAA,EAAAC,IAAA,EAAAC,MAAA,EAAAC,IAAA;EACA,IAAAF,IAAA,WAAAA,IAAA,wBAAAA,IAAA;IACA,SAAAG,GAAA,IAAAhB,iBAAA,CAAAa,IAAA,GACA,KAAAX,YAAA,CAAAe,IAAA,CAAAL,EAAA,EAAAI,GAAA,KAAAA,GAAA,KAAAF,MAAA,EACAnB,SAAA,CAAAiB,EAAA,EAAAI,GAAA;MAAAP,GAAA,EAAAA,CAAA,KAAAI,IAAA,CAAAG,GAAA;MAAAN,UAAA,IAAAK,IAAA,GAAAjB,gBAAA,CAAAe,IAAA,EAAAG,GAAA,MAAAD,IAAA,CAAAL;IAAA;EAAA;EAEF,OAASE,EAAA;AACT;AA4ES,IAAAM,YAaL,GAbKC,GAAA,
|
|
1
|
+
{"version":3,"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","MoreDropdown_exports","MoreDropdown","module","exports","import_jsx_runtime","require","import_react","import_react2","import_components","import_capabilities","import_ToolbarButton","import_icons","import_shortcuts","import_t","INLINE_ITEMS","label","SHORTCUTS","subscript","command","icon","SubscriptIcon","isActive","s","isSubscript","label2","superscript","SuperscriptIcon","isActive2","isSuperscript","ALIGN_ITEMS","label3","alignLeft","AlignLeftIcon","isActive3","textAlign","label4","alignCenter","AlignCenterIcon","isActive4","label5","alignRight","AlignRightIcon","isActive5","label6","alignJustify","AlignJustifyIcon","isActive6","BLOCK_ITEMS","label7","horizontalRule","HorizontalRuleIcon","label8","clearFormatting","ClearFormattingIcon","MoreIcon","jsx","DotsThreeIcon","size","weight","ItemSection","param","items","state","onSelect","Fragment","children","map","item","_item_isActive","Icon","label9","DropdownMenu","Item","closeOnSelect","backgroundColor","onSelect1","payload","editorRef","capabilities","disabled","open","setOpen","useState","selectedRef","useRef","handleSelect","handleSelect2","_editorRef_current","current","executeCommand","inlineItems","filter"],"sources":["../../../src/toolbar/MoreDropdown.tsx"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiplatform.one/rich-text",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.8.0",
|
|
4
4
|
"description": "Cross-platform rich text editor with markdown support and syntax highlighting",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cross-platform",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"@phosphor-icons/react": "^2.1.10",
|
|
61
61
|
"@tamagui/linear-gradient": "2.7.6",
|
|
62
62
|
"@tanstack/react-form": "^1.29.1",
|
|
63
|
+
"@tiptap/core": "^3.30.0",
|
|
63
64
|
"@tiptap/extension-character-count": "^3.30.0",
|
|
64
65
|
"@tiptap/extension-code-block-lowlight": "^3.30.0",
|
|
65
66
|
"@tiptap/extension-color": "^3.30.0",
|
|
@@ -93,13 +94,14 @@
|
|
|
93
94
|
"marked": "^17.0.6",
|
|
94
95
|
"tamagui": "2.7.6",
|
|
95
96
|
"turndown": "^7.2.4",
|
|
96
|
-
"@multiplatform.one/components": "7.
|
|
97
|
-
"@multiplatform.one/
|
|
98
|
-
"@multiplatform.one/
|
|
99
|
-
"@multiplatform.one/
|
|
97
|
+
"@multiplatform.one/components": "7.8.0",
|
|
98
|
+
"@multiplatform.one/theme": "7.8.0",
|
|
99
|
+
"@multiplatform.one/forms": "7.8.0",
|
|
100
|
+
"@multiplatform.one/markdown": "7.8.0"
|
|
100
101
|
},
|
|
101
102
|
"devDependencies": {
|
|
102
103
|
"@tamagui/build": "2.7.6",
|
|
104
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
103
105
|
"@testing-library/react": "^16.3.2",
|
|
104
106
|
"@types/katex": "^0.16.7",
|
|
105
107
|
"@vitejs/plugin-react": "^6.0.1",
|
|
@@ -108,9 +110,9 @@
|
|
|
108
110
|
"react": "19.2.5",
|
|
109
111
|
"react-dom": "19.2.5",
|
|
110
112
|
"vitest": "^4.1.5",
|
|
111
|
-
"@multiplatform.one/
|
|
112
|
-
"@multiplatform.one/storybook": "7.
|
|
113
|
-
"@multiplatform.one/
|
|
113
|
+
"@multiplatform.one/test-utils": "7.8.0",
|
|
114
|
+
"@multiplatform.one/storybook": "7.8.0",
|
|
115
|
+
"@multiplatform.one/config": "7.8.0"
|
|
114
116
|
},
|
|
115
117
|
"peerDependencies": {
|
|
116
118
|
"i18next": "^25.0.0",
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { runInNewContext } from "node:vm";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import * as jsxRuntime from "react/jsx-runtime";
|
|
7
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
9
|
+
import { fontCategoryStacks } from "@multiplatform.one/theme";
|
|
10
|
+
import { JsxEmit, ModuleKind, transpileModule } from "typescript";
|
|
11
|
+
|
|
12
|
+
let bodyFamily: string | undefined = "Inter";
|
|
13
|
+
let headingFamily: string | undefined = "Inter";
|
|
14
|
+
let bodyToken = "$body";
|
|
15
|
+
let headingToken = "$heading";
|
|
16
|
+
const Container = ({ children }: any) => <div>{children}</div>;
|
|
17
|
+
const Pressable = ({ children, onPress, accessibilityLabel }: any) => (
|
|
18
|
+
<button aria-label={accessibilityLabel} onClick={onPress}>
|
|
19
|
+
{children}
|
|
20
|
+
</button>
|
|
21
|
+
);
|
|
22
|
+
const Tentap = React.forwardRef((_props: any, _ref) => (
|
|
23
|
+
<div
|
|
24
|
+
data-testid="tentap"
|
|
25
|
+
data-body={_props.bodyFontFamily}
|
|
26
|
+
data-heading={_props.headingFontFamily}
|
|
27
|
+
/>
|
|
28
|
+
));
|
|
29
|
+
|
|
30
|
+
// RichEditor deliberately uses a guarded native require for its optional
|
|
31
|
+
// WebView backend. Execute the real wrapper with that boundary substituted;
|
|
32
|
+
// importing through the web bundler would turn it into the unavailable branch.
|
|
33
|
+
const nativeExports: Record<string, any> = {};
|
|
34
|
+
const modules: Record<string, any> = {
|
|
35
|
+
react: React,
|
|
36
|
+
"react/jsx-runtime": jsxRuntime,
|
|
37
|
+
"react-native": {
|
|
38
|
+
KeyboardAvoidingView: Container,
|
|
39
|
+
SafeAreaView: Container,
|
|
40
|
+
Pressable,
|
|
41
|
+
Modal: ({ visible, children }: any) => (visible ? <div>{children}</div> : null),
|
|
42
|
+
Platform: { OS: "ios" },
|
|
43
|
+
StyleSheet: { create: (styles: any) => styles },
|
|
44
|
+
},
|
|
45
|
+
tamagui: {
|
|
46
|
+
Text: Container,
|
|
47
|
+
View: Container,
|
|
48
|
+
XStack: Container,
|
|
49
|
+
YStack: Container,
|
|
50
|
+
getConfig: () => ({
|
|
51
|
+
fonts: {
|
|
52
|
+
[bodyToken.slice(1)]: { family: bodyFamily },
|
|
53
|
+
[headingToken.slice(1)]: { family: headingFamily },
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
withStaticProperties: Object.assign,
|
|
57
|
+
},
|
|
58
|
+
"@multiplatform.one/theme": {
|
|
59
|
+
fontCategoryStacks,
|
|
60
|
+
useGlyphColor: () => "black",
|
|
61
|
+
sizeRecipeForToken: () => ({ iconSize: 18 }),
|
|
62
|
+
useResolvedKnobs: () => ({
|
|
63
|
+
knobProps: {
|
|
64
|
+
body: { fontFamily: bodyToken },
|
|
65
|
+
heading: { fontFamily: headingToken },
|
|
66
|
+
borderRadius: {},
|
|
67
|
+
panelPadding: { padding: 12 },
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
},
|
|
71
|
+
"@multiplatform.one/components": { Pencil: () => null },
|
|
72
|
+
"@multiplatform.one/forms": { Button: Pressable, clampRadiusForLargeComponent: () => 4 },
|
|
73
|
+
"./types": { defaultEditorState: {} },
|
|
74
|
+
"./context": { RichEditorContext: { Provider: Container } },
|
|
75
|
+
"./styled": { EditorFrame: Container },
|
|
76
|
+
"./toolbar/FixedToolbar": { FixedToolbar: () => null },
|
|
77
|
+
"./preview": { parseHtmlPreview: () => ({ blocks: [], truncated: false }) },
|
|
78
|
+
"./editor/markdown": { htmlToMarkdown: (v: string) => v, markdownToHtml: (v: string) => v },
|
|
79
|
+
"./shared/t": { t: (v: string) => v },
|
|
80
|
+
"./editor/TentapEditor.native": { TentapEditor: Tentap },
|
|
81
|
+
};
|
|
82
|
+
runInNewContext(
|
|
83
|
+
transpileModule(
|
|
84
|
+
readFileSync(
|
|
85
|
+
path.join(path.dirname(fileURLToPath(import.meta.url)), "RichEditor.native.tsx"),
|
|
86
|
+
"utf8",
|
|
87
|
+
),
|
|
88
|
+
{
|
|
89
|
+
compilerOptions: { module: ModuleKind.CommonJS, jsx: JsxEmit.ReactJSX },
|
|
90
|
+
},
|
|
91
|
+
).outputText,
|
|
92
|
+
{
|
|
93
|
+
exports: nativeExports,
|
|
94
|
+
require: (name: string) => {
|
|
95
|
+
if (!(name in modules)) throw new Error(`Unexpected native wrapper import ${name}`);
|
|
96
|
+
return modules[name];
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
const RichEditor = nativeExports.RichEditor;
|
|
101
|
+
afterEach(cleanup);
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
bodyFamily = "Inter";
|
|
104
|
+
headingFamily = "Inter";
|
|
105
|
+
bodyToken = "$body";
|
|
106
|
+
headingToken = "$heading";
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("native RichEditor font propagation", () => {
|
|
110
|
+
it.each([false, true])(
|
|
111
|
+
"passes resolved families to the editing document, inline=%s",
|
|
112
|
+
(inlineEditing) => {
|
|
113
|
+
render(<RichEditor inlineEditing={inlineEditing} initialContent="<p>Saved note</p>" />);
|
|
114
|
+
if (!inlineEditing) fireEvent.click(screen.getByRole("button", { name: "Edit rich text" }));
|
|
115
|
+
const editor = screen.getByTestId("tentap");
|
|
116
|
+
expect(editor.getAttribute("data-body")).toBe(
|
|
117
|
+
`${bodyFamily}, ${fontCategoryStacks["sans-serif"]}`,
|
|
118
|
+
);
|
|
119
|
+
expect(editor.getAttribute("data-heading")).toBe(
|
|
120
|
+
`${headingFamily}, ${fontCategoryStacks["sans-serif"]}`,
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("native editor CSS font fallbacks", () => {
|
|
127
|
+
it("keeps configured families first and uses the selected category fallback", () => {
|
|
128
|
+
bodyFamily = "Custom Serif";
|
|
129
|
+
headingFamily = "Custom Mono";
|
|
130
|
+
bodyToken = "$serif";
|
|
131
|
+
headingToken = "$mono";
|
|
132
|
+
render(<RichEditor defaultFullscreen initialContent="<p>Saved note</p>" />);
|
|
133
|
+
const editor = screen.getByTestId("tentap");
|
|
134
|
+
expect(editor.getAttribute("data-body")).toBe(`${bodyFamily}, ${fontCategoryStacks.serif}`);
|
|
135
|
+
expect(editor.getAttribute("data-heading")).toBe(
|
|
136
|
+
`${headingFamily}, ${fontCategoryStacks.mono}`,
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("preserves explicit CSS stacks and intentional generic families", () => {
|
|
141
|
+
bodyFamily = "Georgia, serif";
|
|
142
|
+
headingFamily = "system-ui";
|
|
143
|
+
render(<RichEditor defaultFullscreen />);
|
|
144
|
+
const editor = screen.getByTestId("tentap");
|
|
145
|
+
expect(editor.getAttribute("data-body")).toBe(bodyFamily);
|
|
146
|
+
expect(editor.getAttribute("data-heading")).toBe(headingFamily);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("uses the canonical fallback when no configured font exists", () => {
|
|
150
|
+
bodyFamily = undefined;
|
|
151
|
+
headingFamily = undefined;
|
|
152
|
+
headingToken = "$mono";
|
|
153
|
+
render(<RichEditor defaultFullscreen />);
|
|
154
|
+
const editor = screen.getByTestId("tentap");
|
|
155
|
+
expect(editor.getAttribute("data-body")).toBe(fontCategoryStacks["sans-serif"]);
|
|
156
|
+
expect(editor.getAttribute("data-heading")).toBe(fontCategoryStacks.mono);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -34,7 +34,13 @@ import {
|
|
|
34
34
|
} from "react-native";
|
|
35
35
|
import type { SizeTokens } from "tamagui";
|
|
36
36
|
import { Text, View, XStack, YStack, getConfig, withStaticProperties } from "tamagui";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
fontCategoryStacks,
|
|
39
|
+
sizeRecipeForToken,
|
|
40
|
+
useGlyphColor,
|
|
41
|
+
useResolvedKnobs,
|
|
42
|
+
} from "@multiplatform.one/theme";
|
|
43
|
+
import { Pencil } from "@multiplatform.one/components";
|
|
38
44
|
import { Button, clampRadiusForLargeComponent } from "@multiplatform.one/forms";
|
|
39
45
|
import type { EditorState, RichEditorProps, RichEditorRef, ToolbarSection } from "./types";
|
|
40
46
|
import { defaultEditorState } from "./types";
|
|
@@ -265,6 +271,7 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
|
|
|
265
271
|
size,
|
|
266
272
|
compact,
|
|
267
273
|
});
|
|
274
|
+
const editGlyphColor = useGlyphColor("$color11");
|
|
268
275
|
|
|
269
276
|
// Same resolver as the web RichEditor: the knob names a registered font
|
|
270
277
|
// token ($body / $heading); the WebView needs its concrete CSS family
|
|
@@ -272,9 +279,22 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
|
|
|
272
279
|
const { bodyFontFamily, headingFontFamily } = useMemo(() => {
|
|
273
280
|
const fonts = getConfig()?.fonts;
|
|
274
281
|
const resolve = (token?: string) => {
|
|
275
|
-
|
|
276
|
-
const
|
|
277
|
-
|
|
282
|
+
const key = token?.replace(/^\$/, "") ?? "body";
|
|
283
|
+
const configured = fonts?.[key]?.family;
|
|
284
|
+
const family = typeof configured === "string" ? configured.trim() : undefined;
|
|
285
|
+
// RN's bare Inter name is not a font installed inside WKWebView.
|
|
286
|
+
// Preserve explicit CSS stacks; otherwise use the shared category fallback.
|
|
287
|
+
if (
|
|
288
|
+
family &&
|
|
289
|
+
/(?:^|,)\s*(?:serif|sans-serif|monospace|cursive|fantasy|system-ui|ui-serif|ui-sans-serif|ui-monospace|ui-rounded)\s*$/i.test(
|
|
290
|
+
family,
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
return family;
|
|
294
|
+
const fallback = Object.hasOwn(fontCategoryStacks, key)
|
|
295
|
+
? fontCategoryStacks[key as keyof typeof fontCategoryStacks]
|
|
296
|
+
: fontCategoryStacks["sans-serif"];
|
|
297
|
+
return family ? `${family}, ${fallback}` : fallback;
|
|
278
298
|
};
|
|
279
299
|
return {
|
|
280
300
|
bodyFontFamily: resolve(knobProps.body?.fontFamily),
|
|
@@ -381,6 +401,7 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
|
|
|
381
401
|
const cappedRadius = clampRadiusForLargeComponent(knobProps.borderRadius.borderRadius, 4);
|
|
382
402
|
const disabled = !editable;
|
|
383
403
|
const sizeToken = (knobProps.sizeToken ?? "$true") as any;
|
|
404
|
+
const editGlyphSize = sizeRecipeForToken(sizeToken, { touch: true }).iconSize;
|
|
384
405
|
|
|
385
406
|
const preview = useMemo(() => parseHtmlPreview(committed.html, 6), [committed.html]);
|
|
386
407
|
const previewEmpty = preview.blocks.length === 0;
|
|
@@ -484,9 +505,7 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
|
|
|
484
505
|
</YStack>
|
|
485
506
|
{!disabled && (
|
|
486
507
|
<View position="absolute" top="$2.5" right="$2.5">
|
|
487
|
-
<
|
|
488
|
-
{"✎"}
|
|
489
|
-
</Text>
|
|
508
|
+
<Pencil color={editGlyphColor} size={editGlyphSize} accessible={false} />
|
|
490
509
|
</View>
|
|
491
510
|
)}
|
|
492
511
|
</EditorFrame>
|
|
@@ -538,6 +557,8 @@ const RichEditorImpl = forwardRef<RichEditorRef, RichEditorCombinedProps>(
|
|
|
538
557
|
{TentapEditor ? (
|
|
539
558
|
<TentapEditor
|
|
540
559
|
ref={internalRef}
|
|
560
|
+
bodyFontFamily={bodyFontFamily}
|
|
561
|
+
headingFontFamily={headingFontFamily}
|
|
541
562
|
{...editorProps}
|
|
542
563
|
initialContent={committed.html}
|
|
543
564
|
initialJSON={committed.json ?? undefined}
|
|
@@ -56,9 +56,11 @@ function applyExternalContent(
|
|
|
56
56
|
) {
|
|
57
57
|
if (!editor) return;
|
|
58
58
|
if (capabilities === "markdown") {
|
|
59
|
+
if (isWeb && editor.getMarkdown() === next) return;
|
|
59
60
|
editor.setMarkdown(next);
|
|
60
61
|
return;
|
|
61
62
|
}
|
|
63
|
+
if (isWeb && editor.getHTML() === next) return;
|
|
62
64
|
editor.setHTML(next);
|
|
63
65
|
}
|
|
64
66
|
|
|
@@ -135,12 +137,12 @@ export type RichTextEditorProps<
|
|
|
135
137
|
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
|
|
136
138
|
> = Omit<
|
|
137
139
|
FormFieldProps<TParentData, TName, TFieldValidator, TFormValidator, TData>,
|
|
138
|
-
"children" | "field"
|
|
140
|
+
"children" | "field" | "onChange"
|
|
139
141
|
> &
|
|
140
142
|
Partial<
|
|
141
143
|
Omit<
|
|
142
144
|
FieldComponentProps<TParentData, TName, TFieldValidator, TFormValidator, TData>,
|
|
143
|
-
"children"
|
|
145
|
+
"children" | "onChange"
|
|
144
146
|
>
|
|
145
147
|
> & {
|
|
146
148
|
value?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef, useImperativeHandle } from "react";
|
|
1
|
+
import { forwardRef, useImperativeHandle, useRef, useState } from "react";
|
|
2
2
|
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
|
|
4
4
|
const editorSpies = vi.hoisted(() => ({
|
|
@@ -92,14 +92,21 @@ vi.mock("@multiplatform.one/forms", async () => {
|
|
|
92
92
|
|
|
93
93
|
vi.mock("../RichEditor", () => ({
|
|
94
94
|
RichEditor: forwardRef((props: any, ref) => {
|
|
95
|
+
const content = useRef(props.initialContent ?? "");
|
|
95
96
|
useImperativeHandle(ref, () => ({
|
|
96
|
-
setHTML:
|
|
97
|
-
|
|
97
|
+
setHTML: (next: string) => {
|
|
98
|
+
content.current = next;
|
|
99
|
+
editorSpies.setHTML(next);
|
|
100
|
+
},
|
|
101
|
+
setMarkdown: (next: string) => {
|
|
102
|
+
content.current = next;
|
|
103
|
+
editorSpies.setMarkdown(next);
|
|
104
|
+
},
|
|
98
105
|
focus: editorSpies.focus,
|
|
99
106
|
blur: vi.fn(),
|
|
100
|
-
getHTML: () =>
|
|
107
|
+
getHTML: () => content.current,
|
|
101
108
|
getJSON: () => null,
|
|
102
|
-
getMarkdown: () =>
|
|
109
|
+
getMarkdown: () => content.current,
|
|
103
110
|
getState: () => ({}),
|
|
104
111
|
executeCommand: vi.fn(),
|
|
105
112
|
}));
|
|
@@ -111,7 +118,18 @@ vi.mock("../RichEditor", () => ({
|
|
|
111
118
|
data-initial={props.initialContent}
|
|
112
119
|
data-bubble={props.showBubbleToolbar ? "true" : undefined}
|
|
113
120
|
data-sections={props.sections ? JSON.stringify(props.sections) : undefined}
|
|
114
|
-
|
|
121
|
+
>
|
|
122
|
+
<button
|
|
123
|
+
type="button"
|
|
124
|
+
data-testid="simulate-edit"
|
|
125
|
+
onClick={() => {
|
|
126
|
+
content.current = props.capabilities === "markdown" ? "edited" : "<p>edited</p>";
|
|
127
|
+
props.onChange(content.current);
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
edit
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
115
133
|
);
|
|
116
134
|
}),
|
|
117
135
|
}));
|
|
@@ -198,6 +216,22 @@ describe("RichTextEditor form-integrated field", () => {
|
|
|
198
216
|
expect(editorSpies.setMarkdown).toHaveBeenCalledWith("world");
|
|
199
217
|
});
|
|
200
218
|
|
|
219
|
+
it.each(["full", "markdown"] as const)(
|
|
220
|
+
"does not replace a %s document when its user edit is echoed",
|
|
221
|
+
async (capabilities) => {
|
|
222
|
+
editorSpies.setHTML.mockClear();
|
|
223
|
+
editorSpies.setMarkdown.mockClear();
|
|
224
|
+
function Controlled() {
|
|
225
|
+
const [value, setValue] = useState("");
|
|
226
|
+
return <RichTextEditor value={value} onChange={setValue} capabilities={capabilities} />;
|
|
227
|
+
}
|
|
228
|
+
const { getByTestId } = renderWithProviders(<Controlled />);
|
|
229
|
+
await act(async () => getByTestId("simulate-edit").click());
|
|
230
|
+
expect(editorSpies.setHTML).not.toHaveBeenCalled();
|
|
231
|
+
expect(editorSpies.setMarkdown).not.toHaveBeenCalled();
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
201
235
|
it("clears the editor when the form field value is reset", async () => {
|
|
202
236
|
editorSpies.setHTML.mockClear();
|
|
203
237
|
const { getByTestId } = renderWithProviders(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
|
-
const suggestion = vi.hoisted(() => ({
|
|
3
|
+
const suggestion = vi.hoisted(() => ({
|
|
4
|
+
fn: vi.fn((_options: Record<string, unknown>) => ({ props: {} })),
|
|
5
|
+
}));
|
|
4
6
|
|
|
5
7
|
vi.mock("@tiptap/suggestion", () => ({ default: suggestion.fn }));
|
|
6
8
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { act, cleanup, render } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { TentapEditor } from "./TentapEditor.native";
|
|
4
|
+
|
|
5
|
+
const state = vi.hoisted(() => ({
|
|
6
|
+
props: {} as any,
|
|
7
|
+
scheme: "light",
|
|
8
|
+
theme: { color12: { val: "black" } },
|
|
9
|
+
editor: {
|
|
10
|
+
injectCSS: vi.fn(),
|
|
11
|
+
setContent: vi.fn(),
|
|
12
|
+
_subscribeToEditorStateUpdate: vi.fn(() => vi.fn()),
|
|
13
|
+
getEditorState: vi.fn(() => ({})),
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("react-native", () => ({
|
|
18
|
+
StyleSheet: { create: (styles: any) => styles },
|
|
19
|
+
useColorScheme: () => "light",
|
|
20
|
+
}));
|
|
21
|
+
vi.mock("tamagui", () => ({
|
|
22
|
+
View: ({ children }: any) => <div>{children}</div>,
|
|
23
|
+
useTheme: () => state.theme,
|
|
24
|
+
useThemeName: () => state.scheme,
|
|
25
|
+
}));
|
|
26
|
+
vi.mock("../context", () => ({
|
|
27
|
+
RichEditorContext: { useStyledContext: () => ({ disabled: false }) },
|
|
28
|
+
}));
|
|
29
|
+
vi.mock("../shared/t", () => ({ t: (value: string) => value }));
|
|
30
|
+
vi.mock("@10play/tentap-editor", () => ({
|
|
31
|
+
RichText: (props: any) => {
|
|
32
|
+
state.props = props;
|
|
33
|
+
return <div data-testid="native-webview" />;
|
|
34
|
+
},
|
|
35
|
+
useEditorBridge: () => state.editor,
|
|
36
|
+
TenTapStartKit: [],
|
|
37
|
+
UnderlineBridge: {},
|
|
38
|
+
LinkBridge: {},
|
|
39
|
+
ColorBridge: {},
|
|
40
|
+
HighlightBridge: {},
|
|
41
|
+
ImageBridge: {},
|
|
42
|
+
TaskListBridge: {},
|
|
43
|
+
PlaceholderBridge: { configureExtension: () => ({}) },
|
|
44
|
+
darkEditorCss: "body { background: black; color: white; }",
|
|
45
|
+
darkEditorTheme: {},
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
vi.useFakeTimers();
|
|
50
|
+
vi.clearAllMocks();
|
|
51
|
+
state.scheme = "light";
|
|
52
|
+
state.theme = { color12: { val: "black" } };
|
|
53
|
+
});
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
cleanup();
|
|
56
|
+
vi.useRealTimers();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("Tentap native stylesheet lifecycle", () => {
|
|
60
|
+
it("waits for load, reapplies after the iOS replacement document, and updates live styling", () => {
|
|
61
|
+
const view = render(
|
|
62
|
+
<TentapEditor initialContent="<p>Saved note</p>" bodyFontFamily="system-ui" />,
|
|
63
|
+
);
|
|
64
|
+
act(() => {
|
|
65
|
+
vi.advanceTimersByTime(1000);
|
|
66
|
+
});
|
|
67
|
+
expect(state.editor.injectCSS).not.toHaveBeenCalled();
|
|
68
|
+
|
|
69
|
+
// Installed RichText calls its supplied props.onLoad after its own loaded
|
|
70
|
+
// state and first-iOS-load key change. That replacement fires onLoad again.
|
|
71
|
+
const originalLoad = state.props.onLoad;
|
|
72
|
+
act(() => originalLoad({ nativeEvent: { url: "about:blank" } }));
|
|
73
|
+
expect(state.editor.injectCSS).toHaveBeenCalledTimes(3);
|
|
74
|
+
act(() => state.props.onLoad({ nativeEvent: { url: "about:blank" } }));
|
|
75
|
+
expect(state.editor.injectCSS).toHaveBeenCalledTimes(6);
|
|
76
|
+
expect(state.editor.injectCSS).toHaveBeenLastCalledWith(
|
|
77
|
+
expect.stringContaining("font-family: system-ui"),
|
|
78
|
+
"multiplatform-tentap-content",
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
state.scheme = "dark";
|
|
82
|
+
state.theme = { color12: { val: "white" } };
|
|
83
|
+
view.rerender(
|
|
84
|
+
<TentapEditor initialContent="<p>Saved note</p>" bodyFontFamily="Georgia, serif" />,
|
|
85
|
+
);
|
|
86
|
+
expect(state.editor.injectCSS).toHaveBeenCalledWith(
|
|
87
|
+
expect.stringContaining("background: black"),
|
|
88
|
+
"multiplatform-tentap-dark",
|
|
89
|
+
);
|
|
90
|
+
expect(state.editor.injectCSS).toHaveBeenCalledWith(
|
|
91
|
+
expect.stringContaining("--color12: white"),
|
|
92
|
+
"multiplatform-tentap-palette",
|
|
93
|
+
);
|
|
94
|
+
expect(state.editor.injectCSS).toHaveBeenLastCalledWith(
|
|
95
|
+
expect.stringContaining("font-family: Georgia, serif"),
|
|
96
|
+
"multiplatform-tentap-content",
|
|
97
|
+
);
|
|
98
|
+
expect(state.editor.setContent).not.toHaveBeenCalled();
|
|
99
|
+
const calls = state.editor.injectCSS.mock.calls.length;
|
|
100
|
+
view.unmount();
|
|
101
|
+
act(() => {
|
|
102
|
+
vi.advanceTimersByTime(1000);
|
|
103
|
+
});
|
|
104
|
+
expect(state.editor.injectCSS).toHaveBeenCalledTimes(calls);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("uses the latest scheme when styling changes before the WebView loads", () => {
|
|
108
|
+
const view = render(<TentapEditor />);
|
|
109
|
+
state.scheme = "dark";
|
|
110
|
+
state.theme = { color12: { val: "white" } };
|
|
111
|
+
view.rerender(<TentapEditor />);
|
|
112
|
+
expect(state.editor.injectCSS).not.toHaveBeenCalled();
|
|
113
|
+
act(() => state.props.onLoad({ nativeEvent: { url: "about:blank" } }));
|
|
114
|
+
expect(state.editor.injectCSS).toHaveBeenCalledTimes(3);
|
|
115
|
+
expect(state.editor.injectCSS).toHaveBeenCalledWith(
|
|
116
|
+
expect.stringContaining("--color12: white"),
|
|
117
|
+
"multiplatform-tentap-palette",
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -43,6 +43,7 @@ import { PALETTE_THEME_TOKENS } from "../constants";
|
|
|
43
43
|
import { t } from "../shared/t";
|
|
44
44
|
import { getEditorStateFromBridge } from "./useEditorState";
|
|
45
45
|
import { getEditorCSS } from "./editorStyles";
|
|
46
|
+
import { escapeNativeEditorCSS } from "./nativeEditorCSS";
|
|
46
47
|
import { htmlToMarkdown, markdownToHtml } from "./markdown";
|
|
47
48
|
|
|
48
49
|
function useNativeEditorDarkMode(): boolean {
|
|
@@ -126,62 +127,43 @@ export const TentapEditor = forwardRef(
|
|
|
126
127
|
},
|
|
127
128
|
});
|
|
128
129
|
|
|
129
|
-
// 10Tap defaults the WebView to a white shell; darkEditorTheme fixes the RN WebView,
|
|
130
|
-
// and darkEditorCss aligns the inner ProseMirror document with dark mode.
|
|
131
|
-
useEffect(() => {
|
|
132
|
-
const id = setTimeout(() => {
|
|
133
|
-
if (isDarkMode) {
|
|
134
|
-
editor.injectCSS(darkEditorCss, "multiplatform-tentap-dark");
|
|
135
|
-
} else {
|
|
136
|
-
editor.injectCSS("", "multiplatform-tentap-dark");
|
|
137
|
-
}
|
|
138
|
-
}, 150);
|
|
139
|
-
return () => clearTimeout(id);
|
|
140
|
-
}, [editor, isDarkMode]);
|
|
141
|
-
|
|
142
|
-
// Web-authored color/highlight marks store theme custom properties
|
|
143
|
-
// (`var(--red11)`); the 10Tap webview has no tamagui stylesheet, so
|
|
144
|
-
// define the palette vars there from the active theme (Axiom 5: same
|
|
145
|
-
// tokens, resolved once per scheme).
|
|
146
130
|
const theme = useTheme();
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
131
|
+
const webviewLoaded = useRef(false);
|
|
132
|
+
const applyEditorStyles = useCallback(() => {
|
|
133
|
+
const inject = (css: string, tag: string) => {
|
|
134
|
+
editor.injectCSS(escapeNativeEditorCSS(css), tag);
|
|
135
|
+
};
|
|
136
|
+
inject(isDarkMode ? darkEditorCss : "", "multiplatform-tentap-dark");
|
|
137
|
+
// The WebView has no Tamagui stylesheet. Resolve the same palette as
|
|
138
|
+
// web-authored color/highlight marks before applying content rules.
|
|
139
|
+
const decls = PALETTE_THEME_TOKENS.map((token) => {
|
|
140
|
+
const value = (theme as any)[token]?.val;
|
|
141
|
+
return value ? `--${token}: ${value};` : "";
|
|
142
|
+
}).join(" ");
|
|
143
|
+
inject(`:root { ${decls} }`, "multiplatform-tentap-palette");
|
|
144
|
+
inject(
|
|
145
|
+
getEditorCSS({
|
|
146
|
+
minHeight,
|
|
147
|
+
maxHeight,
|
|
148
|
+
bodyFontFamily,
|
|
149
|
+
headingFontFamily,
|
|
150
|
+
fullscreen: fill,
|
|
151
|
+
motionMs: 0,
|
|
152
|
+
}),
|
|
153
|
+
"multiplatform-tentap-content",
|
|
154
|
+
);
|
|
155
|
+
}, [editor, isDarkMode, theme, minHeight, maxHeight, bodyFontFamily, headingFontFamily, fill]);
|
|
157
156
|
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// either side lands on both. motionMs 0 because there is no bubble menu on
|
|
166
|
-
// native, and the palette block above already defines every var(--colorN)
|
|
167
|
-
// this CSS reads. `.ProseMirror` outranks darkEditorCss's `* { color }`, so
|
|
168
|
-
// the foreground also moves onto the theme's --color12 in both schemes.
|
|
157
|
+
// TenTap invokes this after its internal onLoad handling, including its
|
|
158
|
+
// first-load iOS reload. Apply on every load so the replacement document
|
|
159
|
+
// receives the styles too; a mount-time timer can target the old one.
|
|
160
|
+
const handleEditorLoad = useCallback(() => {
|
|
161
|
+
webviewLoaded.current = true;
|
|
162
|
+
applyEditorStyles();
|
|
163
|
+
}, [applyEditorStyles]);
|
|
169
164
|
useEffect(() => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
getEditorCSS({
|
|
173
|
-
minHeight,
|
|
174
|
-
maxHeight,
|
|
175
|
-
bodyFontFamily,
|
|
176
|
-
headingFontFamily,
|
|
177
|
-
fullscreen: fill,
|
|
178
|
-
motionMs: 0,
|
|
179
|
-
}),
|
|
180
|
-
"multiplatform-tentap-content",
|
|
181
|
-
);
|
|
182
|
-
}, 150);
|
|
183
|
-
return () => clearTimeout(id);
|
|
184
|
-
}, [editor, minHeight, maxHeight, bodyFontFamily, headingFontFamily, fill]);
|
|
165
|
+
if (webviewLoaded.current) applyEditorStyles();
|
|
166
|
+
}, [applyEditorStyles]);
|
|
185
167
|
|
|
186
168
|
// Subscribe to editor state updates
|
|
187
169
|
useEffect(() => {
|
|
@@ -349,7 +331,11 @@ export const TentapEditor = forwardRef(
|
|
|
349
331
|
overflow="hidden"
|
|
350
332
|
opacity={ctx.disabled ? 0.5 : 1}
|
|
351
333
|
>
|
|
352
|
-
<RichText
|
|
334
|
+
<RichText
|
|
335
|
+
editor={editor}
|
|
336
|
+
style={fill ? styles.editorFill : styles.editor}
|
|
337
|
+
onLoad={handleEditorLoad}
|
|
338
|
+
/>
|
|
353
339
|
</View>
|
|
354
340
|
);
|
|
355
341
|
},
|