Move editor stuff to its own folder

This commit is contained in:
Jon Staab
2024-09-23 13:58:01 -07:00
parent 26eb4faf37
commit ad4944d512
28 changed files with 312 additions and 397 deletions
+9 -19
View File
@@ -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<Editor>
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}))
})
</script>
@@ -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">
<Button
data-tip="Add an image"
class="center h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200 tooltip"
class="center tooltip h-10 w-10 rounded-box bg-base-300 transition-colors hover:bg-base-200"
on:click={() => addFile($editor)}>
{#if $uploading}
{#if $loading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon="gallery-send" />
{/if}
</Button>
<div class="flex-grow overflow-hidden chat-editor">
<div class="chat-editor flex-grow overflow-hidden">
<EditorContent editor={$editor} />
</div>
</div>
@@ -1,20 +0,0 @@
<script lang="ts">
import cx from "classnames"
import type {NodeViewProps} from "@tiptap/core"
import {NodeViewWrapper} from "svelte-tiptap"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import {clip} from "@app/toast"
export let node: NodeViewProps["node"]
export let selected: NodeViewProps["selected"]
const copy = () => clip(node.attrs.lnbc)
</script>
<NodeViewWrapper class="inline">
<Button on:click={copy} class={cx("link-content", {"link-content-selected": selected})}>
<Icon icon="bolt" size={3} class="inline-block translate-y-px" />
{node.attrs.lnbc.slice(0, 16)}...
</Button>
</NodeViewWrapper>
@@ -1,24 +0,0 @@
<script lang="ts">
import type {NodeViewProps} from "@tiptap/core"
import {NodeViewWrapper} from "svelte-tiptap"
import {ellipsize} from "@welshman/lib"
import {type TrustedEvent, fromNostrURI, Address} from "@welshman/util"
import Link from "@lib/components/Link.svelte"
import {deriveEvent} from "@app/state"
export let node: NodeViewProps["node"]
const displayEvent = (e: TrustedEvent) =>
e?.content.length > 1
? ellipsize(e.content, 50)
: fromNostrURI(nevent || naddr).slice(0, 16) + "..."
$: ({identifier, pubkey, kind, id, relays = [], nevent, naddr} = node.attrs)
$: event = deriveEvent(id || new Address(kind, pubkey, identifier).toString(), relays)
</script>
<NodeViewWrapper class="inline">
<Link external href="https://njump.me/{node.attrs.nevent}" class="link-content">
{displayEvent($event)}
</Link>
</NodeViewWrapper>
@@ -1,14 +0,0 @@
<script lang="ts">
import cx from "classnames"
import type {NodeViewProps} from "@tiptap/core"
import {NodeViewWrapper} from "svelte-tiptap"
import Icon from "@lib/components/Icon.svelte"
export let node: NodeViewProps["node"]
export let selected: NodeViewProps["selected"]
</script>
<NodeViewWrapper class={cx("link-content inline", {"link-content-selected": selected})}>
<Icon icon="paperclip" size={3} class="inline-block translate-y-px" />
{node.attrs.file.name}
</NodeViewWrapper>
-21
View File
@@ -1,21 +0,0 @@
<script lang="ts">
import cx from "classnames"
import type {NodeViewProps} from "@tiptap/core"
import {NodeViewWrapper} from "svelte-tiptap"
import {displayUrl} from "@welshman/lib"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
export let node: NodeViewProps["node"]
export let selected: NodeViewProps["selected"]
</script>
<NodeViewWrapper class="inline-block">
<Link
external
href={node.attrs.url}
class={cx("link-content", {"link-content-selected": selected})}>
<Icon icon="link-round" size={3} class="inline-block" />
{displayUrl(node.attrs.url)}
</Link>
</NodeViewWrapper>
@@ -1,22 +0,0 @@
<script lang="ts">
import cx from "classnames"
import type {NodeViewProps} from "@tiptap/core"
import {NodeViewWrapper} from "svelte-tiptap"
import {displayProfile} from "@welshman/util"
import {deriveProfile} from "@welshman/app"
import Link from "@lib/components/Link.svelte"
export let node: NodeViewProps["node"]
export let selected: NodeViewProps["selected"]
$: profile = deriveProfile(node.attrs.pubkey, node.attrs.relays)
</script>
<NodeViewWrapper class="inline">
<Link
external
href="https://njump.me/{node.attrs.nprofile}"
class={cx("link-content", {"link-content-selected": selected})}>
@{displayProfile($profile)}
</Link>
</NodeViewWrapper>
@@ -1,96 +0,0 @@
<svelte:options accessors />
<script lang="ts">
import {throttle} from "throttle-debounce"
import {slide} from "svelte/transition"
import {clamp} from "@welshman/lib"
import {theme} from "@app/theme"
export let term
export let search
export let select
export let component
export let loading = false
export let allowCreate = false
let index = 0
let element: Element
let items: string[] = []
$: populateItems(term)
const populateItems = throttle(300, term => {
items = $search.searchValues(term).slice(0, 30)
})
const setIndex = (newIndex: number, block: any) => {
index = clamp([0, items.length - 1], newIndex)
element.querySelector(`button:nth-child(${index})`)?.scrollIntoView({block})
}
export const onKeyDown = (e: any) => {
if (["Enter", "Tab"].includes(e.code)) {
const value = items[index]
if (value) {
select(value)
return true
} else if (term && allowCreate) {
select(term)
return true
}
}
if (e.code === "Space" && term && allowCreate) {
select(term)
return true
}
if (e.code === "ArrowUp") {
setIndex(index - 1, "start")
return true
}
if (e.code === "ArrowDown") {
setIndex(index + 1, "start")
return true
}
}
</script>
{#if items.length > 0 || (term && allowCreate)}
<div
data-theme={$theme}
bind:this={element}
transition:slide|local={{duration: 100}}
class="mt-2 max-h-[350px] overflow-y-auto overflow-x-hidden shadow-xl">
{#if term && allowCreate}
<button
class="white-space-nowrap block w-full min-w-0 cursor-pointer overflow-x-hidden text-ellipsis px-4 py-2 text-left transition-colors hover:bg-primary hover:text-primary-content"
on:mousedown|preventDefault
on:click|preventDefault={() => select(term)}>
Use "<svelte:component this={component} value={term} />"
</button>
{/if}
{#each items as value, i (value)}
<button
class="white-space-nowrap block w-full min-w-0 cursor-pointer overflow-x-hidden text-ellipsis px-4 py-2 text-left transition-colors hover:bg-primary hover:text-primary-content"
class:bg-primary={index === i}
class:text-primary-content={index === i}
on:mousedown|preventDefault
on:click|preventDefault={() => select(value)}>
<svelte:component this={component} {value} />
</button>
{/each}
</div>
{#if loading}
<div transition:slide|local class="bg-tinted-700 flex gap-2 px-4 py-2 text-neutral-200">
<div>
<i class="fa fa-circle-notch fa-spin" />
</div>
Loading more options...
</div>
{/if}
{/if}
@@ -1,12 +0,0 @@
<script lang="ts">
import type {NodeViewProps} from "@tiptap/core"
import {NodeViewWrapper} from "svelte-tiptap"
import Icon from "@lib/components/Icon.svelte"
export let node: NodeViewProps["node"]
</script>
<NodeViewWrapper class="link-content inline">
<Icon icon="paperclip" size={3} class="inline-block translate-y-px" />
{node.attrs.file.name}
</NodeViewWrapper>
+3 -8
View File
@@ -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))
})
</script>
@@ -1,9 +0,0 @@
<script lang="ts">
import {deriveProfileDisplay} from "@welshman/app"
export let value
const display = deriveProfileDisplay(value)
</script>
@{$display}
+3 -8
View File
@@ -1,17 +1,12 @@
<script lang="ts">
import cx from "classnames"
import {fromPairs} from "@welshman/lib"
import {Tags, getAddress} from "@welshman/util"
import {repository, pubkey, secondsToDate, getLocale, formatTimestamp, formatTimestampAsDate, deriveProfileDisplay} from "@welshman/app"
import {getAddress} from "@welshman/util"
import {secondsToDate, getLocale, formatTimestamp, formatTimestampAsDate} from "@welshman/app"
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
export let event
const address = getAddress(event)
const timeFmt = new Intl.DateTimeFormat(getLocale(), {timeStyle: "short"})
const datetimeFmt = new Intl.DateTimeFormat(getLocale(), {dateStyle: "short", timeStyle: "short"})
const profileDisplay = deriveProfileDisplay(event.pubkey)
$: meta = fromPairs(event.tags) as Record<string, string>
$: end = parseInt(meta.end)
@@ -23,7 +18,7 @@
$: isSingleDay = startDateDisplay === endDateDisplay
</script>
<div class="card2 flex justify-between items-center gap-2">
<div class="card2 flex items-center justify-between gap-2">
<span>{meta.title || meta.name}</span>
<div class="flex items-center gap-2 text-sm">
<Icon icon="clock-circle" size={4} />
+14 -32
View File
@@ -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<Editor>
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}))
})
</script>
<form class="column gap-4" on:submit|preventDefault={submit}>
<form class="column gap-4" on:submit|preventDefault={startSubmit}>
<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>
@@ -95,14 +83,11 @@
<div
slot="input"
class="relative z-feature flex gap-2 border-t border-solid border-base-100 bg-base-100">
<div class="flex-grow overflow-hidden input-editor">
<div class="input-editor flex-grow overflow-hidden">
<EditorContent editor={$editor} />
</div>
<Button
data-tip="Add an image"
class="btn center tooltip"
on:click={() => addFile($editor)}>
{#if $uploading}
<Button data-tip="Add an image" class="center btn tooltip" on:click={() => addFile($editor)}>
{#if $loading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon="gallery-send" />
@@ -134,9 +119,6 @@
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button type="submit" class="btn btn-primary">
Create Event
</Button>
<Button type="submit" class="btn btn-primary">Create Event</Button>
</div>
</form>
+6 -7
View File
@@ -1,12 +1,11 @@
<script lang="ts">
import cx from "classnames"
import {onMount} from "svelte"
import type {Readable} from "svelte/store"
import {createEditor, type Editor, EditorContent} from "svelte-tiptap"
import {displayPubkey} from '@welshman/util'
import {displayPubkey} from "@welshman/util"
import {deriveProfile, deriveProfileDisplay, formatTimestamp} from "@welshman/app"
import Avatar from '@lib/components/Avatar.svelte'
import {getChatViewOptions} from '@app/editor'
import Avatar from "@lib/components/Avatar.svelte"
import {getViewOptions} from "@lib/editor"
export let root
export let replies
@@ -17,13 +16,13 @@
let editor: Readable<Editor>
onMount(() => {
editor = createEditor(getChatViewOptions(root.content))
editor = createEditor(getViewOptions(root.content))
})
</script>
<div>
<div class="card2 flex flex-col gap-2">
<div class="flex justify-between items-center gap-2">
<div class="flex items-center justify-between gap-2">
<div class="flex gap-2">
<div class="py-1">
<Avatar src={$profile?.picture} size={10} />
@@ -40,6 +39,6 @@
</div>
</div>
{#if replies.length > 0}
Show {replies.length} {replies.length === 1 ? 'reply' : 'replies'}
Show {replies.length} {replies.length === 1 ? "reply" : "replies"}
{/if}
</div>
+14 -37
View File
@@ -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<Editor>
onMount(() => {
editor = createEditor(getNoteEditorOptions({uploading, sendMessage}))
editor = createEditor(getEditorOptions({submit, loading, getPubkeyHints}))
})
</script>
<form class="column gap-4" on:submit|preventDefault={submit}>
<form class="column gap-4" on:submit|preventDefault={startSubmit}>
<div class="py-2">
<h1 class="heading">Create a Thread</h1>
<p class="text-center">Share your thoughts, or start a discussion.</p>
</div>
<div class="relative">
<div class="flex-grow overflow-hidden note-editor">
<div class="note-editor flex-grow overflow-hidden">
<EditorContent editor={$editor} />
</div>
<Button
data-tip="Add an image"
class="tooltip tooltip-left absolute right-2 bottom-1"
class="tooltip tooltip-left absolute bottom-1 right-2"
on:click={() => addFile($editor)}>
{#if $uploading}
{#if $loading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Icon icon="paperclip" size={3} />
@@ -78,9 +58,6 @@
<Icon icon="alt-arrow-left" />
Go back
</Button>
<Button type="submit" class="btn btn-primary">
Create Thread
</Button>
<Button type="submit" class="btn btn-primary">Create Thread</Button>
</div>
</form>