rome -> eslint + prettier
This commit is contained in:
@@ -1,132 +1,134 @@
|
||||
import { PlusCircleIcon } from "@shared/icons";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { Body, fetch } from "@tauri-apps/api/http";
|
||||
import { createBlobFromFile } from "@utils/createBlobFromFile";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Transforms } from "slate";
|
||||
import { useSlateStatic } from "slate-react";
|
||||
import { open } from '@tauri-apps/api/dialog';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { Body, fetch } from '@tauri-apps/api/http';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Transforms } from 'slate';
|
||||
import { useSlateStatic } from 'slate-react';
|
||||
|
||||
import { PlusCircleIcon } from '@shared/icons';
|
||||
|
||||
import { createBlobFromFile } from '@utils/createBlobFromFile';
|
||||
|
||||
export function ImageUploader() {
|
||||
const editor = useSlateStatic();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const editor = useSlateStatic();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const insertImage = (editor, url) => {
|
||||
const image = { type: "image", url, children: [{ text: url }] };
|
||||
Transforms.insertNodes(editor, image);
|
||||
};
|
||||
const insertImage = (editor, url) => {
|
||||
const image = { type: 'image', url, children: [{ text: url }] };
|
||||
Transforms.insertNodes(editor, image);
|
||||
};
|
||||
|
||||
const uploadToVoidCat = useCallback(
|
||||
async (filepath) => {
|
||||
const filename = filepath.split("/").pop();
|
||||
const file = await createBlobFromFile(filepath);
|
||||
const buf = await file.arrayBuffer();
|
||||
const uploadToVoidCat = useCallback(
|
||||
async (filepath) => {
|
||||
const filename = filepath.split('/').pop();
|
||||
const file = await createBlobFromFile(filepath);
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
[editor],
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
},
|
||||
[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);
|
||||
}
|
||||
};
|
||||
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]);
|
||||
});
|
||||
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();
|
||||
};
|
||||
}
|
||||
return () => {
|
||||
unlisten();
|
||||
};
|
||||
}
|
||||
|
||||
initFileDrop();
|
||||
}, [uploadToVoidCat]);
|
||||
initFileDrop();
|
||||
}, [uploadToVoidCat]);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
{loading ? (
|
||||
<svg
|
||||
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<PlusCircleIcon width={20} height={20} className="text-zinc-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openFileDialog()}
|
||||
className="inline-flex h-8 w-8 items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
{loading ? (
|
||||
<svg
|
||||
className="h-4 w-4 animate-spin text-black dark:text-zinc-100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<title id="loading">Loading</title>
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<PlusCircleIcon width={20} height={20} className="text-zinc-500" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,96 +1,94 @@
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Button } from "@shared/button";
|
||||
import { Post } from "@shared/composer/types/post";
|
||||
import { User } from "@shared/composer/user";
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { Fragment } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { Post } from '@shared/composer/types/post';
|
||||
import { User } from '@shared/composer/user';
|
||||
import {
|
||||
CancelIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
ComposeIcon,
|
||||
} from "@shared/icons";
|
||||
import { useComposer } from "@stores/composer";
|
||||
import { COMPOSE_SHORTCUT } from "@stores/shortcuts";
|
||||
import { useAccount } from "@utils/hooks/useAccount";
|
||||
import { Fragment } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
CancelIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
ComposeIcon,
|
||||
} from '@shared/icons';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
import { COMPOSE_SHORTCUT } from '@stores/shortcuts';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
export function Composer() {
|
||||
const { account } = useAccount();
|
||||
const { account } = useAccount();
|
||||
|
||||
const [toggle, open] = useComposer((state) => [
|
||||
state.toggleModal,
|
||||
state.open,
|
||||
]);
|
||||
const [toggle, open] = useComposer((state) => [state.toggleModal, state.open]);
|
||||
|
||||
const closeModal = () => {
|
||||
toggle(false);
|
||||
};
|
||||
const closeModal = () => {
|
||||
toggle(false);
|
||||
};
|
||||
|
||||
useHotkeys(COMPOSE_SHORTCUT, () => toggle(true));
|
||||
useHotkeys(COMPOSE_SHORTCUT, () => toggle(true));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => toggle(true)} preset="small">
|
||||
<ComposeIcon width={14} height={14} />
|
||||
Compose
|
||||
</Button>
|
||||
<Transition appear show={open} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative h-min w-full max-w-xl rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="flex items-center justify-between px-4 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div>{account && <User pubkey={account.pubkey} />}</div>
|
||||
<span>
|
||||
<ChevronRightIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-500"
|
||||
/>
|
||||
</span>
|
||||
<div className="inline-flex h-7 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-sm font-medium text-zinc-400">
|
||||
New Post
|
||||
<ChevronDownIcon width={14} height={14} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={closeModal}
|
||||
onKeyDown={closeModal}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
<CancelIcon
|
||||
width={16}
|
||||
height={16}
|
||||
className="text-zinc-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{account && <Post />}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => toggle(true)} preset="small">
|
||||
<ComposeIcon width={14} height={14} />
|
||||
Compose
|
||||
</Button>
|
||||
<Transition appear show={open} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={closeModal}>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 z-50 bg-black bg-opacity-30 backdrop-blur-md" />
|
||||
</Transition.Child>
|
||||
<div className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative h-min w-full max-w-xl rounded-xl border-t border-zinc-800/50 bg-zinc-900">
|
||||
<div className="flex items-center justify-between px-4 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<div>{account && <User pubkey={account.pubkey} />}</div>
|
||||
<span>
|
||||
<ChevronRightIcon
|
||||
width={14}
|
||||
height={14}
|
||||
className="text-zinc-500"
|
||||
/>
|
||||
</span>
|
||||
<div className="inline-flex h-7 w-max items-center justify-center gap-0.5 rounded bg-zinc-800 pl-3 pr-1.5 text-sm font-medium text-zinc-400">
|
||||
New Post
|
||||
<ChevronDownIcon width={14} height={14} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={closeModal}
|
||||
onKeyDown={closeModal}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="inline-flex h-5 w-5 items-center justify-center rounded hover:bg-zinc-800"
|
||||
>
|
||||
<CancelIcon width={16} height={16} className="text-zinc-500" />
|
||||
</div>
|
||||
</div>
|
||||
{account && <Post />}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,179 +1,171 @@
|
||||
import { usePublish } from "@libs/ndk";
|
||||
import { Button } from "@shared/button";
|
||||
import { ImageUploader } from "@shared/composer/imageUploader";
|
||||
import { TrashIcon } from "@shared/icons";
|
||||
import { MentionNote } from "@shared/notes/mentions/note";
|
||||
import { useComposer } from "@stores/composer";
|
||||
import { FULL_RELAYS } from "@stores/constants";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { Node, Transforms, createEditor } from "slate";
|
||||
import { withHistory } from "slate-history";
|
||||
import {
|
||||
Editable,
|
||||
ReactEditor,
|
||||
Slate,
|
||||
useSlateStatic,
|
||||
withReact,
|
||||
} from "slate-react";
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Node, Transforms, createEditor } from 'slate';
|
||||
import { withHistory } from 'slate-history';
|
||||
import { Editable, ReactEditor, Slate, useSlateStatic, withReact } from 'slate-react';
|
||||
|
||||
import { usePublish } from '@libs/ndk';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { ImageUploader } from '@shared/composer/imageUploader';
|
||||
import { TrashIcon } from '@shared/icons';
|
||||
import { MentionNote } from '@shared/notes/mentions/note';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
import { FULL_RELAYS } from '@stores/constants';
|
||||
|
||||
const withImages = (editor) => {
|
||||
const { isVoid } = editor;
|
||||
const { isVoid } = editor;
|
||||
|
||||
editor.isVoid = (element) => {
|
||||
return element.type === "image" ? true : isVoid(element);
|
||||
};
|
||||
editor.isVoid = (element) => {
|
||||
return element.type === 'image' ? true : isVoid(element);
|
||||
};
|
||||
|
||||
return editor;
|
||||
return editor;
|
||||
};
|
||||
|
||||
const ImagePreview = ({
|
||||
attributes,
|
||||
children,
|
||||
element,
|
||||
attributes,
|
||||
children,
|
||||
element,
|
||||
}: {
|
||||
attributes: any;
|
||||
children: any;
|
||||
element: any;
|
||||
attributes: any;
|
||||
children: any;
|
||||
element: any;
|
||||
}) => {
|
||||
const editor: any = useSlateStatic();
|
||||
const path = ReactEditor.findPath(editor, element);
|
||||
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
|
||||
alt={element.url}
|
||||
src={element.url}
|
||||
className="m-0 h-auto max-h-[300px] w-full rounded-md object-cover"
|
||||
/>
|
||||
<button
|
||||
type="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-base font-medium text-zinc-400 shadow-mini-button hover:bg-zinc-700"
|
||||
>
|
||||
<TrashIcon width={14} height={14} className="text-zinc-100" />
|
||||
</button>
|
||||
</div>
|
||||
</figure>
|
||||
);
|
||||
return (
|
||||
<figure {...attributes} className="m-0 mt-3">
|
||||
{children}
|
||||
<div contentEditable={false} className="relative">
|
||||
<img
|
||||
alt={element.url}
|
||||
src={element.url}
|
||||
className="m-0 h-auto max-h-[300px] w-full rounded-md object-cover"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
||||
className="shadow-mini-button absolute right-2 top-2 inline-flex h-7 w-7 items-center justify-center gap-0.5 rounded bg-zinc-800 text-base font-medium text-zinc-400 hover:bg-zinc-700"
|
||||
>
|
||||
<TrashIcon width={14} height={14} className="text-zinc-100" />
|
||||
</button>
|
||||
</div>
|
||||
</figure>
|
||||
);
|
||||
};
|
||||
|
||||
export function Post() {
|
||||
const publish = usePublish();
|
||||
const editor = useMemo(
|
||||
() => withReact(withImages(withHistory(createEditor()))),
|
||||
[],
|
||||
);
|
||||
const publish = usePublish();
|
||||
const editor = useMemo(() => withReact(withImages(withHistory(createEditor()))), []);
|
||||
|
||||
const [repost, reply, toggle] = useComposer((state) => [
|
||||
state.repost,
|
||||
state.reply,
|
||||
state.toggleModal,
|
||||
]);
|
||||
const [content, setContent] = useState<Node[]>([
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const [repost, reply, toggle] = useComposer((state) => [
|
||||
state.repost,
|
||||
state.reply,
|
||||
state.toggleModal,
|
||||
]);
|
||||
const [content, setContent] = useState<Node[]>([
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const serialize = useCallback((nodes: Node[]) => {
|
||||
return nodes.map((n) => Node.string(n)).join("\n");
|
||||
}, []);
|
||||
const serialize = useCallback((nodes: Node[]) => {
|
||||
return nodes.map((n) => Node.string(n)).join('\n');
|
||||
}, []);
|
||||
|
||||
const getRef = () => {
|
||||
if (repost.id) {
|
||||
return repost.id;
|
||||
} else if (reply.id) {
|
||||
return reply.id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const getRef = () => {
|
||||
if (repost.id) {
|
||||
return repost.id;
|
||||
} else if (reply.id) {
|
||||
return reply.id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const refID = getRef();
|
||||
const refID = getRef();
|
||||
|
||||
const submit = async () => {
|
||||
let tags: string[][] = [];
|
||||
let kind: number;
|
||||
const submit = async () => {
|
||||
let tags: string[][] = [];
|
||||
let kind: number;
|
||||
|
||||
if (repost.id && repost.pubkey) {
|
||||
kind = 6;
|
||||
tags = [
|
||||
["e", repost.id, FULL_RELAYS[0], "root"],
|
||||
["p", repost.pubkey],
|
||||
];
|
||||
} else if (reply.id && reply.pubkey) {
|
||||
kind = 1;
|
||||
if (reply.root && reply.root !== reply.id) {
|
||||
tags = [
|
||||
["e", reply.id, FULL_RELAYS[0], "root"],
|
||||
["e", reply.root, FULL_RELAYS[0], "reply"],
|
||||
["p", reply.pubkey],
|
||||
];
|
||||
} else {
|
||||
tags = [
|
||||
["e", reply.id, FULL_RELAYS[0], "root"],
|
||||
["p", reply.pubkey],
|
||||
];
|
||||
}
|
||||
} else {
|
||||
kind = 1;
|
||||
tags = [];
|
||||
}
|
||||
if (repost.id && repost.pubkey) {
|
||||
kind = 6;
|
||||
tags = [
|
||||
['e', repost.id, FULL_RELAYS[0], 'root'],
|
||||
['p', repost.pubkey],
|
||||
];
|
||||
} else if (reply.id && reply.pubkey) {
|
||||
kind = 1;
|
||||
if (reply.root && reply.root !== reply.id) {
|
||||
tags = [
|
||||
['e', reply.id, FULL_RELAYS[0], 'root'],
|
||||
['e', reply.root, FULL_RELAYS[0], 'reply'],
|
||||
['p', reply.pubkey],
|
||||
];
|
||||
} else {
|
||||
tags = [
|
||||
['e', reply.id, FULL_RELAYS[0], 'root'],
|
||||
['p', reply.pubkey],
|
||||
];
|
||||
}
|
||||
} else {
|
||||
kind = 1;
|
||||
tags = [];
|
||||
}
|
||||
|
||||
// serialize content
|
||||
const serializedContent = serialize(content);
|
||||
// serialize content
|
||||
const serializedContent = serialize(content);
|
||||
|
||||
// publish message
|
||||
await publish({ content: serializedContent, kind, tags });
|
||||
// publish message
|
||||
await publish({ content: serializedContent, kind, tags });
|
||||
|
||||
// close modal
|
||||
toggle(false);
|
||||
};
|
||||
// close modal
|
||||
toggle(false);
|
||||
};
|
||||
|
||||
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>;
|
||||
}
|
||||
}, []);
|
||||
const renderElement = useCallback((props: any) => {
|
||||
switch (props.element.type) {
|
||||
case 'image':
|
||||
if (props.element.url) {
|
||||
return <ImagePreview {...props} />;
|
||||
}
|
||||
break;
|
||||
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 className="w-full">
|
||||
<Editable
|
||||
autoFocus
|
||||
placeholder={
|
||||
refID ? "Share your thoughts on it" : "What's on your mind?"
|
||||
}
|
||||
spellCheck="false"
|
||||
className={`${refID ? "!min-h-42" : "!min-h-[86px]"} markdown`}
|
||||
renderElement={renderElement}
|
||||
/>
|
||||
{refID && <MentionNote id={refID} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<ImageUploader />
|
||||
<Button onClick={() => submit()} preset="publish">
|
||||
Publish
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Slate>
|
||||
);
|
||||
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 className="w-full">
|
||||
<Editable
|
||||
placeholder={refID ? 'Share your thoughts on it' : "What's on your mind?"}
|
||||
spellCheck="false"
|
||||
className={`${refID ? '!min-h-42' : '!min-h-[86px]'} markdown`}
|
||||
renderElement={renderElement}
|
||||
/>
|
||||
{refID && <MentionNote id={refID} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<ImageUploader />
|
||||
<Button onClick={() => submit()} preset="publish">
|
||||
Publish
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Slate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import { Image } from "@shared/image";
|
||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||
import { useProfile } from "@utils/hooks/useProfile";
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
|
||||
export function User({ pubkey }: { pubkey: string }) {
|
||||
const { user } = useProfile(pubkey);
|
||||
const { user } = useProfile(pubkey);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<h5 className="text-base font-semibold leading-none text-zinc-100">
|
||||
{user?.nip05 || user?.name || (
|
||||
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 shrink-0 overflow-hidden rounded bg-zinc-900">
|
||||
<Image
|
||||
src={user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={pubkey}
|
||||
className="h-8 w-8 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<h5 className="text-base font-semibold leading-none text-zinc-100">
|
||||
{user?.nip05 || user?.name || (
|
||||
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||
)}
|
||||
</h5>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user