feat(rail): edit title & open user notes

This commit is contained in:
Fernando López Guevara 2024-01-06 11:38:34 -03:00
parent a93ebd3861
commit 2e23b3ae06
12 changed files with 129 additions and 23 deletions

View File

@ -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 {

View File

@ -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)}
</div>
<div className="flex w-full items-center justify-between">
<div className="max-w-[10rem] truncate text-sm">{decryptedContent}</div>

View File

@ -47,7 +47,7 @@ export function MentionPopupItem({ pubkey, embed }: { pubkey: string; embed?: st
{user?.display_name || user?.displayName || user?.name}
</h5>
<span className="text-sm leading-none text-neutral-600 dark:text-neutral-400">
{displayNpub(pubkey, 16)}
{displayNpub(pubkey)}
</span>
</div>
</div>

View File

@ -115,7 +115,7 @@ export function ArticleNoteScreen() {
</div>
)}
</div>
<div className="col-span-4 border-l border-neutral-100 px-3 dark:border-neutral-900 xl:col-span-3">
<div className="col-span-4 border-l border-neutral-100 px-3 xl:col-span-3 dark:border-neutral-900">
<div className="mb-3 border-b border-neutral-100 pb-3 dark:border-neutral-900">
<NoteReplyForm rootEvent={data} />
</div>

View File

@ -104,7 +104,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
/>
) : (
<span className="max-w-[15rem] truncate text-sm text-neutral-500 dark:text-neutral-400">
{displayNpub(pubkey, 16)}
{displayNpub(pubkey)}
</span>
)}
</div>

View File

@ -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(

View File

@ -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
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<button
type="button"
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-200 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-800"
onClick={() =>
addWidget.mutate({
kind: WIDGET_KIND.user,
title: pubkey,
content: pubkey,
})
}
>
Open Notes
</button>
</DropdownMenu.Item>
<DropdownMenu.Item asChild>
<Link
to={`/users/${pubkey}`}

View File

@ -474,6 +474,8 @@ export const User = memo(function User({
<span>{createdAt}</span>
<span>·</span>
<span>{fallbackName}</span>
<div className="grow"></div>
<MoreActions id={eventId} pubkey={pubkey} />
</div>
</div>
</div>

View File

@ -96,7 +96,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
/>
) : (
<span className="max-w-[15rem] truncate text-sm text-neutral-600 dark:text-neutral-400">
{displayNpub(pubkey, 16)}
{displayNpub(pubkey)}
</span>
)}
</div>

View File

@ -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 (
<div className="grid h-11 w-full shrink-0 grid-cols-3 items-center px-3">
@ -44,14 +55,50 @@ export function TitleBar({
</div>
) : null}
</div>
) : (
<h3 className="text-sm font-semibold text-neutral-900 dark:text-neutral-100">
{title}
) : !editing ? (
<h3
title={title}
className="text-sm font-semibold text-neutral-900 dark:text-neutral-100"
>
{cropText(title)}
</h3>
) : (
<input
onChange={(e) => 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"
></input>
)}
</div>
<div className="col-span-1 flex justify-end">
{id !== '9999' && id !== '9998' ? (
{id !== '9999' && id !== '9998' ? (
<div className="col-span-1 flex justify-end">
<button
type="button"
onClick={() => (editing ? submitTitleChange() : setEditing(true))}
className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded text-neutral-900 backdrop-blur-xl hover:bg-neutral-100 dark:text-neutral-100 dark:hover:bg-neutral-900"
>
{editing ? (
<EnterIcon className="h-3 w-3" />
) : (
<EditIcon className="h-3 w-3" />
)}
</button>
<button
type="button"
onClick={() => removeWidget.mutate(id)}
@ -59,8 +106,8 @@ export function TitleBar({
>
<CancelIcon className="h-3 w-3" />
</button>
) : null}
</div>
</div>
) : null}
</div>
);
}

View File

@ -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.

View File

@ -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 };
}