@apollohg/react-native-prose-editor 0.2.0 → 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/android/src/main/java/com/apollohg/editor/EditorEditText.kt +228 -2
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +4 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +3 -0
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +6 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +347 -10
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +76 -8
- package/dist/EditorToolbar.d.ts +9 -2
- package/dist/EditorToolbar.js +20 -10
- package/dist/NativeEditorBridge.d.ts +2 -0
- package/dist/NativeEditorBridge.js +3 -0
- package/dist/NativeRichTextEditor.d.ts +17 -1
- package/dist/NativeRichTextEditor.js +94 -37
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -1
- package/dist/schemas.d.ts +12 -0
- package/dist/schemas.js +45 -1
- 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 +0 -16
- package/ios/Generated_editor_core.swift +20 -2
- package/ios/NativeEditorExpoView.swift +51 -16
- package/ios/NativeEditorModule.swift +3 -0
- package/ios/RenderBridge.swift +208 -0
- package/ios/RichTextEditorView.swift +896 -15
- package/ios/editor_coreFFI/editor_coreFFI.h +11 -0
- package/package.json +1 -1
- 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 +25 -2
package/dist/EditorToolbar.js
CHANGED
|
@@ -65,6 +65,7 @@ const DEFAULT_GLYPH_ICONS = {
|
|
|
65
65
|
underline: 'U',
|
|
66
66
|
strike: 'S',
|
|
67
67
|
link: '🔗',
|
|
68
|
+
image: '🖼',
|
|
68
69
|
blockquote: '❝',
|
|
69
70
|
bulletList: '•≡',
|
|
70
71
|
orderedList: '1.',
|
|
@@ -81,6 +82,7 @@ const DEFAULT_MATERIAL_ICONS = {
|
|
|
81
82
|
underline: 'format-underlined',
|
|
82
83
|
strike: 'strikethrough-s',
|
|
83
84
|
link: 'link',
|
|
85
|
+
image: 'image',
|
|
84
86
|
blockquote: 'format-quote',
|
|
85
87
|
bulletList: 'format-list-bulleted',
|
|
86
88
|
orderedList: 'format-list-numbered',
|
|
@@ -91,7 +93,7 @@ const DEFAULT_MATERIAL_ICONS = {
|
|
|
91
93
|
undo: 'undo',
|
|
92
94
|
redo: 'redo',
|
|
93
95
|
};
|
|
94
|
-
function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic, onToggleUnderline, onToggleStrike, onToggleBulletList, onToggleBlockquote, onToggleOrderedList, onIndentList, onOutdentList, onInsertHorizontalRule, onInsertLineBreak, onUndo, onRedo, onToggleMark, onToggleListType, onInsertNodeType, onRunCommand, onToolbarAction, onRequestLink, toolbarItems = exports.DEFAULT_EDITOR_TOOLBAR_ITEMS, theme, showTopBorder = true, }) {
|
|
96
|
+
function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic, onToggleUnderline, onToggleStrike, onToggleBulletList, onToggleBlockquote, onToggleOrderedList, onIndentList, onOutdentList, onInsertHorizontalRule, onInsertLineBreak, onUndo, onRedo, onToggleMark, onToggleListType, onInsertNodeType, onRunCommand, onToolbarAction, onRequestLink, onRequestImage, toolbarItems = exports.DEFAULT_EDITOR_TOOLBAR_ITEMS, theme, showTopBorder = true, }) {
|
|
95
97
|
const marks = activeState.marks ?? {};
|
|
96
98
|
const nodes = activeState.nodes ?? {};
|
|
97
99
|
const commands = activeState.commands ?? {};
|
|
@@ -130,6 +132,8 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
130
132
|
: (onToggleOrderedList ?? null);
|
|
131
133
|
case 'link':
|
|
132
134
|
return onRequestLink ?? null;
|
|
135
|
+
case 'image':
|
|
136
|
+
return onRequestImage ?? null;
|
|
133
137
|
case 'blockquote':
|
|
134
138
|
return onToggleBlockquote ?? null;
|
|
135
139
|
case 'node':
|
|
@@ -170,6 +174,7 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
170
174
|
onRedo,
|
|
171
175
|
onRunCommand,
|
|
172
176
|
onRequestLink,
|
|
177
|
+
onRequestImage,
|
|
173
178
|
onToolbarAction,
|
|
174
179
|
onToggleBold,
|
|
175
180
|
onToggleBlockquote,
|
|
@@ -187,15 +192,17 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
187
192
|
? `mark:${item.mark}:${index}`
|
|
188
193
|
: item.type === 'link'
|
|
189
194
|
? `link:${index}`
|
|
190
|
-
: item.type === '
|
|
191
|
-
? `
|
|
192
|
-
: item.type === '
|
|
193
|
-
? `
|
|
194
|
-
: item.type === '
|
|
195
|
-
? `
|
|
196
|
-
: item.type === '
|
|
197
|
-
? `
|
|
198
|
-
:
|
|
195
|
+
: item.type === 'image'
|
|
196
|
+
? `image:${index}`
|
|
197
|
+
: item.type === 'blockquote'
|
|
198
|
+
? `blockquote:${index}`
|
|
199
|
+
: item.type === 'list'
|
|
200
|
+
? `list:${item.listType}:${index}`
|
|
201
|
+
: item.type === 'command'
|
|
202
|
+
? `command:${item.command}:${index}`
|
|
203
|
+
: item.type === 'node'
|
|
204
|
+
? `node:${item.nodeType}:${index}`
|
|
205
|
+
: `action:${item.key}:${index}`), []);
|
|
199
206
|
const renderedItems = [];
|
|
200
207
|
for (let index = 0; index < toolbarItems.length; index += 1) {
|
|
201
208
|
const item = toolbarItems[index];
|
|
@@ -221,6 +228,9 @@ function EditorToolbar({ activeState, historyState, onToggleBold, onToggleItalic
|
|
|
221
228
|
isActive = isMarkActive('link');
|
|
222
229
|
isDisabled = !allowedMarks.includes('link') || !onRequestLink;
|
|
223
230
|
break;
|
|
231
|
+
case 'image':
|
|
232
|
+
isDisabled = !insertableNodes.includes('image') || !onRequestImage;
|
|
233
|
+
break;
|
|
224
234
|
case 'blockquote':
|
|
225
235
|
isActive = !!nodes['blockquote'];
|
|
226
236
|
isDisabled = !commands['toggleBlockquote'];
|
|
@@ -87,6 +87,7 @@ export interface RenderElement {
|
|
|
87
87
|
depth?: number;
|
|
88
88
|
docPos?: number;
|
|
89
89
|
label?: string;
|
|
90
|
+
attrs?: Record<string, unknown>;
|
|
90
91
|
listContext?: ListContext;
|
|
91
92
|
}
|
|
92
93
|
export interface ActiveState {
|
|
@@ -139,6 +140,7 @@ export declare class NativeEditorBridge {
|
|
|
139
140
|
static create(config?: {
|
|
140
141
|
maxLength?: number;
|
|
141
142
|
schemaJson?: string;
|
|
143
|
+
allowBase64Images?: boolean;
|
|
142
144
|
}): NativeEditorBridge;
|
|
143
145
|
/** The underlying native editor ID. */
|
|
144
146
|
get editorId(): number;
|
|
@@ -227,6 +227,9 @@ class NativeEditorBridge {
|
|
|
227
227
|
const configObj = {};
|
|
228
228
|
if (config?.maxLength != null)
|
|
229
229
|
configObj.maxLength = config.maxLength;
|
|
230
|
+
if (config?.allowBase64Images != null) {
|
|
231
|
+
configObj.allowBase64Images = config.allowBase64Images;
|
|
232
|
+
}
|
|
230
233
|
if (config?.schemaJson != null) {
|
|
231
234
|
try {
|
|
232
235
|
configObj.schema = JSON.parse(config.schemaJson);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
-
import { type ActiveState, type DocumentJSON, type Selection } from './NativeEditorBridge';
|
|
3
|
+
import { type ActiveState, type DocumentJSON, type HistoryState, type Selection } from './NativeEditorBridge';
|
|
4
4
|
import { type EditorToolbarItem } from './EditorToolbar';
|
|
5
5
|
import { type EditorTheme } from './EditorTheme';
|
|
6
6
|
import { type EditorAddons } from './addons';
|
|
7
7
|
import { type SchemaDefinition } from './schemas';
|
|
8
|
+
import { type ImageNodeAttributes } from './schemas';
|
|
8
9
|
export type NativeRichTextEditorHeightBehavior = 'fixed' | 'autoGrow';
|
|
9
10
|
export type NativeRichTextEditorToolbarPlacement = 'keyboard' | 'inline';
|
|
10
11
|
export interface RemoteSelectionDecoration {
|
|
@@ -23,6 +24,11 @@ export interface LinkRequestContext {
|
|
|
23
24
|
setLink: (href: string) => void;
|
|
24
25
|
unsetLink: () => void;
|
|
25
26
|
}
|
|
27
|
+
export interface ImageRequestContext {
|
|
28
|
+
selection: Selection;
|
|
29
|
+
allowBase64: boolean;
|
|
30
|
+
insertImage: (src: string, attrs?: Omit<ImageNodeAttributes, 'src'>) => void;
|
|
31
|
+
}
|
|
26
32
|
export interface NativeRichTextEditorProps {
|
|
27
33
|
/** Initial content as HTML (uncontrolled mode). */
|
|
28
34
|
initialContent?: string;
|
|
@@ -54,6 +60,12 @@ export interface NativeRichTextEditorProps {
|
|
|
54
60
|
onToolbarAction?: (key: string) => void;
|
|
55
61
|
/** Called when a toolbar link item is pressed so the host can collect/edit a URL. */
|
|
56
62
|
onRequestLink?: (context: LinkRequestContext) => void;
|
|
63
|
+
/** Called when a toolbar image item is pressed so the host can choose an image source. */
|
|
64
|
+
onRequestImage?: (context: ImageRequestContext) => void;
|
|
65
|
+
/** Whether `data:image/...` sources are accepted for image insertion and HTML parsing. */
|
|
66
|
+
allowBase64Images?: boolean;
|
|
67
|
+
/** Whether selected images show native resize handles. */
|
|
68
|
+
allowImageResizing?: boolean;
|
|
57
69
|
/** Called when content changes with the current HTML. */
|
|
58
70
|
onContentChange?: (html: string) => void;
|
|
59
71
|
/** Called when content changes with the current ProseMirror JSON. */
|
|
@@ -62,6 +74,8 @@ export interface NativeRichTextEditorProps {
|
|
|
62
74
|
onSelectionChange?: (selection: Selection) => void;
|
|
63
75
|
/** Called when active formatting state changes. */
|
|
64
76
|
onActiveStateChange?: (state: ActiveState) => void;
|
|
77
|
+
/** Called when undo/redo availability changes. */
|
|
78
|
+
onHistoryStateChange?: (state: HistoryState) => void;
|
|
65
79
|
/** Called when the editor gains focus. */
|
|
66
80
|
onFocus?: () => void;
|
|
67
81
|
/** Called when the editor loses focus. */
|
|
@@ -98,6 +112,8 @@ export interface NativeRichTextEditorRef {
|
|
|
98
112
|
outdentListItem(): void;
|
|
99
113
|
/** Insert a void node (e.g. 'horizontalRule'). */
|
|
100
114
|
insertNode(nodeType: string): void;
|
|
115
|
+
/** Insert a block image node with the given source and optional metadata. */
|
|
116
|
+
insertImage(src: string, attrs?: Omit<ImageNodeAttributes, 'src'>): void;
|
|
101
117
|
/** Insert text at the current cursor position. */
|
|
102
118
|
insertText(text: string): void;
|
|
103
119
|
/** Insert HTML content at the current selection. */
|
|
@@ -10,11 +10,16 @@ const EditorToolbar_1 = require("./EditorToolbar");
|
|
|
10
10
|
const EditorTheme_1 = require("./EditorTheme");
|
|
11
11
|
const addons_1 = require("./addons");
|
|
12
12
|
const schemas_1 = require("./schemas");
|
|
13
|
+
const schemas_2 = require("./schemas");
|
|
13
14
|
const NativeEditorView = (0, expo_modules_core_1.requireNativeViewManager)('NativeEditor');
|
|
14
15
|
const DEV_NATIVE_VIEW_KEY = __DEV__
|
|
15
16
|
? `native-editor-dev:${Math.random().toString(36).slice(2)}`
|
|
16
17
|
: 'native-editor';
|
|
17
18
|
const LINK_TOOLBAR_ACTION_KEY = '__native-editor-link__';
|
|
19
|
+
const IMAGE_TOOLBAR_ACTION_KEY = '__native-editor-image__';
|
|
20
|
+
function isImageDataUrl(value) {
|
|
21
|
+
return /^data:image\//i.test(value.trim());
|
|
22
|
+
}
|
|
18
23
|
function isPromiseLike(value) {
|
|
19
24
|
return (value != null &&
|
|
20
25
|
typeof value === 'object' &&
|
|
@@ -59,7 +64,7 @@ function serializeRemoteSelections(remoteSelections) {
|
|
|
59
64
|
}
|
|
60
65
|
return JSON.stringify(remoteSelections);
|
|
61
66
|
}
|
|
62
|
-
exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, schema, placeholder, editable = true, maxLength, autoFocus = false, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, }, ref) {
|
|
67
|
+
exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEditor({ initialContent, initialJSON, value, valueJSON, schema, placeholder, editable = true, maxLength, autoFocus = false, heightBehavior = 'autoGrow', showToolbar = true, toolbarPlacement = 'keyboard', toolbarItems = EditorToolbar_1.DEFAULT_EDITOR_TOOLBAR_ITEMS, onToolbarAction, onRequestLink, onRequestImage, onContentChange, onContentChangeJSON, onSelectionChange, onActiveStateChange, onHistoryStateChange, onFocus, onBlur, style, containerStyle, theme, addons, remoteSelections, allowBase64Images = false, allowImageResizing = true, }, ref) {
|
|
63
68
|
const bridgeRef = (0, react_1.useRef)(null);
|
|
64
69
|
const nativeViewRef = (0, react_1.useRef)(null);
|
|
65
70
|
const [isReady, setIsReady] = (0, react_1.useState)(false);
|
|
@@ -97,6 +102,8 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
97
102
|
onSelectionChangeRef.current = onSelectionChange;
|
|
98
103
|
const onActiveStateChangeRef = (0, react_1.useRef)(onActiveStateChange);
|
|
99
104
|
onActiveStateChangeRef.current = onActiveStateChange;
|
|
105
|
+
const onHistoryStateChangeRef = (0, react_1.useRef)(onHistoryStateChange);
|
|
106
|
+
onHistoryStateChangeRef.current = onHistoryStateChange;
|
|
100
107
|
const onFocusRef = (0, react_1.useRef)(onFocus);
|
|
101
108
|
onFocusRef.current = onFocus;
|
|
102
109
|
const onBlurRef = (0, react_1.useRef)(onBlur);
|
|
@@ -171,6 +178,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
171
178
|
}
|
|
172
179
|
syncStateFromUpdate(update);
|
|
173
180
|
onActiveStateChangeRef.current?.(update.activeState);
|
|
181
|
+
onHistoryStateChangeRef.current?.(update.historyState);
|
|
174
182
|
if (!options?.suppressContentCallbacks) {
|
|
175
183
|
if (onContentChangeRef.current && bridgeRef.current) {
|
|
176
184
|
onContentChangeRef.current(bridgeRef.current.getHtml());
|
|
@@ -187,7 +195,10 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
187
195
|
? (0, addons_1.withMentionsSchema)(schema ?? schemas_1.tiptapSchema)
|
|
188
196
|
: schema;
|
|
189
197
|
const schemaJson = effectiveSchema ? JSON.stringify(effectiveSchema) : undefined;
|
|
190
|
-
const
|
|
198
|
+
const bridgeConfig = maxLength != null || schemaJson || allowBase64Images
|
|
199
|
+
? { maxLength, schemaJson, allowBase64Images }
|
|
200
|
+
: undefined;
|
|
201
|
+
const bridge = NativeEditorBridge_1.NativeEditorBridge.create(bridgeConfig);
|
|
191
202
|
bridgeRef.current = bridge;
|
|
192
203
|
setEditorInstanceId(bridge.editorId);
|
|
193
204
|
// Four-way content initialization: value > valueJSON > initialJSON > initialContent
|
|
@@ -213,7 +224,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
213
224
|
setIsReady(false);
|
|
214
225
|
};
|
|
215
226
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
216
|
-
}, [schema, maxLength, syncStateFromUpdate, Boolean(addons?.mentions)]);
|
|
227
|
+
}, [schema, maxLength, syncStateFromUpdate, Boolean(addons?.mentions), allowBase64Images]);
|
|
217
228
|
(0, react_1.useEffect)(() => {
|
|
218
229
|
if (value == null)
|
|
219
230
|
return;
|
|
@@ -280,6 +291,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
280
291
|
return;
|
|
281
292
|
syncStateFromUpdate(update);
|
|
282
293
|
onActiveStateChangeRef.current?.(update.activeState);
|
|
294
|
+
onHistoryStateChangeRef.current?.(update.historyState);
|
|
283
295
|
if (onContentChangeRef.current) {
|
|
284
296
|
onContentChangeRef.current(bridgeRef.current.getHtml());
|
|
285
297
|
}
|
|
@@ -312,6 +324,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
312
324
|
selectionRef.current = nextSelection;
|
|
313
325
|
if (currentState) {
|
|
314
326
|
onActiveStateChangeRef.current?.(currentState.activeState);
|
|
327
|
+
onHistoryStateChangeRef.current?.(currentState.historyState);
|
|
315
328
|
}
|
|
316
329
|
onSelectionChangeRef.current?.(nextSelection);
|
|
317
330
|
}, [syncStateFromUpdate]);
|
|
@@ -334,25 +347,42 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
334
347
|
return;
|
|
335
348
|
setAutoGrowHeight((prev) => (prev === nextHeight ? prev : nextHeight));
|
|
336
349
|
}, [autoGrowHeight, heightBehavior]);
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (
|
|
341
|
-
const { anchor, head } = requestSelection;
|
|
342
|
-
if (anchor == null || head == null) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
bridgeRef.current?.setSelection(anchor, head);
|
|
350
|
+
const restoreSelection = (0, react_1.useCallback)((selection) => {
|
|
351
|
+
if (selection.type === 'text') {
|
|
352
|
+
const { anchor, head } = selection;
|
|
353
|
+
if (anchor == null || head == null) {
|
|
346
354
|
return;
|
|
347
355
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
356
|
+
bridgeRef.current?.setSelection(anchor, head);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (selection.type === 'node') {
|
|
360
|
+
const { pos } = selection;
|
|
361
|
+
if (pos == null) {
|
|
362
|
+
return;
|
|
354
363
|
}
|
|
355
|
-
|
|
364
|
+
bridgeRef.current?.setSelection(pos, pos);
|
|
365
|
+
}
|
|
366
|
+
}, []);
|
|
367
|
+
const insertImage = (0, react_1.useCallback)((src, attrs, selection) => {
|
|
368
|
+
const trimmedSrc = src.trim();
|
|
369
|
+
if (!trimmedSrc)
|
|
370
|
+
return;
|
|
371
|
+
if (!allowBase64Images && isImageDataUrl(trimmedSrc)) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
runAndApply(() => {
|
|
375
|
+
if (selection) {
|
|
376
|
+
restoreSelection(selection);
|
|
377
|
+
}
|
|
378
|
+
return (bridgeRef.current?.insertContentJson((0, schemas_2.buildImageFragmentJson)({
|
|
379
|
+
src: trimmedSrc,
|
|
380
|
+
...(attrs ?? {}),
|
|
381
|
+
})) ?? null);
|
|
382
|
+
});
|
|
383
|
+
}, [allowBase64Images, restoreSelection, runAndApply]);
|
|
384
|
+
const openLinkRequest = (0, react_1.useCallback)(() => {
|
|
385
|
+
const requestSelection = selectionRef.current;
|
|
356
386
|
onRequestLink?.({
|
|
357
387
|
href: currentLinkHref,
|
|
358
388
|
isActive: activeState.marks.link === true,
|
|
@@ -362,7 +392,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
362
392
|
if (!trimmedHref)
|
|
363
393
|
return;
|
|
364
394
|
runAndApply(() => {
|
|
365
|
-
|
|
395
|
+
restoreSelection(requestSelection);
|
|
366
396
|
return (bridgeRef.current?.setMark('link', {
|
|
367
397
|
href: trimmedHref,
|
|
368
398
|
}) ?? null);
|
|
@@ -370,19 +400,31 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
370
400
|
},
|
|
371
401
|
unsetLink: () => {
|
|
372
402
|
runAndApply(() => {
|
|
373
|
-
|
|
403
|
+
restoreSelection(requestSelection);
|
|
374
404
|
return bridgeRef.current?.unsetMark('link') ?? null;
|
|
375
405
|
}, { skipNativeApplyIfContentUnchanged: true });
|
|
376
406
|
},
|
|
377
407
|
});
|
|
378
|
-
}, [activeState.marks.link, currentLinkHref, onRequestLink, runAndApply]);
|
|
408
|
+
}, [activeState.marks.link, currentLinkHref, onRequestLink, restoreSelection, runAndApply]);
|
|
409
|
+
const openImageRequest = (0, react_1.useCallback)(() => {
|
|
410
|
+
const requestSelection = selectionRef.current;
|
|
411
|
+
onRequestImage?.({
|
|
412
|
+
selection: requestSelection,
|
|
413
|
+
allowBase64: allowBase64Images,
|
|
414
|
+
insertImage: (src, attrs) => insertImage(src, attrs, requestSelection),
|
|
415
|
+
});
|
|
416
|
+
}, [allowBase64Images, insertImage, onRequestImage]);
|
|
379
417
|
const handleToolbarAction = (0, react_1.useCallback)((event) => {
|
|
380
418
|
if (event.nativeEvent.key === LINK_TOOLBAR_ACTION_KEY) {
|
|
381
419
|
openLinkRequest();
|
|
382
420
|
return;
|
|
383
421
|
}
|
|
422
|
+
if (event.nativeEvent.key === IMAGE_TOOLBAR_ACTION_KEY) {
|
|
423
|
+
openImageRequest();
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
384
426
|
onToolbarAction?.(event.nativeEvent.key);
|
|
385
|
-
}, [onToolbarAction, openLinkRequest]);
|
|
427
|
+
}, [onToolbarAction, openImageRequest, openLinkRequest]);
|
|
386
428
|
const handleAddonEvent = (0, react_1.useCallback)((event) => {
|
|
387
429
|
let parsed = null;
|
|
388
430
|
try {
|
|
@@ -451,6 +493,9 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
451
493
|
insertNode(nodeType) {
|
|
452
494
|
runAndApply(() => bridgeRef.current?.insertNode(nodeType) ?? null);
|
|
453
495
|
},
|
|
496
|
+
insertImage(src, attrs) {
|
|
497
|
+
insertImage(src, attrs);
|
|
498
|
+
},
|
|
454
499
|
insertText(text) {
|
|
455
500
|
runAndApply(() => bridgeRef.current?.replaceSelectionText(text) ?? null);
|
|
456
501
|
},
|
|
@@ -497,21 +542,33 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
497
542
|
return false;
|
|
498
543
|
return bridgeRef.current.canRedo();
|
|
499
544
|
},
|
|
500
|
-
}), [runAndApply]);
|
|
545
|
+
}), [insertImage, runAndApply]);
|
|
501
546
|
if (!isReady)
|
|
502
547
|
return null;
|
|
503
548
|
const toolbarItemsForNative = toolbarItems.map((item) => {
|
|
504
|
-
if (item.type
|
|
505
|
-
return
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
549
|
+
if (item.type === 'link') {
|
|
550
|
+
return {
|
|
551
|
+
type: 'action',
|
|
552
|
+
key: LINK_TOOLBAR_ACTION_KEY,
|
|
553
|
+
label: item.label,
|
|
554
|
+
icon: item.icon,
|
|
555
|
+
isActive: activeState.marks.link === true,
|
|
556
|
+
isDisabled: !editable || !onRequestLink || !activeState.allowedMarks.includes('link'),
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (item.type === 'image') {
|
|
560
|
+
return {
|
|
561
|
+
type: 'action',
|
|
562
|
+
key: IMAGE_TOOLBAR_ACTION_KEY,
|
|
563
|
+
label: item.label,
|
|
564
|
+
icon: item.icon,
|
|
565
|
+
isActive: false,
|
|
566
|
+
isDisabled: !editable
|
|
567
|
+
|| !onRequestImage
|
|
568
|
+
|| !activeState.insertableNodes.includes(schemas_2.IMAGE_NODE_NAME),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
return item;
|
|
515
572
|
});
|
|
516
573
|
const themeJson = (0, EditorTheme_1.serializeEditorTheme)(theme);
|
|
517
574
|
const addonsJson = (0, addons_1.serializeEditorAddons)(addons);
|
|
@@ -568,7 +625,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
568
625
|
runAndApply(() => bridgeRef.current?.redo() ?? null);
|
|
569
626
|
break;
|
|
570
627
|
}
|
|
571
|
-
}, onRequestLink: openLinkRequest, onToolbarAction: onToolbarAction, onToggleBold: () => runAndApply(() => bridgeRef.current?.toggleMark('bold') ?? null, {
|
|
628
|
+
}, onRequestLink: openLinkRequest, onRequestImage: openImageRequest, onToolbarAction: onToolbarAction, onToggleBold: () => runAndApply(() => bridgeRef.current?.toggleMark('bold') ?? null, {
|
|
572
629
|
skipNativeApplyIfContentUnchanged: true,
|
|
573
630
|
}), onToggleItalic: () => runAndApply(() => bridgeRef.current?.toggleMark('italic') ?? null, {
|
|
574
631
|
skipNativeApplyIfContentUnchanged: true,
|
|
@@ -577,7 +634,7 @@ exports.NativeRichTextEditor = (0, react_1.forwardRef)(function NativeRichTextEd
|
|
|
577
634
|
}), onToggleStrike: () => runAndApply(() => bridgeRef.current?.toggleMark('strike') ?? null, {
|
|
578
635
|
skipNativeApplyIfContentUnchanged: true,
|
|
579
636
|
}), onToggleBulletList: () => runAndApply(() => bridgeRef.current?.toggleList('bulletList') ?? null), onToggleOrderedList: () => runAndApply(() => bridgeRef.current?.toggleList('orderedList') ?? null), onIndentList: () => runAndApply(() => bridgeRef.current?.indentListItem() ?? null), onOutdentList: () => runAndApply(() => bridgeRef.current?.outdentListItem() ?? null), onInsertHorizontalRule: () => runAndApply(() => bridgeRef.current?.insertNode('horizontalRule') ?? null), onInsertLineBreak: () => runAndApply(() => bridgeRef.current?.insertNode('hardBreak') ?? null), onUndo: () => runAndApply(() => bridgeRef.current?.undo() ?? null), onRedo: () => runAndApply(() => bridgeRef.current?.redo() ?? null) }) }));
|
|
580
|
-
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, containerStyle], children: [(0, jsx_runtime_1.jsx)(NativeEditorView, { ref: nativeViewRef, style: nativeViewStyle, editorId: editorInstanceId, placeholder: placeholder, editable: editable, autoFocus: autoFocus, showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, heightBehavior: heightBehavior, themeJson: themeJson, addonsJson: addonsJson, toolbarItemsJson: toolbarItemsJson, remoteSelectionsJson: remoteSelectionsJson, toolbarFrameJson: toolbarPlacement === 'inline' && isFocused ? toolbarFrameJson : undefined, editorUpdateJson: pendingNativeUpdate.json, editorUpdateRevision: pendingNativeUpdate.revision, onEditorUpdate: handleUpdate, onSelectionChange: handleSelectionChange, onFocusChange: handleFocusChange, onContentHeightChange: handleContentHeightChange, onToolbarAction: handleToolbarAction, onAddonEvent: handleAddonEvent }, DEV_NATIVE_VIEW_KEY), shouldRenderJsToolbar && jsToolbar] }));
|
|
637
|
+
return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.container, containerStyle], children: [(0, jsx_runtime_1.jsx)(NativeEditorView, { ref: nativeViewRef, style: nativeViewStyle, editorId: editorInstanceId, placeholder: placeholder, editable: editable, autoFocus: autoFocus, showToolbar: showToolbar, toolbarPlacement: toolbarPlacement, heightBehavior: heightBehavior, allowImageResizing: allowImageResizing, themeJson: themeJson, addonsJson: addonsJson, toolbarItemsJson: toolbarItemsJson, remoteSelectionsJson: remoteSelectionsJson, toolbarFrameJson: toolbarPlacement === 'inline' && isFocused ? toolbarFrameJson : undefined, editorUpdateJson: pendingNativeUpdate.json, editorUpdateRevision: pendingNativeUpdate.revision, onEditorUpdate: handleUpdate, onSelectionChange: handleSelectionChange, onFocusChange: handleFocusChange, onContentHeightChange: handleContentHeightChange, onToolbarAction: handleToolbarAction, onAddonEvent: handleAddonEvent }, DEV_NATIVE_VIEW_KEY), shouldRenderJsToolbar && jsToolbar] }));
|
|
581
638
|
});
|
|
582
639
|
const styles = react_native_1.StyleSheet.create({
|
|
583
640
|
container: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { NativeRichTextEditor, type NativeRichTextEditorProps, type NativeRichTextEditorRef, type NativeRichTextEditorHeightBehavior, type NativeRichTextEditorToolbarPlacement, type RemoteSelectionDecoration, type LinkRequestContext, } from './NativeRichTextEditor';
|
|
1
|
+
export { NativeRichTextEditor, type NativeRichTextEditorProps, type NativeRichTextEditorRef, type NativeRichTextEditorHeightBehavior, type NativeRichTextEditorToolbarPlacement, type RemoteSelectionDecoration, type LinkRequestContext, type ImageRequestContext, } from './NativeRichTextEditor';
|
|
2
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
3
|
export type { EditorContentInsets, EditorTheme, EditorTextStyle, EditorHeadingTheme, EditorListTheme, EditorHorizontalRuleTheme, EditorMentionTheme, EditorToolbarTheme, EditorToolbarAppearance, EditorFontStyle, EditorFontWeight, } from './EditorTheme';
|
|
4
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, type SchemaDefinition, type NodeSpec, type MarkSpec, type AttrSpec, } from './schemas';
|
|
5
|
+
export { tiptapSchema, prosemirrorSchema, IMAGE_NODE_NAME, imageNodeSpec, withImagesSchema, buildImageFragmentJson, type SchemaDefinition, type NodeSpec, type MarkSpec, type AttrSpec, type ImageNodeAttributes, } from './schemas';
|
|
6
6
|
export { createYjsCollaborationController, useYjsCollaboration, type YjsCollaborationOptions, type YjsCollaborationState, type YjsTransportStatus, type LocalAwarenessState, type LocalAwarenessUser, type UseYjsCollaborationResult, type YjsCollaborationController, } from './YjsCollaboration';
|
|
7
7
|
export type { Selection, ActiveState, HistoryState, EditorUpdate, DocumentJSON, CollaborationPeer, EncodedCollaborationStateInput, } from './NativeEditorBridge';
|
|
8
8
|
export { encodeCollaborationStateBase64, decodeCollaborationStateBase64, } from './NativeEditorBridge';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.decodeCollaborationStateBase64 = exports.encodeCollaborationStateBase64 = exports.useYjsCollaboration = exports.createYjsCollaborationController = 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;
|
|
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
4
|
var NativeRichTextEditor_1 = require("./NativeRichTextEditor");
|
|
5
5
|
Object.defineProperty(exports, "NativeRichTextEditor", { enumerable: true, get: function () { return NativeRichTextEditor_1.NativeRichTextEditor; } });
|
|
6
6
|
var EditorToolbar_1 = require("./EditorToolbar");
|
|
@@ -14,6 +14,10 @@ Object.defineProperty(exports, "buildMentionFragmentJson", { enumerable: true, g
|
|
|
14
14
|
var schemas_1 = require("./schemas");
|
|
15
15
|
Object.defineProperty(exports, "tiptapSchema", { enumerable: true, get: function () { return schemas_1.tiptapSchema; } });
|
|
16
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; } });
|
|
17
21
|
var YjsCollaboration_1 = require("./YjsCollaboration");
|
|
18
22
|
Object.defineProperty(exports, "createYjsCollaborationController", { enumerable: true, get: function () { return YjsCollaboration_1.createYjsCollaborationController; } });
|
|
19
23
|
Object.defineProperty(exports, "useYjsCollaboration", { enumerable: true, get: function () { return YjsCollaboration_1.useYjsCollaboration; } });
|
package/dist/schemas.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DocumentJSON } from './NativeEditorBridge';
|
|
1
2
|
export interface AttrSpec {
|
|
2
3
|
default?: unknown;
|
|
3
4
|
}
|
|
@@ -19,5 +20,16 @@ export interface SchemaDefinition {
|
|
|
19
20
|
nodes: NodeSpec[];
|
|
20
21
|
marks: MarkSpec[];
|
|
21
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;
|
|
22
34
|
export declare const tiptapSchema: SchemaDefinition;
|
|
23
35
|
export declare const prosemirrorSchema: SchemaDefinition;
|
package/dist/schemas.js
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.prosemirrorSchema = exports.tiptapSchema = void 0;
|
|
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
|
+
};
|
|
24
|
+
}
|
|
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
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function buildImageFragmentJson(attrs) {
|
|
36
|
+
return {
|
|
37
|
+
type: 'doc',
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: exports.IMAGE_NODE_NAME,
|
|
41
|
+
attrs,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
4
46
|
const MARKS = [
|
|
5
47
|
{ name: 'bold' },
|
|
6
48
|
{ name: 'italic' },
|
|
@@ -66,6 +108,7 @@ exports.tiptapSchema = {
|
|
|
66
108
|
htmlTag: 'hr',
|
|
67
109
|
isVoid: true,
|
|
68
110
|
},
|
|
111
|
+
imageNodeSpec(),
|
|
69
112
|
{
|
|
70
113
|
name: 'text',
|
|
71
114
|
content: '',
|
|
@@ -133,6 +176,7 @@ exports.prosemirrorSchema = {
|
|
|
133
176
|
htmlTag: 'hr',
|
|
134
177
|
isVoid: true,
|
|
135
178
|
},
|
|
179
|
+
imageNodeSpec('image'),
|
|
136
180
|
{
|
|
137
181
|
name: 'text',
|
|
138
182
|
content: '',
|
|
@@ -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
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import UIKit
|
|
2
2
|
import CoreText
|
|
3
|
-
import os
|
|
4
3
|
|
|
5
4
|
/// Draws list markers visually in the gutter without inserting them into the
|
|
6
5
|
/// editable text storage. This keeps UIKit paragraph-start behaviors, such as
|
|
7
6
|
/// sentence auto-capitalization, working naturally inside list items.
|
|
8
7
|
final class EditorLayoutManager: NSLayoutManager {
|
|
9
|
-
private static let blockquoteLog = Logger(
|
|
10
|
-
subsystem: "com.apollohg.prose-editor",
|
|
11
|
-
category: "blockquote"
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
private var blockquoteDrawPassCounter: UInt64 = 0
|
|
15
8
|
private(set) var blockquoteStripeDrawPassesForTesting: [[CGRect]] = []
|
|
16
9
|
|
|
17
10
|
func blockquoteStripeRectsForTesting(
|
|
@@ -66,9 +59,6 @@ final class EditorLayoutManager: NSLayoutManager {
|
|
|
66
59
|
|
|
67
60
|
guard let textStorage, glyphsToShow.length > 0 else { return }
|
|
68
61
|
|
|
69
|
-
blockquoteDrawPassCounter &+= 1
|
|
70
|
-
let drawPass = blockquoteDrawPassCounter
|
|
71
|
-
|
|
72
62
|
let characterRange = characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil)
|
|
73
63
|
let nsString = textStorage.string as NSString
|
|
74
64
|
var drawnParagraphStarts = Set<Int>()
|
|
@@ -126,9 +116,6 @@ final class EditorLayoutManager: NSLayoutManager {
|
|
|
126
116
|
stripeRect: stripeRect,
|
|
127
117
|
color: color
|
|
128
118
|
)
|
|
129
|
-
Self.blockquoteLog.notice(
|
|
130
|
-
"[drawGlyphs] pass=\(drawPass, privacy: .public) glyphRange=\(glyphsToShow.location, privacy: .public)..<\(glyphsToShow.location + glyphsToShow.length, privacy: .public) groupRange=\(groupRange.location, privacy: .public)..<\(groupRange.location + groupRange.length, privacy: .public) stripe=(x:\(stripeRect.minX, privacy: .public), y:\(stripeRect.minY, privacy: .public), w:\(stripeRect.width, privacy: .public), h:\(stripeRect.height, privacy: .public))"
|
|
131
|
-
)
|
|
132
119
|
drawnStripeRects.append(stripeRect)
|
|
133
120
|
}
|
|
134
121
|
|
|
@@ -259,9 +246,6 @@ final class EditorLayoutManager: NSLayoutManager {
|
|
|
259
246
|
width: borderWidth,
|
|
260
247
|
height: bottomEdge - topEdge
|
|
261
248
|
)
|
|
262
|
-
Self.blockquoteLog.notice(
|
|
263
|
-
"[stripeRect] chars=\(characterRange.location, privacy: .public)..<\(characterRange.location + characterRange.length, privacy: .public) textLeading=\(textLeadingEdge, privacy: .public) verticalEdges=(top:\(topEdge, privacy: .public), bottom:\(bottomEdge, privacy: .public)) stripe=(x:\(stripeRect.minX, privacy: .public), y:\(stripeRect.minY, privacy: .public), w:\(stripeRect.width, privacy: .public), h:\(stripeRect.height, privacy: .public)) borderWidth=\(borderWidth, privacy: .public) gap=\(gap, privacy: .public)"
|
|
264
|
-
)
|
|
265
249
|
return stripeRect
|
|
266
250
|
}
|
|
267
251
|
|