@bigbinary/neetoui-rn 3.4.0 → 3.5.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 (88) hide show
  1. package/lib/typescript/src/components/ChatInput/AttachmentsView.d.ts +2 -1
  2. package/lib/typescript/src/components/ChatInput/AttachmentsView.d.ts.map +1 -1
  3. package/lib/typescript/src/components/ChatInput/ChatInput.d.ts +85 -0
  4. package/lib/typescript/src/components/ChatInput/ChatInput.d.ts.map +1 -1
  5. package/lib/typescript/src/components/ChatInput/MentionSuggestionList.d.ts +9 -0
  6. package/lib/typescript/src/components/ChatInput/MentionSuggestionList.d.ts.map +1 -0
  7. package/lib/typescript/src/components/ChatInput/MentionsInput.d.ts +3 -1
  8. package/lib/typescript/src/components/ChatInput/MentionsInput.d.ts.map +1 -1
  9. package/lib/typescript/src/components/ChatInput/SegmentedOptions.d.ts +10 -0
  10. package/lib/typescript/src/components/ChatInput/SegmentedOptions.d.ts.map +1 -0
  11. package/lib/typescript/src/components/ChatInput/constants.d.ts +3 -0
  12. package/lib/typescript/src/components/ChatInput/constants.d.ts.map +1 -1
  13. package/lib/typescript/src/components/ChatInput/index.d.ts +1 -1
  14. package/lib/typescript/src/components/ChatInput/index.d.ts.map +1 -1
  15. package/lib/typescript/src/components/ChatInput/mentions.d.ts +2 -0
  16. package/lib/typescript/src/components/ChatInput/mentions.d.ts.map +1 -1
  17. package/lib/typescript/src/components/RichTextEditor/LazyRichTextEditor.d.ts +3 -0
  18. package/lib/typescript/src/components/RichTextEditor/LazyRichTextEditor.d.ts.map +1 -0
  19. package/lib/typescript/src/components/RichTextEditor/MentionSuggestionBar.d.ts +8 -0
  20. package/lib/typescript/src/components/RichTextEditor/MentionSuggestionBar.d.ts.map +1 -0
  21. package/lib/typescript/src/components/RichTextEditor/RichTextEditor.d.ts +45 -0
  22. package/lib/typescript/src/components/RichTextEditor/RichTextEditor.d.ts.map +1 -0
  23. package/lib/typescript/src/components/RichTextEditor/RichTextToolbar.d.ts +11 -0
  24. package/lib/typescript/src/components/RichTextEditor/RichTextToolbar.d.ts.map +1 -0
  25. package/lib/typescript/src/components/RichTextEditor/bridges/CodeBlockBridge.d.ts +13 -0
  26. package/lib/typescript/src/components/RichTextEditor/bridges/CodeBlockBridge.d.ts.map +1 -0
  27. package/lib/typescript/src/components/RichTextEditor/bridges/ComposerBridge.d.ts +27 -0
  28. package/lib/typescript/src/components/RichTextEditor/bridges/ComposerBridge.d.ts.map +1 -0
  29. package/lib/typescript/src/components/RichTextEditor/bridges/FigureBridge.d.ts +16 -0
  30. package/lib/typescript/src/components/RichTextEditor/bridges/FigureBridge.d.ts.map +1 -0
  31. package/lib/typescript/src/components/RichTextEditor/bridges/MentionBridge.d.ts +35 -0
  32. package/lib/typescript/src/components/RichTextEditor/bridges/MentionBridge.d.ts.map +1 -0
  33. package/lib/typescript/src/components/RichTextEditor/bridges/VariableBridge.d.ts +3 -0
  34. package/lib/typescript/src/components/RichTextEditor/bridges/VariableBridge.d.ts.map +1 -0
  35. package/lib/typescript/src/components/RichTextEditor/bridges/index.d.ts +8 -0
  36. package/lib/typescript/src/components/RichTextEditor/bridges/index.d.ts.map +1 -0
  37. package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Figure.d.ts +3 -0
  38. package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Figure.d.ts.map +1 -0
  39. package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Mention.d.ts +3 -0
  40. package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Mention.d.ts.map +1 -0
  41. package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Variable.d.ts +3 -0
  42. package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Variable.d.ts.map +1 -0
  43. package/lib/typescript/src/components/RichTextEditor/bridges/types.d.ts +8 -0
  44. package/lib/typescript/src/components/RichTextEditor/bridges/types.d.ts.map +1 -0
  45. package/lib/typescript/src/components/RichTextEditor/editorCss.d.ts +21 -0
  46. package/lib/typescript/src/components/RichTextEditor/editorCss.d.ts.map +1 -0
  47. package/lib/typescript/src/components/RichTextEditor/icons/index.d.ts +9 -0
  48. package/lib/typescript/src/components/RichTextEditor/icons/index.d.ts.map +1 -0
  49. package/lib/typescript/src/components/RichTextEditor/index.d.ts +4 -0
  50. package/lib/typescript/src/components/RichTextEditor/index.d.ts.map +1 -0
  51. package/lib/typescript/src/components/RichTextEditor/injectableText.d.ts +2 -0
  52. package/lib/typescript/src/components/RichTextEditor/injectableText.d.ts.map +1 -0
  53. package/lib/typescript/src/components/RichTextEditor/types.d.ts +6 -0
  54. package/lib/typescript/src/components/RichTextEditor/types.d.ts.map +1 -0
  55. package/lib/typescript/src/components/RichTextEditor/web/build/editorHtml.d.ts +2 -0
  56. package/lib/typescript/src/components/RichTextEditor/web/build/editorHtml.d.ts.map +1 -0
  57. package/lib/typescript/src/index.d.ts +2 -1
  58. package/lib/typescript/src/index.d.ts.map +1 -1
  59. package/package.json +30 -7
  60. package/src/components/ChatInput/AttachmentsView.tsx +6 -0
  61. package/src/components/ChatInput/ChatInput.tsx +569 -91
  62. package/src/components/ChatInput/MentionSuggestionList.tsx +71 -0
  63. package/src/components/ChatInput/MentionsInput.tsx +34 -46
  64. package/src/components/ChatInput/SegmentedOptions.tsx +84 -0
  65. package/src/components/ChatInput/constants.ts +10 -0
  66. package/src/components/ChatInput/index.ts +3 -0
  67. package/src/components/ChatInput/mentions.ts +22 -0
  68. package/src/components/RichTextEditor/LazyRichTextEditor.tsx +19 -0
  69. package/src/components/RichTextEditor/MentionSuggestionBar.tsx +79 -0
  70. package/src/components/RichTextEditor/RichTextEditor.tsx +227 -0
  71. package/src/components/RichTextEditor/RichTextToolbar.tsx +476 -0
  72. package/src/components/RichTextEditor/bridges/CodeBlockBridge.ts +29 -0
  73. package/src/components/RichTextEditor/bridges/ComposerBridge.ts +107 -0
  74. package/src/components/RichTextEditor/bridges/FigureBridge.ts +38 -0
  75. package/src/components/RichTextEditor/bridges/MentionBridge.ts +79 -0
  76. package/src/components/RichTextEditor/bridges/VariableBridge.ts +7 -0
  77. package/src/components/RichTextEditor/bridges/index.ts +66 -0
  78. package/src/components/RichTextEditor/bridges/nodes/Figure.ts +127 -0
  79. package/src/components/RichTextEditor/bridges/nodes/Mention.ts +48 -0
  80. package/src/components/RichTextEditor/bridges/nodes/Variable.ts +68 -0
  81. package/src/components/RichTextEditor/bridges/types.ts +26 -0
  82. package/src/components/RichTextEditor/editorCss.ts +69 -0
  83. package/src/components/RichTextEditor/icons/index.tsx +41 -0
  84. package/src/components/RichTextEditor/index.ts +3 -0
  85. package/src/components/RichTextEditor/injectableText.ts +11 -0
  86. package/src/components/RichTextEditor/types.ts +1 -0
  87. package/src/components/RichTextEditor/web/build/editorHtml.ts +3 -0
  88. package/src/index.ts +9 -0
@@ -0,0 +1,79 @@
1
+ import { BridgeExtension } from "@10play/tentap-editor";
2
+ import type { Editor } from "@tiptap/core";
3
+
4
+ import { Mention } from "./nodes/Mention";
5
+
6
+ export type MentionQuery = { query: string; from: number; to: number };
7
+
8
+ export type MentionEditorState = {
9
+ /** The "@word" being typed at the caret, while there is one. */
10
+ mentionQuery: MentionQuery | null;
11
+ };
12
+
13
+ export type MentionEditorInstance = {
14
+ insertMention: (
15
+ mention: { id: string; label: string },
16
+ range: { from: number; to: number }
17
+ ) => void;
18
+ };
19
+
20
+ type MentionMessage = {
21
+ type: "insert-mention";
22
+ payload: {
23
+ id: string;
24
+ label: string;
25
+ range: { from: number; to: number };
26
+ };
27
+ };
28
+
29
+ const TRIGGER = /(?:^|\s)@([^\s@]*)$/;
30
+
31
+ export const findMentionQuery = (editor: Editor): MentionQuery | null => {
32
+ const { empty, $from } = editor.state.selection;
33
+ if (!empty || !$from.parent.isTextblock) return null;
34
+
35
+ const textBefore = $from.parent.textBetween(
36
+ 0,
37
+ $from.parentOffset,
38
+ undefined,
39
+ ""
40
+ );
41
+ const match = TRIGGER.exec(textBefore);
42
+ if (!match) return null;
43
+
44
+ const query = match[1] ?? "";
45
+
46
+ return { query, from: $from.pos - query.length - 1, to: $from.pos };
47
+ };
48
+
49
+ export const MentionBridge = new BridgeExtension<
50
+ MentionEditorState,
51
+ MentionEditorInstance,
52
+ MentionMessage
53
+ >({
54
+ forceName: "mention",
55
+ tiptapExtension: Mention,
56
+ onBridgeMessage: (editor, message) => {
57
+ if (message.type !== "insert-mention") return false;
58
+
59
+ const { id, label, range } = message.payload;
60
+ editor
61
+ .chain()
62
+ .focus()
63
+ .insertContentAt(range, [
64
+ { type: "mention", attrs: { id, label } },
65
+ { type: "text", text: " " },
66
+ ])
67
+ .run();
68
+
69
+ return true;
70
+ },
71
+ extendEditorState: editor => ({ mentionQuery: findMentionQuery(editor) }),
72
+ extendEditorInstance: sendBridgeMessage => ({
73
+ insertMention: ({ id, label }, range) =>
74
+ sendBridgeMessage({
75
+ type: "insert-mention",
76
+ payload: { id, label, range },
77
+ }),
78
+ }),
79
+ });
@@ -0,0 +1,7 @@
1
+ import { BridgeExtension } from "@10play/tentap-editor";
2
+
3
+ import { Variable } from "./nodes/Variable";
4
+
5
+ export const VariableBridge = new BridgeExtension<object, object, unknown>({
6
+ tiptapExtension: Variable,
7
+ });
@@ -0,0 +1,66 @@
1
+ import {
2
+ BridgeExtension,
3
+ ImageBridge,
4
+ TenTapStartKit,
5
+ } from "@10play/tentap-editor";
6
+ import type { AnyExtension } from "@tiptap/core";
7
+
8
+ import { CodeBlockBridge } from "./CodeBlockBridge";
9
+ import { ComposerBridge } from "./ComposerBridge";
10
+ import { FigureBridge } from "./FigureBridge";
11
+ import { MentionBridge } from "./MentionBridge";
12
+ import { VariableBridge } from "./VariableBridge";
13
+
14
+ // Several bridges list the same dependency (bullet and ordered lists both
15
+ // bring listItem; colour and highlight both bring textStyle), and tiptap warns
16
+ // when an extension is registered twice. Each dependency is kept once.
17
+ const withoutRepeatedDeps = (bridges: BridgeExtension[]) => {
18
+ const seen = new Set(
19
+ bridges.flatMap(bridge =>
20
+ bridge.tiptapExtension ? [bridge.tiptapExtension.name] : []
21
+ )
22
+ );
23
+
24
+ return bridges.map(bridge => {
25
+ const deps = bridge.tiptapExtensionDeps;
26
+ if (!deps?.length) return bridge;
27
+
28
+ const kept = deps.filter(extension => !seen.has(extension.name));
29
+ kept.forEach(extension => seen.add(extension.name));
30
+ if (kept.length === deps.length) return bridge;
31
+
32
+ return new BridgeExtension({
33
+ ...bridge,
34
+ forceName: bridge.name,
35
+ tiptapExtensionDeps: kept,
36
+ });
37
+ });
38
+ };
39
+
40
+ // tentap's ImageBridge registers a node also named "image"; FigureBridge
41
+ // replaces it with neeto-editor's <figure> schema.
42
+ export const NEETO_BRIDGES = withoutRepeatedDeps([
43
+ ...TenTapStartKit.filter(bridge => bridge !== ImageBridge),
44
+ CodeBlockBridge,
45
+ VariableBridge,
46
+ FigureBridge,
47
+ MentionBridge,
48
+ ComposerBridge,
49
+ ]);
50
+
51
+ export const editorBridges = ({ variables = false } = {}) =>
52
+ variables
53
+ ? NEETO_BRIDGES
54
+ : NEETO_BRIDGES.filter(bridge => bridge !== VariableBridge);
55
+
56
+ export const tiptapExtensions = (): AnyExtension[] => {
57
+ const byName = new Map<string, AnyExtension>();
58
+ NEETO_BRIDGES.flatMap(bridge => [
59
+ ...(bridge.tiptapExtension ? [bridge.tiptapExtension] : []),
60
+ ...(bridge.tiptapExtensionDeps ?? []),
61
+ ]).forEach(extension => {
62
+ if (!byName.has(extension.name)) byName.set(extension.name, extension);
63
+ });
64
+
65
+ return [...byName.values()];
66
+ };
@@ -0,0 +1,127 @@
1
+ import { Node, mergeAttributes } from "@tiptap/core";
2
+
3
+ const imageOf = (element: HTMLElement) =>
4
+ element.tagName === "IMG" ? element : element.querySelector("img");
5
+
6
+ // A pasted <img> carries its size as width="80" or style="width: 80px",
7
+ // not neeto-editor's figwidth; without this it renders at the 500px default.
8
+ const pastedWidth = (image: HTMLElement | null) => {
9
+ if (!image) return null;
10
+
11
+ const width =
12
+ parseInt(image.getAttribute("width") ?? "", 10) ||
13
+ parseInt(image.style.width, 10);
14
+
15
+ return Number.isFinite(width) && width > 0 ? width : null;
16
+ };
17
+
18
+ export const Figure = Node.create({
19
+ name: "image",
20
+
21
+ addOptions() {
22
+ return { HTMLAttributes: {}, openImageInNewTab: true };
23
+ },
24
+
25
+ group: "block",
26
+
27
+ content: "inline*",
28
+
29
+ draggable: false,
30
+
31
+ isolating: true,
32
+
33
+ addAttributes() {
34
+ return {
35
+ id: {
36
+ default: null,
37
+ parseHTML: element => element.getAttribute("id"),
38
+ },
39
+ src: {
40
+ default: null,
41
+ parseHTML: element => imageOf(element)?.getAttribute("src"),
42
+ },
43
+ alt: {
44
+ default: "image",
45
+ parseHTML: element => imageOf(element)?.getAttribute("alt") || "image",
46
+ },
47
+ figheight: {
48
+ default: "auto",
49
+ parseHTML: element => imageOf(element)?.getAttribute("figheight"),
50
+ },
51
+ figwidth: {
52
+ default: 500,
53
+ parseHTML: element =>
54
+ imageOf(element)?.getAttribute("figwidth") ??
55
+ pastedWidth(imageOf(element)),
56
+ },
57
+ align: {
58
+ default: "left",
59
+ parseHTML: element => imageOf(element)?.getAttribute("align"),
60
+ },
61
+ border: {
62
+ default: true,
63
+ parseHTML: element =>
64
+ imageOf(element)?.getAttribute("data-border") === "true",
65
+ },
66
+ };
67
+ },
68
+
69
+ parseHTML() {
70
+ return [
71
+ {
72
+ tag: "figure",
73
+ contentElement: (figure: HTMLElement) =>
74
+ figure.querySelector("figcaption") ??
75
+ figure.ownerDocument.createElement("figcaption"),
76
+ },
77
+ { tag: "img[src]" },
78
+ ];
79
+ },
80
+
81
+ renderHTML({ node, HTMLAttributes }) {
82
+ const { align, src, figheight, figwidth, border } = node.attrs;
83
+
84
+ if (!src) return ["span"];
85
+
86
+ const pointerEvents = this.options.openImageInNewTab
87
+ ? ""
88
+ : "pointer-events:none;";
89
+ const height = figheight === "auto" ? "auto" : `${figheight}px`;
90
+
91
+ return [
92
+ "div",
93
+ {
94
+ class: [
95
+ "neeto-editor__image-wrapper",
96
+ `neeto-editor__image--${align}`,
97
+ border && "neeto-editor__image--bordered",
98
+ ]
99
+ .filter(Boolean)
100
+ .join(" "),
101
+ },
102
+ [
103
+ "figure",
104
+ this.options.HTMLAttributes,
105
+ [
106
+ "a",
107
+ {
108
+ href: src,
109
+ target: "_blank",
110
+ rel: "noopener noreferrer",
111
+ class: "neeto-editor__image",
112
+ style: `height:${height};width:${figwidth}px;display:inline-block;${pointerEvents}`,
113
+ },
114
+ [
115
+ "img",
116
+ mergeAttributes(HTMLAttributes, {
117
+ draggable: false,
118
+ contenteditable: false,
119
+ "data-border": border,
120
+ }),
121
+ ],
122
+ ],
123
+ ["figcaption", { style: `width:${figwidth}px;` }, 0],
124
+ ],
125
+ ];
126
+ },
127
+ });
@@ -0,0 +1,48 @@
1
+ import { Node, mergeAttributes } from "@tiptap/core";
2
+
3
+ // @tiptap/extension-mention's schema and HTML, which neeto-editor uses
4
+ // unconfigured; the suggestion popup is native, so its plugin is left out.
5
+ export const Mention = Node.create({
6
+ name: "mention",
7
+
8
+ group: "inline",
9
+
10
+ inline: true,
11
+
12
+ selectable: false,
13
+
14
+ atom: true,
15
+
16
+ addAttributes() {
17
+ return {
18
+ id: {
19
+ default: null,
20
+ parseHTML: element => element.getAttribute("data-id"),
21
+ renderHTML: attributes =>
22
+ attributes.id ? { "data-id": attributes.id } : {},
23
+ },
24
+ label: {
25
+ default: null,
26
+ parseHTML: element => element.getAttribute("data-label"),
27
+ renderHTML: attributes =>
28
+ attributes.label ? { "data-label": attributes.label } : {},
29
+ },
30
+ };
31
+ },
32
+
33
+ parseHTML() {
34
+ return [{ tag: `span[data-type="${this.name}"]` }];
35
+ },
36
+
37
+ renderHTML({ node, HTMLAttributes }) {
38
+ return [
39
+ "span",
40
+ mergeAttributes({ "data-type": this.name }, HTMLAttributes),
41
+ `@${node.attrs.label ?? node.attrs.id}`,
42
+ ];
43
+ },
44
+
45
+ renderText({ node }) {
46
+ return `@${node.attrs.label ?? node.attrs.id}`;
47
+ },
48
+ });
@@ -0,0 +1,68 @@
1
+ import { Node, mergeAttributes } from "@tiptap/core";
2
+
3
+ type RenderLabelArgs = {
4
+ options: { charOpen: string; charClose: string };
5
+ node: { attrs: Record<string, string | null> };
6
+ };
7
+
8
+ export const Variable = Node.create({
9
+ name: "variable",
10
+
11
+ addOptions() {
12
+ return {
13
+ HTMLAttributes: { class: "neeto-editor-variable" },
14
+ charOpen: "{{",
15
+ charClose: "}}",
16
+ renderLabel({ options, node }: RenderLabelArgs) {
17
+ return `${options.charOpen}${node.attrs.id || node.attrs.label}${
18
+ options.charClose
19
+ }`;
20
+ },
21
+ };
22
+ },
23
+
24
+ group: "inline",
25
+
26
+ inline: true,
27
+
28
+ selectable: false,
29
+
30
+ atom: true,
31
+
32
+ addAttributes() {
33
+ return {
34
+ id: {
35
+ default: null,
36
+ parseHTML: element => element.getAttribute("data-id"),
37
+ renderHTML: attributes =>
38
+ attributes.id ? { "data-id": attributes.id } : {},
39
+ },
40
+ label: {
41
+ default: null,
42
+ parseHTML: element => element.getAttribute("data-label"),
43
+ renderHTML: attributes =>
44
+ attributes.label ? { "data-label": attributes.label } : {},
45
+ },
46
+ };
47
+ },
48
+
49
+ parseHTML() {
50
+ return [{ tag: "span[data-variable]" }];
51
+ },
52
+
53
+ renderHTML({ node, HTMLAttributes }) {
54
+ return [
55
+ "span",
56
+ mergeAttributes(
57
+ { "data-variable": "" },
58
+ this.options.HTMLAttributes,
59
+ HTMLAttributes
60
+ ),
61
+ this.options.renderLabel({ options: this.options, node }),
62
+ ];
63
+ },
64
+
65
+ renderText({ node }) {
66
+ return this.options.renderLabel({ options: this.options, node });
67
+ },
68
+ });
@@ -0,0 +1,26 @@
1
+ import type { BridgeState, EditorBridge } from "@10play/tentap-editor";
2
+
3
+ import type {
4
+ CodeBlockEditorInstance,
5
+ CodeBlockEditorState,
6
+ } from "./CodeBlockBridge";
7
+ import type {
8
+ ComposerEditorInstance,
9
+ ComposerEditorState,
10
+ } from "./ComposerBridge";
11
+ import type { FigureEditorInstance } from "./FigureBridge";
12
+ import type {
13
+ MentionEditorInstance,
14
+ MentionEditorState,
15
+ } from "./MentionBridge";
16
+
17
+ export type NeetoEditorBridge = EditorBridge &
18
+ CodeBlockEditorInstance &
19
+ FigureEditorInstance &
20
+ MentionEditorInstance &
21
+ ComposerEditorInstance;
22
+
23
+ export type NeetoBridgeState = BridgeState &
24
+ CodeBlockEditorState &
25
+ MentionEditorState &
26
+ ComposerEditorState;
@@ -0,0 +1,69 @@
1
+ import { Platform } from "react-native";
2
+
3
+ type EditorTheme = {
4
+ colors: {
5
+ primary: string;
6
+ text: { primary: string };
7
+ background: { primary: string; secondary: string };
8
+ border: { primary: string };
9
+ };
10
+ fontSizes: { m: number };
11
+ };
12
+
13
+ // Android's WebView can only reach the app's bundled SF Pro through the
14
+ // android_asset base URL RichTextEditor sets; iOS has it as the system font.
15
+ const FONT_FACE = Platform.select({
16
+ android:
17
+ "@font-face { font-family: 'SFProText'; src: url('fonts/SFProText-Regular.ttf'); }",
18
+ default: "",
19
+ });
20
+
21
+ const FONT_FAMILY = Platform.select({
22
+ ios: "-apple-system, sans-serif",
23
+ default: "'SFProText', sans-serif",
24
+ });
25
+
26
+ export const editorCss = (
27
+ theme: EditorTheme,
28
+ backgroundColor = theme.colors.background.primary
29
+ ) => `
30
+ ${FONT_FACE}
31
+ * {
32
+ font-family: ${FONT_FAMILY};
33
+ }
34
+ body {
35
+ background-color: ${backgroundColor};
36
+ color: ${theme.colors.text.primary};
37
+ font-size: ${theme.fontSizes.m}px;
38
+ }
39
+ .ProseMirror {
40
+ padding: 12px 16px;
41
+ }
42
+ .ProseMirror p {
43
+ line-height: 1.6;
44
+ margin: 0 0 0.5rem;
45
+ }
46
+ .ProseMirror p.is-empty {
47
+ margin-bottom: 0;
48
+ }
49
+ [data-type="mention"] {
50
+ color: ${theme.colors.primary};
51
+ font-weight: 600;
52
+ }
53
+ .neeto-editor-variable {
54
+ background-color: ${theme.colors.background.secondary};
55
+ border-radius: 4px;
56
+ padding: 0 2px;
57
+ }
58
+ .neeto-editor__image-wrapper img {
59
+ max-width: 100%;
60
+ }
61
+ pre {
62
+ background-color: ${theme.colors.background.secondary};
63
+ }
64
+ blockquote {
65
+ border-left: 3px solid ${theme.colors.border.primary};
66
+ margin-left: 0;
67
+ padding-left: 12px;
68
+ }
69
+ `;
@@ -0,0 +1,41 @@
1
+ import Svg, { Path } from "react-native-svg";
2
+
3
+ type IconProps = { size?: number; color?: string };
4
+
5
+ const strokeProps = (color: string) => ({
6
+ stroke: color,
7
+ strokeWidth: 1.2,
8
+ strokeLinecap: "round" as const,
9
+ strokeLinejoin: "round" as const,
10
+ });
11
+
12
+ // Paths from lucide (ISC), the set desk-web's editor menu uses; neeto-icons
13
+ // has no pilcrow or plain T.
14
+ export const Pilcrow = ({ size = 24, color = "#344054" }: IconProps) => (
15
+ <Svg fill="none" height={size} viewBox="0 0 24 24" width={size}>
16
+ <Path d="M13 4v16M17 4v16" {...strokeProps(color)} />
17
+ <Path d="M19 4H9.5a4.5 4.5 0 0 0 0 9H13" {...strokeProps(color)} />
18
+ </Svg>
19
+ );
20
+
21
+ export const TextType = ({ size = 24, color = "#344054" }: IconProps) => (
22
+ <Svg fill="none" height={size} viewBox="0 0 24 24" width={size}>
23
+ <Path d="M4 7V4h16v3M9 20h6M12 4v16" {...strokeProps(color)} />
24
+ </Svg>
25
+ );
26
+
27
+ export const KeyboardDismiss = ({
28
+ size = 24,
29
+ color = "#344054",
30
+ }: IconProps) => (
31
+ <Svg fill="none" height={size} viewBox="0 0 24 24" width={size}>
32
+ <Path
33
+ d="M4.6 3.6h14.8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4.6a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2z"
34
+ {...strokeProps(color)}
35
+ />
36
+ <Path
37
+ d="M6 7h.01M10 7h.01M14 7h.01M18 7h.01M8 10h.01M12 10h.01M16 10h.01M8 12.8h8M9 18.4l3 2.4 3-2.4"
38
+ {...strokeProps(color)}
39
+ />
40
+ </Svg>
41
+ );
@@ -0,0 +1,3 @@
1
+ export { RichTextEditor } from "./LazyRichTextEditor";
2
+ export type { RichTextEditorProps, RichTextEditorRef } from "./RichTextEditor";
3
+ export { type RichTextImage } from "./types";
@@ -0,0 +1,11 @@
1
+ // tentap injects its bridge config as '<JSON>' into the WebView, so a quote
2
+ // in text it carries (the placeholder) breaks the page and a backslash needs
3
+ // doubling to survive the string literal.
4
+ export const injectableText = (text: string) =>
5
+ text
6
+ .replace(/"([^"]*)"/g, "“$1”")
7
+ .replace(/"/g, "”")
8
+ .replace(/'/g, "’")
9
+ .replace(/\\/g, "\\\\")
10
+ .replace(/\s{2,}/g, " ")
11
+ .trim();
@@ -0,0 +1 @@
1
+ export type RichTextImage = { src: string; width: number; height?: number };