@medway-ui/native 1.11.0 → 1.13.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 +1 -0
- package/lib/commonjs/components/TextMarker.js +485 -0
- package/lib/commonjs/components/TextMarker.js.map +1 -0
- package/lib/commonjs/editor/NoteEditor.js +29 -152
- package/lib/commonjs/editor/NoteEditor.js.map +1 -1
- package/lib/commonjs/editor/editorHtml.generated.js +1 -1
- package/lib/commonjs/editor/editorHtml.generated.js.map +1 -1
- package/lib/commonjs/editor/protocol.js +2 -3
- package/lib/commonjs/editor/protocol.js.map +1 -1
- package/lib/commonjs/index.js +19 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/components/TextMarker.js +479 -0
- package/lib/module/components/TextMarker.js.map +1 -0
- package/lib/module/editor/NoteEditor.js +32 -155
- package/lib/module/editor/NoteEditor.js.map +1 -1
- package/lib/module/editor/editorHtml.generated.js +1 -1
- package/lib/module/editor/editorHtml.generated.js.map +1 -1
- package/lib/module/editor/protocol.js +2 -3
- package/lib/module/editor/protocol.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/components/TextMarker.d.ts +94 -0
- package/lib/typescript/components/TextMarker.d.ts.map +1 -0
- package/lib/typescript/editor/NoteEditor.d.ts +1 -1
- package/lib/typescript/editor/NoteEditor.d.ts.map +1 -1
- package/lib/typescript/editor/editorHtml.generated.d.ts.map +1 -1
- package/lib/typescript/editor/protocol.d.ts +2 -10
- package/lib/typescript/editor/protocol.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/TextMarker.tsx +696 -0
- package/src/editor/NoteEditor.tsx +10 -192
- package/src/editor/editorHtml.generated.ts +1 -1
- package/src/editor/protocol.ts +2 -8
- package/src/index.ts +14 -0
|
@@ -13,12 +13,9 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
Platform,
|
|
15
15
|
Pressable,
|
|
16
|
-
ScrollView,
|
|
17
16
|
StyleSheet,
|
|
18
|
-
Text as RNText,
|
|
19
17
|
TextInput,
|
|
20
18
|
View,
|
|
21
|
-
type LayoutChangeEvent,
|
|
22
19
|
type NativeSyntheticEvent,
|
|
23
20
|
type StyleProp,
|
|
24
21
|
type TextInputKeyPressEventData,
|
|
@@ -35,14 +32,10 @@ import type {
|
|
|
35
32
|
EditorDocument,
|
|
36
33
|
EditorSelectionState,
|
|
37
34
|
EditorTheme,
|
|
38
|
-
SlashCommandId,
|
|
39
|
-
SlashMenuState,
|
|
40
35
|
} from "@medway-ui/notes-core";
|
|
41
36
|
import {
|
|
42
37
|
EMPTY_DOCUMENT,
|
|
43
|
-
EMPTY_SLASH_STATE,
|
|
44
38
|
INITIAL_EDITOR_STATE,
|
|
45
|
-
getSlashCommand,
|
|
46
39
|
normalizeDocument,
|
|
47
40
|
normalizeLinkInput,
|
|
48
41
|
sanitizeImageSrc,
|
|
@@ -52,14 +45,17 @@ import {
|
|
|
52
45
|
Bold,
|
|
53
46
|
Code,
|
|
54
47
|
DoubleQuotesL,
|
|
48
|
+
HeadingH1,
|
|
55
49
|
HeadingH2,
|
|
56
50
|
HeadingH3,
|
|
57
51
|
Image as ImageIcon,
|
|
58
52
|
Italic,
|
|
59
53
|
Link as LinkIcon,
|
|
54
|
+
ListChecklist,
|
|
60
55
|
ListOrdered,
|
|
61
56
|
ListUnordered,
|
|
62
57
|
Redo,
|
|
58
|
+
RemoveMinus,
|
|
63
59
|
Strikethrough,
|
|
64
60
|
Underline,
|
|
65
61
|
WindowCodeBlock,
|
|
@@ -166,7 +162,7 @@ export type NoteEditorProps = {
|
|
|
166
162
|
* picker was cancelled. **Different shape from the web prop**: there is no
|
|
167
163
|
* file input here and this package carries no image-picker dependency, so
|
|
168
164
|
* picking the file is the host's job. Providing it is what enables the
|
|
169
|
-
* toolbar "Inserir imagem" button
|
|
165
|
+
* toolbar "Inserir imagem" button.
|
|
170
166
|
*/
|
|
171
167
|
onImageUpload?: () => Promise<{ src: string; alt?: string | null } | null>;
|
|
172
168
|
/**
|
|
@@ -221,17 +217,6 @@ const nextTitle = (previous: string, value: string): string =>
|
|
|
221
217
|
|
|
222
218
|
const TOOLBAR_BUTTON_SIZE = 32;
|
|
223
219
|
|
|
224
|
-
const SLASH_MENU_WIDTH = 256;
|
|
225
|
-
const SLASH_MENU_GAP = 6;
|
|
226
|
-
|
|
227
|
-
/** Starts at the caret, pulled left so the menu never crosses the editor's right edge. */
|
|
228
|
-
const getSlashMenuLeft = (caretX: number, editorWidth: number): number =>
|
|
229
|
-
editorWidth > 0
|
|
230
|
-
? Math.max(
|
|
231
|
-
SLASH_MENU_GAP,
|
|
232
|
-
Math.min(caretX, editorWidth - SLASH_MENU_WIDTH - SLASH_MENU_GAP)
|
|
233
|
-
)
|
|
234
|
-
: caretX;
|
|
235
220
|
const TOOLBAR_BUTTON_HIT_SLOP = getMinTouchHitSlop(TOOLBAR_BUTTON_SIZE);
|
|
236
221
|
|
|
237
222
|
const MAX_WEBVIEW_REMOUNTS = 3;
|
|
@@ -314,8 +299,8 @@ type ToolbarItem = {
|
|
|
314
299
|
};
|
|
315
300
|
|
|
316
301
|
/**
|
|
317
|
-
*
|
|
318
|
-
* `link` and `undo`/`redo` are filled in per-instance: they need the live
|
|
302
|
+
* The web `NoteEditor`'s `TOOLBAR_GROUPS`, plus Título 1, Checklist and Divisor:
|
|
303
|
+
* the web reaches those through the `/` menu, which mobile does not have. `link` and `undo`/`redo` are filled in per-instance: they need the live
|
|
319
304
|
* `EditorSelectionState` and a dialog opener that only exist in the component.
|
|
320
305
|
*/
|
|
321
306
|
const buildToolbarGroups = (
|
|
@@ -330,14 +315,17 @@ const buildToolbarGroups = (
|
|
|
330
315
|
{ id: "code", label: "Código", Icon: Code, command: { name: "toggleCode" }, activeKey: "code" },
|
|
331
316
|
],
|
|
332
317
|
[
|
|
318
|
+
{ id: "heading1", label: "Título 1", Icon: HeadingH1, command: { name: "toggleHeading", level: 1 }, activeKey: "heading1" },
|
|
333
319
|
{ id: "heading2", label: "Título 2", Icon: HeadingH2, command: { name: "toggleHeading", level: 2 }, activeKey: "heading2" },
|
|
334
320
|
{ id: "heading3", label: "Título 3", Icon: HeadingH3, command: { name: "toggleHeading", level: 3 }, activeKey: "heading3" },
|
|
335
321
|
],
|
|
336
322
|
[
|
|
337
323
|
{ id: "bulletList", label: "Lista com marcadores", Icon: ListUnordered, command: { name: "toggleBulletList" }, activeKey: "bulletList" },
|
|
338
324
|
{ id: "orderedList", label: "Lista numerada", Icon: ListOrdered, command: { name: "toggleOrderedList" }, activeKey: "orderedList" },
|
|
325
|
+
{ id: "taskList", label: "Checklist", Icon: ListChecklist, command: { name: "toggleTaskList" }, activeKey: "taskList" },
|
|
339
326
|
{ id: "blockquote", label: "Citação", Icon: DoubleQuotesL, command: { name: "toggleBlockquote" }, activeKey: "blockquote" },
|
|
340
327
|
{ id: "codeBlock", label: "Bloco de código", Icon: WindowCodeBlock, command: { name: "toggleCodeBlock" }, activeKey: "codeBlock" },
|
|
328
|
+
{ id: "horizontalRule", label: "Divisor", Icon: RemoveMinus, command: { name: "setHorizontalRule" } },
|
|
341
329
|
],
|
|
342
330
|
[{ id: "link", label: "Inserir link", Icon: LinkIcon, activeKey: "link", onPress: openLinkDialog }],
|
|
343
331
|
[
|
|
@@ -346,17 +334,6 @@ const buildToolbarGroups = (
|
|
|
346
334
|
],
|
|
347
335
|
];
|
|
348
336
|
|
|
349
|
-
/**
|
|
350
|
-
* Unlike every other command — a pure ProseMirror op — "Imagem" needs a file
|
|
351
|
-
* first, so it is only offered when the host gave us a way to resolve one.
|
|
352
|
-
* Kept local because `notes-core` cannot know what a given host can do.
|
|
353
|
-
*/
|
|
354
|
-
const visibleSlashCommandIds = (
|
|
355
|
-
commandIds: SlashCommandId[],
|
|
356
|
-
canUploadImage: boolean
|
|
357
|
-
): SlashCommandId[] =>
|
|
358
|
-
canUploadImage ? commandIds : commandIds.filter((id) => id !== "image");
|
|
359
|
-
|
|
360
337
|
const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
361
338
|
(
|
|
362
339
|
{
|
|
@@ -386,7 +363,6 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
386
363
|
const [selectionState, setSelectionState] = useState<EditorSelectionState>(
|
|
387
364
|
INITIAL_EDITOR_STATE
|
|
388
365
|
);
|
|
389
|
-
const [slashState, setSlashState] = useState<SlashMenuState>(EMPTY_SLASH_STATE);
|
|
390
366
|
const [contentHeight, setContentHeight] = useState(height);
|
|
391
367
|
|
|
392
368
|
const [title, setTitle] = useState(() => sanitizeTitle(initialTitle ?? ""));
|
|
@@ -428,7 +404,6 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
428
404
|
|
|
429
405
|
const handleWebViewReset = useCallback(() => {
|
|
430
406
|
setIsReady(false);
|
|
431
|
-
setSlashState(EMPTY_SLASH_STATE);
|
|
432
407
|
}, []);
|
|
433
408
|
|
|
434
409
|
const [webViewKey, setWebViewKey] = useState(0);
|
|
@@ -573,9 +548,6 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
573
548
|
case "state":
|
|
574
549
|
setSelectionState(editorMessage.state);
|
|
575
550
|
return;
|
|
576
|
-
case "slash":
|
|
577
|
-
setSlashState(editorMessage.slash);
|
|
578
|
-
return;
|
|
579
551
|
case "change":
|
|
580
552
|
// Already debounced by the bundle (`CHANGE_DEBOUNCE_MS`). The extra
|
|
581
553
|
// round trip happens only when a host asked for `onChange`, which
|
|
@@ -734,22 +706,8 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
734
706
|
[selectionState, openLinkDialog]
|
|
735
707
|
);
|
|
736
708
|
|
|
737
|
-
const handleSlashSelect = useCallback(
|
|
738
|
-
(id: SlashCommandId) => {
|
|
739
|
-
if (!editable) return;
|
|
740
|
-
send({ type: "runSlashCommand", commandId: id });
|
|
741
|
-
if (id === "image") insertImage();
|
|
742
|
-
},
|
|
743
|
-
[editable, send, insertImage]
|
|
744
|
-
);
|
|
745
|
-
|
|
746
709
|
const containerHeight = isEmbedded ? contentHeight : undefined;
|
|
747
710
|
|
|
748
|
-
const [editorWidth, setEditorWidth] = useState(0);
|
|
749
|
-
const handleEditorLayout = useCallback((event: LayoutChangeEvent) => {
|
|
750
|
-
setEditorWidth(event.nativeEvent.layout.width);
|
|
751
|
-
}, []);
|
|
752
|
-
|
|
753
711
|
const resolvedBackground = backgroundColor ?? theme.background;
|
|
754
712
|
|
|
755
713
|
return (
|
|
@@ -792,10 +750,7 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
792
750
|
style={[styles.title, { color: titleColor }]}
|
|
793
751
|
/>
|
|
794
752
|
|
|
795
|
-
<View
|
|
796
|
-
style={{ flex: isEmbedded ? undefined : 1 }}
|
|
797
|
-
onLayout={handleEditorLayout}
|
|
798
|
-
>
|
|
753
|
+
<View style={{ flex: isEmbedded ? undefined : 1 }}>
|
|
799
754
|
<WebView
|
|
800
755
|
key={webViewKey}
|
|
801
756
|
ref={webViewRef}
|
|
@@ -845,15 +800,6 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
845
800
|
{ backgroundColor: resolvedBackground },
|
|
846
801
|
]}
|
|
847
802
|
/>
|
|
848
|
-
|
|
849
|
-
{slashState.active && slashState.caret ? (
|
|
850
|
-
<NoteEditorSlashMenu
|
|
851
|
-
slash={slashState}
|
|
852
|
-
editorWidth={editorWidth}
|
|
853
|
-
canUploadImage={Boolean(onImageUpload)}
|
|
854
|
-
onSelect={handleSlashSelect}
|
|
855
|
-
/>
|
|
856
|
-
) : null}
|
|
857
803
|
</View>
|
|
858
804
|
|
|
859
805
|
<Dialog open={linkDialogOpen} onOpenChange={setLinkDialogOpen}>
|
|
@@ -973,94 +919,6 @@ function NoteEditorToolbar({
|
|
|
973
919
|
);
|
|
974
920
|
}
|
|
975
921
|
|
|
976
|
-
/**
|
|
977
|
-
* Positioned from the `caret` rect the bundle reports. No arrow-key navigation:
|
|
978
|
-
* a touch UI has no keyboard focus to drive one, so every item is tappable.
|
|
979
|
-
*
|
|
980
|
-
* `caret` is in CSS pixels inside the WebView viewport, treated here as `dp` —
|
|
981
|
-
* valid as long as the WebView isn't scaled.
|
|
982
|
-
*/
|
|
983
|
-
function NoteEditorSlashMenu({
|
|
984
|
-
slash,
|
|
985
|
-
editorWidth,
|
|
986
|
-
canUploadImage,
|
|
987
|
-
onSelect,
|
|
988
|
-
}: {
|
|
989
|
-
slash: SlashMenuState;
|
|
990
|
-
editorWidth: number;
|
|
991
|
-
canUploadImage: boolean;
|
|
992
|
-
onSelect: (id: SlashCommandId) => void;
|
|
993
|
-
}) {
|
|
994
|
-
const surfaceColor = useEditorThemeColor("surface-elevated", "#FFFFFF");
|
|
995
|
-
const borderColor = useThemeColor("gray-stroke-muted");
|
|
996
|
-
const primaryText = useThemeColor("gray-foreground");
|
|
997
|
-
const mutedText = useThemeColor("gray-foreground-muted");
|
|
998
|
-
const activeBackground = useEditorThemeColor(
|
|
999
|
-
"secondary-background-soft-emphasis",
|
|
1000
|
-
"rgba(1, 208, 180, 0.08)"
|
|
1001
|
-
);
|
|
1002
|
-
|
|
1003
|
-
if (!slash.caret) return null;
|
|
1004
|
-
|
|
1005
|
-
const commandIds = visibleSlashCommandIds(slash.commandIds, canUploadImage);
|
|
1006
|
-
|
|
1007
|
-
return (
|
|
1008
|
-
<View
|
|
1009
|
-
style={[
|
|
1010
|
-
styles.slashMenuShadow,
|
|
1011
|
-
{
|
|
1012
|
-
left: getSlashMenuLeft(slash.caret.x, editorWidth),
|
|
1013
|
-
top: slash.caret.y + slash.caret.height + SLASH_MENU_GAP,
|
|
1014
|
-
},
|
|
1015
|
-
]}
|
|
1016
|
-
>
|
|
1017
|
-
<View
|
|
1018
|
-
style={[
|
|
1019
|
-
styles.slashMenu,
|
|
1020
|
-
{ backgroundColor: surfaceColor, borderColor },
|
|
1021
|
-
]}
|
|
1022
|
-
>
|
|
1023
|
-
<ScrollView
|
|
1024
|
-
style={styles.slashMenuScroll}
|
|
1025
|
-
contentContainerStyle={styles.slashMenuContent}
|
|
1026
|
-
keyboardShouldPersistTaps="handled"
|
|
1027
|
-
showsVerticalScrollIndicator
|
|
1028
|
-
>
|
|
1029
|
-
{commandIds.length === 0 ? (
|
|
1030
|
-
<RNText style={[styles.slashEmpty, { color: mutedText }]}>
|
|
1031
|
-
Nenhum resultado
|
|
1032
|
-
</RNText>
|
|
1033
|
-
) : (
|
|
1034
|
-
commandIds.map((id) => {
|
|
1035
|
-
const command = getSlashCommand(id);
|
|
1036
|
-
if (!command) return null;
|
|
1037
|
-
|
|
1038
|
-
return (
|
|
1039
|
-
<Pressable
|
|
1040
|
-
key={id}
|
|
1041
|
-
accessibilityRole="button"
|
|
1042
|
-
onPress={() => onSelect(id)}
|
|
1043
|
-
style={({ pressed }) => [
|
|
1044
|
-
styles.slashItem,
|
|
1045
|
-
pressed ? { backgroundColor: activeBackground } : null,
|
|
1046
|
-
]}
|
|
1047
|
-
>
|
|
1048
|
-
<RNText style={[styles.slashItemLabel, { color: primaryText }]}>
|
|
1049
|
-
{command.label}
|
|
1050
|
-
</RNText>
|
|
1051
|
-
<RNText style={[styles.slashItemDescription, { color: mutedText }]}>
|
|
1052
|
-
{command.description}
|
|
1053
|
-
</RNText>
|
|
1054
|
-
</Pressable>
|
|
1055
|
-
);
|
|
1056
|
-
})
|
|
1057
|
-
)}
|
|
1058
|
-
</ScrollView>
|
|
1059
|
-
</View>
|
|
1060
|
-
</View>
|
|
1061
|
-
);
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
922
|
const styles = StyleSheet.create({
|
|
1065
923
|
container: { flex: 1 },
|
|
1066
924
|
containerEmbedded: { flex: 0 },
|
|
@@ -1104,46 +962,6 @@ const styles = StyleSheet.create({
|
|
|
1104
962
|
toolbarButtonDisabled: {
|
|
1105
963
|
opacity: 0.4,
|
|
1106
964
|
},
|
|
1107
|
-
slashMenuShadow: {
|
|
1108
|
-
position: "absolute",
|
|
1109
|
-
zIndex: 50,
|
|
1110
|
-
width: SLASH_MENU_WIDTH,
|
|
1111
|
-
shadowColor: "#000",
|
|
1112
|
-
shadowOffset: { width: 0, height: 4 },
|
|
1113
|
-
shadowOpacity: 0.14,
|
|
1114
|
-
shadowRadius: 14,
|
|
1115
|
-
elevation: 8,
|
|
1116
|
-
},
|
|
1117
|
-
slashMenu: {
|
|
1118
|
-
borderRadius: 6,
|
|
1119
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
1120
|
-
overflow: "hidden",
|
|
1121
|
-
},
|
|
1122
|
-
slashMenuScroll: {
|
|
1123
|
-
maxHeight: 288,
|
|
1124
|
-
},
|
|
1125
|
-
slashMenuContent: {
|
|
1126
|
-
padding: 4,
|
|
1127
|
-
gap: 12,
|
|
1128
|
-
},
|
|
1129
|
-
slashEmpty: {
|
|
1130
|
-
paddingHorizontal: 12,
|
|
1131
|
-
paddingVertical: 10,
|
|
1132
|
-
fontSize: 13,
|
|
1133
|
-
},
|
|
1134
|
-
slashItem: {
|
|
1135
|
-
paddingHorizontal: 12,
|
|
1136
|
-
paddingVertical: 8,
|
|
1137
|
-
borderRadius: 4,
|
|
1138
|
-
},
|
|
1139
|
-
slashItemLabel: {
|
|
1140
|
-
fontSize: 14,
|
|
1141
|
-
fontWeight: "500",
|
|
1142
|
-
},
|
|
1143
|
-
slashItemDescription: {
|
|
1144
|
-
fontSize: 12,
|
|
1145
|
-
marginTop: 2,
|
|
1146
|
-
},
|
|
1147
965
|
});
|
|
1148
966
|
|
|
1149
967
|
export default memo(NoteEditor);
|