From 7bd38f3c74a18a800246afe25ab52a13b4cb9e18 Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Fri, 12 May 2023 08:34:08 +0700 Subject: [PATCH] update composer --- package.json | 1 + pnpm-lock.yaml | 13 ++++ src/renderer/index.css | 4 ++ src/shared/composer/imageUploader.tsx | 99 +++++++++++++++++++++++++++ src/shared/composer/modal.tsx | 27 ++------ src/shared/composer/types/post.tsx | 89 +++++++++++++++++++++++- src/stores/composer.tsx | 4 +- 7 files changed, 211 insertions(+), 26 deletions(-) create mode 100644 src/shared/composer/imageUploader.tsx diff --git a/package.json b/package.json index d555c128..7c7a7053 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react-virtuoso": "^4.3.5", "remark-gfm": "^3.0.1", "slate": "^0.94.1", + "slate-history": "^0.93.0", "slate-react": "^0.94.0", "swr": "^2.1.5", "tailwind-merge": "^1.12.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9edd340d..668ef803 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,9 @@ dependencies: slate: specifier: ^0.94.1 version: 0.94.1 + slate-history: + specifier: ^0.93.0 + version: 0.93.0(slate@0.94.1) slate-react: specifier: ^0.94.0 version: 0.94.0(react-dom@18.2.0)(react@18.2.0)(slate@0.94.1) @@ -4275,6 +4278,16 @@ packages: engines: { node: '>=8' } dev: true + /slate-history@0.93.0(slate@0.94.1): + resolution: + { integrity: sha512-Gr1GMGPipRuxIz41jD2/rbvzPj8eyar56TVMyJBvBeIpQSSjNISssvGNDYfJlSWM8eaRqf6DAcxMKzsLCYeX6g== } + peerDependencies: + slate: '>=0.65.3' + dependencies: + is-plain-object: 5.0.0 + slate: 0.94.1 + dev: false + /slate-react@0.94.0(react-dom@18.2.0)(react@18.2.0)(slate@0.94.1): resolution: { integrity: sha512-8LW3HdC3Rya+3E1Gm5voyXdEfQrJGvm0QYl0bgtW2C06Dh82imxhGIPqRv2sj8j2Zf869oJGNp2bGUoCuNBJ6g== } diff --git a/src/renderer/index.css b/src/renderer/index.css index 3b4e7114..52f83d3f 100644 --- a/src/renderer/index.css +++ b/src/renderer/index.css @@ -24,6 +24,10 @@ background-clip: padding-box; } +span[data-slate-placeholder] { + @apply top-0; +} + @keyframes loop { 0% { transform: translateX(0); diff --git a/src/shared/composer/imageUploader.tsx b/src/shared/composer/imageUploader.tsx new file mode 100644 index 00000000..adf0cb93 --- /dev/null +++ b/src/shared/composer/imageUploader.tsx @@ -0,0 +1,99 @@ +import PlusCircleIcon from '@shared/icons/plusCircle'; + +import { createBlobFromFile } from '@utils/createBlobFromFile'; + +import { open } from '@tauri-apps/api/dialog'; +import { Body, fetch } from '@tauri-apps/api/http'; +import { useState } from 'react'; +import { Transforms } from 'slate'; +import { useSlateStatic } from 'slate-react'; + +export function ImageUploader() { + const editor = useSlateStatic(); + const [loading, setLoading] = useState(false); + + const insertImage = (editor, url) => { + const text = { text: url }; + const image = { type: 'image', url, children: [text] }; + Transforms.insertNodes(editor, image); + }; + + const openFileDialog = async () => { + const selected: any = await open({ + multiple: false, + filters: [ + { + name: 'Image', + extensions: ['png', 'jpeg', 'jpg', 'gif'], + }, + ], + }); + if (Array.isArray(selected)) { + // user selected multiple files + } else if (selected === null) { + // user cancelled the selection + } else { + setLoading(true); + + const filename = selected.split('/').pop(); + const file = await createBlobFromFile(selected); + const buf = await file.arrayBuffer(); + + try { + const res: { data: { file: { id: string } } } = await fetch('https://void.cat/upload?cli=false', { + method: 'POST', + timeout: 5, + headers: { + accept: '*/*', + 'Content-Type': 'application/octet-stream', + 'V-Filename': filename, + 'V-Description': 'Uploaded from https://lume.nu', + 'V-Strip-Metadata': 'true', + }, + body: Body.bytes(buf), + }); + const image = 'https://void.cat/d/' + res.data.file.id + '.webp'; + // update parent state + insertImage(editor, image); + // reset loading state + setLoading(false); + } catch (error) { + // reset loading state + setLoading(false); + // handle error + if (error instanceof SyntaxError) { + // Unexpected token < in JSON + console.log('There was a SyntaxError', error); + } else { + console.log('There was an error', error); + } + } + } + }; + + return ( + + ); +} diff --git a/src/shared/composer/modal.tsx b/src/shared/composer/modal.tsx index 2d2ed562..f715ecaa 100644 --- a/src/shared/composer/modal.tsx +++ b/src/shared/composer/modal.tsx @@ -14,8 +14,6 @@ import { Dialog, Transition } from '@headlessui/react'; import { useAtom } from 'jotai'; import { Fragment, useState } from 'react'; -import PlusCircleIcon from '../icons/plusCircle'; - export function ComposerModal() { const [isOpen, setIsOpen] = useState(false); const [composer] = useAtom(composerAtom); @@ -78,29 +76,14 @@ export function ComposerModal() { -
+
-
-
-
-
-
- {composer.type === 'post' && } -
-
- - -
-
+ {composer.type === 'post' && account && } diff --git a/src/shared/composer/types/post.tsx b/src/shared/composer/types/post.tsx index c290fa70..8239df72 100644 --- a/src/shared/composer/types/post.tsx +++ b/src/shared/composer/types/post.tsx @@ -1,3 +1,88 @@ -export function Post() { - return
; +import { ImageUploader } from '@shared/composer/imageUploader'; +import { RelayContext } from '@shared/relayProvider'; + +import { WRITEONLY_RELAYS } from '@stores/constants'; + +import { dateToUnix } from '@utils/date'; + +import { getEventHash, signEvent } from 'nostr-tools'; +import { useCallback, useContext, useMemo, useState } from 'react'; +import { Node, createEditor } from 'slate'; +import { withHistory } from 'slate-history'; +import { Editable, Slate, withReact } from 'slate-react'; + +const initialValue = [ + { + type: 'paragraph', + children: [{ text: '' }], + }, +]; + +export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) { + const pool: any = useContext(RelayContext); + const editor = useMemo(() => withHistory(withReact(createEditor())), []); + const [content, setContent] = useState(null); + + const serialize = useCallback((nodes: Node[]) => { + return nodes.map((n) => Node.string(n)).join('\n'); + }, []); + + const submit = () => { + const event: any = { + content: content, + created_at: dateToUnix(), + kind: 1, + pubkey: pubkey, + tags: [], + }; + event.id = getEventHash(event); + event.sig = signEvent(event, privkey); + + // publish note + pool.publish(event, WRITEONLY_RELAYS); + // reset form + setContent(''); + }; + + return ( + { + const isAstChange = editor.operations.some((op) => 'set_selection' !== op.type); + if (isAstChange) { + const content = serialize(value); + setContent(content); + } + }} + > +
+
+
+
+
+
+ +
+
+
+ + +
+
+
+ ); } diff --git a/src/stores/composer.tsx b/src/stores/composer.tsx index d7bd6534..97073e96 100644 --- a/src/stores/composer.tsx +++ b/src/stores/composer.tsx @@ -1,3 +1,3 @@ -import { atomWithReset } from 'jotai/utils'; +import { atom } from 'jotai'; -export const composerAtom = atomWithReset({ type: 'post', content: '' }); +export const composerAtom = atom({ type: 'post' });