@medway-ui/native 1.9.1 → 1.10.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 +2 -2
- package/lib/commonjs/components/Alert.js +36 -25
- package/lib/commonjs/components/Alert.js.map +1 -1
- package/lib/commonjs/components/AlertDialog.js +22 -23
- package/lib/commonjs/components/AlertDialog.js.map +1 -1
- package/lib/commonjs/components/Avatar.js +4 -4
- package/lib/commonjs/components/Badge.js +22 -15
- package/lib/commonjs/components/Badge.js.map +1 -1
- package/lib/commonjs/components/Button.js +56 -42
- package/lib/commonjs/components/Button.js.map +1 -1
- package/lib/commonjs/components/CardNews.js +18 -13
- package/lib/commonjs/components/CardNews.js.map +1 -1
- package/lib/commonjs/components/Carousel.js +12 -8
- package/lib/commonjs/components/Carousel.js.map +1 -1
- package/lib/commonjs/components/Checkbox.js +5 -5
- package/lib/commonjs/components/Checkbox.js.map +1 -1
- package/lib/commonjs/components/Chip.js +52 -23
- package/lib/commonjs/components/Chip.js.map +1 -1
- package/lib/commonjs/components/Choice.js +26 -12
- package/lib/commonjs/components/Choice.js.map +1 -1
- package/lib/commonjs/components/Dialog.js +16 -8
- package/lib/commonjs/components/Dialog.js.map +1 -1
- package/lib/commonjs/components/DropdownMenu.js +35 -14
- package/lib/commonjs/components/DropdownMenu.js.map +1 -1
- package/lib/commonjs/components/EmptyState.js +5 -5
- package/lib/commonjs/components/EmptyState.js.map +1 -1
- package/lib/commonjs/components/FallbackImage.js +42 -35
- package/lib/commonjs/components/FallbackImage.js.map +1 -1
- package/lib/commonjs/components/Feed.js +6 -5
- package/lib/commonjs/components/Feed.js.map +1 -1
- package/lib/commonjs/components/FeedNotice.js +3 -3
- package/lib/commonjs/components/Feedback.js +2 -4
- package/lib/commonjs/components/Feedback.js.map +1 -1
- package/lib/commonjs/components/Filter.js +6 -9
- package/lib/commonjs/components/Filter.js.map +1 -1
- package/lib/commonjs/components/IndexLevel.js +4 -4
- package/lib/commonjs/components/Input.js +25 -20
- package/lib/commonjs/components/Input.js.map +1 -1
- package/lib/commonjs/components/InvitePopover.js +19 -8
- package/lib/commonjs/components/InvitePopover.js.map +1 -1
- package/lib/commonjs/components/Label.js +2 -2
- package/lib/commonjs/components/LeaderboardCard.js +3 -2
- package/lib/commonjs/components/LeaderboardCard.js.map +1 -1
- package/lib/commonjs/components/Loading.js +18 -15
- package/lib/commonjs/components/Loading.js.map +1 -1
- package/lib/commonjs/components/LoadingOverlay.js +36 -43
- package/lib/commonjs/components/LoadingOverlay.js.map +1 -1
- package/lib/commonjs/components/MemberRow.js +7 -6
- package/lib/commonjs/components/MemberRow.js.map +1 -1
- package/lib/commonjs/components/PageHeader.js +3 -2
- package/lib/commonjs/components/PageHeader.js.map +1 -1
- package/lib/commonjs/components/Popover.js +1 -1
- package/lib/commonjs/components/PostForm.js +38 -24
- package/lib/commonjs/components/PostForm.js.map +1 -1
- package/lib/commonjs/components/PrizeCollection.js +5 -5
- package/lib/commonjs/components/PrizeCollection.js.map +1 -1
- package/lib/commonjs/components/RadioGroup.js +65 -15
- package/lib/commonjs/components/RadioGroup.js.map +1 -1
- package/lib/commonjs/components/SectionHeader.js +3 -2
- package/lib/commonjs/components/SectionHeader.js.map +1 -1
- package/lib/commonjs/components/SegmentControl.js +4 -4
- package/lib/commonjs/components/Select.js +7 -6
- package/lib/commonjs/components/Select.js.map +1 -1
- package/lib/commonjs/components/Separator.js +2 -2
- package/lib/commonjs/components/Separator.js.map +1 -1
- package/lib/commonjs/components/Sheet.js +17 -11
- package/lib/commonjs/components/Sheet.js.map +1 -1
- package/lib/commonjs/components/Skeleton.js +2 -2
- package/lib/commonjs/components/Skeleton.js.map +1 -1
- package/lib/commonjs/components/Switch.js +3 -3
- package/lib/commonjs/components/Tabs.js +3 -2
- package/lib/commonjs/components/Tabs.js.map +1 -1
- package/lib/commonjs/components/Tag.js +52 -32
- package/lib/commonjs/components/Tag.js.map +1 -1
- package/lib/commonjs/components/Text.js +1 -1
- package/lib/commonjs/components/ThemedIcon.js +1 -1
- package/lib/commonjs/components/Toast.js +12 -12
- package/lib/commonjs/components/Toast.js.map +1 -1
- package/lib/commonjs/components/Tooltip.js +2 -2
- package/lib/commonjs/editor/NoteEditor.js +190 -67
- 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/index.js.map +1 -1
- package/lib/commonjs/editor/protocol.js +1 -2
- package/lib/commonjs/editor/protocol.js.map +1 -1
- package/lib/commonjs/hooks/useThemeColor.js +41 -19
- package/lib/commonjs/hooks/useThemeColor.js.map +1 -1
- package/lib/commonjs/index.js +46 -46
- package/lib/commonjs/lib/backdrop.js +18 -0
- package/lib/commonjs/lib/backdrop.js.map +1 -0
- package/lib/commonjs/lib/overlay-host.js +5 -3
- package/lib/commonjs/lib/overlay-host.js.map +1 -1
- package/lib/commonjs/lib/raised-surface.js +1 -1
- package/lib/module/components/Alert.js +36 -25
- package/lib/module/components/Alert.js.map +1 -1
- package/lib/module/components/AlertDialog.js +21 -22
- package/lib/module/components/AlertDialog.js.map +1 -1
- package/lib/module/components/Avatar.js +4 -4
- package/lib/module/components/Badge.js +22 -15
- package/lib/module/components/Badge.js.map +1 -1
- package/lib/module/components/Button.js +56 -42
- package/lib/module/components/Button.js.map +1 -1
- package/lib/module/components/CardNews.js +18 -13
- package/lib/module/components/CardNews.js.map +1 -1
- package/lib/module/components/Carousel.js +12 -8
- package/lib/module/components/Carousel.js.map +1 -1
- package/lib/module/components/Checkbox.js +5 -5
- package/lib/module/components/Checkbox.js.map +1 -1
- package/lib/module/components/Chip.js +52 -23
- package/lib/module/components/Chip.js.map +1 -1
- package/lib/module/components/Choice.js +26 -12
- package/lib/module/components/Choice.js.map +1 -1
- package/lib/module/components/Dialog.js +16 -8
- package/lib/module/components/Dialog.js.map +1 -1
- package/lib/module/components/DropdownMenu.js +36 -15
- package/lib/module/components/DropdownMenu.js.map +1 -1
- package/lib/module/components/EmptyState.js +5 -5
- package/lib/module/components/EmptyState.js.map +1 -1
- package/lib/module/components/FallbackImage.js +42 -35
- package/lib/module/components/FallbackImage.js.map +1 -1
- package/lib/module/components/Feed.js +6 -5
- package/lib/module/components/Feed.js.map +1 -1
- package/lib/module/components/FeedNotice.js +3 -3
- package/lib/module/components/Feedback.js +2 -4
- package/lib/module/components/Feedback.js.map +1 -1
- package/lib/module/components/Filter.js +6 -9
- package/lib/module/components/Filter.js.map +1 -1
- package/lib/module/components/IndexLevel.js +4 -4
- package/lib/module/components/Input.js +24 -18
- package/lib/module/components/Input.js.map +1 -1
- package/lib/module/components/InvitePopover.js +20 -9
- package/lib/module/components/InvitePopover.js.map +1 -1
- package/lib/module/components/Label.js +2 -2
- package/lib/module/components/LeaderboardCard.js +3 -2
- package/lib/module/components/LeaderboardCard.js.map +1 -1
- package/lib/module/components/Loading.js +18 -15
- package/lib/module/components/Loading.js.map +1 -1
- package/lib/module/components/LoadingOverlay.js +37 -44
- package/lib/module/components/LoadingOverlay.js.map +1 -1
- package/lib/module/components/MemberRow.js +7 -6
- package/lib/module/components/MemberRow.js.map +1 -1
- package/lib/module/components/PageHeader.js +3 -2
- package/lib/module/components/PageHeader.js.map +1 -1
- package/lib/module/components/Popover.js +1 -1
- package/lib/module/components/PostForm.js +38 -24
- package/lib/module/components/PostForm.js.map +1 -1
- package/lib/module/components/PrizeCollection.js +5 -5
- package/lib/module/components/PrizeCollection.js.map +1 -1
- package/lib/module/components/RadioGroup.js +65 -15
- package/lib/module/components/RadioGroup.js.map +1 -1
- package/lib/module/components/SectionHeader.js +3 -2
- package/lib/module/components/SectionHeader.js.map +1 -1
- package/lib/module/components/SegmentControl.js +4 -4
- package/lib/module/components/Select.js +6 -5
- package/lib/module/components/Select.js.map +1 -1
- package/lib/module/components/Separator.js +2 -2
- package/lib/module/components/Separator.js.map +1 -1
- package/lib/module/components/Sheet.js +17 -11
- package/lib/module/components/Sheet.js.map +1 -1
- package/lib/module/components/Skeleton.js +2 -2
- package/lib/module/components/Skeleton.js.map +1 -1
- package/lib/module/components/Switch.js +3 -3
- package/lib/module/components/Tabs.js +3 -2
- package/lib/module/components/Tabs.js.map +1 -1
- package/lib/module/components/Tag.js +52 -32
- package/lib/module/components/Tag.js.map +1 -1
- package/lib/module/components/Text.js +1 -1
- package/lib/module/components/ThemedIcon.js +1 -1
- package/lib/module/components/Toast.js +12 -12
- package/lib/module/components/Toast.js.map +1 -1
- package/lib/module/components/Tooltip.js +2 -2
- package/lib/module/editor/NoteEditor.js +193 -69
- 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/index.js.map +1 -1
- package/lib/module/editor/protocol.js +1 -2
- package/lib/module/editor/protocol.js.map +1 -1
- package/lib/module/hooks/useThemeColor.js +41 -19
- package/lib/module/hooks/useThemeColor.js.map +1 -1
- package/lib/module/index.js +46 -46
- package/lib/module/lib/backdrop.js +15 -0
- package/lib/module/lib/backdrop.js.map +1 -0
- package/lib/module/lib/overlay-host.js +6 -4
- package/lib/module/lib/overlay-host.js.map +1 -1
- package/lib/module/lib/raised-surface.js +1 -1
- package/lib/typescript/components/Alert.d.ts +1 -1
- package/lib/typescript/components/Alert.d.ts.map +1 -1
- package/lib/typescript/components/AlertDialog.d.ts +1 -1
- package/lib/typescript/components/AlertDialog.d.ts.map +1 -1
- package/lib/typescript/components/Badge.d.ts.map +1 -1
- package/lib/typescript/components/Button.d.ts +1 -0
- package/lib/typescript/components/Button.d.ts.map +1 -1
- package/lib/typescript/components/CardNews.d.ts +7 -2
- package/lib/typescript/components/CardNews.d.ts.map +1 -1
- package/lib/typescript/components/Checkbox.d.ts +3 -1
- package/lib/typescript/components/Checkbox.d.ts.map +1 -1
- package/lib/typescript/components/Chip.d.ts.map +1 -1
- package/lib/typescript/components/Choice.d.ts +4 -1
- package/lib/typescript/components/Choice.d.ts.map +1 -1
- package/lib/typescript/components/Dialog.d.ts +2 -1
- package/lib/typescript/components/Dialog.d.ts.map +1 -1
- package/lib/typescript/components/DropdownMenu.d.ts +3 -2
- package/lib/typescript/components/DropdownMenu.d.ts.map +1 -1
- package/lib/typescript/components/FallbackImage.d.ts.map +1 -1
- package/lib/typescript/components/Feed.d.ts.map +1 -1
- package/lib/typescript/components/Feedback.d.ts.map +1 -1
- package/lib/typescript/components/Filter.d.ts.map +1 -1
- package/lib/typescript/components/IndexLevel.d.ts +1 -1
- package/lib/typescript/components/Input.d.ts +5 -5
- package/lib/typescript/components/Input.d.ts.map +1 -1
- package/lib/typescript/components/InvitePopover.d.ts +1 -1
- package/lib/typescript/components/InvitePopover.d.ts.map +1 -1
- package/lib/typescript/components/LeaderboardCard.d.ts.map +1 -1
- package/lib/typescript/components/Loading.d.ts +1 -1
- package/lib/typescript/components/Loading.d.ts.map +1 -1
- package/lib/typescript/components/LoadingOverlay.d.ts +9 -9
- package/lib/typescript/components/LoadingOverlay.d.ts.map +1 -1
- package/lib/typescript/components/PageHeader.d.ts.map +1 -1
- package/lib/typescript/components/PostForm.d.ts.map +1 -1
- package/lib/typescript/components/RadioGroup.d.ts +4 -2
- package/lib/typescript/components/RadioGroup.d.ts.map +1 -1
- package/lib/typescript/components/SectionHeader.d.ts.map +1 -1
- package/lib/typescript/components/SegmentControl.d.ts +1 -1
- package/lib/typescript/components/Select.d.ts +2 -1
- package/lib/typescript/components/Select.d.ts.map +1 -1
- package/lib/typescript/components/Separator.d.ts.map +1 -1
- package/lib/typescript/components/Sheet.d.ts +2 -1
- package/lib/typescript/components/Sheet.d.ts.map +1 -1
- package/lib/typescript/components/Tabs.d.ts +4 -3
- package/lib/typescript/components/Tabs.d.ts.map +1 -1
- package/lib/typescript/components/Tag.d.ts +3 -1
- package/lib/typescript/components/Tag.d.ts.map +1 -1
- package/lib/typescript/components/ThemedIcon.d.ts +1 -1
- package/lib/typescript/components/Toast.d.ts +1 -1
- package/lib/typescript/components/Toast.d.ts.map +1 -1
- package/lib/typescript/editor/NoteEditor.d.ts +28 -8
- 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/index.d.ts +1 -1
- package/lib/typescript/editor/index.d.ts.map +1 -1
- package/lib/typescript/editor/protocol.d.ts +4 -14
- package/lib/typescript/editor/protocol.d.ts.map +1 -1
- package/lib/typescript/hooks/use-toast.d.ts +1 -1
- package/lib/typescript/hooks/useThemeColor.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +91 -91
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/lib/backdrop.d.ts +4 -0
- package/lib/typescript/lib/backdrop.d.ts.map +1 -0
- package/lib/typescript/lib/overlay-host.d.ts +1 -0
- package/lib/typescript/lib/overlay-host.d.ts.map +1 -1
- package/package.json +9 -14
- package/setup/metro.js +26 -73
- package/setup/tailwind.js +16 -8
- package/setup/tokens.native.css +51 -20
- package/src/components/Alert.tsx +37 -20
- package/src/components/AlertDialog.tsx +18 -19
- package/src/components/Badge.tsx +16 -15
- package/src/components/Button.tsx +64 -31
- package/src/components/CardNews.tsx +17 -6
- package/src/components/Carousel.tsx +5 -5
- package/src/components/Checkbox.tsx +17 -7
- package/src/components/Chip.tsx +49 -21
- package/src/components/Choice.tsx +34 -14
- package/src/components/Dialog.tsx +15 -5
- package/src/components/DropdownMenu.tsx +62 -14
- package/src/components/EmptyState.tsx +3 -3
- package/src/components/FallbackImage.tsx +32 -23
- package/src/components/Feed.tsx +2 -1
- package/src/components/Feedback.tsx +0 -1
- package/src/components/Filter.tsx +2 -3
- package/src/components/Input.tsx +83 -64
- package/src/components/InvitePopover.tsx +11 -3
- package/src/components/LeaderboardCard.tsx +4 -1
- package/src/components/Loading.tsx +17 -14
- package/src/components/LoadingOverlay.tsx +33 -50
- package/src/components/MemberRow.tsx +3 -3
- package/src/components/PageHeader.tsx +4 -1
- package/src/components/PostForm.tsx +28 -16
- package/src/components/PrizeCollection.tsx +3 -3
- package/src/components/RadioGroup.tsx +69 -12
- package/src/components/SectionHeader.tsx +4 -1
- package/src/components/Select.tsx +4 -3
- package/src/components/Separator.tsx +2 -2
- package/src/components/Sheet.tsx +15 -8
- package/src/components/Skeleton.tsx +1 -1
- package/src/components/Tabs.tsx +5 -4
- package/src/components/Tag.tsx +56 -35
- package/src/components/Toast.tsx +10 -6
- package/src/editor/NoteEditor.tsx +246 -72
- package/src/editor/editorHtml.generated.ts +1 -1
- package/src/editor/index.ts +8 -4
- package/src/editor/protocol.ts +4 -12
- package/src/hooks/useThemeColor.ts +41 -19
- package/src/index.ts +1 -1
- package/src/lib/backdrop.ts +16 -0
- package/src/lib/overlay-host.tsx +5 -5
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
Text as RNText,
|
|
19
19
|
TextInput,
|
|
20
20
|
View,
|
|
21
|
+
type LayoutChangeEvent,
|
|
21
22
|
type NativeSyntheticEvent,
|
|
22
23
|
type StyleProp,
|
|
23
24
|
type TextInputKeyPressEventData,
|
|
@@ -29,8 +30,6 @@ import UntypedWebView, {
|
|
|
29
30
|
} from "react-native-webview";
|
|
30
31
|
import { useColorScheme } from "nativewind";
|
|
31
32
|
|
|
32
|
-
import { EDITOR_HTML } from "./editorHtml.generated";
|
|
33
|
-
|
|
34
33
|
import type {
|
|
35
34
|
EditorCommand,
|
|
36
35
|
EditorDocument,
|
|
@@ -45,6 +44,8 @@ import {
|
|
|
45
44
|
INITIAL_EDITOR_STATE,
|
|
46
45
|
getSlashCommand,
|
|
47
46
|
normalizeDocument,
|
|
47
|
+
normalizeLinkInput,
|
|
48
|
+
sanitizeImageSrc,
|
|
48
49
|
} from "@medway-ui/notes-core";
|
|
49
50
|
import {
|
|
50
51
|
ArrowUndoUpLeft,
|
|
@@ -66,6 +67,7 @@ import {
|
|
|
66
67
|
|
|
67
68
|
import { Button } from "@/components/Button";
|
|
68
69
|
import { Input } from "@/components/Input";
|
|
70
|
+
import { Text } from "@/components/Text";
|
|
69
71
|
import {
|
|
70
72
|
Dialog,
|
|
71
73
|
DialogContainer,
|
|
@@ -74,20 +76,21 @@ import {
|
|
|
74
76
|
DialogHeader,
|
|
75
77
|
DialogTitle,
|
|
76
78
|
} from "@/components/Dialog";
|
|
77
|
-
import {
|
|
78
|
-
|
|
79
|
-
function useEditorThemeColor(token: string, fallback: string): string {
|
|
80
|
-
const value = useThemeColor(token);
|
|
81
|
-
return value === DEFAULT_COLOR ? fallback : value;
|
|
82
|
-
}
|
|
83
|
-
|
|
79
|
+
import { EDITOR_HTML } from "@/editor/editorHtml.generated";
|
|
84
80
|
import {
|
|
85
81
|
EDITOR_PROTOCOL_VERSION,
|
|
86
82
|
EDITOR_RECEIVE_HANDLER,
|
|
87
83
|
isEditorMessage,
|
|
88
84
|
type EditorToNativeMessage,
|
|
89
85
|
type NativeToEditorMessage,
|
|
90
|
-
} from "
|
|
86
|
+
} from "@/editor/protocol";
|
|
87
|
+
import { DEFAULT_COLOR, useThemeColor } from "@/hooks/useThemeColor";
|
|
88
|
+
import { getMinTouchHitSlop } from "@/lib/touch-target";
|
|
89
|
+
|
|
90
|
+
function useEditorThemeColor(token: string, fallback: string): string {
|
|
91
|
+
const value = useThemeColor(token);
|
|
92
|
+
return value === DEFAULT_COLOR ? fallback : value;
|
|
93
|
+
}
|
|
91
94
|
|
|
92
95
|
/**
|
|
93
96
|
* The ref surface of `react-native-webview`, kept local so consumers need not
|
|
@@ -114,6 +117,15 @@ const WebView = UntypedWebView as unknown as ComponentType<
|
|
|
114
117
|
export type NoteEditorValue = {
|
|
115
118
|
title: string;
|
|
116
119
|
json: EditorDocument;
|
|
120
|
+
/**
|
|
121
|
+
* Images `onImageUpload` produced this session that `json` no longer has.
|
|
122
|
+
* Keep the list from the last save that succeeded (replace it on every
|
|
123
|
+
* save, never merge) and delete those files only when the editor unmounts
|
|
124
|
+
* (closing it, or switching notes with a new `key`): until then an undo can
|
|
125
|
+
* bring an image back. `setDocument` keeps the undo history, so it is not
|
|
126
|
+
* the moment to delete them.
|
|
127
|
+
*/
|
|
128
|
+
removedImages: string[];
|
|
117
129
|
};
|
|
118
130
|
|
|
119
131
|
/**
|
|
@@ -132,12 +144,12 @@ export type NoteEditorHandle = {
|
|
|
132
144
|
) => void;
|
|
133
145
|
};
|
|
134
146
|
|
|
135
|
-
type NoteEditorProps = {
|
|
147
|
+
export type NoteEditorProps = {
|
|
136
148
|
/**
|
|
137
149
|
* Initial document: the note's JSON, or an HTML string (parsed bundle-side,
|
|
138
|
-
* the only side with a DOM).
|
|
139
|
-
*
|
|
140
|
-
*
|
|
150
|
+
* the only side with a DOM). Read once, on mount: changing it later does
|
|
151
|
+
* nothing. To replace the content use the ref's `setDocument`; to open
|
|
152
|
+
* another note with a fresh undo stack, pass a new React `key`.
|
|
141
153
|
*/
|
|
142
154
|
document?: EditorDocument | string;
|
|
143
155
|
/** Initial title, shown above the content in its own always-visible area. */
|
|
@@ -154,15 +166,26 @@ type NoteEditorProps = {
|
|
|
154
166
|
* picker was cancelled. **Different shape from the web prop**: there is no
|
|
155
167
|
* file input here and this package carries no image-picker dependency, so
|
|
156
168
|
* picking the file is the host's job. Providing it is what enables the
|
|
157
|
-
* toolbar "
|
|
169
|
+
* toolbar "Inserir imagem" button and the `/Imagem` slash command.
|
|
158
170
|
*/
|
|
159
171
|
onImageUpload?: () => Promise<{ src: string; alt?: string | null } | null>;
|
|
160
172
|
/**
|
|
161
|
-
* Fires
|
|
162
|
-
*
|
|
173
|
+
* Fires right away for an uploaded file that never made it into the note:
|
|
174
|
+
* a `src` the editor refuses (anything but absolute `http(s):`/`data:`) or
|
|
175
|
+
* one that arrives after the editor stopped accepting it. Images removed
|
|
176
|
+
* while editing come from `getSaveData().removedImages` instead, so the
|
|
177
|
+
* host only deletes what a successful save left out.
|
|
178
|
+
*
|
|
179
|
+
* Neither covers images present when the document loaded, nor a file pasted
|
|
180
|
+
* into another note: this component cannot see other notes, so checking
|
|
181
|
+
* that no note still references a file before deleting it is the backend's
|
|
182
|
+
* job.
|
|
163
183
|
*/
|
|
164
184
|
onImageRemoved?: (src: string) => void;
|
|
165
|
-
/**
|
|
185
|
+
/**
|
|
186
|
+
* @deprecated Ignored. Removed images come from
|
|
187
|
+
* `getSaveData().removedImages`, deleted by the host when the editor closes.
|
|
188
|
+
*/
|
|
166
189
|
imageDeleteGraceMs?: number;
|
|
167
190
|
/**
|
|
168
191
|
* Native-only. The WebView never navigates, so the host decides what to open
|
|
@@ -188,7 +211,45 @@ type NoteEditorProps = {
|
|
|
188
211
|
/** The title is single-line; newlines can still arrive via paste. */
|
|
189
212
|
const sanitizeTitle = (value: string): string => value.replace(/\r?\n/g, " ");
|
|
190
213
|
|
|
191
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Enter in the multiline title still commits a "\n" (`onKeyPress` cannot
|
|
216
|
+
* cancel it): one newline added to the previous title is that key, dropped
|
|
217
|
+
* instead of becoming a trailing space. Pasted newlines still become spaces.
|
|
218
|
+
*/
|
|
219
|
+
const nextTitle = (previous: string, value: string): string =>
|
|
220
|
+
value.replace(/\r?\n/, "") === previous ? previous : sanitizeTitle(value);
|
|
221
|
+
|
|
222
|
+
const TOOLBAR_BUTTON_SIZE = 32;
|
|
223
|
+
|
|
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
|
+
const TOOLBAR_BUTTON_HIT_SLOP = getMinTouchHitSlop(TOOLBAR_BUTTON_SIZE);
|
|
236
|
+
|
|
237
|
+
const MAX_WEBVIEW_REMOUNTS = 3;
|
|
238
|
+
const WEBVIEW_REMOUNT_WINDOW_MS = 60_000;
|
|
239
|
+
|
|
240
|
+
const collectImageSrcs = (doc: EditorDocument): Set<string> => {
|
|
241
|
+
const srcs = new Set<string>();
|
|
242
|
+
const walk = (nodes: EditorDocument["content"]) => {
|
|
243
|
+
nodes.forEach((node) => {
|
|
244
|
+
if (node.type === "image" && typeof node.attrs?.src === "string") {
|
|
245
|
+
srcs.add(node.attrs.src);
|
|
246
|
+
}
|
|
247
|
+
if (node.content) walk(node.content);
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
walk(doc.content);
|
|
251
|
+
return srcs;
|
|
252
|
+
};
|
|
192
253
|
|
|
193
254
|
let requestCounter = 0;
|
|
194
255
|
/** Correlates a `getDocument` request with the `document` response that answers it. */
|
|
@@ -206,10 +267,11 @@ const useNoteEditorTheme = (): EditorTheme => {
|
|
|
206
267
|
const { colorScheme } = useColorScheme();
|
|
207
268
|
const background = useThemeColor("surface-card");
|
|
208
269
|
const text = useThemeColor("content-paragraph");
|
|
209
|
-
const heading = useThemeColor("
|
|
270
|
+
const heading = useThemeColor("gray-foreground");
|
|
210
271
|
const muted = useThemeColor("gray-foreground-muted");
|
|
211
272
|
const border = useThemeColor("gray-stroke-muted");
|
|
212
273
|
const accent = useThemeColor("primary-foreground");
|
|
274
|
+
const link = useThemeColor("content-link");
|
|
213
275
|
const codeBackground = useEditorThemeColor("gray-background-soft", "#F0F1F5");
|
|
214
276
|
|
|
215
277
|
return useMemo<EditorTheme>(
|
|
@@ -222,6 +284,7 @@ const useNoteEditorTheme = (): EditorTheme => {
|
|
|
222
284
|
placeholder: muted,
|
|
223
285
|
border,
|
|
224
286
|
accent,
|
|
287
|
+
link,
|
|
225
288
|
caret: accent,
|
|
226
289
|
selection: border,
|
|
227
290
|
codeBackground,
|
|
@@ -233,7 +296,7 @@ const useNoteEditorTheme = (): EditorTheme => {
|
|
|
233
296
|
paddingTop: 12,
|
|
234
297
|
paddingBottom: 32,
|
|
235
298
|
}),
|
|
236
|
-
[colorScheme, background, text, heading, muted, border, accent, codeBackground]
|
|
299
|
+
[colorScheme, background, text, heading, muted, border, accent, link, codeBackground]
|
|
237
300
|
);
|
|
238
301
|
};
|
|
239
302
|
|
|
@@ -299,14 +362,13 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
299
362
|
{
|
|
300
363
|
document: initialDocument,
|
|
301
364
|
initialTitle,
|
|
302
|
-
titlePlaceholder = "
|
|
365
|
+
titlePlaceholder = "Título",
|
|
303
366
|
onChange,
|
|
304
367
|
editable = true,
|
|
305
368
|
autofocus = false,
|
|
306
369
|
toolbar = false,
|
|
307
370
|
onImageUpload,
|
|
308
371
|
onImageRemoved,
|
|
309
|
-
imageDeleteGraceMs = DEFAULT_IMAGE_DELETE_GRACE_MS,
|
|
310
372
|
onLinkPress,
|
|
311
373
|
height,
|
|
312
374
|
backgroundColor,
|
|
@@ -333,12 +395,10 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
333
395
|
{ start: number; end: number } | undefined
|
|
334
396
|
>(undefined);
|
|
335
397
|
|
|
336
|
-
const autofocusTitleRef = useRef(autofocus
|
|
398
|
+
const autofocusTitleRef = useRef(Boolean(autofocus) && title.length === 0);
|
|
337
399
|
|
|
338
400
|
useEffect(() => {
|
|
339
401
|
if (autofocusTitleRef.current) titleInputRef.current?.focus();
|
|
340
|
-
// Mount-only: the one-time "new note" autofocus decision.
|
|
341
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
342
402
|
}, []);
|
|
343
403
|
|
|
344
404
|
const focusTitleAtEnd = useCallback(() => {
|
|
@@ -371,15 +431,60 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
371
431
|
setSlashState(EMPTY_SLASH_STATE);
|
|
372
432
|
}, []);
|
|
373
433
|
|
|
434
|
+
const [webViewKey, setWebViewKey] = useState(0);
|
|
435
|
+
const recentCrashesRef = useRef<number[]>([]);
|
|
436
|
+
|
|
437
|
+
// A WebView whose content/render process died can't be reused (Android
|
|
438
|
+
// tears it down after `onRenderProcessGone`), so a fresh one is mounted.
|
|
439
|
+
// Capped, so a document that crashes it on every load doesn't loop.
|
|
440
|
+
const handleWebViewCrash = useCallback(() => {
|
|
441
|
+
handleWebViewReset();
|
|
442
|
+
|
|
443
|
+
const now = Date.now();
|
|
444
|
+
const recent = recentCrashesRef.current.filter(
|
|
445
|
+
(crashedAt) => now - crashedAt < WEBVIEW_REMOUNT_WINDOW_MS
|
|
446
|
+
);
|
|
447
|
+
if (recent.length >= MAX_WEBVIEW_REMOUNTS) {
|
|
448
|
+
recentCrashesRef.current = recent;
|
|
449
|
+
console.error(
|
|
450
|
+
`@medway-ui/native NoteEditor: the editor WebView crashed ${MAX_WEBVIEW_REMOUNTS + 1} times within ${WEBVIEW_REMOUNT_WINDOW_MS / 1000}s; not remounting it again.`
|
|
451
|
+
);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
recentCrashesRef.current = [...recent, now];
|
|
456
|
+
setWebViewKey((key) => key + 1);
|
|
457
|
+
}, [handleWebViewReset]);
|
|
458
|
+
|
|
459
|
+
// What `setContent` sends on every `ready`, so a remount after a crash comes
|
|
460
|
+
// back with the latest content the host has seen rather than `initialDocument`.
|
|
461
|
+
const lastKnownContentRef = useRef<EditorDocument | string>(
|
|
462
|
+
initialDocument ?? EMPTY_DOCUMENT
|
|
463
|
+
);
|
|
464
|
+
const uploadedThisSessionRef = useRef(new Set<string>());
|
|
465
|
+
const isMountedRef = useRef(true);
|
|
466
|
+
const hasLoadedContentRef = useRef(false);
|
|
467
|
+
// Bumped by `setDocument`: a `document` answering a request made before it
|
|
468
|
+
// describes the replaced note.
|
|
469
|
+
const documentGenerationRef = useRef(0);
|
|
470
|
+
|
|
374
471
|
const pendingDocumentRequests = useRef(
|
|
375
|
-
new Map<
|
|
472
|
+
new Map<
|
|
473
|
+
string,
|
|
474
|
+
{
|
|
475
|
+
generation: number;
|
|
476
|
+
resolve: (doc: EditorDocument) => void;
|
|
477
|
+
reject: (error: Error) => void;
|
|
478
|
+
}
|
|
479
|
+
>()
|
|
376
480
|
);
|
|
377
481
|
|
|
378
482
|
const requestDocument = useCallback((): Promise<EditorDocument> => {
|
|
379
483
|
const requestId = nextRequestId();
|
|
484
|
+
const generation = documentGenerationRef.current;
|
|
380
485
|
|
|
381
486
|
return new Promise<EditorDocument>((resolve, reject) => {
|
|
382
|
-
pendingDocumentRequests.current.set(requestId, { resolve, reject });
|
|
487
|
+
pendingDocumentRequests.current.set(requestId, { generation, resolve, reject });
|
|
383
488
|
send({ type: "getDocument", requestId });
|
|
384
489
|
|
|
385
490
|
setTimeout(() => {
|
|
@@ -407,6 +512,13 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
407
512
|
onLinkPressRef.current = onLinkPress;
|
|
408
513
|
}, [onLinkPress]);
|
|
409
514
|
|
|
515
|
+
useEffect(() => {
|
|
516
|
+
isMountedRef.current = true;
|
|
517
|
+
return () => {
|
|
518
|
+
isMountedRef.current = false;
|
|
519
|
+
};
|
|
520
|
+
}, []);
|
|
521
|
+
|
|
410
522
|
const insertImage = useCallback(async () => {
|
|
411
523
|
const upload = onImageUploadRef.current;
|
|
412
524
|
if (!upload || !editableRef.current) return;
|
|
@@ -414,17 +526,22 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
414
526
|
try {
|
|
415
527
|
const result = await upload();
|
|
416
528
|
if (!result) return;
|
|
417
|
-
if (!editableRef.current) {
|
|
529
|
+
if (!isMountedRef.current || !editableRef.current) {
|
|
418
530
|
onImageRemovedRef.current?.(result.src);
|
|
419
531
|
return;
|
|
420
532
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
533
|
+
const src = sanitizeImageSrc(result.src);
|
|
534
|
+
if (!src) {
|
|
535
|
+
console.error(
|
|
536
|
+
"@medway-ui/native NoteEditor: onImageUpload resolved with an unsupported URL (only absolute http(s): or data:)",
|
|
537
|
+
result.src
|
|
538
|
+
);
|
|
539
|
+
onImageRemovedRef.current?.(result.src);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
uploadedThisSessionRef.current.add(src);
|
|
543
|
+
sendMutation({ type: "insertImage", src, alt: result.alt ?? null });
|
|
426
544
|
} catch (error) {
|
|
427
|
-
// eslint-disable-next-line no-console
|
|
428
545
|
console.error("@medway-ui/native NoteEditor: onImageUpload failed", error);
|
|
429
546
|
}
|
|
430
547
|
}, [sendMutation]);
|
|
@@ -446,7 +563,6 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
446
563
|
// Refused loudly rather than letting commands quietly not match
|
|
447
564
|
// what the bundle understands.
|
|
448
565
|
if (editorMessage.protocol !== EDITOR_PROTOCOL_VERSION) {
|
|
449
|
-
// eslint-disable-next-line no-console
|
|
450
566
|
console.error(
|
|
451
567
|
`@medway-ui/native NoteEditor: editor bundle reports protocol ${editorMessage.protocol}, expected ${EDITOR_PROTOCOL_VERSION}. Rebuild editor-bundle (its generated HTML is stale).`
|
|
452
568
|
);
|
|
@@ -465,8 +581,12 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
465
581
|
// round trip happens only when a host asked for `onChange`, which
|
|
466
582
|
// keeps serialization per-settled-keystroke, not per-keystroke.
|
|
467
583
|
if (onChangeRef.current) {
|
|
584
|
+
const generation = documentGenerationRef.current;
|
|
468
585
|
requestDocument()
|
|
469
|
-
.then((doc) =>
|
|
586
|
+
.then((doc) => {
|
|
587
|
+
if (generation !== documentGenerationRef.current) return;
|
|
588
|
+
onChangeRef.current?.(doc);
|
|
589
|
+
})
|
|
470
590
|
.catch(() => {});
|
|
471
591
|
}
|
|
472
592
|
return;
|
|
@@ -478,7 +598,13 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
478
598
|
// whitelist/depth pass is what makes it an `EditorDocument`, and it
|
|
479
599
|
// runs on the way out too, not just on `setContent` — the WebView
|
|
480
600
|
// renders student content, so what it hands back is untrusted.
|
|
481
|
-
|
|
601
|
+
const doc = normalizeDocument(editorMessage.doc);
|
|
602
|
+
// Still the answer to what was asked, but no longer the content
|
|
603
|
+
// to restore after a crash.
|
|
604
|
+
if (pending.generation === documentGenerationRef.current) {
|
|
605
|
+
lastKnownContentRef.current = doc;
|
|
606
|
+
}
|
|
607
|
+
pending.resolve(doc);
|
|
482
608
|
return;
|
|
483
609
|
}
|
|
484
610
|
case "resize":
|
|
@@ -490,11 +616,7 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
490
616
|
case "backspaceAtDocStart":
|
|
491
617
|
focusTitleAtEnd();
|
|
492
618
|
return;
|
|
493
|
-
case "imageRemoved":
|
|
494
|
-
onImageRemovedRef.current?.(editorMessage.src);
|
|
495
|
-
return;
|
|
496
619
|
case "error":
|
|
497
|
-
// eslint-disable-next-line no-console
|
|
498
620
|
console.error("@medway-ui/native NoteEditor:", editorMessage.message);
|
|
499
621
|
return;
|
|
500
622
|
default:
|
|
@@ -504,24 +626,24 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
504
626
|
[focusTitleAtEnd, isEmbedded, requestDocument]
|
|
505
627
|
);
|
|
506
628
|
|
|
507
|
-
// Fires
|
|
508
|
-
//
|
|
629
|
+
// Fires on every `ready`: the first load, and the remount after a crash.
|
|
630
|
+
// `editable`/`autofocus` stay out of deps on purpose: they seed the editor
|
|
631
|
+
// the bundle builds on load; `editable` is kept in sync by the next effect.
|
|
509
632
|
useEffect(() => {
|
|
510
633
|
if (!isReady) return;
|
|
511
634
|
|
|
635
|
+
const isFirstLoad = !hasLoadedContentRef.current;
|
|
636
|
+
hasLoadedContentRef.current = true;
|
|
637
|
+
|
|
512
638
|
send({
|
|
513
639
|
type: "setContent",
|
|
514
|
-
doc:
|
|
640
|
+
doc: lastKnownContentRef.current,
|
|
515
641
|
editable,
|
|
516
|
-
focus: autofocusTitleRef.current
|
|
642
|
+
focus: isFirstLoad && !autofocusTitleRef.current && Boolean(autofocus),
|
|
517
643
|
focusPosition: typeof autofocus === "string" ? autofocus : undefined,
|
|
518
|
-
imageDeleteGraceMs,
|
|
519
644
|
});
|
|
520
645
|
|
|
521
646
|
if (isEmbedded) send({ type: "setLayout", embedded: true });
|
|
522
|
-
// `editable`/`autofocus`/`imageDeleteGraceMs` are excluded from deps on
|
|
523
|
-
// purpose: they seed the *initial* editor, like `document`.
|
|
524
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
525
647
|
}, [isReady, send]);
|
|
526
648
|
|
|
527
649
|
// `editable` is the one prop that stays reactive after mount.
|
|
@@ -542,34 +664,64 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
542
664
|
focus: (position) => send({ type: "focus", position: position ?? "end" }),
|
|
543
665
|
blur: () => send({ type: "blur" }),
|
|
544
666
|
getDocument: () => requestDocument(),
|
|
545
|
-
getSaveData: async () =>
|
|
546
|
-
|
|
667
|
+
getSaveData: async () => {
|
|
668
|
+
const generation = documentGenerationRef.current;
|
|
669
|
+
const json = await requestDocument();
|
|
670
|
+
// After a `setDocument` the session uploads belong to the new note.
|
|
671
|
+
if (generation !== documentGenerationRef.current) {
|
|
672
|
+
return { title, json, removedImages: [] };
|
|
673
|
+
}
|
|
674
|
+
const kept = collectImageSrcs(json);
|
|
675
|
+
const removedImages = [...uploadedThisSessionRef.current].filter(
|
|
676
|
+
(src) => !kept.has(src)
|
|
677
|
+
);
|
|
678
|
+
return { title, json, removedImages };
|
|
679
|
+
},
|
|
680
|
+
setDocument: (doc, options) => {
|
|
681
|
+
documentGenerationRef.current += 1;
|
|
682
|
+
lastKnownContentRef.current = doc;
|
|
683
|
+
uploadedThisSessionRef.current.clear();
|
|
547
684
|
send({
|
|
548
685
|
type: "setContent",
|
|
549
686
|
doc,
|
|
550
687
|
editable,
|
|
551
688
|
focus: Boolean(options?.focus),
|
|
552
689
|
reset: false,
|
|
553
|
-
})
|
|
690
|
+
});
|
|
691
|
+
},
|
|
554
692
|
}),
|
|
555
693
|
[title, editable, requestDocument, send]
|
|
556
694
|
);
|
|
557
695
|
|
|
558
696
|
const [linkDialogOpen, setLinkDialogOpen] = useState(false);
|
|
559
697
|
const [linkValue, setLinkValue] = useState("");
|
|
698
|
+
const [linkInvalid, setLinkInvalid] = useState(false);
|
|
560
699
|
|
|
561
700
|
const openLinkDialog = useCallback(() => {
|
|
562
701
|
setLinkValue(selectionState.linkHref ?? "");
|
|
702
|
+
setLinkInvalid(false);
|
|
563
703
|
setLinkDialogOpen(true);
|
|
564
704
|
}, [selectionState.linkHref]);
|
|
565
705
|
|
|
706
|
+
const handleLinkValueChange = useCallback((value: string) => {
|
|
707
|
+
setLinkValue(value);
|
|
708
|
+
setLinkInvalid(false);
|
|
709
|
+
}, []);
|
|
710
|
+
|
|
566
711
|
const confirmLink = useCallback(() => {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
712
|
+
if (!linkValue.trim()) {
|
|
713
|
+
sendMutation({ type: "command", command: { name: "unsetLink" } });
|
|
714
|
+
setLinkDialogOpen(false);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const href = normalizeLinkInput(linkValue);
|
|
719
|
+
if (!href) {
|
|
720
|
+
setLinkInvalid(true);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
sendMutation({ type: "command", command: { name: "setLink", href } });
|
|
573
725
|
setLinkDialogOpen(false);
|
|
574
726
|
}, [linkValue, sendMutation]);
|
|
575
727
|
|
|
@@ -593,6 +745,11 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
593
745
|
|
|
594
746
|
const containerHeight = isEmbedded ? contentHeight : undefined;
|
|
595
747
|
|
|
748
|
+
const [editorWidth, setEditorWidth] = useState(0);
|
|
749
|
+
const handleEditorLayout = useCallback((event: LayoutChangeEvent) => {
|
|
750
|
+
setEditorWidth(event.nativeEvent.layout.width);
|
|
751
|
+
}, []);
|
|
752
|
+
|
|
596
753
|
const resolvedBackground = backgroundColor ?? theme.background;
|
|
597
754
|
|
|
598
755
|
return (
|
|
@@ -616,11 +773,10 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
616
773
|
ref={titleInputRef}
|
|
617
774
|
multiline
|
|
618
775
|
value={title}
|
|
619
|
-
onChangeText={(value) => setTitle(
|
|
776
|
+
onChangeText={(value) => setTitle((previous) => nextTitle(previous, value))}
|
|
620
777
|
onKeyPress={(event: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
|
|
621
|
-
// Enter moves focus into the content's first block
|
|
622
|
-
//
|
|
623
|
-
// commits the `\n` before this handler runs.
|
|
778
|
+
// Enter moves focus into the content's first block; the `\n` it
|
|
779
|
+
// still commits is dropped by `nextTitle`.
|
|
624
780
|
if (event.nativeEvent.key !== "Enter") return;
|
|
625
781
|
send({ type: "focus", position: "start" });
|
|
626
782
|
}}
|
|
@@ -636,8 +792,12 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
636
792
|
style={[styles.title, { color: titleColor }]}
|
|
637
793
|
/>
|
|
638
794
|
|
|
639
|
-
<View
|
|
795
|
+
<View
|
|
796
|
+
style={{ flex: isEmbedded ? undefined : 1 }}
|
|
797
|
+
onLayout={handleEditorLayout}
|
|
798
|
+
>
|
|
640
799
|
<WebView
|
|
800
|
+
key={webViewKey}
|
|
641
801
|
ref={webViewRef}
|
|
642
802
|
testID={testID}
|
|
643
803
|
containerStyle={
|
|
@@ -650,8 +810,8 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
650
810
|
originWhitelist={["^about:blank$"]}
|
|
651
811
|
onMessage={handleMessage}
|
|
652
812
|
onLoadStart={handleWebViewReset}
|
|
653
|
-
onContentProcessDidTerminate={
|
|
654
|
-
onRenderProcessGone={
|
|
813
|
+
onContentProcessDidTerminate={handleWebViewCrash}
|
|
814
|
+
onRenderProcessGone={handleWebViewCrash}
|
|
655
815
|
// Links already leave the editor via the `linkPress` message, so
|
|
656
816
|
// nothing rendered here needs to navigate. Allowing only the
|
|
657
817
|
// initial `about:blank` load closes off `data:`/script-driven
|
|
@@ -689,6 +849,7 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
689
849
|
{slashState.active && slashState.caret ? (
|
|
690
850
|
<NoteEditorSlashMenu
|
|
691
851
|
slash={slashState}
|
|
852
|
+
editorWidth={editorWidth}
|
|
692
853
|
canUploadImage={Boolean(onImageUpload)}
|
|
693
854
|
onSelect={handleSlashSelect}
|
|
694
855
|
/>
|
|
@@ -703,13 +864,22 @@ const NoteEditor = forwardRef<NoteEditorHandle, NoteEditorProps>(
|
|
|
703
864
|
<DialogContent>
|
|
704
865
|
<Input
|
|
705
866
|
value={linkValue}
|
|
706
|
-
onChangeText={
|
|
867
|
+
onChangeText={handleLinkValueChange}
|
|
868
|
+
invalid={linkInvalid}
|
|
707
869
|
placeholder="https://"
|
|
708
870
|
label="URL"
|
|
709
871
|
autoCapitalize="none"
|
|
710
872
|
autoCorrect={false}
|
|
711
873
|
keyboardType="url"
|
|
712
874
|
/>
|
|
875
|
+
{linkInvalid ? (
|
|
876
|
+
<Text
|
|
877
|
+
accessibilityRole="alert"
|
|
878
|
+
className="mt-1 text-xs text-feedback-error-foreground"
|
|
879
|
+
>
|
|
880
|
+
Link inválido. Use um endereço como medway.com.br
|
|
881
|
+
</Text>
|
|
882
|
+
) : null}
|
|
713
883
|
</DialogContent>
|
|
714
884
|
<DialogFooter>
|
|
715
885
|
<Button onPress={confirmLink}>Confirmar</Button>
|
|
@@ -769,6 +939,7 @@ function NoteEditorToolbar({
|
|
|
769
939
|
accessibilityLabel={item.label}
|
|
770
940
|
accessibilityState={{ disabled, selected: active }}
|
|
771
941
|
disabled={disabled}
|
|
942
|
+
hitSlop={TOOLBAR_BUTTON_HIT_SLOP}
|
|
772
943
|
onPress={item.onPress ?? (() => item.command && onCommand(item.command))}
|
|
773
944
|
style={buttonStyle}
|
|
774
945
|
>
|
|
@@ -787,6 +958,7 @@ function NoteEditorToolbar({
|
|
|
787
958
|
accessibilityLabel="Inserir imagem"
|
|
788
959
|
accessibilityState={{ disabled: allDisabled }}
|
|
789
960
|
disabled={allDisabled}
|
|
961
|
+
hitSlop={TOOLBAR_BUTTON_HIT_SLOP}
|
|
790
962
|
onPress={onImagePress}
|
|
791
963
|
style={[
|
|
792
964
|
styles.toolbarButton,
|
|
@@ -810,10 +982,12 @@ function NoteEditorToolbar({
|
|
|
810
982
|
*/
|
|
811
983
|
function NoteEditorSlashMenu({
|
|
812
984
|
slash,
|
|
985
|
+
editorWidth,
|
|
813
986
|
canUploadImage,
|
|
814
987
|
onSelect,
|
|
815
988
|
}: {
|
|
816
989
|
slash: SlashMenuState;
|
|
990
|
+
editorWidth: number;
|
|
817
991
|
canUploadImage: boolean;
|
|
818
992
|
onSelect: (id: SlashCommandId) => void;
|
|
819
993
|
}) {
|
|
@@ -835,8 +1009,8 @@ function NoteEditorSlashMenu({
|
|
|
835
1009
|
style={[
|
|
836
1010
|
styles.slashMenuShadow,
|
|
837
1011
|
{
|
|
838
|
-
left: slash.caret.x,
|
|
839
|
-
top: slash.caret.y + slash.caret.height +
|
|
1012
|
+
left: getSlashMenuLeft(slash.caret.x, editorWidth),
|
|
1013
|
+
top: slash.caret.y + slash.caret.height + SLASH_MENU_GAP,
|
|
840
1014
|
},
|
|
841
1015
|
]}
|
|
842
1016
|
>
|
|
@@ -921,8 +1095,8 @@ const styles = StyleSheet.create({
|
|
|
921
1095
|
marginHorizontal: 4,
|
|
922
1096
|
},
|
|
923
1097
|
toolbarButton: {
|
|
924
|
-
width:
|
|
925
|
-
height:
|
|
1098
|
+
width: TOOLBAR_BUTTON_SIZE,
|
|
1099
|
+
height: TOOLBAR_BUTTON_SIZE,
|
|
926
1100
|
alignItems: "center",
|
|
927
1101
|
justifyContent: "center",
|
|
928
1102
|
borderRadius: 6,
|
|
@@ -933,7 +1107,7 @@ const styles = StyleSheet.create({
|
|
|
933
1107
|
slashMenuShadow: {
|
|
934
1108
|
position: "absolute",
|
|
935
1109
|
zIndex: 50,
|
|
936
|
-
width:
|
|
1110
|
+
width: SLASH_MENU_WIDTH,
|
|
937
1111
|
shadowColor: "#000",
|
|
938
1112
|
shadowOffset: { width: 0, height: 4 },
|
|
939
1113
|
shadowOpacity: 0.14,
|