forked from coracle/flotilla
Rough out calendar
This commit is contained in:
@@ -48,8 +48,9 @@
|
||||
<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($editor)}
|
||||
class="center h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200">
|
||||
data-tip="Add an image"
|
||||
class="center h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200 tooltip"
|
||||
on:click={() => addFile($editor)}>
|
||||
{#if $uploading}
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
{:else}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<script lang="ts">
|
||||
import {onMount} from "svelte"
|
||||
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, EVENT_DATE, EVENT_TIME} from "@welshman/util"
|
||||
import {publishThunk, makeThunk} 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 {makeMention, makeIMeta} from "@app/commands"
|
||||
import {getNoteEditorOptions, addFile} from "@app/editor"
|
||||
import {pushModal} from "@app/modal"
|
||||
|
||||
export let url
|
||||
|
||||
const next = () => null
|
||||
|
||||
const back = () => history.back()
|
||||
|
||||
const uploading = writable(false)
|
||||
|
||||
const sendMessage = () => {
|
||||
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: [["-"], ...mentionTags, ...imetaTags],
|
||||
})
|
||||
|
||||
publishThunk(makeThunk({event, relays: [url]}))
|
||||
|
||||
$editor.chain().clearContent().run()
|
||||
}
|
||||
|
||||
let editor: Readable<Editor>
|
||||
let isAllDay = false
|
||||
let file: File
|
||||
let title = ""
|
||||
let location = ""
|
||||
let start = ""
|
||||
let end = ""
|
||||
|
||||
onMount(() => {
|
||||
editor = createEditor(getNoteEditorOptions({uploading, sendMessage}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<form class="column gap-4" on:submit|preventDefault={next}>
|
||||
<div class="py-2">
|
||||
<h1 class="heading">Create an Event</h1>
|
||||
<p class="text-center">Invite other group members to events online or in real life.</p>
|
||||
</div>
|
||||
<Field>
|
||||
<p slot="label">Title*</p>
|
||||
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
|
||||
<input bind:value={title} class="grow" type="text" />
|
||||
</label>
|
||||
</Field>
|
||||
<Field>
|
||||
<p slot="label">Summary</p>
|
||||
<div
|
||||
slot="input"
|
||||
class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
|
||||
<Button
|
||||
data-tip="Add an image"
|
||||
class="center h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200 tooltip"
|
||||
on:click={() => addFile($editor)}>
|
||||
{#if $uploading}
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
{:else}
|
||||
<Icon icon="gallery-send" />
|
||||
{/if}
|
||||
</Button>
|
||||
<div class="flex-grow overflow-hidden">
|
||||
<EditorContent editor={$editor} />
|
||||
</div>
|
||||
</div>
|
||||
</Field>
|
||||
<Field>
|
||||
<p slot="label">Location (optional)</p>
|
||||
<label class="input input-bordered flex w-full items-center gap-2" slot="input">
|
||||
<Icon icon="map-point" />
|
||||
<input bind:value={title} class="grow" type="text" />
|
||||
</label>
|
||||
</Field>
|
||||
<div class="flex flex-row items-center justify-between gap-4">
|
||||
<Button class="btn btn-link" on:click={back}>
|
||||
<Icon icon="alt-arrow-left" />
|
||||
Go back
|
||||
</Button>
|
||||
<Button type="submit" class="btn btn-primary">
|
||||
Next
|
||||
<Icon icon="alt-arrow-right" class="!bg-base-300" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Button from "@lib/components/Button.svelte"
|
||||
import Field from "@lib/components/Field.svelte"
|
||||
import Link from "@lib/components/Link.svelte"
|
||||
import Icon from "@lib/components/Icon.svelte"
|
||||
import SpaceInviteAccept from "@app/components/SpaceInviteAccept.svelte"
|
||||
@@ -12,23 +11,17 @@
|
||||
window.open("https://relay.tools/signup")
|
||||
pushModal(SpaceInviteAccept)
|
||||
}
|
||||
|
||||
let file: File
|
||||
let name = ""
|
||||
let relay = ""
|
||||
</script>
|
||||
|
||||
<form class="column gap-4" on:submit|preventDefault={next}>
|
||||
<div class="py-2">
|
||||
<h1 class="heading">Create a Space</h1>
|
||||
<p class="text-center">
|
||||
Host your own space, for your community.
|
||||
</p>
|
||||
<p class="text-center">Host your own space, for your community.</p>
|
||||
</div>
|
||||
<p>
|
||||
<Link class="text-primary" external href="https://relay.tools">relay.tools</Link> is a third-party service
|
||||
that allows anyone to run their own relay for use with Flotilla, or any other
|
||||
nostr-compatible app.
|
||||
<Link class="text-primary" external href="https://relay.tools">relay.tools</Link> is a third-party
|
||||
service that allows anyone to run their own relay for use with Flotilla, or any other nostr-compatible
|
||||
app.
|
||||
</p>
|
||||
<p>
|
||||
Once you've created a relay of your own, come back here to link Flotilla with your new relay.
|
||||
|
||||
+63
-2
@@ -37,12 +37,12 @@ export const addFile = (editor: Editor) => editor.chain().selectFiles().run()
|
||||
|
||||
export const uploadFiles = (editor: Editor) => editor.chain().uploadFiles().run()
|
||||
|
||||
type ChatComposeEditorOptions = {
|
||||
type EditorOptions = {
|
||||
uploading: Writable<boolean>
|
||||
sendMessage: () => void
|
||||
}
|
||||
|
||||
export const getChatEditorOptions = ({uploading, sendMessage}: ChatComposeEditorOptions) => ({
|
||||
export const getChatEditorOptions = ({uploading, sendMessage}: EditorOptions) => ({
|
||||
content: "",
|
||||
autofocus: true,
|
||||
extensions: [
|
||||
@@ -146,3 +146,64 @@ export const getChatViewOptions = (content: string) => ({
|
||||
VideoExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(ChatComposeVideo)})),
|
||||
],
|
||||
})
|
||||
|
||||
export const getNoteEditorOptions = ({uploading, sendMessage}: EditorOptions) => ({
|
||||
content: "",
|
||||
autofocus: true,
|
||||
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()
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
+33
-19
@@ -1,12 +1,14 @@
|
||||
import {nip19} from "nostr-tools"
|
||||
import {get, derived, writable} from "svelte/store"
|
||||
import {get, derived} from "svelte/store"
|
||||
import type {Maybe} from "@welshman/lib"
|
||||
import {setContext, max, groupBy, pushToMapKey, nthEq} from "@welshman/lib"
|
||||
import {setContext, max, pushToMapKey, nthEq} from "@welshman/lib"
|
||||
import {
|
||||
getIdFilters,
|
||||
RELAYS,
|
||||
REACTION,
|
||||
ZAP_RESPONSE,
|
||||
EVENT_DATE,
|
||||
EVENT_TIME,
|
||||
getRelayTagValues,
|
||||
getTopicTagValues,
|
||||
isShareableRelayUrl,
|
||||
@@ -150,27 +152,23 @@ export const makeChatId = (url: string, topic: string) => `${url}'${topic}`
|
||||
|
||||
export const splitChatId = (id: string) => id.split("'")
|
||||
|
||||
export const chats = derived(
|
||||
[trackerStore, messages],
|
||||
([$tracker, $messages]) => {
|
||||
const messagesByChatId = new Map<string, Message[]>()
|
||||
export const chats = derived([trackerStore, messages], ([$tracker, $messages]) => {
|
||||
const messagesByChatId = new Map<string, Message[]>()
|
||||
|
||||
for (const message of $messages) {
|
||||
for (const url of $tracker.getRelays(message.event.id)) {
|
||||
const chatId = makeChatId(url, message.topic)
|
||||
for (const message of $messages) {
|
||||
for (const url of $tracker.getRelays(message.event.id)) {
|
||||
const chatId = makeChatId(url, message.topic)
|
||||
|
||||
pushToMapKey(messagesByChatId, chatId, message)
|
||||
}
|
||||
pushToMapKey(messagesByChatId, chatId, message)
|
||||
}
|
||||
|
||||
return Array.from(messagesByChatId.entries())
|
||||
.map(([id, messages]) => {
|
||||
const [url, topic] = splitChatId(id)
|
||||
|
||||
return {id, url, topic, messages}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
return Array.from(messagesByChatId.entries()).map(([id, messages]) => {
|
||||
const [url, topic] = splitChatId(id)
|
||||
|
||||
return {id, url, topic, messages}
|
||||
})
|
||||
})
|
||||
|
||||
export const {
|
||||
indexStore: chatsById,
|
||||
@@ -190,6 +188,22 @@ export const {
|
||||
},
|
||||
})
|
||||
|
||||
// Calendar vents
|
||||
|
||||
export const events = deriveEvents(repository, {filters: [{kinds: [EVENT_DATE, EVENT_TIME]}]})
|
||||
|
||||
export const eventsByUrl = derived([trackerStore, events], ([$tracker, $events]) => {
|
||||
const eventsByUrl = new Map<string, TrustedEvent[]>()
|
||||
|
||||
for (const event of $events) {
|
||||
for (const url of $tracker.getRelays(event.id)) {
|
||||
pushToMapKey(eventsByUrl, url, event)
|
||||
}
|
||||
}
|
||||
|
||||
return eventsByUrl
|
||||
})
|
||||
|
||||
// Topics
|
||||
|
||||
export const topicsByUrl = derived(chats, $chats => {
|
||||
|
||||
Reference in New Issue
Block a user