From 2e23b3ae0600ea35edf083fed8940be3e64d6a6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20Lo=CC=81pez=20Guevara?= Date: Sat, 6 Jan 2024 11:38:34 -0300 Subject: [PATCH] feat(rail): edit title & open user notes --- src/app.css | 8 +-- src/app/chats/components/chatListItem.tsx | 2 +- src/app/new/components/mentionPopupItem.tsx | 2 +- src/app/notes/article.tsx | 2 +- src/app/users/components/profile.tsx | 2 +- src/libs/ark/ark.ts | 8 +++ src/shared/notes/actions/more.tsx | 20 ++++++ src/shared/user.tsx | 2 + src/shared/widgets/other/userProfile.tsx | 2 +- src/shared/widgets/titleBar.tsx | 67 ++++++++++++++++++--- src/utils/formater.ts | 11 +++- src/utils/hooks/useWidget.ts | 26 +++++++- 12 files changed, 129 insertions(+), 23 deletions(-) diff --git a/src/app.css b/src/app.css index fac2b4d3..db27fba9 100644 --- a/src/app.css +++ b/src/app.css @@ -13,9 +13,9 @@ overflow-wrap: break-word; } - .prose :where(iframe):not(:where([class~="not-prose"] *)) { - @apply aspect-video w-full h-auto mx-auto; - } + .prose :where(iframe):not(:where([class~='not-prose'] *)) { + @apply mx-auto aspect-video h-auto w-full; + } } html { @@ -44,7 +44,7 @@ input::-ms-clear { } .ProseMirror p.is-empty::before { - @apply text-neutral-600 dark:text-neutral-400 float-left h-0 pointer-events-none content-[attr(data-placeholder)]; + @apply pointer-events-none float-left h-0 text-neutral-600 content-[attr(data-placeholder)] dark:text-neutral-400; } .ProseMirror img.ProseMirror-selectednode { diff --git a/src/app/chats/components/chatListItem.tsx b/src/app/chats/components/chatListItem.tsx index 2fa56470..6aa7ab34 100644 --- a/src/app/chats/components/chatListItem.tsx +++ b/src/app/chats/components/chatListItem.tsx @@ -65,7 +65,7 @@ export const ChatListItem = memo(function ChatListItem({ event }: { event: NDKEv {user?.name || user?.display_name || user?.displayName || - displayNpub(event.pubkey, 16)} + displayNpub(event.pubkey)}
{decryptedContent}
diff --git a/src/app/new/components/mentionPopupItem.tsx b/src/app/new/components/mentionPopupItem.tsx index 46645bff..1dde4d29 100644 --- a/src/app/new/components/mentionPopupItem.tsx +++ b/src/app/new/components/mentionPopupItem.tsx @@ -47,7 +47,7 @@ export function MentionPopupItem({ pubkey, embed }: { pubkey: string; embed?: st {user?.display_name || user?.displayName || user?.name} - {displayNpub(pubkey, 16)} + {displayNpub(pubkey)}
diff --git a/src/app/notes/article.tsx b/src/app/notes/article.tsx index 912489db..4a573637 100644 --- a/src/app/notes/article.tsx +++ b/src/app/notes/article.tsx @@ -115,7 +115,7 @@ export function ArticleNoteScreen() { )} -
+
diff --git a/src/app/users/components/profile.tsx b/src/app/users/components/profile.tsx index 7fb7129b..0990beb6 100644 --- a/src/app/users/components/profile.tsx +++ b/src/app/users/components/profile.tsx @@ -104,7 +104,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) { /> ) : ( - {displayNpub(pubkey, 16)} + {displayNpub(pubkey)} )}
diff --git a/src/libs/ark/ark.ts b/src/libs/ark/ark.ts index 456f8771..db85648f 100644 --- a/src/libs/ark/ark.ts +++ b/src/libs/ark/ark.ts @@ -330,6 +330,14 @@ export class Ark { if (res) return id; } + public async renameWidget(id: string, title: string) { + const res = await this.#storage.execute( + 'UPDATE widgets SET title = $2 WHERE id = $1;', + [id, title] + ); + if (res) return res; + } + public async createSetting(key: string, value: string | undefined) { if (value) { return await this.#storage.execute( diff --git a/src/shared/notes/actions/more.tsx b/src/shared/notes/actions/more.tsx index d2f4d9e9..354aa7fc 100644 --- a/src/shared/notes/actions/more.tsx +++ b/src/shared/notes/actions/more.tsx @@ -7,9 +7,14 @@ import { Link } from 'react-router-dom'; import { HorizontalDotsIcon } from '@shared/icons'; +import { WIDGET_KIND } from '@utils/constants'; +import { useWidget } from '@utils/hooks/useWidget'; + export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) { const [open, setOpen] = useState(false); + const { addWidget } = useWidget(); + const copyID = async () => { await writeText(nip19.neventEncode({ id: id, author: pubkey } as EventPointer)); setOpen(false); @@ -49,6 +54,21 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) { Copy ID + + + {createdAt} ยท {fallbackName} +
+
diff --git a/src/shared/widgets/other/userProfile.tsx b/src/shared/widgets/other/userProfile.tsx index 4305018d..30debe8d 100644 --- a/src/shared/widgets/other/userProfile.tsx +++ b/src/shared/widgets/other/userProfile.tsx @@ -96,7 +96,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) { /> ) : ( - {displayNpub(pubkey, 16)} + {displayNpub(pubkey)} )} diff --git a/src/shared/widgets/titleBar.tsx b/src/shared/widgets/titleBar.tsx index 46dfa48b..50d19e7b 100644 --- a/src/shared/widgets/titleBar.tsx +++ b/src/shared/widgets/titleBar.tsx @@ -1,13 +1,16 @@ +import { useState } from 'react'; + import { useArk } from '@libs/ark'; -import { CancelIcon } from '@shared/icons'; +import { CancelIcon, EditIcon, EnterIcon } from '@shared/icons'; import { User } from '@shared/user'; +import { cropText } from '@utils/formater'; import { useWidget } from '@utils/hooks/useWidget'; export function TitleBar({ id, - title, + title: aTitle, isLive, }: { id?: string; @@ -15,7 +18,15 @@ export function TitleBar({ isLive?: boolean; }) { const { ark } = useArk(); - const { removeWidget } = useWidget(); + + const [title, setTitle] = useState(aTitle); + const [editing, setEditing] = useState(false); + const { removeWidget, renameWidget } = useWidget(); + + const submitTitleChange = () => { + renameWidget.mutate({ id, title }); + setEditing(false); + }; return (
@@ -44,14 +55,50 @@ export function TitleBar({
) : null} - ) : ( -

- {title} + ) : !editing ? ( +

+ {cropText(title)}

+ ) : ( + setTitle(e.target.value)} + onKeyUp={(event) => { + if (event.key === 'Enter') { + submitTitleChange(); + } + if (event.key === 'Escape') { + setTitle(aTitle); + setEditing(false); + } + }} + type="text" + spellCheck={false} + autoFocus={editing} + autoComplete="off" + autoCorrect="off" + autoCapitalize="off" + placeholder="type here..." + value={title} + className="dark:transparent max-h-6 border-transparent bg-transparent px-3 text-sm placeholder:text-neutral-500 focus:border-blue-500 focus:ring focus:ring-blue-200 dark:placeholder:text-neutral-400 dark:focus:ring-blue-800" + > )} -
- {id !== '9999' && id !== '9998' ? ( + {id !== '9999' && id !== '9998' ? ( +
+ - ) : null} -
+
+ ) : null} ); } diff --git a/src/utils/formater.ts b/src/utils/formater.ts index b33fcbf7..477dc4f7 100644 --- a/src/utils/formater.ts +++ b/src/utils/formater.ts @@ -43,9 +43,14 @@ export function formatCreatedAt(time: number, message: boolean = false) { return formated; } -export function displayNpub(pubkey: string, len: number, separator?: string) { +export function displayNpub(pubkey: string, len: number = 16, separator?: string) { const npub = pubkey.startsWith('npub1') ? pubkey : (nip19.npubEncode(pubkey) as string); - if (npub.length <= len) return npub; + + return cropText(npub, len, separator); +} + +export function cropText(text: string, len: number = 16, separator?: string) { + if (text.length <= len) return text; separator = separator || ' ... '; @@ -54,7 +59,7 @@ export function displayNpub(pubkey: string, len: number, separator?: string) { frontChars = Math.ceil(charsToShow / 2), backChars = Math.floor(charsToShow / 2); - return npub.substr(0, frontChars) + separator + npub.substr(npub.length - backChars); + return text.substr(0, frontChars) + separator + text.substr(text.length - backChars); } // convert number to K, M, B, T, etc. diff --git a/src/utils/hooks/useWidget.ts b/src/utils/hooks/useWidget.ts index c64d5519..3d91d37b 100644 --- a/src/utils/hooks/useWidget.ts +++ b/src/utils/hooks/useWidget.ts @@ -67,5 +67,29 @@ export function useWidget() { }, }); - return { addWidget, replaceWidget, removeWidget }; + const renameWidget = useMutation({ + mutationFn: async ({ id, title }: { id: string; title: string }) => { + // Cancel any outgoing refetches + await queryClient.cancelQueries({ queryKey: ['widgets'] }); + + // Snapshot the previous value + const prevWidgets = queryClient.getQueryData(['widgets']); + + // Optimistically update to the new value + queryClient.setQueryData(['widgets'], (prev: Widget[]) => + prev.filter((t) => t.id !== id) + ); + + // Update in database + await ark.renameWidget(id, title); + + // Return a context object with the snapshotted value + return { prevWidgets }; + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ['widgets'] }); + }, + }); + + return { addWidget, replaceWidget, removeWidget, renameWidget }; }