@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.
- package/lib/typescript/src/components/ChatInput/AttachmentsView.d.ts +2 -1
- package/lib/typescript/src/components/ChatInput/AttachmentsView.d.ts.map +1 -1
- package/lib/typescript/src/components/ChatInput/ChatInput.d.ts +85 -0
- package/lib/typescript/src/components/ChatInput/ChatInput.d.ts.map +1 -1
- package/lib/typescript/src/components/ChatInput/MentionSuggestionList.d.ts +9 -0
- package/lib/typescript/src/components/ChatInput/MentionSuggestionList.d.ts.map +1 -0
- package/lib/typescript/src/components/ChatInput/MentionsInput.d.ts +3 -1
- package/lib/typescript/src/components/ChatInput/MentionsInput.d.ts.map +1 -1
- package/lib/typescript/src/components/ChatInput/SegmentedOptions.d.ts +10 -0
- package/lib/typescript/src/components/ChatInput/SegmentedOptions.d.ts.map +1 -0
- package/lib/typescript/src/components/ChatInput/constants.d.ts +3 -0
- package/lib/typescript/src/components/ChatInput/constants.d.ts.map +1 -1
- package/lib/typescript/src/components/ChatInput/index.d.ts +1 -1
- package/lib/typescript/src/components/ChatInput/index.d.ts.map +1 -1
- package/lib/typescript/src/components/ChatInput/mentions.d.ts +2 -0
- package/lib/typescript/src/components/ChatInput/mentions.d.ts.map +1 -1
- package/lib/typescript/src/components/RichTextEditor/LazyRichTextEditor.d.ts +3 -0
- package/lib/typescript/src/components/RichTextEditor/LazyRichTextEditor.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/MentionSuggestionBar.d.ts +8 -0
- package/lib/typescript/src/components/RichTextEditor/MentionSuggestionBar.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/RichTextEditor.d.ts +45 -0
- package/lib/typescript/src/components/RichTextEditor/RichTextEditor.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/RichTextToolbar.d.ts +11 -0
- package/lib/typescript/src/components/RichTextEditor/RichTextToolbar.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/CodeBlockBridge.d.ts +13 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/CodeBlockBridge.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/ComposerBridge.d.ts +27 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/ComposerBridge.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/FigureBridge.d.ts +16 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/FigureBridge.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/MentionBridge.d.ts +35 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/MentionBridge.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/VariableBridge.d.ts +3 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/VariableBridge.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/index.d.ts +8 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/index.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Figure.d.ts +3 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Figure.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Mention.d.ts +3 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Mention.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Variable.d.ts +3 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/nodes/Variable.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/types.d.ts +8 -0
- package/lib/typescript/src/components/RichTextEditor/bridges/types.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/editorCss.d.ts +21 -0
- package/lib/typescript/src/components/RichTextEditor/editorCss.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/icons/index.d.ts +9 -0
- package/lib/typescript/src/components/RichTextEditor/icons/index.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/index.d.ts +4 -0
- package/lib/typescript/src/components/RichTextEditor/index.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/injectableText.d.ts +2 -0
- package/lib/typescript/src/components/RichTextEditor/injectableText.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/types.d.ts +6 -0
- package/lib/typescript/src/components/RichTextEditor/types.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextEditor/web/build/editorHtml.d.ts +2 -0
- package/lib/typescript/src/components/RichTextEditor/web/build/editorHtml.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +30 -7
- package/src/components/ChatInput/AttachmentsView.tsx +6 -0
- package/src/components/ChatInput/ChatInput.tsx +569 -91
- package/src/components/ChatInput/MentionSuggestionList.tsx +71 -0
- package/src/components/ChatInput/MentionsInput.tsx +34 -46
- package/src/components/ChatInput/SegmentedOptions.tsx +84 -0
- package/src/components/ChatInput/constants.ts +10 -0
- package/src/components/ChatInput/index.ts +3 -0
- package/src/components/ChatInput/mentions.ts +22 -0
- package/src/components/RichTextEditor/LazyRichTextEditor.tsx +19 -0
- package/src/components/RichTextEditor/MentionSuggestionBar.tsx +79 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +227 -0
- package/src/components/RichTextEditor/RichTextToolbar.tsx +476 -0
- package/src/components/RichTextEditor/bridges/CodeBlockBridge.ts +29 -0
- package/src/components/RichTextEditor/bridges/ComposerBridge.ts +107 -0
- package/src/components/RichTextEditor/bridges/FigureBridge.ts +38 -0
- package/src/components/RichTextEditor/bridges/MentionBridge.ts +79 -0
- package/src/components/RichTextEditor/bridges/VariableBridge.ts +7 -0
- package/src/components/RichTextEditor/bridges/index.ts +66 -0
- package/src/components/RichTextEditor/bridges/nodes/Figure.ts +127 -0
- package/src/components/RichTextEditor/bridges/nodes/Mention.ts +48 -0
- package/src/components/RichTextEditor/bridges/nodes/Variable.ts +68 -0
- package/src/components/RichTextEditor/bridges/types.ts +26 -0
- package/src/components/RichTextEditor/editorCss.ts +69 -0
- package/src/components/RichTextEditor/icons/index.tsx +41 -0
- package/src/components/RichTextEditor/index.ts +3 -0
- package/src/components/RichTextEditor/injectableText.ts +11 -0
- package/src/components/RichTextEditor/types.ts +1 -0
- package/src/components/RichTextEditor/web/build/editorHtml.ts +3 -0
- 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,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,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 };
|