flotilla/src/app/components/GroupCompose.svelte

200 lines
7 KiB
Svelte
Raw Normal View History

2024-08-19 21:22:16 +00:00
<script lang="ts">
2024-08-26 21:43:43 +00:00
import {onMount} from "svelte"
import type {Readable} from "svelte/store"
import {nprofileEncode} from "nostr-tools/nip19"
import {createEditor, type Editor, EditorContent, SvelteNodeViewRenderer} from "svelte-tiptap"
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 {createEvent, CHAT_MESSAGE} from "@welshman/util"
import {LinkExtension, TopicExtension, createSuggestions, findNodes} from "@lib/tiptap"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import GroupComposeMention from "@app/components/GroupComposeMention.svelte"
import GroupComposeTopic from "@app/components/GroupComposeTopic.svelte"
import GroupComposeEvent from "@app/components/GroupComposeEvent.svelte"
import GroupComposeImage from "@app/components/GroupComposeImage.svelte"
import GroupComposeBolt11 from "@app/components/GroupComposeBolt11.svelte"
import GroupComposeVideo from "@app/components/GroupComposeVideo.svelte"
import GroupComposeLink from "@app/components/GroupComposeLink.svelte"
import GroupComposeSuggestions from "@app/components/GroupComposeSuggestions.svelte"
import GroupComposeTopicSuggestion from "@app/components/GroupComposeTopicSuggestion.svelte"
import GroupComposeProfileSuggestion from "@app/components/GroupComposeProfileSuggestion.svelte"
import {signer} from "@app/base"
import {
searchProfiles,
publishThunk,
makeThunk,
searchTopics,
userRelayUrlsByNom,
} from "@app/state"
import {getPubkeyHints, makeMention, makeIMeta} from "@app/commands"
2024-08-23 20:33:36 +00:00
export let nom
2024-08-19 21:22:16 +00:00
let editor: Readable<Editor>
2024-08-22 23:16:35 +00:00
let uploading = false
2024-08-19 21:22:16 +00:00
2024-08-26 21:43:43 +00:00
const asInline = (extend: Record<string, any>) => ({inline: true, group: "inline", ...extend})
2024-08-19 21:22:16 +00:00
2024-08-22 23:16:35 +00:00
const addFile = () => $editor.chain().selectFiles().run()
const uploadFiles = () => $editor.chain().uploadFiles().run()
2024-08-23 20:33:36 +00:00
const sendMessage = async () => {
const json = $editor.getJSON()
const relays = $userRelayUrlsByNom.get(nom)
const event = createEvent(CHAT_MESSAGE, {
content: $editor.getText(),
tags: [
["h", nom],
...findNodes(TopicExtension.name, json).map(t => ["t", t.attrs!.name.toLowerCase()]),
2024-08-26 21:43:43 +00:00
...findNodes(NProfileExtension.name, json).map(m =>
makeMention(m.attrs!.pubkey, m.attrs!.relays),
),
...findNodes(ImageExtension.name, json).map(({attrs: {src, sha256: x}}: any) =>
makeIMeta(src, {x, ox: x}),
),
2024-08-23 20:33:36 +00:00
],
2024-08-22 23:16:35 +00:00
})
2024-08-23 20:33:36 +00:00
publishThunk(makeThunk({event, relays}))
$editor.chain().clearContent().run()
2024-08-22 23:16:35 +00:00
}
2024-08-19 21:22:16 +00:00
onMount(() => {
editor = createEditor({
2024-08-22 23:16:35 +00:00
autofocus: true,
2024-08-19 21:22:16 +00:00
extensions: [
2024-08-23 20:33:36 +00:00
Code,
CodeBlock,
Document,
Dropcursor,
Gapcursor,
History,
Paragraph,
Text,
2024-08-22 23:16:35 +00:00
HardBreakExtension.extend({
addKeyboardShortcuts() {
return {
2024-08-26 21:43:43 +00:00
"Shift-Enter": () => this.editor.commands.setHardBreak(),
"Mod-Enter": () => this.editor.commands.setHardBreak(),
Enter: () => {
2024-08-23 00:25:53 +00:00
if (this.editor.getText().trim()) {
uploadFiles()
2024-08-22 23:16:35 +00:00
2024-08-23 00:25:53 +00:00
return true
}
return false
2024-08-22 23:16:35 +00:00
},
}
2024-08-26 21:43:43 +00:00
},
2024-08-19 21:22:16 +00:00
}),
LinkExtension.extend({
addNodeView: () => SvelteNodeViewRenderer(GroupComposeLink),
}),
2024-08-26 21:43:43 +00:00
Bolt11Extension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeBolt11)}),
),
2024-08-23 00:25:53 +00:00
NProfileExtension.extend({
addNodeView: () => SvelteNodeViewRenderer(GroupComposeMention),
addProseMirrorPlugins() {
return [
createSuggestions({
2024-08-26 21:43:43 +00:00
char: "@",
name: "nprofile",
2024-08-23 00:25:53 +00:00
editor: this.editor,
search: searchProfiles,
2024-08-23 20:33:36 +00:00
select: (pubkey: string, props: any) => {
const relays = getPubkeyHints(pubkey)
const nprofile = nprofileEncode({pubkey, relays})
return props.command({pubkey, nprofile, relays})
},
2024-08-23 00:25:53 +00:00
suggestionComponent: GroupComposeProfileSuggestion,
suggestionsComponent: GroupComposeSuggestions,
}),
]
2024-08-26 21:43:43 +00:00
},
2024-08-23 00:25:53 +00:00
}),
2024-08-26 21:43:43 +00:00
NEventExtension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeEvent)}),
),
NAddrExtension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeEvent)}),
),
ImageExtension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeImage)}),
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
VideoExtension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(GroupComposeVideo)}),
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
2024-08-23 00:25:53 +00:00
TopicExtension.extend({
addNodeView: () => SvelteNodeViewRenderer(GroupComposeTopic),
addProseMirrorPlugins() {
return [
createSuggestions({
2024-08-26 21:43:43 +00:00
char: "#",
name: "topic",
2024-08-23 00:25:53 +00:00
editor: this.editor,
search: searchTopics,
select: (name: string, props: any) => props.command({name}),
allowCreate: true,
suggestionComponent: GroupComposeTopicSuggestion,
suggestionsComponent: GroupComposeSuggestions,
}),
]
2024-08-19 21:22:16 +00:00
},
2024-08-23 00:25:53 +00:00
}),
FileUploadExtension.configure({
immediateUpload: false,
sign: (event: StampedEvent) => {
uploading = true
2024-08-22 23:16:35 +00:00
2024-08-23 00:25:53 +00:00
return $signer!.sign(event)
},
onComplete: () => {
uploading = false
sendMessage()
2024-08-19 21:22:16 +00:00
},
2024-08-22 23:16:35 +00:00
}),
2024-08-19 21:22:16 +00:00
],
2024-08-26 21:43:43 +00:00
content: "",
2024-08-19 21:22:16 +00:00
})
})
</script>
2024-08-26 21:43:43 +00:00
<div
class="shadow-top-xl relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100 p-2">
<Button
on:click={addFile}
class="center h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200">
2024-08-22 23:16:35 +00:00
{#if uploading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon="gallery-send" />
{/if}
</Button>
<div class="flex-grow">
<EditorContent editor={$editor} />
</div>
2024-08-19 21:22:16 +00:00
</div>