@apollohg/react-native-prose-editor 0.1.1 → 0.3.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/README.md +12 -7
- package/android/build.gradle +7 -2
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +289 -2
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +51 -1
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +16 -3
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +82 -1
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +403 -45
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +246 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +841 -155
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +125 -8
- package/{src/EditorTheme.ts → dist/EditorTheme.d.ts} +12 -52
- package/dist/EditorTheme.js +29 -0
- package/dist/EditorToolbar.d.ts +129 -0
- package/dist/EditorToolbar.js +394 -0
- package/dist/NativeEditorBridge.d.ts +242 -0
- package/dist/NativeEditorBridge.js +647 -0
- package/dist/NativeRichTextEditor.d.ts +142 -0
- package/dist/NativeRichTextEditor.js +649 -0
- package/dist/YjsCollaboration.d.ts +83 -0
- package/dist/YjsCollaboration.js +585 -0
- package/dist/addons.d.ts +70 -0
- package/dist/addons.js +77 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +26 -0
- package/dist/schemas.d.ts +35 -0
- package/{src/schemas.ts → dist/schemas.js} +62 -27
- package/dist/useNativeEditor.d.ts +40 -0
- package/dist/useNativeEditor.js +117 -0
- package/ios/EditorAddons.swift +26 -3
- package/ios/EditorCore.xcframework/Info.plist +5 -5
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorLayoutManager.swift +236 -0
- package/ios/EditorTheme.swift +51 -1
- package/ios/Generated_editor_core.swift +270 -2
- package/ios/NativeEditorExpoView.swift +612 -45
- package/ios/NativeEditorModule.swift +81 -0
- package/ios/PositionBridge.swift +22 -0
- package/ios/RenderBridge.swift +427 -39
- package/ios/RichTextEditorView.swift +1342 -18
- package/ios/editor_coreFFI/editor_coreFFI.h +209 -0
- package/package.json +80 -64
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +404 -4
- package/src/EditorToolbar.tsx +0 -620
- package/src/NativeEditorBridge.ts +0 -607
- package/src/NativeRichTextEditor.tsx +0 -951
- package/src/addons.ts +0 -158
- package/src/index.ts +0 -63
- package/src/useNativeEditor.ts +0 -173
package/dist/addons.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { EditorMentionTheme } from './EditorTheme';
|
|
2
|
+
import type { DocumentJSON } from './NativeEditorBridge';
|
|
3
|
+
import type { SchemaDefinition, NodeSpec } from './schemas';
|
|
4
|
+
export interface MentionSuggestion {
|
|
5
|
+
key: string;
|
|
6
|
+
title: string;
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
attrs?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export interface MentionQueryChangeEvent {
|
|
12
|
+
query: string;
|
|
13
|
+
trigger: string;
|
|
14
|
+
range: {
|
|
15
|
+
anchor: number;
|
|
16
|
+
head: number;
|
|
17
|
+
};
|
|
18
|
+
isActive: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface MentionSelectEvent {
|
|
21
|
+
trigger: string;
|
|
22
|
+
suggestion: MentionSuggestion;
|
|
23
|
+
attrs: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
export interface MentionsAddonConfig {
|
|
26
|
+
trigger?: string;
|
|
27
|
+
suggestions?: readonly MentionSuggestion[];
|
|
28
|
+
theme?: EditorMentionTheme;
|
|
29
|
+
onQueryChange?: (event: MentionQueryChangeEvent) => void;
|
|
30
|
+
onSelect?: (event: MentionSelectEvent) => void;
|
|
31
|
+
}
|
|
32
|
+
export interface EditorAddons {
|
|
33
|
+
mentions?: MentionsAddonConfig;
|
|
34
|
+
}
|
|
35
|
+
export interface SerializedMentionSuggestion {
|
|
36
|
+
key: string;
|
|
37
|
+
title: string;
|
|
38
|
+
subtitle?: string;
|
|
39
|
+
label: string;
|
|
40
|
+
attrs: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
export interface SerializedMentionsAddonConfig {
|
|
43
|
+
trigger: string;
|
|
44
|
+
theme?: EditorMentionTheme;
|
|
45
|
+
suggestions: SerializedMentionSuggestion[];
|
|
46
|
+
}
|
|
47
|
+
export interface SerializedEditorAddons {
|
|
48
|
+
mentions?: SerializedMentionsAddonConfig;
|
|
49
|
+
}
|
|
50
|
+
export type EditorAddonEvent = {
|
|
51
|
+
type: 'mentionsQueryChange';
|
|
52
|
+
query: string;
|
|
53
|
+
trigger: string;
|
|
54
|
+
range: {
|
|
55
|
+
anchor: number;
|
|
56
|
+
head: number;
|
|
57
|
+
};
|
|
58
|
+
isActive: boolean;
|
|
59
|
+
} | {
|
|
60
|
+
type: 'mentionsSelect';
|
|
61
|
+
trigger: string;
|
|
62
|
+
suggestionKey: string;
|
|
63
|
+
attrs: Record<string, unknown>;
|
|
64
|
+
};
|
|
65
|
+
export declare const MENTION_NODE_NAME = "mention";
|
|
66
|
+
export declare function mentionNodeSpec(): NodeSpec;
|
|
67
|
+
export declare function withMentionsSchema(schema: SchemaDefinition): SchemaDefinition;
|
|
68
|
+
export declare function normalizeEditorAddons(addons?: EditorAddons): SerializedEditorAddons | undefined;
|
|
69
|
+
export declare function serializeEditorAddons(addons?: EditorAddons): string | undefined;
|
|
70
|
+
export declare function buildMentionFragmentJson(attrs: Record<string, unknown>): DocumentJSON;
|
package/dist/addons.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MENTION_NODE_NAME = void 0;
|
|
4
|
+
exports.mentionNodeSpec = mentionNodeSpec;
|
|
5
|
+
exports.withMentionsSchema = withMentionsSchema;
|
|
6
|
+
exports.normalizeEditorAddons = normalizeEditorAddons;
|
|
7
|
+
exports.serializeEditorAddons = serializeEditorAddons;
|
|
8
|
+
exports.buildMentionFragmentJson = buildMentionFragmentJson;
|
|
9
|
+
exports.MENTION_NODE_NAME = 'mention';
|
|
10
|
+
const DEFAULT_MENTION_TRIGGER = '@';
|
|
11
|
+
function mentionNodeSpec() {
|
|
12
|
+
return {
|
|
13
|
+
name: exports.MENTION_NODE_NAME,
|
|
14
|
+
content: '',
|
|
15
|
+
group: 'inline',
|
|
16
|
+
role: 'inline',
|
|
17
|
+
isVoid: true,
|
|
18
|
+
attrs: {
|
|
19
|
+
label: { default: null },
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function withMentionsSchema(schema) {
|
|
24
|
+
const hasMentionNode = schema.nodes.some((node) => node.name === exports.MENTION_NODE_NAME);
|
|
25
|
+
if (hasMentionNode) {
|
|
26
|
+
return schema;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
...schema,
|
|
30
|
+
nodes: [...schema.nodes, mentionNodeSpec()],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function normalizeEditorAddons(addons) {
|
|
34
|
+
if (!addons?.mentions) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const trigger = addons.mentions.trigger?.trim() || DEFAULT_MENTION_TRIGGER;
|
|
38
|
+
const suggestions = (addons.mentions.suggestions ?? []).map((suggestion) => {
|
|
39
|
+
const label = suggestion.label?.trim() || `${trigger}${suggestion.title}`;
|
|
40
|
+
const attrs = {
|
|
41
|
+
label,
|
|
42
|
+
...(suggestion.attrs ?? {}),
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
key: suggestion.key,
|
|
46
|
+
title: suggestion.title,
|
|
47
|
+
subtitle: suggestion.subtitle,
|
|
48
|
+
label,
|
|
49
|
+
attrs,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
mentions: {
|
|
54
|
+
trigger,
|
|
55
|
+
theme: addons.mentions.theme,
|
|
56
|
+
suggestions,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function serializeEditorAddons(addons) {
|
|
61
|
+
const normalized = normalizeEditorAddons(addons);
|
|
62
|
+
if (!normalized?.mentions) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return JSON.stringify(normalized);
|
|
66
|
+
}
|
|
67
|
+
function buildMentionFragmentJson(attrs) {
|
|
68
|
+
return {
|
|
69
|
+
type: 'doc',
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: exports.MENTION_NODE_NAME,
|
|
73
|
+
attrs,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { NativeRichTextEditor, type NativeRichTextEditorProps, type NativeRichTextEditorRef, type NativeRichTextEditorHeightBehavior, type NativeRichTextEditorToolbarPlacement, type RemoteSelectionDecoration, type LinkRequestContext, type ImageRequestContext, } from './NativeRichTextEditor';
|
|
2
|
+
export { EditorToolbar, DEFAULT_EDITOR_TOOLBAR_ITEMS, type EditorToolbarProps, type EditorToolbarItem, type EditorToolbarIcon, type EditorToolbarDefaultIconId, type EditorToolbarSFSymbolIcon, type EditorToolbarMaterialIcon, type EditorToolbarCommand, type EditorToolbarListType, } from './EditorToolbar';
|
|
3
|
+
export type { EditorContentInsets, EditorTheme, EditorTextStyle, EditorHeadingTheme, EditorListTheme, EditorHorizontalRuleTheme, EditorMentionTheme, EditorToolbarTheme, EditorToolbarAppearance, EditorFontStyle, EditorFontWeight, } from './EditorTheme';
|
|
4
|
+
export { MENTION_NODE_NAME, mentionNodeSpec, withMentionsSchema, buildMentionFragmentJson, type EditorAddons, type MentionsAddonConfig, type MentionSuggestion, type MentionQueryChangeEvent, type MentionSelectEvent, type EditorAddonEvent, } from './addons';
|
|
5
|
+
export { tiptapSchema, prosemirrorSchema, IMAGE_NODE_NAME, imageNodeSpec, withImagesSchema, buildImageFragmentJson, type SchemaDefinition, type NodeSpec, type MarkSpec, type AttrSpec, type ImageNodeAttributes, } from './schemas';
|
|
6
|
+
export { createYjsCollaborationController, useYjsCollaboration, type YjsCollaborationOptions, type YjsCollaborationState, type YjsTransportStatus, type LocalAwarenessState, type LocalAwarenessUser, type UseYjsCollaborationResult, type YjsCollaborationController, } from './YjsCollaboration';
|
|
7
|
+
export type { Selection, ActiveState, HistoryState, EditorUpdate, DocumentJSON, CollaborationPeer, EncodedCollaborationStateInput, } from './NativeEditorBridge';
|
|
8
|
+
export { encodeCollaborationStateBase64, decodeCollaborationStateBase64, } from './NativeEditorBridge';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeCollaborationStateBase64 = exports.encodeCollaborationStateBase64 = exports.useYjsCollaboration = exports.createYjsCollaborationController = exports.buildImageFragmentJson = exports.withImagesSchema = exports.imageNodeSpec = exports.IMAGE_NODE_NAME = exports.prosemirrorSchema = exports.tiptapSchema = exports.buildMentionFragmentJson = exports.withMentionsSchema = exports.mentionNodeSpec = exports.MENTION_NODE_NAME = exports.DEFAULT_EDITOR_TOOLBAR_ITEMS = exports.EditorToolbar = exports.NativeRichTextEditor = void 0;
|
|
4
|
+
var NativeRichTextEditor_1 = require("./NativeRichTextEditor");
|
|
5
|
+
Object.defineProperty(exports, "NativeRichTextEditor", { enumerable: true, get: function () { return NativeRichTextEditor_1.NativeRichTextEditor; } });
|
|
6
|
+
var EditorToolbar_1 = require("./EditorToolbar");
|
|
7
|
+
Object.defineProperty(exports, "EditorToolbar", { enumerable: true, get: function () { return EditorToolbar_1.EditorToolbar; } });
|
|
8
|
+
Object.defineProperty(exports, "DEFAULT_EDITOR_TOOLBAR_ITEMS", { enumerable: true, get: function () { return EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS; } });
|
|
9
|
+
var addons_1 = require("./addons");
|
|
10
|
+
Object.defineProperty(exports, "MENTION_NODE_NAME", { enumerable: true, get: function () { return addons_1.MENTION_NODE_NAME; } });
|
|
11
|
+
Object.defineProperty(exports, "mentionNodeSpec", { enumerable: true, get: function () { return addons_1.mentionNodeSpec; } });
|
|
12
|
+
Object.defineProperty(exports, "withMentionsSchema", { enumerable: true, get: function () { return addons_1.withMentionsSchema; } });
|
|
13
|
+
Object.defineProperty(exports, "buildMentionFragmentJson", { enumerable: true, get: function () { return addons_1.buildMentionFragmentJson; } });
|
|
14
|
+
var schemas_1 = require("./schemas");
|
|
15
|
+
Object.defineProperty(exports, "tiptapSchema", { enumerable: true, get: function () { return schemas_1.tiptapSchema; } });
|
|
16
|
+
Object.defineProperty(exports, "prosemirrorSchema", { enumerable: true, get: function () { return schemas_1.prosemirrorSchema; } });
|
|
17
|
+
Object.defineProperty(exports, "IMAGE_NODE_NAME", { enumerable: true, get: function () { return schemas_1.IMAGE_NODE_NAME; } });
|
|
18
|
+
Object.defineProperty(exports, "imageNodeSpec", { enumerable: true, get: function () { return schemas_1.imageNodeSpec; } });
|
|
19
|
+
Object.defineProperty(exports, "withImagesSchema", { enumerable: true, get: function () { return schemas_1.withImagesSchema; } });
|
|
20
|
+
Object.defineProperty(exports, "buildImageFragmentJson", { enumerable: true, get: function () { return schemas_1.buildImageFragmentJson; } });
|
|
21
|
+
var YjsCollaboration_1 = require("./YjsCollaboration");
|
|
22
|
+
Object.defineProperty(exports, "createYjsCollaborationController", { enumerable: true, get: function () { return YjsCollaboration_1.createYjsCollaborationController; } });
|
|
23
|
+
Object.defineProperty(exports, "useYjsCollaboration", { enumerable: true, get: function () { return YjsCollaboration_1.useYjsCollaboration; } });
|
|
24
|
+
var NativeEditorBridge_1 = require("./NativeEditorBridge");
|
|
25
|
+
Object.defineProperty(exports, "encodeCollaborationStateBase64", { enumerable: true, get: function () { return NativeEditorBridge_1.encodeCollaborationStateBase64; } });
|
|
26
|
+
Object.defineProperty(exports, "decodeCollaborationStateBase64", { enumerable: true, get: function () { return NativeEditorBridge_1.decodeCollaborationStateBase64; } });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DocumentJSON } from './NativeEditorBridge';
|
|
2
|
+
export interface AttrSpec {
|
|
3
|
+
default?: unknown;
|
|
4
|
+
}
|
|
5
|
+
export interface NodeSpec {
|
|
6
|
+
name: string;
|
|
7
|
+
content: string;
|
|
8
|
+
group?: string;
|
|
9
|
+
attrs?: Record<string, AttrSpec>;
|
|
10
|
+
role: string;
|
|
11
|
+
htmlTag?: string;
|
|
12
|
+
isVoid?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface MarkSpec {
|
|
15
|
+
name: string;
|
|
16
|
+
attrs?: Record<string, AttrSpec>;
|
|
17
|
+
excludes?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SchemaDefinition {
|
|
20
|
+
nodes: NodeSpec[];
|
|
21
|
+
marks: MarkSpec[];
|
|
22
|
+
}
|
|
23
|
+
export interface ImageNodeAttributes {
|
|
24
|
+
src: string;
|
|
25
|
+
alt?: string | null;
|
|
26
|
+
title?: string | null;
|
|
27
|
+
width?: number | null;
|
|
28
|
+
height?: number | null;
|
|
29
|
+
}
|
|
30
|
+
export declare const IMAGE_NODE_NAME = "image";
|
|
31
|
+
export declare function imageNodeSpec(name?: string): NodeSpec;
|
|
32
|
+
export declare function withImagesSchema(schema: SchemaDefinition): SchemaDefinition;
|
|
33
|
+
export declare function buildImageFragmentJson(attrs: ImageNodeAttributes): DocumentJSON;
|
|
34
|
+
export declare const tiptapSchema: SchemaDefinition;
|
|
35
|
+
export declare const prosemirrorSchema: SchemaDefinition;
|
|
@@ -1,36 +1,56 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prosemirrorSchema = exports.tiptapSchema = exports.IMAGE_NODE_NAME = void 0;
|
|
4
|
+
exports.imageNodeSpec = imageNodeSpec;
|
|
5
|
+
exports.withImagesSchema = withImagesSchema;
|
|
6
|
+
exports.buildImageFragmentJson = buildImageFragmentJson;
|
|
7
|
+
exports.IMAGE_NODE_NAME = 'image';
|
|
8
|
+
function imageNodeSpec(name = exports.IMAGE_NODE_NAME) {
|
|
9
|
+
return {
|
|
10
|
+
name,
|
|
11
|
+
content: '',
|
|
12
|
+
group: 'block',
|
|
13
|
+
attrs: {
|
|
14
|
+
src: {},
|
|
15
|
+
alt: { default: null },
|
|
16
|
+
title: { default: null },
|
|
17
|
+
width: { default: null },
|
|
18
|
+
height: { default: null },
|
|
19
|
+
},
|
|
20
|
+
role: 'block',
|
|
21
|
+
htmlTag: 'img',
|
|
22
|
+
isVoid: true,
|
|
23
|
+
};
|
|
3
24
|
}
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
function withImagesSchema(schema) {
|
|
26
|
+
const hasImageNode = schema.nodes.some((node) => node.name === exports.IMAGE_NODE_NAME);
|
|
27
|
+
if (hasImageNode) {
|
|
28
|
+
return schema;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...schema,
|
|
32
|
+
nodes: [...schema.nodes, imageNodeSpec()],
|
|
33
|
+
};
|
|
13
34
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
35
|
+
function buildImageFragmentJson(attrs) {
|
|
36
|
+
return {
|
|
37
|
+
type: 'doc',
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: exports.IMAGE_NODE_NAME,
|
|
41
|
+
attrs,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
19
45
|
}
|
|
20
|
-
|
|
21
|
-
export interface SchemaDefinition {
|
|
22
|
-
nodes: NodeSpec[];
|
|
23
|
-
marks: MarkSpec[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const MARKS: MarkSpec[] = [
|
|
46
|
+
const MARKS = [
|
|
27
47
|
{ name: 'bold' },
|
|
28
48
|
{ name: 'italic' },
|
|
29
49
|
{ name: 'underline' },
|
|
30
50
|
{ name: 'strike' },
|
|
51
|
+
{ name: 'link', attrs: { href: {} } },
|
|
31
52
|
];
|
|
32
|
-
|
|
33
|
-
export const tiptapSchema: SchemaDefinition = {
|
|
53
|
+
exports.tiptapSchema = {
|
|
34
54
|
nodes: [
|
|
35
55
|
{
|
|
36
56
|
name: 'doc',
|
|
@@ -44,6 +64,13 @@ export const tiptapSchema: SchemaDefinition = {
|
|
|
44
64
|
role: 'textBlock',
|
|
45
65
|
htmlTag: 'p',
|
|
46
66
|
},
|
|
67
|
+
{
|
|
68
|
+
name: 'blockquote',
|
|
69
|
+
content: 'block+',
|
|
70
|
+
group: 'block',
|
|
71
|
+
role: 'block',
|
|
72
|
+
htmlTag: 'blockquote',
|
|
73
|
+
},
|
|
47
74
|
{
|
|
48
75
|
name: 'bulletList',
|
|
49
76
|
content: 'listItem+',
|
|
@@ -81,6 +108,7 @@ export const tiptapSchema: SchemaDefinition = {
|
|
|
81
108
|
htmlTag: 'hr',
|
|
82
109
|
isVoid: true,
|
|
83
110
|
},
|
|
111
|
+
imageNodeSpec(),
|
|
84
112
|
{
|
|
85
113
|
name: 'text',
|
|
86
114
|
content: '',
|
|
@@ -90,8 +118,7 @@ export const tiptapSchema: SchemaDefinition = {
|
|
|
90
118
|
],
|
|
91
119
|
marks: MARKS,
|
|
92
120
|
};
|
|
93
|
-
|
|
94
|
-
export const prosemirrorSchema: SchemaDefinition = {
|
|
121
|
+
exports.prosemirrorSchema = {
|
|
95
122
|
nodes: [
|
|
96
123
|
{
|
|
97
124
|
name: 'doc',
|
|
@@ -105,6 +132,13 @@ export const prosemirrorSchema: SchemaDefinition = {
|
|
|
105
132
|
role: 'textBlock',
|
|
106
133
|
htmlTag: 'p',
|
|
107
134
|
},
|
|
135
|
+
{
|
|
136
|
+
name: 'blockquote',
|
|
137
|
+
content: 'block+',
|
|
138
|
+
group: 'block',
|
|
139
|
+
role: 'block',
|
|
140
|
+
htmlTag: 'blockquote',
|
|
141
|
+
},
|
|
108
142
|
{
|
|
109
143
|
name: 'bullet_list',
|
|
110
144
|
content: 'list_item+',
|
|
@@ -142,6 +176,7 @@ export const prosemirrorSchema: SchemaDefinition = {
|
|
|
142
176
|
htmlTag: 'hr',
|
|
143
177
|
isVoid: true,
|
|
144
178
|
},
|
|
179
|
+
imageNodeSpec('image'),
|
|
145
180
|
{
|
|
146
181
|
name: 'text',
|
|
147
182
|
content: '',
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NativeEditorBridge, type ActiveState, type HistoryState, type RenderElement, type Selection } from './NativeEditorBridge';
|
|
2
|
+
export interface UseNativeEditorOptions {
|
|
3
|
+
/** Maximum character length. Omit for no limit. */
|
|
4
|
+
maxLength?: number;
|
|
5
|
+
/** Initial HTML content to load after creation. */
|
|
6
|
+
initialHtml?: string;
|
|
7
|
+
/** Called when content changes after an editing operation. */
|
|
8
|
+
onChange?: (html: string) => void;
|
|
9
|
+
/** Called when selection changes. */
|
|
10
|
+
onSelectionChange?: (selection: Selection) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface UseNativeEditorReturn {
|
|
13
|
+
/** The underlying bridge instance, or null before creation. */
|
|
14
|
+
bridge: NativeEditorBridge | null;
|
|
15
|
+
/** Whether the editor has been created and is ready. */
|
|
16
|
+
isReady: boolean;
|
|
17
|
+
/** Current selection state. */
|
|
18
|
+
selection: Selection;
|
|
19
|
+
/** Currently active marks/nodes at the selection. */
|
|
20
|
+
activeState: ActiveState;
|
|
21
|
+
/** Current undo/redo availability. */
|
|
22
|
+
historyState: HistoryState;
|
|
23
|
+
/** Current render elements. */
|
|
24
|
+
renderElements: RenderElement[];
|
|
25
|
+
/** Toggle a mark (e.g. 'bold', 'italic', 'underline'). */
|
|
26
|
+
toggleMark: (markType: string) => void;
|
|
27
|
+
/** Undo the last operation. */
|
|
28
|
+
undo: () => void;
|
|
29
|
+
/** Redo the last undone operation. */
|
|
30
|
+
redo: () => void;
|
|
31
|
+
/** Toggle blockquote wrapping around the current block selection. */
|
|
32
|
+
toggleBlockquote: () => void;
|
|
33
|
+
/** Insert text at a position. */
|
|
34
|
+
insertText: (pos: number, text: string) => void;
|
|
35
|
+
/** Delete a range [from, to). */
|
|
36
|
+
deleteRange: (from: number, to: number) => void;
|
|
37
|
+
/** Get the current HTML content. */
|
|
38
|
+
getHtml: () => string;
|
|
39
|
+
}
|
|
40
|
+
export declare function useNativeEditor(options?: UseNativeEditorOptions): UseNativeEditorReturn;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useNativeEditor = useNativeEditor;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const NativeEditorBridge_1 = require("./NativeEditorBridge");
|
|
6
|
+
const DEFAULT_SELECTION = { type: 'text', anchor: 0, head: 0 };
|
|
7
|
+
const DEFAULT_ACTIVE_STATE = {
|
|
8
|
+
marks: {},
|
|
9
|
+
markAttrs: {},
|
|
10
|
+
nodes: {},
|
|
11
|
+
commands: {},
|
|
12
|
+
allowedMarks: [],
|
|
13
|
+
insertableNodes: [],
|
|
14
|
+
};
|
|
15
|
+
const DEFAULT_HISTORY_STATE = { canUndo: false, canRedo: false };
|
|
16
|
+
function useNativeEditor(options = {}) {
|
|
17
|
+
const { maxLength, initialHtml, onChange, onSelectionChange } = options;
|
|
18
|
+
const bridgeRef = (0, react_1.useRef)(null);
|
|
19
|
+
const onChangeRef = (0, react_1.useRef)(onChange);
|
|
20
|
+
onChangeRef.current = onChange;
|
|
21
|
+
const onSelectionChangeRef = (0, react_1.useRef)(onSelectionChange);
|
|
22
|
+
onSelectionChangeRef.current = onSelectionChange;
|
|
23
|
+
const [isReady, setIsReady] = (0, react_1.useState)(false);
|
|
24
|
+
const [selection, setSelection] = (0, react_1.useState)(DEFAULT_SELECTION);
|
|
25
|
+
const [activeState, setActiveState] = (0, react_1.useState)(DEFAULT_ACTIVE_STATE);
|
|
26
|
+
const [historyState, setHistoryState] = (0, react_1.useState)(DEFAULT_HISTORY_STATE);
|
|
27
|
+
const [renderElements, setRenderElements] = (0, react_1.useState)([]);
|
|
28
|
+
const syncStateFromUpdate = (0, react_1.useCallback)((update) => {
|
|
29
|
+
if (!update)
|
|
30
|
+
return;
|
|
31
|
+
setRenderElements(update.renderElements);
|
|
32
|
+
setSelection(update.selection);
|
|
33
|
+
setActiveState(update.activeState);
|
|
34
|
+
setHistoryState(update.historyState);
|
|
35
|
+
}, []);
|
|
36
|
+
const applyUpdate = (0, react_1.useCallback)((update) => {
|
|
37
|
+
if (!update)
|
|
38
|
+
return;
|
|
39
|
+
syncStateFromUpdate(update);
|
|
40
|
+
onSelectionChangeRef.current?.(update.selection);
|
|
41
|
+
// Fetch current HTML and notify onChange
|
|
42
|
+
if (onChangeRef.current && bridgeRef.current && !bridgeRef.current.isDestroyed) {
|
|
43
|
+
const html = bridgeRef.current.getHtml();
|
|
44
|
+
onChangeRef.current(html);
|
|
45
|
+
}
|
|
46
|
+
}, [syncStateFromUpdate]);
|
|
47
|
+
(0, react_1.useEffect)(() => {
|
|
48
|
+
const bridge = NativeEditorBridge_1.NativeEditorBridge.create(maxLength != null ? { maxLength } : undefined);
|
|
49
|
+
bridgeRef.current = bridge;
|
|
50
|
+
if (initialHtml) {
|
|
51
|
+
bridge.setHtml(initialHtml);
|
|
52
|
+
}
|
|
53
|
+
syncStateFromUpdate(bridge.getCurrentState());
|
|
54
|
+
setIsReady(true);
|
|
55
|
+
return () => {
|
|
56
|
+
bridge.destroy();
|
|
57
|
+
bridgeRef.current = null;
|
|
58
|
+
setIsReady(false);
|
|
59
|
+
};
|
|
60
|
+
}, [maxLength, initialHtml, syncStateFromUpdate]);
|
|
61
|
+
const toggleMark = (0, react_1.useCallback)((markType) => {
|
|
62
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
63
|
+
return;
|
|
64
|
+
const update = bridgeRef.current.toggleMark(markType);
|
|
65
|
+
applyUpdate(update);
|
|
66
|
+
}, [applyUpdate]);
|
|
67
|
+
const undo = (0, react_1.useCallback)(() => {
|
|
68
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
69
|
+
return;
|
|
70
|
+
const update = bridgeRef.current.undo();
|
|
71
|
+
applyUpdate(update);
|
|
72
|
+
}, [applyUpdate]);
|
|
73
|
+
const redo = (0, react_1.useCallback)(() => {
|
|
74
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
75
|
+
return;
|
|
76
|
+
const update = bridgeRef.current.redo();
|
|
77
|
+
applyUpdate(update);
|
|
78
|
+
}, [applyUpdate]);
|
|
79
|
+
const toggleBlockquote = (0, react_1.useCallback)(() => {
|
|
80
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
81
|
+
return;
|
|
82
|
+
const update = bridgeRef.current.toggleBlockquote();
|
|
83
|
+
applyUpdate(update);
|
|
84
|
+
}, [applyUpdate]);
|
|
85
|
+
const insertText = (0, react_1.useCallback)((pos, text) => {
|
|
86
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
87
|
+
return;
|
|
88
|
+
const update = bridgeRef.current.insertText(pos, text);
|
|
89
|
+
applyUpdate(update);
|
|
90
|
+
}, [applyUpdate]);
|
|
91
|
+
const deleteRange = (0, react_1.useCallback)((from, to) => {
|
|
92
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
93
|
+
return;
|
|
94
|
+
const update = bridgeRef.current.deleteRange(from, to);
|
|
95
|
+
applyUpdate(update);
|
|
96
|
+
}, [applyUpdate]);
|
|
97
|
+
const getHtml = (0, react_1.useCallback)(() => {
|
|
98
|
+
if (!bridgeRef.current || bridgeRef.current.isDestroyed)
|
|
99
|
+
return '';
|
|
100
|
+
return bridgeRef.current.getHtml();
|
|
101
|
+
}, []);
|
|
102
|
+
return {
|
|
103
|
+
bridge: bridgeRef.current,
|
|
104
|
+
isReady,
|
|
105
|
+
selection,
|
|
106
|
+
activeState,
|
|
107
|
+
historyState,
|
|
108
|
+
renderElements,
|
|
109
|
+
toggleMark,
|
|
110
|
+
undo,
|
|
111
|
+
redo,
|
|
112
|
+
toggleBlockquote,
|
|
113
|
+
insertText,
|
|
114
|
+
deleteRange,
|
|
115
|
+
getHtml,
|
|
116
|
+
};
|
|
117
|
+
}
|
package/ios/EditorAddons.swift
CHANGED
|
@@ -131,12 +131,18 @@ final class MentionSuggestionChipButton: UIButton {
|
|
|
131
131
|
private let subtitleLabelView = UILabel()
|
|
132
132
|
private let stackView = UIStackView()
|
|
133
133
|
private var theme: EditorMentionTheme?
|
|
134
|
+
private var toolbarAppearance: EditorToolbarAppearance = .custom
|
|
134
135
|
|
|
135
136
|
let suggestion: NativeMentionSuggestion
|
|
136
137
|
|
|
137
|
-
init(
|
|
138
|
+
init(
|
|
139
|
+
suggestion: NativeMentionSuggestion,
|
|
140
|
+
theme: EditorMentionTheme?,
|
|
141
|
+
toolbarAppearance: EditorToolbarAppearance = .custom
|
|
142
|
+
) {
|
|
138
143
|
self.suggestion = suggestion
|
|
139
144
|
self.theme = theme
|
|
145
|
+
self.toolbarAppearance = toolbarAppearance
|
|
140
146
|
super.init(frame: .zero)
|
|
141
147
|
translatesAutoresizingMaskIntoConstraints = false
|
|
142
148
|
layer.cornerRadius = 12
|
|
@@ -179,7 +185,7 @@ final class MentionSuggestionChipButton: UIButton {
|
|
|
179
185
|
|
|
180
186
|
addTarget(self, action: #selector(handleTouchDown), for: [.touchDown, .touchDragEnter])
|
|
181
187
|
addTarget(self, action: #selector(handleTouchUp), for: [.touchCancel, .touchDragExit, .touchUpInside, .touchUpOutside])
|
|
182
|
-
apply(theme: theme)
|
|
188
|
+
apply(theme: theme, toolbarAppearance: toolbarAppearance)
|
|
183
189
|
updateAppearance(highlighted: false)
|
|
184
190
|
}
|
|
185
191
|
|
|
@@ -187,8 +193,9 @@ final class MentionSuggestionChipButton: UIButton {
|
|
|
187
193
|
return nil
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
func apply(theme: EditorMentionTheme
|
|
196
|
+
func apply(theme: EditorMentionTheme?, toolbarAppearance: EditorToolbarAppearance = .custom) {
|
|
191
197
|
self.theme = theme
|
|
198
|
+
self.toolbarAppearance = toolbarAppearance
|
|
192
199
|
layer.cornerRadius = theme?.borderRadius ?? 12
|
|
193
200
|
layer.borderColor = (theme?.borderColor ?? UIColor.clear).cgColor
|
|
194
201
|
layer.borderWidth = theme?.borderWidth ?? 0
|
|
@@ -211,6 +218,18 @@ final class MentionSuggestionChipButton: UIButton {
|
|
|
211
218
|
}
|
|
212
219
|
|
|
213
220
|
private func updateAppearance(highlighted: Bool) {
|
|
221
|
+
if toolbarAppearance == .native {
|
|
222
|
+
layer.cornerRadius = 18
|
|
223
|
+
layer.borderColor = UIColor.clear.cgColor
|
|
224
|
+
layer.borderWidth = 0
|
|
225
|
+
backgroundColor = highlighted
|
|
226
|
+
? UIColor.white.withAlphaComponent(0.18)
|
|
227
|
+
: .clear
|
|
228
|
+
titleLabelView.textColor = .label
|
|
229
|
+
subtitleLabelView.textColor = .secondaryLabel
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
214
233
|
backgroundColor = highlighted
|
|
215
234
|
? (theme?.optionHighlightedBackgroundColor ?? UIColor.systemBlue.withAlphaComponent(0.12))
|
|
216
235
|
: (theme?.backgroundColor ?? UIColor.secondarySystemBackground)
|
|
@@ -225,4 +244,8 @@ final class MentionSuggestionChipButton: UIButton {
|
|
|
225
244
|
&& !titleLabelView.isUserInteractionEnabled
|
|
226
245
|
&& !subtitleLabelView.isUserInteractionEnabled
|
|
227
246
|
}
|
|
247
|
+
|
|
248
|
+
func usesNativeAppearanceForTesting() -> Bool {
|
|
249
|
+
toolbarAppearance == .native
|
|
250
|
+
}
|
|
228
251
|
}
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>libeditor_core.a</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>libeditor_core.a</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
-
<string>x86_64</string>
|
|
18
17
|
</array>
|
|
19
18
|
<key>SupportedPlatform</key>
|
|
20
19
|
<string>ios</string>
|
|
21
|
-
<key>SupportedPlatformVariant</key>
|
|
22
|
-
<string>simulator</string>
|
|
23
20
|
</dict>
|
|
24
21
|
<dict>
|
|
25
22
|
<key>BinaryPath</key>
|
|
26
23
|
<string>libeditor_core.a</string>
|
|
27
24
|
<key>LibraryIdentifier</key>
|
|
28
|
-
<string>ios-
|
|
25
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
29
26
|
<key>LibraryPath</key>
|
|
30
27
|
<string>libeditor_core.a</string>
|
|
31
28
|
<key>SupportedArchitectures</key>
|
|
32
29
|
<array>
|
|
33
30
|
<string>arm64</string>
|
|
31
|
+
<string>x86_64</string>
|
|
34
32
|
</array>
|
|
35
33
|
<key>SupportedPlatform</key>
|
|
36
34
|
<string>ios</string>
|
|
35
|
+
<key>SupportedPlatformVariant</key>
|
|
36
|
+
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|
|
Binary file
|
|
Binary file
|