mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-29 16:30:55 +00:00
fix: editor crash on open
This commit is contained in:
parent
5ca9444358
commit
32f3315344
@ -1,18 +1,11 @@
|
||||
import { CheckCircleIcon, InfoCircleIcon, CancelCircleIcon } from "@lume/icons";
|
||||
import type { Interests, Metadata, Settings } from "@lume/types";
|
||||
import type { Settings } from "@lume/types";
|
||||
import { Spinner } from "@lume/ui";
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
|
||||
import type { Platform } from "@tauri-apps/plugin-os";
|
||||
import type { Descendant } from "slate";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
type EditorElement = {
|
||||
type: string;
|
||||
children: Descendant[];
|
||||
eventId?: string;
|
||||
};
|
||||
|
||||
interface RouterContext {
|
||||
// System
|
||||
queryClient: QueryClient;
|
||||
@ -21,13 +14,8 @@ interface RouterContext {
|
||||
locale?: string;
|
||||
// Settings
|
||||
settings?: Settings;
|
||||
interests?: Interests;
|
||||
// Profile
|
||||
// Accounts
|
||||
accounts?: string[];
|
||||
profile?: Metadata;
|
||||
isNewUser?: boolean;
|
||||
// Editor
|
||||
initialValue?: EditorElement[];
|
||||
}
|
||||
|
||||
export const Route = createRootRouteWithContext<RouterContext>()({
|
||||
|
@ -13,7 +13,7 @@ export function MediaButton({ className }: { className?: string }) {
|
||||
const editor = useSlateStatic();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const uploadToNostrBuild = async () => {
|
||||
const upload = async () => {
|
||||
try {
|
||||
// start loading
|
||||
setLoading(true);
|
||||
@ -66,7 +66,7 @@ export function MediaButton({ className }: { className?: string }) {
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => uploadToNostrBuild()}
|
||||
onClick={() => upload()}
|
||||
disabled={loading}
|
||||
className={cn("inline-flex items-center justify-center", className)}
|
||||
>
|
||||
|
@ -60,20 +60,26 @@ export function MentionButton({ className }: { className?: string }) {
|
||||
</Tooltip.Provider>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content className="flex w-[220px] h-[220px] scrollbar-none flex-col overflow-y-auto rounded-xl bg-black py-1 shadow-md shadow-neutral-500/20 focus:outline-none dark:bg-white">
|
||||
{contacts.map((contact) => (
|
||||
<DropdownMenu.Item
|
||||
key={contact}
|
||||
onClick={() => select(contact)}
|
||||
className="shrink-0 h-11 flex items-center hover:bg-white/10 px-2"
|
||||
>
|
||||
<User.Provider pubkey={contact}>
|
||||
<User.Root className="flex items-center gap-2">
|
||||
<User.Avatar className="shrink-0 size-8 rounded-full" />
|
||||
<User.Name className="text-sm font-medium text-white dark:text-black" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</DropdownMenu.Item>
|
||||
))}
|
||||
{contacts.length < 1 ? (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
<p className="text-sm text-white">Contact List is empty.</p>
|
||||
</div>
|
||||
) : (
|
||||
contacts.map((contact) => (
|
||||
<DropdownMenu.Item
|
||||
key={contact}
|
||||
onClick={() => select(contact)}
|
||||
className="shrink-0 h-11 flex items-center hover:bg-white/10 px-2"
|
||||
>
|
||||
<User.Provider pubkey={contact}>
|
||||
<User.Root className="flex items-center gap-2">
|
||||
<User.Avatar className="shrink-0 size-8 rounded-full" />
|
||||
<User.Name className="text-sm font-medium text-white dark:text-black" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
</DropdownMenu.Item>
|
||||
))
|
||||
)}
|
||||
<DropdownMenu.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
|
@ -3,13 +3,13 @@ import { cn } from "@lume/utils";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
export function NsfwToggle({
|
||||
nsfw,
|
||||
setNsfw,
|
||||
export function WarningToggle({
|
||||
warning,
|
||||
setWarning,
|
||||
className,
|
||||
}: {
|
||||
nsfw: boolean;
|
||||
setNsfw: Dispatch<SetStateAction<boolean>>;
|
||||
warning: boolean;
|
||||
setWarning: Dispatch<SetStateAction<boolean>>;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
@ -18,11 +18,11 @@ export function NsfwToggle({
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setNsfw((prev) => !prev)}
|
||||
onClick={() => setWarning((prev) => !prev)}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center",
|
||||
className,
|
||||
nsfw ? "bg-blue-500 text-white" : "",
|
||||
warning ? "bg-blue-500 text-white" : "",
|
||||
)}
|
||||
>
|
||||
<NsfwIcon className="size-4" />
|
@ -1,4 +1,4 @@
|
||||
import { ComposeFilledIcon, TrashIcon } from "@lume/icons";
|
||||
import { ComposeFilledIcon } from "@lume/icons";
|
||||
import { Spinner } from "@lume/ui";
|
||||
import {
|
||||
cn,
|
||||
@ -8,7 +8,6 @@ import {
|
||||
sendNativeNotification,
|
||||
} from "@lume/utils";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { type Descendant, Node, Transforms, createEditor } from "slate";
|
||||
@ -22,16 +21,22 @@ import {
|
||||
withReact,
|
||||
} from "slate-react";
|
||||
import { MediaButton } from "./-components/media";
|
||||
import { NsfwToggle } from "./-components/nsfw";
|
||||
import { MentionButton } from "./-components/mention";
|
||||
import { MentionNote } from "@/components/note/mentions/note";
|
||||
import { LumeEvent } from "@lume/system";
|
||||
import { WarningToggle } from "./-components/warning";
|
||||
import { MentionNote } from "@/components/note/mentions/note";
|
||||
|
||||
type EditorSearch = {
|
||||
reply_to: string;
|
||||
quote: boolean;
|
||||
};
|
||||
|
||||
type EditorElement = {
|
||||
type: string;
|
||||
children: Descendant[];
|
||||
eventId?: string;
|
||||
};
|
||||
|
||||
export const Route = createFileRoute("/editor/")({
|
||||
validateSearch: (search: Record<string, string>): EditorSearch => {
|
||||
return {
|
||||
@ -39,43 +44,23 @@ export const Route = createFileRoute("/editor/")({
|
||||
quote: search.quote === "true" || false,
|
||||
};
|
||||
},
|
||||
beforeLoad: async ({ search }) => {
|
||||
return {
|
||||
initialValue: search.quote
|
||||
? [
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
{
|
||||
type: "event",
|
||||
eventId: `nostr:${nip19.noteEncode(search.reply_to)}`,
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
const initialValue: EditorElement[] = [
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [{ text: "" }],
|
||||
},
|
||||
];
|
||||
|
||||
function Screen() {
|
||||
const { reply_to, quote } = Route.useSearch();
|
||||
const { initialValue } = Route.useRouteContext();
|
||||
const search = Route.useSearch();
|
||||
|
||||
const [t] = useTranslation();
|
||||
const [editorValue, setEditorValue] = useState(initialValue);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [nsfw, setNsfw] = useState(false);
|
||||
const [warning, setWarning] = useState(false);
|
||||
const [editor] = useState(() =>
|
||||
withMentions(withNostrEvent(withImages(withReact(createEditor())))),
|
||||
);
|
||||
@ -116,7 +101,12 @@ function Screen() {
|
||||
setLoading(true);
|
||||
|
||||
const content = serialize(editor.children);
|
||||
const eventId = await LumeEvent.publish(content, reply_to, quote);
|
||||
const eventId = await LumeEvent.publish(
|
||||
content,
|
||||
search.reply_to,
|
||||
search.quote,
|
||||
warning,
|
||||
);
|
||||
|
||||
if (eventId) {
|
||||
await sendNativeNotification(
|
||||
@ -137,15 +127,15 @@ function Screen() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full">
|
||||
<div className="w-full h-full flex flex-col">
|
||||
<Slate editor={editor} initialValue={editorValue}>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex h-14 w-full shrink-0 items-center justify-end gap-2 px-2 border-b border-black/10 dark:border-white/10"
|
||||
className="shrink-0 flex h-14 w-full items-center justify-end gap-2 px-2 border-b border-black/10 dark:border-white/10"
|
||||
>
|
||||
<NsfwToggle
|
||||
nsfw={nsfw}
|
||||
setNsfw={setNsfw}
|
||||
<WarningToggle
|
||||
warning={warning}
|
||||
setWarning={setWarning}
|
||||
className="size-8 rounded-full bg-black/10 hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20"
|
||||
/>
|
||||
<MentionButton className="size-8 rounded-full bg-black/10 hover:bg-black/20 dark:bg-white/10 dark:hover:bg-white/20" />
|
||||
@ -163,13 +153,13 @@ function Screen() {
|
||||
{t("global.post")}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-1 flex-col">
|
||||
{reply_to && !quote ? (
|
||||
<div className="flex-1 overflow-y-auto flex flex-col">
|
||||
{search.reply_to ? (
|
||||
<div className="px-4 py-2">
|
||||
<MentionNote eventId={reply_to} />
|
||||
<MentionNote eventId={search.reply_to} />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="overflow-y-auto p-4">
|
||||
<div className="overflow-y-auto scrollbar-none p-4">
|
||||
<Editable
|
||||
key={JSON.stringify(editorValue)}
|
||||
autoFocus={true}
|
||||
@ -178,7 +168,7 @@ function Screen() {
|
||||
spellCheck={false}
|
||||
renderElement={(props) => <Element {...props} />}
|
||||
placeholder={
|
||||
reply_to ? "Type your reply..." : t("editor.placeholder")
|
||||
search.reply_to ? "Type your reply..." : t("editor.placeholder")
|
||||
}
|
||||
className="focus:outline-none"
|
||||
/>
|
||||
@ -252,35 +242,24 @@ const withImages = (editor: ReactEditor) => {
|
||||
return editor;
|
||||
};
|
||||
|
||||
const Image = ({ attributes, children, element }) => {
|
||||
const Image = ({ attributes, element, children }) => {
|
||||
const editor = useSlateStatic();
|
||||
const path = ReactEditor.findPath(editor as ReactEditor, element);
|
||||
|
||||
const selected = useSelected();
|
||||
const focused = useFocused();
|
||||
const path = ReactEditor.findPath(editor as ReactEditor, element);
|
||||
|
||||
return (
|
||||
<div {...attributes}>
|
||||
{children}
|
||||
<div contentEditable={false} className="relative my-2">
|
||||
<img
|
||||
src={element.url}
|
||||
alt={element.url}
|
||||
className={cn(
|
||||
"h-auto w-full rounded-lg border border-neutral-100 object-cover ring-2 dark:border-neutral-900",
|
||||
selected && focused ? "ring-blue-500" : "ring-transparent",
|
||||
)}
|
||||
contentEditable={false}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
contentEditable={false}
|
||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
||||
className="absolute right-2 top-2 inline-flex size-8 items-center justify-center rounded-lg bg-red-500 text-white hover:bg-red-600"
|
||||
>
|
||||
<TrashIcon className="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
<img
|
||||
src={element.url}
|
||||
alt={element.url}
|
||||
className={cn(
|
||||
"my-2 h-auto w-1/2 rounded-lg object-cover ring-2 outline outline-1 -outline-offset-1 outline-black/15",
|
||||
selected && focused ? "ring-blue-500" : "ring-transparent",
|
||||
)}
|
||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -60,17 +60,10 @@ export const insertMention = (
|
||||
name: contact.profile.name || contact.profile.display_name || "anon",
|
||||
children: [text],
|
||||
};
|
||||
const extraText = [
|
||||
{
|
||||
type: "paragraph",
|
||||
children: [text],
|
||||
},
|
||||
];
|
||||
|
||||
// @ts-ignore, idk
|
||||
ReactEditor.focus(editor);
|
||||
Transforms.insertNodes(editor, mention);
|
||||
Transforms.insertNodes(editor, extraText);
|
||||
};
|
||||
|
||||
export const insertNostrEvent = (
|
||||
|
Loading…
Reference in New Issue
Block a user