add mention to composer
This commit is contained in:
@@ -2,7 +2,7 @@ import destr from 'destr';
|
||||
import Database from 'tauri-plugin-sql-api';
|
||||
|
||||
import { getParentID } from '@utils/transform';
|
||||
import { Account, Block, Chats, LumeEvent, Settings } from '@utils/types';
|
||||
import { Account, Block, Chats, LumeEvent, Profile, Settings } from '@utils/types';
|
||||
|
||||
let db: null | Database = null;
|
||||
|
||||
@@ -451,7 +451,20 @@ export async function createMetadata(id: string, pubkey: string, content: string
|
||||
);
|
||||
}
|
||||
|
||||
// get metadata
|
||||
export async function getAllMetadata() {
|
||||
const db = await connect();
|
||||
const result: LumeEvent[] = await db.select(`SELECT * FROM metadata;`);
|
||||
const users: Profile[] = result.map((el) => {
|
||||
const profile: Profile = destr(el.content);
|
||||
return {
|
||||
pubkey: el.pubkey,
|
||||
ident: profile.name || profile.display_name || profile.username,
|
||||
};
|
||||
});
|
||||
return users;
|
||||
}
|
||||
|
||||
// get user metadata
|
||||
export async function getUserMetadata(pubkey: string) {
|
||||
const db = await connect();
|
||||
const result = await db.select(`SELECT * FROM metadata WHERE pubkey = "${pubkey}";`);
|
||||
|
||||
@@ -2,11 +2,13 @@ import Mention from '@tiptap/extension-mention';
|
||||
import Placeholder from '@tiptap/extension-placeholder';
|
||||
import { EditorContent, useEditor } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { Suggestion } from '@shared/composer';
|
||||
import { CancelIcon } from '@shared/icons';
|
||||
import { CancelIcon, LoaderIcon } from '@shared/icons';
|
||||
import { MentionNote } from '@shared/notes';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
@@ -15,6 +17,7 @@ import { FULL_RELAYS } from '@stores/constants';
|
||||
import { usePublish } from '@utils/hooks/usePublish';
|
||||
|
||||
export function Composer() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [reply, clearReply, toggle] = useComposer((state) => [
|
||||
state.reply,
|
||||
state.clearReply,
|
||||
@@ -27,17 +30,17 @@ export function Composer() {
|
||||
StarterKit,
|
||||
Placeholder.configure({ placeholder: "What's on your mind?" }),
|
||||
Mention.configure({
|
||||
HTMLAttributes: {
|
||||
class: 'mention',
|
||||
},
|
||||
suggestion: Suggestion,
|
||||
renderLabel({ node }) {
|
||||
return `nostr:${nip19.npubEncode(node.attrs.id.pubkey)} `;
|
||||
},
|
||||
}),
|
||||
],
|
||||
content: '',
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: twMerge(
|
||||
'markdown max-h-[500px] overflow-y-auto outline-none',
|
||||
'markdown break-all max-h-[500px] overflow-y-auto outline-none',
|
||||
`${reply.id ? '!min-h-42' : '!min-h-[86px]'}`
|
||||
),
|
||||
},
|
||||
@@ -45,25 +48,40 @@ export function Composer() {
|
||||
});
|
||||
|
||||
const submit = async () => {
|
||||
let tags: string[][] = [];
|
||||
setLoading(true);
|
||||
try {
|
||||
let tags: string[][] = [];
|
||||
|
||||
if (reply.id && reply.pubkey) {
|
||||
tags = [
|
||||
['e', reply.id, FULL_RELAYS[0], 'reply'],
|
||||
['p', reply.pubkey],
|
||||
];
|
||||
} else {
|
||||
tags = [];
|
||||
if (reply.id && reply.pubkey) {
|
||||
if (reply.root) {
|
||||
tags = [
|
||||
['e', reply.root, FULL_RELAYS[0], 'root'],
|
||||
['e', reply.id, FULL_RELAYS[0], 'reply'],
|
||||
['p', reply.pubkey],
|
||||
];
|
||||
} else {
|
||||
tags = [
|
||||
['e', reply.id, FULL_RELAYS[0], 'reply'],
|
||||
['p', reply.pubkey],
|
||||
];
|
||||
}
|
||||
} else {
|
||||
tags = [];
|
||||
}
|
||||
|
||||
// get plaintext content
|
||||
const serializedContent = editor.getText();
|
||||
|
||||
// publish message
|
||||
await publish({ content: serializedContent, kind: 1, tags });
|
||||
|
||||
// close modal
|
||||
setLoading(false);
|
||||
toggle(false);
|
||||
} catch {
|
||||
setLoading(false);
|
||||
console.log('failed to publish');
|
||||
}
|
||||
|
||||
// serialize content
|
||||
const serializedContent = editor.getText();
|
||||
|
||||
// publish message
|
||||
// await publish({ content: serializedContent, kind: 1, tags });
|
||||
|
||||
// close modal
|
||||
toggle(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -91,7 +109,11 @@ export function Composer() {
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div />
|
||||
<Button onClick={() => submit()} preset="publish">
|
||||
Publish
|
||||
{loading ? (
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-zinc-100" />
|
||||
) : (
|
||||
'Publish'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,4 +2,5 @@ export * from './user';
|
||||
export * from './modal';
|
||||
export * from './composer';
|
||||
export * from './mention/list';
|
||||
export * from './mention/item';
|
||||
export * from './mention/suggestion';
|
||||
|
||||
31
src/shared/composer/mention/item.tsx
Normal file
31
src/shared/composer/mention/item.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
|
||||
import { displayNpub } from '@utils/shortenKey';
|
||||
import { Profile } from '@utils/types';
|
||||
|
||||
export function MentionItem({ profile }: { profile: Profile }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-8 w-8 shrink-0 overflow-hidden rounded-md bg-zinc-900">
|
||||
<Image
|
||||
src={profile.picture || profile.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={profile.pubkey}
|
||||
className="h-8 w-8 object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-px">
|
||||
<h5 className="max-w-[15rem] text-sm font-medium leading-none text-zinc-100">
|
||||
{profile.ident || (
|
||||
<div className="h-3 w-20 animate-pulse rounded-sm bg-zinc-700" />
|
||||
)}
|
||||
</h5>
|
||||
<span className="text-sm leading-none text-zinc-400">
|
||||
{profile.nip05 || profile.username || displayNpub(profile.pubkey, 16)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { MentionItem } from '@shared/composer';
|
||||
|
||||
export const MentionList = forwardRef((props: any, ref: any) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
@@ -47,19 +51,22 @@ export const MentionList = forwardRef((props: any, ref: any) => {
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="items">
|
||||
<div className="flex w-[250px] flex-col rounded-xl border-t border-zinc-700/50 bg-zinc-800 px-3 py-3">
|
||||
{props.items.length ? (
|
||||
props.items.map((item, index) => (
|
||||
props.items.map((item: NDKUserProfile, index: number) => (
|
||||
<button
|
||||
className={`item ${index === selectedIndex ? 'is-selected' : ''}`}
|
||||
className={twMerge(
|
||||
'h-11 w-full rounded-lg px-2 text-start text-sm font-medium hover:bg-zinc-700',
|
||||
`${index === selectedIndex ? 'is-selected' : ''}`
|
||||
)}
|
||||
key={index}
|
||||
onClick={() => selectItem(index)}
|
||||
>
|
||||
{item}
|
||||
<MentionItem profile={item} />
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
<div className="item">No result</div>
|
||||
<div>No result</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,38 +1,16 @@
|
||||
import { ReactRenderer } from '@tiptap/react';
|
||||
import tippy from 'tippy.js';
|
||||
|
||||
import { getAllMetadata } from '@libs/storage';
|
||||
|
||||
import { MentionList } from '@shared/composer';
|
||||
|
||||
const users = await getAllMetadata();
|
||||
|
||||
export const Suggestion = {
|
||||
items: ({ query }) => {
|
||||
return [
|
||||
'Lea Thompson',
|
||||
'Cyndi Lauper',
|
||||
'Tom Cruise',
|
||||
'Madonna',
|
||||
'Jerry Hall',
|
||||
'Joan Collins',
|
||||
'Winona Ryder',
|
||||
'Christina Applegate',
|
||||
'Alyssa Milano',
|
||||
'Molly Ringwald',
|
||||
'Ally Sheedy',
|
||||
'Debbie Harry',
|
||||
'Olivia Newton-John',
|
||||
'Elton John',
|
||||
'Michael J. Fox',
|
||||
'Axl Rose',
|
||||
'Emilio Estevez',
|
||||
'Ralph Macchio',
|
||||
'Rob Lowe',
|
||||
'Jennifer Grey',
|
||||
'Mickey Rourke',
|
||||
'John Cusack',
|
||||
'Matthew Broderick',
|
||||
'Justine Bateman',
|
||||
'Lisa Bonet',
|
||||
]
|
||||
.filter((item) => item.toLowerCase().startsWith(query.toLowerCase()))
|
||||
return users
|
||||
.filter((item) => item.ident.toLowerCase().startsWith(query.toLowerCase()))
|
||||
.slice(0, 5);
|
||||
},
|
||||
|
||||
|
||||
@@ -71,15 +71,13 @@ export function ComposerModal() {
|
||||
<ChevronDownIcon width={14} height={14} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
<button
|
||||
onClick={closeModal}
|
||||
onKeyDown={closeModal}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
type="button"
|
||||
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>
|
||||
</button>
|
||||
</div>
|
||||
<Composer />
|
||||
</Dialog.Panel>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { create } from 'zustand';
|
||||
|
||||
interface ComposerState {
|
||||
open: boolean;
|
||||
reply: { id: string; pubkey: string };
|
||||
reply: { id: string; pubkey: string; root?: string };
|
||||
toggleModal: (status: boolean) => void;
|
||||
setReply: (id: string, pubkey: string) => void;
|
||||
clearReply: () => void;
|
||||
@@ -14,11 +14,11 @@ export const useComposer = create<ComposerState>((set) => ({
|
||||
toggleModal: (status: boolean) => {
|
||||
set({ open: status });
|
||||
},
|
||||
setReply: (id: string, pubkey: string) => {
|
||||
set({ reply: { id: id, pubkey: pubkey } });
|
||||
setReply: (id: string, pubkey: string, root?: string) => {
|
||||
set({ reply: { id: id, root: root, pubkey: pubkey } });
|
||||
set({ open: true });
|
||||
},
|
||||
clearReply: () => {
|
||||
set({ reply: { id: null, pubkey: null } });
|
||||
set({ reply: { id: null, root: null, pubkey: null } });
|
||||
},
|
||||
}));
|
||||
|
||||
7
src/utils/types.d.ts
vendored
7
src/utils/types.d.ts
vendored
@@ -1,4 +1,4 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||
|
||||
export interface LumeEvent extends NDKEvent {
|
||||
event_id?: string;
|
||||
@@ -15,6 +15,11 @@ export interface Account {
|
||||
is_active: number;
|
||||
}
|
||||
|
||||
export interface Profile extends NDKUserProfile {
|
||||
ident?: string;
|
||||
pubkey?: string;
|
||||
}
|
||||
|
||||
export interface Block {
|
||||
id: string;
|
||||
account_id: number;
|
||||
|
||||
Reference in New Issue
Block a user