@openeditor/native 0.0.33 → 0.0.34
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 +47 -65
- package/dist/controller.d.ts +79 -0
- package/dist/controller.js +254 -0
- package/dist/index.d.ts +4 -101
- package/dist/index.js +2 -910
- package/dist/native-editor.d.ts +43 -0
- package/dist/native-editor.js +171 -0
- package/dist/toolbar-host.d.ts +17 -0
- package/dist/toolbar-host.js +206 -0
- package/dist/toolbar-items.d.ts +88 -0
- package/dist/toolbar-items.js +52 -0
- package/package.json +6 -18
- package/dist/document.d.ts +0 -7
- package/dist/document.js +0 -69
- package/dist/emoji-button.d.ts +0 -8
- package/dist/emoji-button.js +0 -14
- package/dist/emoji-data.d.ts +0 -44
- package/dist/emoji-data.js +0 -50
- package/dist/emoji-picker.d.ts +0 -12
- package/dist/emoji-picker.js +0 -67
- package/dist/selection.d.ts +0 -3
- package/dist/selection.js +0 -32
- package/dist/theme.d.ts +0 -2
- package/dist/theme.js +0 -39
- package/dist/toolbar.d.ts +0 -30
- package/dist/toolbar.js +0 -282
package/dist/document.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { cloneNode, createDocument, findBlockSpecForNode, fromProseMirrorDocument, normalizeDocument, toProseMirrorDocument, } from "@openeditor/core";
|
|
2
|
-
import { defaultBlockRegistry } from "@openeditor/extensions";
|
|
3
|
-
const NATIVE_HEADING_PATTERN = /^h([1-6])$/;
|
|
4
|
-
const clampHeadingLevel = (value) => typeof value === "number" ? Math.min(Math.max(value, 1), 6) : 2;
|
|
5
|
-
const cloneAttrsWithoutHeadingLevel = (attrs) => {
|
|
6
|
-
if (!attrs) {
|
|
7
|
-
return undefined;
|
|
8
|
-
}
|
|
9
|
-
const { level: _level, ...nextAttrs } = attrs;
|
|
10
|
-
return Object.keys(nextAttrs).length ? nextAttrs : undefined;
|
|
11
|
-
};
|
|
12
|
-
const withMappedContent = (node, mapNode) => {
|
|
13
|
-
const cloned = cloneNode(node);
|
|
14
|
-
return {
|
|
15
|
-
...cloned,
|
|
16
|
-
...(cloned.content ? { content: cloned.content.map(mapNode) } : {}),
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
const sanitizeNativeNode = (node) => {
|
|
20
|
-
const spec = findBlockSpecForNode(defaultBlockRegistry, node);
|
|
21
|
-
if (spec?.support?.native === "unsupported") {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
const cloned = cloneNode(node);
|
|
25
|
-
const nextContent = cloned.content
|
|
26
|
-
?.map(sanitizeNativeNode)
|
|
27
|
-
.filter((child) => child !== null);
|
|
28
|
-
return {
|
|
29
|
-
...cloned,
|
|
30
|
-
...(nextContent ? { content: nextContent } : {}),
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
export const sanitizeNativeEditorDocument = (document, meta = document.meta) => createDocument(normalizeDocument(document).content
|
|
34
|
-
.map(sanitizeNativeNode)
|
|
35
|
-
.filter((node) => node !== null), meta);
|
|
36
|
-
export const toNativeEditorNode = (node) => {
|
|
37
|
-
if (node.type === "heading") {
|
|
38
|
-
const cloned = withMappedContent(node, toNativeEditorNode);
|
|
39
|
-
const attrs = cloneAttrsWithoutHeadingLevel(node.attrs);
|
|
40
|
-
const { attrs: _attrs, ...headingNode } = cloned;
|
|
41
|
-
return {
|
|
42
|
-
...headingNode,
|
|
43
|
-
type: `h${clampHeadingLevel(node.attrs?.level)}`,
|
|
44
|
-
...(attrs ? { attrs } : {}),
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return withMappedContent(node, toNativeEditorNode);
|
|
48
|
-
};
|
|
49
|
-
export const fromNativeEditorNode = (node) => {
|
|
50
|
-
const headingMatch = NATIVE_HEADING_PATTERN.exec(node.type);
|
|
51
|
-
if (headingMatch) {
|
|
52
|
-
const cloned = withMappedContent(node, fromNativeEditorNode);
|
|
53
|
-
return {
|
|
54
|
-
...cloned,
|
|
55
|
-
type: "heading",
|
|
56
|
-
attrs: { ...node.attrs, level: Number(headingMatch[1]) },
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
return withMappedContent(node, fromNativeEditorNode);
|
|
60
|
-
};
|
|
61
|
-
export const toNativeEditorDocument = (document) => ({
|
|
62
|
-
type: "doc",
|
|
63
|
-
content: toProseMirrorDocument(sanitizeNativeEditorDocument(document)).content.map(toNativeEditorNode),
|
|
64
|
-
});
|
|
65
|
-
export const fromNativeEditorDocument = (document, meta) => normalizeDocument(fromProseMirrorDocument({
|
|
66
|
-
type: "doc",
|
|
67
|
-
content: document.content.map(fromNativeEditorNode),
|
|
68
|
-
}, meta));
|
|
69
|
-
export const createNativeEditorDocument = (content, meta) => toNativeEditorDocument(createDocument(content, meta));
|
package/dist/emoji-button.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type OpenEditorNativeEmoji } from "./emoji-data.js";
|
|
2
|
-
type EmojiButtonProps = {
|
|
3
|
-
item: OpenEditorNativeEmoji;
|
|
4
|
-
size: number;
|
|
5
|
-
onSelect: (emoji: string) => void;
|
|
6
|
-
};
|
|
7
|
-
export declare const EmojiButton: (props: EmojiButtonProps) => import("react").JSX.Element;
|
|
8
|
-
export {};
|
package/dist/emoji-button.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { MenuView } from "@expo/ui/community/menu";
|
|
3
|
-
import { useMemo } from "react";
|
|
4
|
-
import { Pressable, Text } from "react-native";
|
|
5
|
-
import { emojiForNativeSkinTone, openEditorNativeEmojiSkinToneOptions, } from "./emoji-data.js";
|
|
6
|
-
const EmojiTrigger = ({ item, size, onSelect }) => (_jsx(Pressable, { accessibilityLabel: item.emoji, accessibilityRole: "button", onPress: () => onSelect(item.emoji), style: { alignItems: "center", height: 44, justifyContent: "center", width: size }, children: _jsx(Text, { style: { fontSize: 27 }, children: item.emoji }) }));
|
|
7
|
-
const ToneEmojiButton = ({ item, size, onSelect }) => {
|
|
8
|
-
const actions = useMemo(() => openEditorNativeEmojiSkinToneOptions.map((option) => {
|
|
9
|
-
const emoji = emojiForNativeSkinTone(item, option.tone);
|
|
10
|
-
return { id: emoji, title: `${emoji} ${option.label}` };
|
|
11
|
-
}), [item]);
|
|
12
|
-
return (_jsx(MenuView, { actions: actions, onPressAction: ({ nativeEvent }) => onSelect(nativeEvent.event), shouldOpenOnLongPress: true, style: { height: 44, width: size }, children: _jsx(EmojiTrigger, { item: item, onSelect: onSelect, size: size }) }));
|
|
13
|
-
};
|
|
14
|
-
export const EmojiButton = (props) => (props.item.skins?.length ? _jsx(ToneEmojiButton, { ...props }) : _jsx(EmojiTrigger, { ...props }));
|
package/dist/emoji-data.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export type OpenEditorNativeEmojiSkinTone = 0 | 1 | 2 | 3 | 4 | 5;
|
|
2
|
-
export type OpenEditorNativeEmoji = {
|
|
3
|
-
emoji: string;
|
|
4
|
-
label: string;
|
|
5
|
-
search: string;
|
|
6
|
-
group: number;
|
|
7
|
-
order: number;
|
|
8
|
-
skins?: readonly {
|
|
9
|
-
emoji: string;
|
|
10
|
-
tone?: number;
|
|
11
|
-
}[];
|
|
12
|
-
};
|
|
13
|
-
export type OpenEditorNativeEmojiSection = {
|
|
14
|
-
key: string;
|
|
15
|
-
title: string;
|
|
16
|
-
data: readonly OpenEditorNativeEmoji[];
|
|
17
|
-
};
|
|
18
|
-
export declare const openEditorNativeEmojiSkinToneOptions: readonly [{
|
|
19
|
-
readonly tone: 0;
|
|
20
|
-
readonly emoji: "✋";
|
|
21
|
-
readonly label: "Default skin tone";
|
|
22
|
-
}, {
|
|
23
|
-
readonly tone: 1;
|
|
24
|
-
readonly emoji: "✋🏻";
|
|
25
|
-
readonly label: "Light skin tone";
|
|
26
|
-
}, {
|
|
27
|
-
readonly tone: 2;
|
|
28
|
-
readonly emoji: "✋🏼";
|
|
29
|
-
readonly label: "Medium-light skin tone";
|
|
30
|
-
}, {
|
|
31
|
-
readonly tone: 3;
|
|
32
|
-
readonly emoji: "✋🏽";
|
|
33
|
-
readonly label: "Medium skin tone";
|
|
34
|
-
}, {
|
|
35
|
-
readonly tone: 4;
|
|
36
|
-
readonly emoji: "✋🏾";
|
|
37
|
-
readonly label: "Medium-dark skin tone";
|
|
38
|
-
}, {
|
|
39
|
-
readonly tone: 5;
|
|
40
|
-
readonly emoji: "✋🏿";
|
|
41
|
-
readonly label: "Dark skin tone";
|
|
42
|
-
}];
|
|
43
|
-
export declare const emojiForNativeSkinTone: (item: OpenEditorNativeEmoji, skinTone: OpenEditorNativeEmojiSkinTone) => string;
|
|
44
|
-
export declare const getOpenEditorNativeEmojiSections: (query?: string) => readonly OpenEditorNativeEmojiSection[];
|
package/dist/emoji-data.js
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import emojiDataJson from "emojibase-data/en/data.json";
|
|
2
|
-
import emojiMessagesJson from "emojibase-data/en/messages.json";
|
|
3
|
-
const normalizeSearch = (value) => value.toLocaleLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").trim();
|
|
4
|
-
const rawEmojiData = emojiDataJson;
|
|
5
|
-
const rawMessages = emojiMessagesJson;
|
|
6
|
-
const groupMessages = new Map(rawMessages.groups.map((group) => [group.order, { key: group.key, title: group.message }]));
|
|
7
|
-
const nativeEmoji = rawEmojiData
|
|
8
|
-
.filter((item) => typeof item.group === "number" && item.group !== 2)
|
|
9
|
-
.map((item) => ({
|
|
10
|
-
emoji: item.emoji,
|
|
11
|
-
label: item.label,
|
|
12
|
-
search: normalizeSearch([item.label, ...(item.tags ?? [])].join(" ")),
|
|
13
|
-
group: item.group,
|
|
14
|
-
order: item.order ?? Number.MAX_SAFE_INTEGER,
|
|
15
|
-
skins: item.skins,
|
|
16
|
-
}))
|
|
17
|
-
.sort((left, right) => left.order - right.order);
|
|
18
|
-
export const openEditorNativeEmojiSkinToneOptions = [
|
|
19
|
-
{ tone: 0, emoji: "✋", label: "Default skin tone" },
|
|
20
|
-
{ tone: 1, emoji: "✋🏻", label: "Light skin tone" },
|
|
21
|
-
{ tone: 2, emoji: "✋🏼", label: "Medium-light skin tone" },
|
|
22
|
-
{ tone: 3, emoji: "✋🏽", label: "Medium skin tone" },
|
|
23
|
-
{ tone: 4, emoji: "✋🏾", label: "Medium-dark skin tone" },
|
|
24
|
-
{ tone: 5, emoji: "✋🏿", label: "Dark skin tone" },
|
|
25
|
-
];
|
|
26
|
-
export const emojiForNativeSkinTone = (item, skinTone) => {
|
|
27
|
-
if (skinTone === 0)
|
|
28
|
-
return item.emoji;
|
|
29
|
-
return item.skins?.find((skin) => skin.tone === skinTone)?.emoji ?? item.emoji;
|
|
30
|
-
};
|
|
31
|
-
export const getOpenEditorNativeEmojiSections = (query = "") => {
|
|
32
|
-
const normalizedQuery = normalizeSearch(query);
|
|
33
|
-
const groups = new Map();
|
|
34
|
-
for (const item of nativeEmoji) {
|
|
35
|
-
if (normalizedQuery && !item.search.includes(normalizedQuery))
|
|
36
|
-
continue;
|
|
37
|
-
const group = groups.get(item.group);
|
|
38
|
-
if (group)
|
|
39
|
-
group.push(item);
|
|
40
|
-
else
|
|
41
|
-
groups.set(item.group, [item]);
|
|
42
|
-
}
|
|
43
|
-
return [...groups.entries()]
|
|
44
|
-
.sort(([left], [right]) => left - right)
|
|
45
|
-
.map(([group, data]) => ({
|
|
46
|
-
key: groupMessages.get(group)?.key ?? String(group),
|
|
47
|
-
title: groupMessages.get(group)?.title ?? "Emoji",
|
|
48
|
-
data,
|
|
49
|
-
}));
|
|
50
|
-
};
|
package/dist/emoji-picker.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type OpenEditorNativeEmojiPickerTheme = {
|
|
2
|
-
surfaceMuted?: string;
|
|
3
|
-
text?: string;
|
|
4
|
-
muted?: string;
|
|
5
|
-
};
|
|
6
|
-
export type OpenEditorNativeEmojiPickerProps = {
|
|
7
|
-
visible: boolean;
|
|
8
|
-
onEmojiSelect: (emoji: string) => void;
|
|
9
|
-
onRequestClose: () => void;
|
|
10
|
-
theme?: OpenEditorNativeEmojiPickerTheme;
|
|
11
|
-
};
|
|
12
|
-
export declare const OpenEditorNativeEmojiPicker: ({ visible, onEmojiSelect, onRequestClose, theme, }: OpenEditorNativeEmojiPickerProps) => import("react").JSX.Element;
|
package/dist/emoji-picker.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { BottomSheet, RNHostView } from "@expo/ui";
|
|
3
|
-
import { useMemo, useState } from "react";
|
|
4
|
-
import { ScrollView, Text, TextInput, useWindowDimensions, View, } from "react-native";
|
|
5
|
-
import { getOpenEditorNativeEmojiSections, } from "./emoji-data.js";
|
|
6
|
-
import { EmojiButton } from "./emoji-button.js";
|
|
7
|
-
const toRows = (items, columns = 8) => {
|
|
8
|
-
const rows = [];
|
|
9
|
-
for (let index = 0; index < items.length; index += columns) {
|
|
10
|
-
rows.push(items.slice(index, index + columns));
|
|
11
|
-
}
|
|
12
|
-
return rows;
|
|
13
|
-
};
|
|
14
|
-
const DEFAULT_THEME = {
|
|
15
|
-
surfaceMuted: "#eeeeee",
|
|
16
|
-
text: "#171717",
|
|
17
|
-
muted: "#737373",
|
|
18
|
-
};
|
|
19
|
-
export const OpenEditorNativeEmojiPicker = ({ visible, onEmojiSelect, onRequestClose, theme, }) => {
|
|
20
|
-
const { height: windowHeight, width: windowWidth } = useWindowDimensions();
|
|
21
|
-
const [query, setQuery] = useState("");
|
|
22
|
-
const resolvedTheme = { ...DEFAULT_THEME, ...theme };
|
|
23
|
-
const nativeScrollHeight = Math.max(240, Math.floor(windowHeight * 0.5) - 32);
|
|
24
|
-
const contentWidth = Math.max(240, Math.min(windowWidth - 32, 520));
|
|
25
|
-
const emojiButtonWidth = contentWidth / 8;
|
|
26
|
-
const sections = useMemo(() => getOpenEditorNativeEmojiSections(query).map((section) => ({
|
|
27
|
-
...section,
|
|
28
|
-
data: toRows(section.data),
|
|
29
|
-
})), [query]);
|
|
30
|
-
const close = () => {
|
|
31
|
-
setQuery("");
|
|
32
|
-
onRequestClose();
|
|
33
|
-
};
|
|
34
|
-
const selectEmoji = (selectedEmoji) => {
|
|
35
|
-
onEmojiSelect(selectedEmoji);
|
|
36
|
-
close();
|
|
37
|
-
};
|
|
38
|
-
return (_jsx(BottomSheet, { isPresented: visible, onDismiss: close, snapPoints: ["half"], children: _jsx(RNHostView, { matchContents: true, children: _jsxs(ScrollView, { contentContainerStyle: { paddingBottom: 32 }, keyboardShouldPersistTaps: "handled", style: { height: nativeScrollHeight, width: contentWidth }, children: [_jsx(TextInput, { autoCapitalize: "none", autoCorrect: false, onChangeText: setQuery, placeholder: "Search emoji\u2026", placeholderTextColor: resolvedTheme.muted, returnKeyType: "search", style: {
|
|
39
|
-
backgroundColor: resolvedTheme.surfaceMuted,
|
|
40
|
-
borderRadius: 10,
|
|
41
|
-
color: resolvedTheme.text,
|
|
42
|
-
fontSize: 16,
|
|
43
|
-
height: 44,
|
|
44
|
-
paddingHorizontal: 12,
|
|
45
|
-
width: contentWidth,
|
|
46
|
-
} }), sections.length === 0 ? (_jsx(Text, { style: {
|
|
47
|
-
color: resolvedTheme.muted,
|
|
48
|
-
fontSize: 15,
|
|
49
|
-
height: 160,
|
|
50
|
-
paddingTop: 48,
|
|
51
|
-
textAlign: "center",
|
|
52
|
-
width: contentWidth,
|
|
53
|
-
}, children: "No emoji found." })) : sections.map((section) => (_jsxs(View, { style: { width: contentWidth }, children: [_jsx(Text, { style: {
|
|
54
|
-
color: resolvedTheme.muted,
|
|
55
|
-
fontSize: 12,
|
|
56
|
-
fontWeight: "600",
|
|
57
|
-
height: 32,
|
|
58
|
-
paddingHorizontal: 8,
|
|
59
|
-
paddingVertical: 8,
|
|
60
|
-
width: contentWidth,
|
|
61
|
-
}, children: section.title }), section.data.map((row, rowIndex) => (_jsx(View, { style: {
|
|
62
|
-
alignItems: "center",
|
|
63
|
-
flexDirection: "row",
|
|
64
|
-
height: 44,
|
|
65
|
-
width: contentWidth,
|
|
66
|
-
}, children: row.map((item) => (_jsx(EmojiButton, { item: item, onSelect: selectEmoji, size: emojiButtonWidth }, item.emoji))) }, `${section.key}-${rowIndex}`)))] }, section.key)))] }) }) }));
|
|
67
|
-
};
|
package/dist/selection.d.ts
DELETED
package/dist/selection.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const getNodeTextLength = (node) => {
|
|
2
|
-
if (node.type === "text") {
|
|
3
|
-
return node.text?.length ?? 0;
|
|
4
|
-
}
|
|
5
|
-
return node.content?.reduce((total, child) => total + getNodeSize(child), 0) ?? 0;
|
|
6
|
-
};
|
|
7
|
-
const getNodeSize = (node) => {
|
|
8
|
-
if (node.type === "text") {
|
|
9
|
-
return getNodeTextLength(node);
|
|
10
|
-
}
|
|
11
|
-
if (!node.content?.length) {
|
|
12
|
-
return 1;
|
|
13
|
-
}
|
|
14
|
-
return node.content.reduce((total, child) => total + getNodeSize(child), 2);
|
|
15
|
-
};
|
|
16
|
-
export const resolveSelectedTopLevelIndex = (document, selection) => {
|
|
17
|
-
const position = selection?.type === "node"
|
|
18
|
-
? selection.pos
|
|
19
|
-
: selection?.type === "text"
|
|
20
|
-
? Math.min(selection.anchor ?? 0, selection.head ?? 0)
|
|
21
|
-
: 0;
|
|
22
|
-
let offset = 0;
|
|
23
|
-
for (let index = 0; index < document.content.length; index += 1) {
|
|
24
|
-
const nodeSize = getNodeSize(document.content[index]);
|
|
25
|
-
// A caret at a block boundary belongs to the following block, not the previous one.
|
|
26
|
-
if (position == null || position < offset + nodeSize || index === document.content.length - 1) {
|
|
27
|
-
return index;
|
|
28
|
-
}
|
|
29
|
-
offset += nodeSize;
|
|
30
|
-
}
|
|
31
|
-
return Math.max(document.content.length - 1, 0);
|
|
32
|
-
};
|
package/dist/theme.d.ts
DELETED
package/dist/theme.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const HSL_COLOR_PATTERN = /^hsla?\(\s*([-+\d.]+)(?:deg)?[\s,]+([-+\d.]+)%[\s,]+([-+\d.]+)%(?:\s*(?:\/|,)\s*([-+\d.]+%?))?\s*\)$/i;
|
|
2
|
-
const clamp = (value, minimum, maximum) => Math.min(Math.max(value, minimum), maximum);
|
|
3
|
-
const hueToRgb = (p, q, input) => {
|
|
4
|
-
let hue = input;
|
|
5
|
-
if (hue < 0)
|
|
6
|
-
hue += 1;
|
|
7
|
-
if (hue > 1)
|
|
8
|
-
hue -= 1;
|
|
9
|
-
if (hue < 1 / 6)
|
|
10
|
-
return p + (q - p) * 6 * hue;
|
|
11
|
-
if (hue < 1 / 2)
|
|
12
|
-
return q;
|
|
13
|
-
if (hue < 2 / 3)
|
|
14
|
-
return p + (q - p) * (2 / 3 - hue) * 6;
|
|
15
|
-
return p;
|
|
16
|
-
};
|
|
17
|
-
const toByte = (value) => Math.round(clamp(value, 0, 1) * 255 + 1e-9);
|
|
18
|
-
const toHexByte = (value) => value.toString(16).padStart(2, "0");
|
|
19
|
-
/** Converts CSS HSL colors into a native-safe hex color and preserves other color strings. */
|
|
20
|
-
export const normalizeNativeThemeColor = (color) => {
|
|
21
|
-
const match = HSL_COLOR_PATTERN.exec(color.trim());
|
|
22
|
-
if (!match)
|
|
23
|
-
return color;
|
|
24
|
-
const hue = (((Number(match[1]) % 360) + 360) % 360) / 360;
|
|
25
|
-
const saturation = clamp(Number(match[2]) / 100, 0, 1);
|
|
26
|
-
const lightness = clamp(Number(match[3]) / 100, 0, 1);
|
|
27
|
-
const alphaRaw = match[4];
|
|
28
|
-
const alpha = alphaRaw
|
|
29
|
-
? clamp(Number(alphaRaw.replace("%", "")) / (alphaRaw.endsWith("%") ? 100 : 1), 0, 1)
|
|
30
|
-
: 1;
|
|
31
|
-
const q = lightness < 0.5
|
|
32
|
-
? lightness * (1 + saturation)
|
|
33
|
-
: lightness + saturation - lightness * saturation;
|
|
34
|
-
const p = 2 * lightness - q;
|
|
35
|
-
const red = saturation === 0 ? lightness : hueToRgb(p, q, hue + 1 / 3);
|
|
36
|
-
const green = saturation === 0 ? lightness : hueToRgb(p, q, hue);
|
|
37
|
-
const blue = saturation === 0 ? lightness : hueToRgb(p, q, hue - 1 / 3);
|
|
38
|
-
return `#${toHexByte(toByte(red))}${toHexByte(toByte(green))}${toHexByte(toByte(blue))}${alpha < 1 ? toHexByte(toByte(alpha)) : ""}`;
|
|
39
|
-
};
|
package/dist/toolbar.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { type ProseMirrorDocument } from "@openeditor/core";
|
|
2
|
-
import type { EditorToolbarItem } from "@openeditor/react-native-prose-editor";
|
|
3
|
-
export declare const openEditorNativeToolbarActionKeys: {
|
|
4
|
-
readonly paragraph: "paragraph";
|
|
5
|
-
readonly bulletList: "bulletList";
|
|
6
|
-
readonly orderedList: "orderedList";
|
|
7
|
-
readonly columns: "columns";
|
|
8
|
-
readonly table: "table";
|
|
9
|
-
readonly toggleList: "toggleList";
|
|
10
|
-
readonly callout: "callout";
|
|
11
|
-
readonly page: "page";
|
|
12
|
-
readonly attachment: "attachment";
|
|
13
|
-
readonly moveBlockUp: "openeditor:block:moveUp";
|
|
14
|
-
readonly moveBlockDown: "openeditor:block:moveDown";
|
|
15
|
-
readonly undo: "openeditor:history:undo";
|
|
16
|
-
readonly redo: "openeditor:history:redo";
|
|
17
|
-
readonly dismissKeyboard: "openeditor:keyboard:dismiss";
|
|
18
|
-
};
|
|
19
|
-
export type OpenEditorNativeToolbarActionKey = (typeof openEditorNativeToolbarActionKeys)[keyof typeof openEditorNativeToolbarActionKeys];
|
|
20
|
-
export declare const createOpenEditorNativeToolbarItems: ({ canUndo, canRedo, enabledBlocks, }?: {
|
|
21
|
-
canUndo?: boolean;
|
|
22
|
-
canRedo?: boolean;
|
|
23
|
-
enabledBlocks?: readonly string[];
|
|
24
|
-
}) => readonly EditorToolbarItem[];
|
|
25
|
-
export declare const openEditorNativeToolbarItems: readonly EditorToolbarItem[];
|
|
26
|
-
export declare const nativeToolbarParityCoverage: () => {
|
|
27
|
-
missingBlockControls: string[];
|
|
28
|
-
missingMarkControls: ("bold" | "link" | "strike" | "italic" | "underline" | "code")[];
|
|
29
|
-
};
|
|
30
|
-
export declare const defaultDocumentForToolbarAction: (key: string) => ProseMirrorDocument | null;
|
package/dist/toolbar.js
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { isOpenEditorBlockEnabled } from "@openeditor/core";
|
|
2
|
-
import { createOpenEditorInsertDocument, defaultBlockSpecs, defaultMarkSpecs, } from "@openeditor/extensions";
|
|
3
|
-
import { toNativeEditorDocument } from "./document.js";
|
|
4
|
-
const glyphIcon = (text) => ({ type: "glyph", text });
|
|
5
|
-
const defaultIcon = (id) => ({ type: "default", id });
|
|
6
|
-
const platformIcon = (iosName, androidName, fallbackText) => ({
|
|
7
|
-
type: "platform",
|
|
8
|
-
ios: { type: "sfSymbol", name: iosName },
|
|
9
|
-
android: { type: "material", name: androidName },
|
|
10
|
-
fallbackText,
|
|
11
|
-
});
|
|
12
|
-
export const openEditorNativeToolbarActionKeys = {
|
|
13
|
-
paragraph: "paragraph",
|
|
14
|
-
bulletList: "bulletList",
|
|
15
|
-
orderedList: "orderedList",
|
|
16
|
-
columns: "columns",
|
|
17
|
-
table: "table",
|
|
18
|
-
toggleList: "toggleList",
|
|
19
|
-
callout: "callout",
|
|
20
|
-
page: "page",
|
|
21
|
-
attachment: "attachment",
|
|
22
|
-
moveBlockUp: "openeditor:block:moveUp",
|
|
23
|
-
moveBlockDown: "openeditor:block:moveDown",
|
|
24
|
-
undo: "openeditor:history:undo",
|
|
25
|
-
redo: "openeditor:history:redo",
|
|
26
|
-
dismissKeyboard: "openeditor:keyboard:dismiss",
|
|
27
|
-
};
|
|
28
|
-
export const createOpenEditorNativeToolbarItems = ({ canUndo = true, canRedo = true, enabledBlocks, } = {}) => {
|
|
29
|
-
const items = [
|
|
30
|
-
{
|
|
31
|
-
type: "group",
|
|
32
|
-
key: "openeditor-text",
|
|
33
|
-
label: "Text",
|
|
34
|
-
icon: glyphIcon("T"),
|
|
35
|
-
presentation: "menu",
|
|
36
|
-
items: [
|
|
37
|
-
{ type: "action", key: openEditorNativeToolbarActionKeys.paragraph, label: "Paragraph", icon: glyphIcon("P") },
|
|
38
|
-
{ type: "heading", level: 1, label: "Heading 1", icon: glyphIcon("H1") },
|
|
39
|
-
{ type: "heading", level: 2, label: "Heading 2", icon: glyphIcon("H2") },
|
|
40
|
-
{ type: "heading", level: 3, label: "Heading 3", icon: glyphIcon("H3") },
|
|
41
|
-
{ type: "heading", level: 4, label: "Heading 4", icon: glyphIcon("H4") },
|
|
42
|
-
{ type: "heading", level: 5, label: "Heading 5", icon: glyphIcon("H5") },
|
|
43
|
-
{ type: "heading", level: 6, label: "Heading 6", icon: glyphIcon("H6") },
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
type: "group",
|
|
48
|
-
key: "openeditor-marks",
|
|
49
|
-
label: "Marks",
|
|
50
|
-
icon: defaultIcon("bold"),
|
|
51
|
-
presentation: "menu",
|
|
52
|
-
items: [
|
|
53
|
-
{ type: "mark", mark: "bold", label: "Bold", icon: defaultIcon("bold") },
|
|
54
|
-
{ type: "mark", mark: "italic", label: "Italic", icon: defaultIcon("italic") },
|
|
55
|
-
{ type: "mark", mark: "underline", label: "Underline", icon: defaultIcon("underline") },
|
|
56
|
-
{ type: "mark", mark: "strike", label: "Strikethrough", icon: defaultIcon("strike") },
|
|
57
|
-
{ type: "mark", mark: "code", label: "Inline Code", icon: platformIcon("curlybraces", "code", "{}") },
|
|
58
|
-
{ type: "link", label: "Link", icon: defaultIcon("link") },
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
type: "group",
|
|
63
|
-
key: "openeditor-lists",
|
|
64
|
-
label: "Lists",
|
|
65
|
-
icon: defaultIcon("bulletList"),
|
|
66
|
-
presentation: "menu",
|
|
67
|
-
items: [
|
|
68
|
-
{ type: "action", key: openEditorNativeToolbarActionKeys.bulletList, label: "Bullet List", icon: defaultIcon("bulletList") },
|
|
69
|
-
{ type: "action", key: openEditorNativeToolbarActionKeys.orderedList, label: "Numbered List", icon: defaultIcon("orderedList") },
|
|
70
|
-
{ type: "command", command: "indentList", label: "Indent List", icon: defaultIcon("indentList") },
|
|
71
|
-
{ type: "command", command: "outdentList", label: "Outdent List", icon: defaultIcon("outdentList") },
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
type: "group",
|
|
76
|
-
key: "openeditor-blocks",
|
|
77
|
-
label: "Blocks",
|
|
78
|
-
icon: defaultIcon("blockquote"),
|
|
79
|
-
presentation: "menu",
|
|
80
|
-
items: [
|
|
81
|
-
{ type: "blockquote", label: "Quote", icon: defaultIcon("blockquote") },
|
|
82
|
-
{ type: "node", nodeType: "horizontalRule", label: "Divider", icon: defaultIcon("horizontalRule") },
|
|
83
|
-
{ type: "action", key: openEditorNativeToolbarActionKeys.columns, label: "Columns", icon: glyphIcon("Cols") },
|
|
84
|
-
{ type: "action", key: openEditorNativeToolbarActionKeys.table, label: "Table", icon: glyphIcon("Tbl") },
|
|
85
|
-
{ type: "action", key: openEditorNativeToolbarActionKeys.toggleList, label: "Toggle List", icon: glyphIcon("Tgl") },
|
|
86
|
-
{ type: "action", key: openEditorNativeToolbarActionKeys.callout, label: "Callout", icon: glyphIcon("!") },
|
|
87
|
-
{ type: "action", key: openEditorNativeToolbarActionKeys.page, label: "Page", icon: glyphIcon("Pg") },
|
|
88
|
-
{ type: "command", command: "insertTableRowAfter", label: "Add Row", icon: glyphIcon("+R") },
|
|
89
|
-
{ type: "command", command: "insertTableColumnAfter", label: "Add Column", icon: glyphIcon("+C") },
|
|
90
|
-
{ type: "command", command: "deleteTableRow", label: "Delete Row", icon: glyphIcon("-R") },
|
|
91
|
-
{ type: "command", command: "deleteTableColumn", label: "Delete Column", icon: glyphIcon("-C") },
|
|
92
|
-
{ type: "image", label: "Image", icon: defaultIcon("image") },
|
|
93
|
-
],
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
type: "action",
|
|
97
|
-
key: openEditorNativeToolbarActionKeys.attachment,
|
|
98
|
-
label: "Upload file",
|
|
99
|
-
icon: platformIcon("paperclip", "attach-file", "File"),
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
type: "action",
|
|
103
|
-
key: openEditorNativeToolbarActionKeys.moveBlockUp,
|
|
104
|
-
label: "Move Block Up",
|
|
105
|
-
icon: {
|
|
106
|
-
type: "platform",
|
|
107
|
-
ios: { type: "sfSymbol", name: "arrow.up" },
|
|
108
|
-
android: { type: "material", name: "keyboard-arrow-up" },
|
|
109
|
-
fallbackText: "Up",
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
type: "action",
|
|
114
|
-
key: openEditorNativeToolbarActionKeys.moveBlockDown,
|
|
115
|
-
label: "Move Block Down",
|
|
116
|
-
icon: {
|
|
117
|
-
type: "platform",
|
|
118
|
-
ios: { type: "sfSymbol", name: "arrow.down" },
|
|
119
|
-
android: { type: "material", name: "keyboard-arrow-down" },
|
|
120
|
-
fallbackText: "Dn",
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
{ type: "separator" },
|
|
124
|
-
{
|
|
125
|
-
type: "action",
|
|
126
|
-
key: openEditorNativeToolbarActionKeys.undo,
|
|
127
|
-
label: "Undo",
|
|
128
|
-
icon: defaultIcon("undo"),
|
|
129
|
-
isDisabled: !canUndo,
|
|
130
|
-
placement: "end",
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
type: "action",
|
|
134
|
-
key: openEditorNativeToolbarActionKeys.redo,
|
|
135
|
-
label: "Redo",
|
|
136
|
-
icon: defaultIcon("redo"),
|
|
137
|
-
isDisabled: !canRedo,
|
|
138
|
-
placement: "end",
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
type: "action",
|
|
142
|
-
key: openEditorNativeToolbarActionKeys.dismissKeyboard,
|
|
143
|
-
label: "Hide Keyboard",
|
|
144
|
-
icon: {
|
|
145
|
-
type: "platform",
|
|
146
|
-
ios: { type: "sfSymbol", name: "keyboard.chevron.compact.down" },
|
|
147
|
-
android: { type: "material", name: "keyboard_hide" },
|
|
148
|
-
fallbackText: "v",
|
|
149
|
-
},
|
|
150
|
-
placement: "end",
|
|
151
|
-
},
|
|
152
|
-
];
|
|
153
|
-
const actionBlocks = new Map([
|
|
154
|
-
[openEditorNativeToolbarActionKeys.paragraph, "paragraph"],
|
|
155
|
-
[openEditorNativeToolbarActionKeys.bulletList, "bulletList"],
|
|
156
|
-
[openEditorNativeToolbarActionKeys.orderedList, "orderedList"],
|
|
157
|
-
[openEditorNativeToolbarActionKeys.columns, "columns"],
|
|
158
|
-
[openEditorNativeToolbarActionKeys.table, "table"],
|
|
159
|
-
[openEditorNativeToolbarActionKeys.toggleList, "toggleList"],
|
|
160
|
-
[openEditorNativeToolbarActionKeys.callout, "callout"],
|
|
161
|
-
[openEditorNativeToolbarActionKeys.page, "page"],
|
|
162
|
-
[openEditorNativeToolbarActionKeys.attachment, "attachment"],
|
|
163
|
-
]);
|
|
164
|
-
const blockForItem = (item) => {
|
|
165
|
-
if (item.type === "action")
|
|
166
|
-
return actionBlocks.get(item.key);
|
|
167
|
-
if (item.type === "heading")
|
|
168
|
-
return "heading";
|
|
169
|
-
if (item.type === "blockquote")
|
|
170
|
-
return "blockquote";
|
|
171
|
-
if (item.type === "image")
|
|
172
|
-
return "image";
|
|
173
|
-
if (item.type === "node" && item.nodeType === "horizontalRule")
|
|
174
|
-
return "divider";
|
|
175
|
-
if (item.type === "command" && item.command.toLowerCase().includes("table"))
|
|
176
|
-
return "table";
|
|
177
|
-
return undefined;
|
|
178
|
-
};
|
|
179
|
-
const filterItems = (source) => {
|
|
180
|
-
const filtered = [];
|
|
181
|
-
for (const item of source) {
|
|
182
|
-
if (item.type === "group") {
|
|
183
|
-
const children = item.items.filter((child) => {
|
|
184
|
-
const block = blockForItem(child);
|
|
185
|
-
return !block || isOpenEditorBlockEnabled(block, { enabledBlocks });
|
|
186
|
-
});
|
|
187
|
-
if (children.length)
|
|
188
|
-
filtered.push({ ...item, items: children });
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
const block = blockForItem(item);
|
|
192
|
-
if (!block || isOpenEditorBlockEnabled(block, { enabledBlocks }))
|
|
193
|
-
filtered.push(item);
|
|
194
|
-
}
|
|
195
|
-
return filtered;
|
|
196
|
-
};
|
|
197
|
-
return filterItems(items);
|
|
198
|
-
};
|
|
199
|
-
export const openEditorNativeToolbarItems = createOpenEditorNativeToolbarItems();
|
|
200
|
-
const walkToolbarItems = (items) => {
|
|
201
|
-
const leafItems = [];
|
|
202
|
-
for (const item of items) {
|
|
203
|
-
if (item.type === "group") {
|
|
204
|
-
leafItems.push(...walkToolbarItems(item.items));
|
|
205
|
-
}
|
|
206
|
-
else if (item.type !== "separator") {
|
|
207
|
-
leafItems.push(item);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return leafItems;
|
|
211
|
-
};
|
|
212
|
-
export const nativeToolbarParityCoverage = () => {
|
|
213
|
-
const leafItems = walkToolbarItems(openEditorNativeToolbarItems);
|
|
214
|
-
const blockControls = new Map([
|
|
215
|
-
["paragraph", openEditorNativeToolbarActionKeys.paragraph],
|
|
216
|
-
["heading", "heading"],
|
|
217
|
-
["bulletList", openEditorNativeToolbarActionKeys.bulletList],
|
|
218
|
-
["orderedList", openEditorNativeToolbarActionKeys.orderedList],
|
|
219
|
-
["blockquote", "blockquote"],
|
|
220
|
-
["divider", "horizontalRule"],
|
|
221
|
-
["image", "image"],
|
|
222
|
-
["table", openEditorNativeToolbarActionKeys.table],
|
|
223
|
-
["toggleList", openEditorNativeToolbarActionKeys.toggleList],
|
|
224
|
-
["callout", openEditorNativeToolbarActionKeys.callout],
|
|
225
|
-
["page", openEditorNativeToolbarActionKeys.page],
|
|
226
|
-
["attachment", openEditorNativeToolbarActionKeys.attachment],
|
|
227
|
-
]);
|
|
228
|
-
const markControls = new Map([
|
|
229
|
-
["bold", "bold"],
|
|
230
|
-
["italic", "italic"],
|
|
231
|
-
["underline", "underline"],
|
|
232
|
-
["strike", "strike"],
|
|
233
|
-
["code", "code"],
|
|
234
|
-
["link", "link"],
|
|
235
|
-
]);
|
|
236
|
-
const hasBlockControl = (control) => leafItems.some((item) => item.type === "action"
|
|
237
|
-
? item.key === control
|
|
238
|
-
: item.type === "heading"
|
|
239
|
-
? control === "heading"
|
|
240
|
-
: item.type === "list"
|
|
241
|
-
? item.listType === control
|
|
242
|
-
: item.type === "image"
|
|
243
|
-
? control === "image"
|
|
244
|
-
: item.type === "blockquote"
|
|
245
|
-
? control === "blockquote"
|
|
246
|
-
: item.type === "node"
|
|
247
|
-
? item.nodeType === control
|
|
248
|
-
: false);
|
|
249
|
-
const hasMarkControl = (control) => leafItems.some((item) => item.type === "link"
|
|
250
|
-
? control === "link"
|
|
251
|
-
: item.type === "mark"
|
|
252
|
-
? item.mark === control
|
|
253
|
-
: false);
|
|
254
|
-
return {
|
|
255
|
-
missingBlockControls: defaultBlockSpecs
|
|
256
|
-
.filter((spec) => spec.support?.native === "supported")
|
|
257
|
-
.map((spec) => spec.name)
|
|
258
|
-
.filter((name) => !hasBlockControl(blockControls.get(name) ?? "")),
|
|
259
|
-
missingMarkControls: defaultMarkSpecs
|
|
260
|
-
.map((mark) => mark.name)
|
|
261
|
-
.filter((name) => !hasMarkControl(markControls.get(name) ?? "")),
|
|
262
|
-
};
|
|
263
|
-
};
|
|
264
|
-
export const defaultDocumentForToolbarAction = (key) => {
|
|
265
|
-
if (key === "image"
|
|
266
|
-
|| key === "link"
|
|
267
|
-
|| key === "taskList"
|
|
268
|
-
|| key === "codeBlock"
|
|
269
|
-
|| key === "columns"
|
|
270
|
-
|| key === openEditorNativeToolbarActionKeys.table
|
|
271
|
-
|| key === openEditorNativeToolbarActionKeys.moveBlockUp
|
|
272
|
-
|| key === openEditorNativeToolbarActionKeys.moveBlockDown
|
|
273
|
-
|| key === openEditorNativeToolbarActionKeys.dismissKeyboard) {
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
try {
|
|
277
|
-
return toNativeEditorDocument(createOpenEditorInsertDocument(key));
|
|
278
|
-
}
|
|
279
|
-
catch {
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
};
|