diff --git a/package-lock.json b/package-lock.json index 0d82b86..4b33c70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,10 @@ "react-native-safe-area-context": "~5.7.0", "react-native-screens": "~4.23.0", "react-native-web": "~0.21.2", - "react-native-worklets": "0.7.4" + "react-native-worklets": "0.7.4", + "slate": "^0.124.1", + "slate-history": "^0.113.1", + "slate-react": "^0.124.0" }, "devDependencies": { "@atproto/lex": "^0.0.25", @@ -3018,6 +3021,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@juggle/resize-observer": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", + "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", + "license": "Apache-2.0" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -6060,6 +6069,12 @@ "node": ">= 0.6" } }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6352,6 +6367,19 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/direction": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", + "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dnssd-advertise": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.4.tgz", @@ -9197,6 +9225,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==", + "license": "MIT" + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -9249,6 +9283,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -10094,6 +10137,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -12539,6 +12588,15 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -12882,6 +12940,63 @@ "node": ">=8" } }, + "node_modules/slate": { + "version": "0.124.1", + "resolved": "https://registry.npmjs.org/slate/-/slate-0.124.1.tgz", + "integrity": "sha512-ii7DwezgvbLAyKtHBIunjTR1kzbNfYLCUKLMzJELlbTZkvHzX4DzN7HKIwcakf6dPxO6AoeT/P7kHOcyTym/hA==", + "license": "MIT" + }, + "node_modules/slate-dom": { + "version": "0.124.1", + "resolved": "https://registry.npmjs.org/slate-dom/-/slate-dom-0.124.1.tgz", + "integrity": "sha512-D3yVibjLZM4Oj4MmXxOEXbjrlf4wJez3OvGABBNYrAP7gXb0d96tKNtWZ0hGm/5y84idw/LHjZ7W1uTYqFR9rQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@juggle/resize-observer": "^3.4.0", + "direction": "^1.0.4", + "is-hotkey": "^0.2.0", + "is-plain-object": "^5.0.0", + "lodash": "^4.17.21", + "scroll-into-view-if-needed": "^3.1.0", + "tiny-invariant": "1.3.1" + }, + "peerDependencies": { + "slate": ">=0.121.0" + } + }, + "node_modules/slate-history": { + "version": "0.113.1", + "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.113.1.tgz", + "integrity": "sha512-J9NSJ+UG2GxoW0lw5mloaKcN0JI0x2IA5M5FxyGiInpn+QEutxT1WK7S/JneZCMFJBoHs1uu7S7e6pxQjubHmQ==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/slate-react": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.124.0.tgz", + "integrity": "sha512-NLN6ME64ChOgJtiVTKwISS1sI/Y8/qN1cwmDTZM9AQJCl+jR3XNCvDsKNrW0kJU+1G3NgIGaYoVWhgIVEIL+Aw==", + "license": "MIT", + "dependencies": { + "@juggle/resize-observer": "^3.4.0", + "direction": "^1.0.4", + "is-hotkey": "^0.2.0", + "lodash": "^4.17.21", + "scroll-into-view-if-needed": "^3.1.0", + "tiny-invariant": "1.3.1" + }, + "peerDependencies": { + "react": ">=18.2.0", + "react-dom": ">=18.2.0", + "slate": ">=0.121.0", + "slate-dom": ">=0.119.1" + } + }, "node_modules/slugify": { "version": "1.6.9", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", @@ -13347,6 +13462,12 @@ "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", + "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", diff --git a/package.json b/package.json index a513e7f..2bca128 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,10 @@ "react-native-safe-area-context": "~5.7.0", "react-native-screens": "~4.23.0", "react-native-web": "~0.21.2", - "react-native-worklets": "0.7.4" + "react-native-worklets": "0.7.4", + "slate": "^0.124.1", + "slate-history": "^0.113.1", + "slate-react": "^0.124.0" }, "devDependencies": { "@atproto/lex": "^0.0.25", diff --git a/src/components/Composer.tsx b/src/components/Composer.tsx index 2b2d59f..fcd38f7 100644 --- a/src/components/Composer.tsx +++ b/src/components/Composer.tsx @@ -3,6 +3,7 @@ import { Text } from "./primitive/Text"; import { useContext, useEffect, useMemo, useState } from "react"; import { AuthContext } from "../context/AuthContext"; import { Profile, resolveProfile } from "../lib/profile"; +import { RichTextEditor } from "../lib/richtext/editor"; const styles = StyleSheet.create({ composerHeader: { @@ -50,16 +51,10 @@ function ComposerHeader() { } function ComposerBody() { - const [height, setHeight] = useState(0) return ( - { setHeight(event.nativeEvent.contentSize.height) }} - editable - multiline - /> + ); } diff --git a/src/lib/richtext/editor-dom.tsx b/src/lib/richtext/editor-dom.tsx new file mode 100644 index 0000000..6e011b2 --- /dev/null +++ b/src/lib/richtext/editor-dom.tsx @@ -0,0 +1,72 @@ +"use dom"; + +import { useCallback, useState } from "react"; +import { StyleSheet } from "react-native"; +import { createEditor } from "slate"; +import { withHistory } from "slate-history"; +import { Editable, RenderElementProps, Slate, withReact } from "slate-react"; + +const formatStyles = { + default: { + fontFamily: "sans-serif", + color: "#fff", + fontSize: 14, + margin: 10, + }, + regular: {}, + h2: { + fontSize: 20, + }, + h1: { + fontSize: 24, + }, + quote: { + fontSize: 28, + }, + chat: { + fontFamily: "monospace", + }, + cursive: { + fontFamily: "serif", + }, + callout: { + borderLeftWidth: 3, + borderLeftColor: "#7e7e7e", + color: "#9c9c9c", + paddingLeft: 15, + paddingVertical: 6, + }, +}; + +const formatStyleMap = new Map(Object.entries(formatStyles)); + +function ParagraphElement(props: RenderElementProps) { + const appliedStyle = formatStyleMap.get((props.element as any)?.format) ?? formatStyles.regular; + return

{props.children}

; +} + +export function DOMEditor() { + const [slate] = useState(() => withReact(withHistory(createEditor()))); + + const initialValue = [ + { + type: "paragraph", + children: [{ text: "A line of text in a paragraph." }], + }, + ]; + + const renderElement = useCallback((props: RenderElementProps) => { + switch ((props.element as any).type) { + case 'paragraph': + return ; + default: + return <> + } + }, []); + + return ( + + + + ); +} diff --git a/src/lib/richtext/editor.tsx b/src/lib/richtext/editor.tsx new file mode 100644 index 0000000..aa8886d --- /dev/null +++ b/src/lib/richtext/editor.tsx @@ -0,0 +1,13 @@ +import { + ViewProps, +} from "react-native"; +import { DOMEditor } from "./editor-dom"; + + +export function RichTextEditor({ + style, + minHeight, + ...props +}: ViewProps & { minHeight?: number }) { + return +}