diff --git a/src/app.css b/src/app.css index 789fe8e5..11d815c5 100644 --- a/src/app.css +++ b/src/app.css @@ -43,7 +43,7 @@ } .card2 { - @apply rounded-box bg-base-100 p-6 text-base-content overflow-hidden text-ellipsis; + @apply overflow-hidden text-ellipsis rounded-box bg-base-100 p-6 text-base-content; } .card2.card2-sm { @@ -96,11 +96,12 @@ /* tiptap */ -.input-editor, .chat-editor, .note-editor { - @apply p-1 -m-1 min-h-12; +.input-editor, +.chat-editor, +.note-editor { + @apply -m-1 min-h-12 p-1; } - .tiptap[contenteditable="true"] { @apply max-h-[350px] overflow-y-auto p-2 px-4; } @@ -110,11 +111,11 @@ } .input-editor .tiptap[contenteditable="true"] { - @apply input input-bordered p-[.65rem] h-auto; + @apply input input-bordered h-auto p-[.65rem]; } .note-editor .tiptap[contenteditable="true"] { - @apply input input-bordered p-[.65rem] h-auto min-h-32 pb-6; + @apply input input-bordered h-auto min-h-32 p-[.65rem] pb-6; } .tiptap pre code { @@ -136,8 +137,8 @@ /* date input */ .date-time-field { - @apply input input-bordered px-0 rounded; + @apply input input-bordered rounded px-0; } .date-time-field input { - @apply !bg-inherit !border-none !text-inherit !w-full !h-full; + @apply !h-full !w-full !border-none !bg-inherit !text-inherit; } diff --git a/src/app/commands.ts b/src/app/commands.ts index 4ec27628..302f7cd6 100644 --- a/src/app/commands.ts +++ b/src/app/commands.ts @@ -107,4 +107,3 @@ export const removeSpaceMembership = (url: string) => export const removeRoomMembership = (url: string, room: string) => updateList(MEMBERSHIPS, (tags: string[][]) => tags.filter(t => !equals([ROOM, room, url], t))) - diff --git a/src/app/components/ChatCompose.svelte b/src/app/components/ChatCompose.svelte index d3de38ce..ea2cc586 100644 --- a/src/app/components/ChatCompose.svelte +++ b/src/app/components/ChatCompose.svelte @@ -3,35 +3,25 @@ import type {Readable} from "svelte/store" import {writable} from "svelte/store" import {createEditor, type Editor, EditorContent} from "svelte-tiptap" - import {NProfileExtension, ImageExtension} from "nostr-editor" import {createEvent} from "@welshman/util" import {publishThunk, makeThunk} from "@welshman/app" - import {findNodes} from "@lib/tiptap" import Icon from "@lib/components/Icon.svelte" import Button from "@lib/components/Button.svelte" - import {makeMention, makeIMeta} from "@app/commands" - import {getChatEditorOptions, addFile} from "@app/editor" + import {getEditorOptions, getEditorTags, addFile} from "@lib/editor" import {ROOM, MESSAGE, GENERAL} from "@app/state" + import {getPubkeyHints} from "@app/commands" export let url export let room = GENERAL - const uploading = writable(false) + const loading = writable(false) let editor: Readable - const sendMessage = () => { - const json = $editor.getJSON() - const mentionTags = findNodes(NProfileExtension.name, json).map(m => - makeMention(m.attrs!.pubkey, m.attrs!.relays), - ) - const imetaTags = findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) => - makeIMeta(src, {x, ox: x}), - ) - + const submit = () => { const event = createEvent(MESSAGE, { content: $editor.getText(), - tags: [[ROOM, room], ...mentionTags, ...imetaTags], + tags: [[ROOM, room], ...getEditorTags($editor)], }) publishThunk(makeThunk({event, relays: [url]})) @@ -40,7 +30,7 @@ } onMount(() => { - editor = createEditor(getChatEditorOptions({uploading, sendMessage})) + editor = createEditor(getEditorOptions({submit, loading, getPubkeyHints, submitOnEnter: true})) }) @@ -48,15 +38,15 @@ class="shadow-top-xl relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100 p-2"> -
+
diff --git a/src/app/components/ChatMessage.svelte b/src/app/components/ChatMessage.svelte index 61a12ee3..050fd7bb 100644 --- a/src/app/components/ChatMessage.svelte +++ b/src/app/components/ChatMessage.svelte @@ -15,19 +15,14 @@ formatTimestampAsTime, } from "@welshman/app" import type {PublishStatusData} from "@welshman/app" - import { - REACTION, - ZAP_RESPONSE, - displayRelayUrl, - getAncestorTags, - } from "@welshman/util" + import {REACTION, ZAP_RESPONSE, displayRelayUrl, getAncestorTags} from "@welshman/util" import {repository} from "@welshman/app" import {fly} from "@lib/transition" import Icon from "@lib/components/Icon.svelte" import Button from "@lib/components/Button.svelte" import Avatar from "@lib/components/Avatar.svelte" import {REPLY, deriveEvent, displayReaction} from "@app/state" - import {getChatViewOptions} from "@app/editor" + import {getViewOptions} from "@lib/editor" export let event: TrustedEvent export let showPubkey: boolean @@ -79,7 +74,7 @@ !isPending && !isPublished && findStatus($ps, [PublishStatus.Failure, PublishStatus.Timeout]) onMount(() => { - editor = createEditor(getChatViewOptions(event.content)) + editor = createEditor(getViewOptions(event)) }) diff --git a/src/app/components/EventCard.svelte b/src/app/components/EventCard.svelte index 2daba514..3af402e0 100644 --- a/src/app/components/EventCard.svelte +++ b/src/app/components/EventCard.svelte @@ -1,17 +1,12 @@ -
+
{meta.title || meta.name}
diff --git a/src/app/components/EventCreate.svelte b/src/app/components/EventCreate.svelte index 36e40d83..2e28c5fd 100644 --- a/src/app/components/EventCreate.svelte +++ b/src/app/components/EventCreate.svelte @@ -3,29 +3,27 @@ import type {Readable} from "svelte/store" import {writable} from "svelte/store" import {createEditor, type Editor, EditorContent} from "svelte-tiptap" - import {NProfileExtension, ImageExtension} from "nostr-editor" import {randomId} from "@welshman/lib" import {createEvent, EVENT_DATE, EVENT_TIME} from "@welshman/util" import {publishThunk, makeThunk, dateToSeconds} from "@welshman/app" - import {findNodes} from "@lib/tiptap" import Icon from "@lib/components/Icon.svelte" import Field from "@lib/components/Field.svelte" import Button from "@lib/components/Button.svelte" import DateTimeInput from "@lib/components/DateTimeInput.svelte" - import {makeMention, makeIMeta} from "@app/commands" - import {getNoteEditorOptions, addFile, uploadFiles} from "@app/editor" - import {pushModal, clearModal} from "@app/modal" + import {getPubkeyHints} from "@app/commands" + import {getEditorOptions, addFile, uploadFiles, getEditorTags} from "@lib/editor" + import {clearModal} from "@app/modal" import {pushToast} from "@app/toast" export let url - const submit = () => uploadFiles($editor) + const startSubmit = () => uploadFiles($editor) const back = () => history.back() - const uploading = writable(false) + const loading = writable(false) - const sendMessage = () => { + const submit = () => { if (!title) { return pushToast({ theme: "error", @@ -40,15 +38,7 @@ }) } - const json = $editor.getJSON() const kind = isAllDay ? EVENT_DATE : EVENT_TIME - const mentionTags = findNodes(NProfileExtension.name, json).map(m => - makeMention(m.attrs!.pubkey, m.attrs!.relays), - ) - const imetaTags = findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) => - makeIMeta(src, {x, ox: x}), - ) - const event = createEvent(kind, { content: $editor.getText(), tags: [ @@ -57,8 +47,7 @@ ["location", location], ["start", dateToSeconds(start).toString()], ["end", dateToSeconds(end).toString()], - ...mentionTags, - ...imetaTags, + ...getEditorTags($editor), ], }) @@ -67,19 +56,18 @@ } let editor: Readable - let isAllDay = false - let file: File + const isAllDay = false let title = "" let location = "" let start: Date let end: Date onMount(() => { - editor = createEditor(getNoteEditorOptions({uploading, sendMessage})) + editor = createEditor(getEditorOptions({submit, loading, getPubkeyHints})) }) -
+

Create an Event

Invite other group members to events online or in real life.

@@ -95,14 +83,11 @@
-
+
- - +
- diff --git a/src/app/components/ThreadCard.svelte b/src/app/components/ThreadCard.svelte index 0d112f33..b50b8d12 100644 --- a/src/app/components/ThreadCard.svelte +++ b/src/app/components/ThreadCard.svelte @@ -1,12 +1,11 @@
-
+
@@ -40,6 +39,6 @@
{#if replies.length > 0} - Show {replies.length} {replies.length === 1 ? 'reply' : 'replies'} + Show {replies.length} {replies.length === 1 ? "reply" : "replies"} {/if}
diff --git a/src/app/components/ThreadCreate.svelte b/src/app/components/ThreadCreate.svelte index 7129c9ac..806c17e9 100644 --- a/src/app/components/ThreadCreate.svelte +++ b/src/app/components/ThreadCreate.svelte @@ -3,44 +3,24 @@ import type {Readable} from "svelte/store" import {writable} from "svelte/store" import {createEditor, type Editor, EditorContent} from "svelte-tiptap" - import {NProfileExtension, ImageExtension} from "nostr-editor" - import {randomId} from "@welshman/lib" import {createEvent, NOTE} from "@welshman/util" - import {publishThunk, makeThunk, dateToSeconds} from "@welshman/app" - import {findNodes} from "@lib/tiptap" + import {publishThunk, makeThunk} from "@welshman/app" import Icon from "@lib/components/Icon.svelte" - import Field from "@lib/components/Field.svelte" import Button from "@lib/components/Button.svelte" - import DateTimeInput from "@lib/components/DateTimeInput.svelte" - import {makeMention, makeIMeta} from "@app/commands" - import {getNoteEditorOptions, addFile, uploadFiles} from "@app/editor" - import {pushModal, clearModal} from "@app/modal" - import {pushToast} from "@app/toast" + import {getPubkeyHints} from "@app/commands" + import {getEditorOptions, addFile, uploadFiles, getEditorTags} from "@lib/editor" + import {clearModal} from "@app/modal" export let url - const submit = () => uploadFiles($editor) + const startSubmit = () => uploadFiles($editor) const back = () => history.back() - const uploading = writable(false) + const loading = writable(false) - const sendMessage = () => { - const json = $editor.getJSON() - const mentionTags = findNodes(NProfileExtension.name, json).map(m => - makeMention(m.attrs!.pubkey, m.attrs!.relays), - ) - const imetaTags = findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) => - makeIMeta(src, {x, ox: x}), - ) - - const event = createEvent(NOTE, { - content: $editor.getText(), - tags: [ - ...mentionTags, - ...imetaTags, - ], - }) + const submit = () => { + const event = createEvent(NOTE, {content: $editor.getText(), tags: getEditorTags($editor)}) publishThunk(makeThunk({event, relays: [url]})) clearModal() @@ -49,24 +29,24 @@ let editor: Readable onMount(() => { - editor = createEditor(getNoteEditorOptions({uploading, sendMessage})) + editor = createEditor(getEditorOptions({submit, loading, getPubkeyHints})) }) -
+

Create a Thread

Share your thoughts, or start a discussion.

-
+
- +
- diff --git a/src/app/editor.ts b/src/app/editor.ts deleted file mode 100644 index 212951fb..00000000 --- a/src/app/editor.ts +++ /dev/null @@ -1,209 +0,0 @@ -import type {Writable} from "svelte/store" -import {nprofileEncode} from "nostr-tools/nip19" -import {SvelteNodeViewRenderer} from "svelte-tiptap" -import {Editor} from "@tiptap/core" -import Code from "@tiptap/extension-code" -import CodeBlock from "@tiptap/extension-code-block" -import Document from "@tiptap/extension-document" -import Dropcursor from "@tiptap/extension-dropcursor" -import Gapcursor from "@tiptap/extension-gapcursor" -import History from "@tiptap/extension-history" -import Paragraph from "@tiptap/extension-paragraph" -import Text from "@tiptap/extension-text" -import HardBreakExtension from "@tiptap/extension-hard-break" -import { - Bolt11Extension, - NProfileExtension, - NEventExtension, - NAddrExtension, - ImageExtension, - VideoExtension, - FileUploadExtension, -} from "nostr-editor" -import type {StampedEvent} from "@welshman/util" -import {signer, profileSearch} from "@welshman/app" -import {LinkExtension, asInline, createSuggestions} from "@lib/tiptap" -import ChatComposeMention from "@app/components/ChatComposeMention.svelte" -import ChatComposeEvent from "@app/components/ChatComposeEvent.svelte" -import ChatComposeImage from "@app/components/ChatComposeImage.svelte" -import ChatComposeBolt11 from "@app/components/ChatComposeBolt11.svelte" -import ChatComposeVideo from "@app/components/ChatComposeVideo.svelte" -import ChatComposeLink from "@app/components/ChatComposeLink.svelte" -import ChatComposeSuggestions from "@app/components/ChatComposeSuggestions.svelte" -import ChatSuggestionProfile from "@app/components/ChatSuggestionProfile.svelte" -import {getPubkeyHints} from "@app/commands" - -export const addFile = (editor: Editor) => editor.chain().selectFiles().run() - -export const uploadFiles = (editor: Editor) => editor.chain().uploadFiles().run() - -type EditorOptions = { - uploading: Writable - sendMessage: () => void -} - -export const getChatEditorOptions = ({uploading, sendMessage}: EditorOptions) => ({ - content: "", - autofocus: true, - extensions: [ - Code, - CodeBlock, - Document, - Dropcursor, - Gapcursor, - History, - Paragraph, - Text, - HardBreakExtension.extend({ - addKeyboardShortcuts() { - return { - "Shift-Enter": () => this.editor.commands.setHardBreak(), - "Mod-Enter": () => this.editor.commands.setHardBreak(), - Enter: () => { - if (this.editor.getText().trim()) { - uploadFiles(this.editor) - - return true - } - - return false - }, - } - }, - }), - LinkExtension.extend({ - addNodeView: () => SvelteNodeViewRenderer(ChatComposeLink), - }), - Bolt11Extension.extend( - asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeBolt11)}), - ), - NProfileExtension.extend({ - addNodeView: () => SvelteNodeViewRenderer(ChatComposeMention), - addProseMirrorPlugins() { - return [ - createSuggestions({ - char: "@", - name: "nprofile", - editor: this.editor, - search: profileSearch, - select: (pubkey: string, props: any) => { - const relays = getPubkeyHints(pubkey) - const nprofile = nprofileEncode({pubkey, relays}) - - return props.command({pubkey, nprofile, relays}) - }, - suggestionComponent: ChatSuggestionProfile, - suggestionsComponent: ChatComposeSuggestions, - }), - ] - }, - }), - NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeEvent)})), - NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeEvent)})), - ImageExtension.extend( - asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeImage)}), - ).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}), - VideoExtension.extend( - asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeVideo)}), - ).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}), - FileUploadExtension.configure({ - immediateUpload: false, - sign: (event: StampedEvent) => { - uploading.set(true) - - return signer.get()!.sign(event) - }, - onComplete: () => { - uploading.set(false) - sendMessage() - }, - }), - ], -}) - -export const getChatViewOptions = (content: string) => ({ - content, - editable: false, - shouldRerenderOnTransaction: false, - extensions: [ - Code, - CodeBlock, - Document, - Paragraph, - Text, - LinkExtension.extend({ - addNodeView: () => SvelteNodeViewRenderer(ChatComposeLink), - }), - Bolt11Extension.extend( - asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeBolt11)}), - ), - NProfileExtension.extend({ - addNodeView: () => SvelteNodeViewRenderer(ChatComposeMention), - }), - NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeEvent)})), - NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeEvent)})), - ImageExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeImage)})), - VideoExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeVideo)})), - ], -}) - -export const getNoteEditorOptions = ({uploading, sendMessage}: EditorOptions) => ({ - content: "", - extensions: [ - Document, - Dropcursor, - Gapcursor, - History, - Paragraph, - Text, - HardBreakExtension, - LinkExtension.extend({ - addNodeView: () => SvelteNodeViewRenderer(ChatComposeLink), - }), - Bolt11Extension.extend( - asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeBolt11)}), - ), - NProfileExtension.extend({ - addNodeView: () => SvelteNodeViewRenderer(ChatComposeMention), - addProseMirrorPlugins() { - return [ - createSuggestions({ - char: "@", - name: "nprofile", - editor: this.editor, - search: profileSearch, - select: (pubkey: string, props: any) => { - const relays = getPubkeyHints(pubkey) - const nprofile = nprofileEncode({pubkey, relays}) - - return props.command({pubkey, nprofile, relays}) - }, - suggestionComponent: ChatSuggestionProfile, - suggestionsComponent: ChatComposeSuggestions, - }), - ] - }, - }), - NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeEvent)})), - NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeEvent)})), - ImageExtension.extend( - asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeImage)}), - ).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}), - VideoExtension.extend( - asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeVideo)}), - ).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}), - FileUploadExtension.configure({ - immediateUpload: false, - sign: (event: StampedEvent) => { - uploading.set(true) - - return signer.get()!.sign(event) - }, - onComplete: () => { - uploading.set(false) - sendMessage() - }, - }), - ], -}) - diff --git a/src/app/state.ts b/src/app/state.ts index 178603f3..d2d630c3 100644 --- a/src/app/state.ts +++ b/src/app/state.ts @@ -190,7 +190,7 @@ export const { const timestamps = chat?.messages.map(m => m.event.created_at) || [] const since = Math.max(0, max(timestamps) - 3600) - return load({...request, relays: [url], filters: [{'#~': [room], since}]}) + return load({...request, relays: [url], filters: [{"#~": [room], since}]}) }, }) diff --git a/src/lib/components/DateTimeInput.svelte b/src/lib/components/DateTimeInput.svelte index d0884be0..eae4adfd 100644 --- a/src/lib/components/DateTimeInput.svelte +++ b/src/lib/components/DateTimeInput.svelte @@ -1,9 +1,7 @@