update composer
This commit is contained in:
@@ -3,8 +3,9 @@ import PlusCircleIcon from '@shared/icons/plusCircle';
|
||||
import { createBlobFromFile } from '@utils/createBlobFromFile';
|
||||
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { Body, fetch } from '@tauri-apps/api/http';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Transforms } from 'slate';
|
||||
import { useSlateStatic } from 'slate-react';
|
||||
|
||||
@@ -13,30 +14,14 @@ export function ImageUploader() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const insertImage = (editor, url) => {
|
||||
const text = { text: url };
|
||||
const image = { type: 'image', url, children: [text] };
|
||||
const image = { type: 'image', url, children: [{ text: url }] };
|
||||
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 uploadToVoidCat = useCallback(
|
||||
async (filepath) => {
|
||||
const filename = filepath.split('/').pop();
|
||||
const file = await createBlobFromFile(filepath);
|
||||
const buf = await file.arrayBuffer();
|
||||
|
||||
try {
|
||||
@@ -68,12 +53,52 @@ export function ImageUploader() {
|
||||
console.log('There was an error', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
[editor]
|
||||
);
|
||||
|
||||
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);
|
||||
// upload file
|
||||
uploadToVoidCat(selected);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function initFileDrop() {
|
||||
const unlisten = await listen('tauri://file-drop', (event) => {
|
||||
// set loading state
|
||||
setLoading(true);
|
||||
// upload file
|
||||
uploadToVoidCat(event.payload[0]);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unlisten();
|
||||
};
|
||||
}
|
||||
|
||||
initFileDrop();
|
||||
}, [uploadToVoidCat]);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
autoFocus={false}
|
||||
onClick={() => openFileDialog()}
|
||||
className="inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
|
||||
@@ -32,8 +32,9 @@ export function ComposerModal() {
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
autoFocus={false}
|
||||
onClick={() => openModal()}
|
||||
className="inline-flex h-7 w-max items-center justify-center gap-1 rounded-md bg-fuchsia-500 px-2.5 text-xs font-medium text-zinc-200 shadow-button hover:bg-fuchsia-600"
|
||||
className="inline-flex h-7 w-max items-center justify-center gap-1 rounded-md bg-fuchsia-500 px-2.5 text-xs font-medium text-zinc-200 shadow-button hover:bg-fuchsia-600 focus:outline-none"
|
||||
>
|
||||
<ComposeIcon width={14} height={14} />
|
||||
Compose
|
||||
@@ -68,17 +69,14 @@ export function ComposerModal() {
|
||||
<span>
|
||||
<ChevronRightIcon width={14} height={14} className="text-zinc-500" />
|
||||
</span>
|
||||
<button
|
||||
autoFocus={false}
|
||||
className="inline-flex h-6 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-xs font-medium text-zinc-400 shadow-mini-button"
|
||||
>
|
||||
Post
|
||||
<div className="inline-flex h-6 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-xs font-medium text-zinc-400 shadow-mini-button">
|
||||
New Post
|
||||
<ChevronDownIcon width={14} height={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={closeModal}
|
||||
className="inline-flex h-5 w-5 cursor-pointer items-center justify-center hover:bg-zinc-800"
|
||||
className="inline-flex h-5 w-5 cursor-pointer items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
<CancelIcon width={16} height={16} className="text-zinc-500" />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ImageUploader } from '@shared/composer/imageUploader';
|
||||
import TrashIcon from '@shared/icons/trash';
|
||||
import { RelayContext } from '@shared/relayProvider';
|
||||
|
||||
import { WRITEONLY_RELAYS } from '@stores/constants';
|
||||
@@ -7,29 +8,65 @@ import { dateToUnix } from '@utils/date';
|
||||
|
||||
import { getEventHash, signEvent } from 'nostr-tools';
|
||||
import { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { Node, createEditor } from 'slate';
|
||||
import { Node, Transforms, createEditor } from 'slate';
|
||||
import { withHistory } from 'slate-history';
|
||||
import { Editable, Slate, withReact } from 'slate-react';
|
||||
import { Editable, ReactEditor, Slate, useSlateStatic, withReact } from 'slate-react';
|
||||
|
||||
const initialValue = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: '' }],
|
||||
},
|
||||
];
|
||||
const withImages = (editor) => {
|
||||
const { isVoid } = editor;
|
||||
|
||||
editor.isVoid = (element) => {
|
||||
return element.type === 'image' ? true : isVoid(element);
|
||||
};
|
||||
|
||||
return editor;
|
||||
};
|
||||
|
||||
const ImagePreview = ({ attributes, children, element }: { attributes: any; children: any; element: any }) => {
|
||||
const editor: any = useSlateStatic();
|
||||
const path = ReactEditor.findPath(editor, element);
|
||||
|
||||
return (
|
||||
<figure {...attributes} className="m-0 mt-3">
|
||||
{children}
|
||||
<div contentEditable={false} className="relative">
|
||||
<img src={element.url} className="m-0 h-auto w-full rounded-md" />
|
||||
<button
|
||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
||||
className="absolute right-2 top-2 inline-flex h-7 w-7 items-center justify-center gap-0.5 rounded bg-zinc-800 text-xs font-medium text-zinc-400 shadow-mini-button hover:bg-zinc-700"
|
||||
>
|
||||
<TrashIcon width={14} height={14} className="text-zinc-100" />
|
||||
</button>
|
||||
</div>
|
||||
</figure>
|
||||
);
|
||||
};
|
||||
|
||||
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 editor = useMemo(() => withReact(withImages(withHistory(createEditor()))), []);
|
||||
const [content, setContent] = useState<Node[]>([
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const serialize = useCallback((nodes: Node[]) => {
|
||||
return nodes.map((n) => Node.string(n)).join('\n');
|
||||
}, []);
|
||||
|
||||
const submit = () => {
|
||||
// serialize content
|
||||
const serializedContent = serialize(content);
|
||||
console.log(serializedContent);
|
||||
|
||||
const event: any = {
|
||||
content: content,
|
||||
content: serializedContent,
|
||||
created_at: dateToUnix(),
|
||||
kind: 1,
|
||||
pubkey: pubkey,
|
||||
@@ -40,35 +77,33 @@ export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) {
|
||||
|
||||
// publish note
|
||||
pool.publish(event, WRITEONLY_RELAYS);
|
||||
// reset form
|
||||
setContent('');
|
||||
};
|
||||
|
||||
return (
|
||||
<Slate
|
||||
editor={editor}
|
||||
value={initialValue}
|
||||
onChange={(value) => {
|
||||
const isAstChange = editor.operations.some((op) => 'set_selection' !== op.type);
|
||||
if (isAstChange) {
|
||||
const content = serialize(value);
|
||||
setContent(content);
|
||||
const renderElement = useCallback((props: any) => {
|
||||
switch (props.element.type) {
|
||||
case 'image':
|
||||
if (props.element.url) {
|
||||
return <ImagePreview {...props} />;
|
||||
}
|
||||
}}
|
||||
>
|
||||
default:
|
||||
return <p {...props.attributes}>{props.children}</p>;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Slate editor={editor} value={content} onChange={setContent}>
|
||||
<div className="flex h-full flex-col px-4 pb-4">
|
||||
<div className="flex h-full w-full gap-2">
|
||||
<div className="flex w-8 shrink-0 items-center justify-center">
|
||||
<div className="h-full w-[2px] bg-zinc-800"></div>
|
||||
</div>
|
||||
<div className="prose prose-zinc relative w-full max-w-none select-text break-words dark:prose-invert prose-p:text-[15px] prose-p:leading-tight prose-a:text-[15px] prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-500 prose-a:no-underline hover:prose-a:text-fuchsia-600 hover:prose-a:underline prose-ol:mb-1 prose-ul:mb-1 prose-li:text-[15px] prose-li:leading-tight">
|
||||
<div className="prose prose-zinc relative h-max w-full max-w-none select-text break-words pb-3 dark:prose-invert prose-p:mb-0.5 prose-p:mt-0 prose-p:text-[15px] prose-p:leading-tight prose-a:text-[15px] prose-a:font-normal prose-a:leading-tight prose-a:text-fuchsia-500 prose-a:no-underline hover:prose-a:text-fuchsia-600 hover:prose-a:underline prose-ol:mb-1 prose-ul:mb-1 prose-li:text-[15px] prose-li:leading-tight">
|
||||
<Editable
|
||||
autoFocus
|
||||
placeholder="What's on your mind?"
|
||||
autoCapitalize="false"
|
||||
autoCorrect="false"
|
||||
spellCheck="false"
|
||||
autoFocus={true}
|
||||
className="min-h-20 mb-3 h-20"
|
||||
className="!min-h-[86px]"
|
||||
renderElement={renderElement}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,6 +111,7 @@ export function Post({ pubkey, privkey }: { pubkey: string; privkey: string }) {
|
||||
<ImageUploader />
|
||||
<button
|
||||
type="button"
|
||||
autoFocus={false}
|
||||
onClick={submit}
|
||||
className="inline-flex h-7 w-max items-center justify-center gap-1 rounded-md bg-fuchsia-500 px-3.5 text-xs font-medium text-zinc-200 shadow-button hover:bg-fuchsia-600"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user