forked from coracle/flotilla
feat: persist composer drafts in memory across navigation
This commit is contained in:
@@ -20,24 +20,33 @@
|
|||||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||||
import {PROTECTED} from "@app/core/state"
|
import {PROTECTED} from "@app/core/state"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
|
import {DraftKey} from "@app/util/drafts"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {canEnforceNip70} from "@app/core/commands"
|
import {canEnforceNip70} from "@app/core/commands"
|
||||||
|
|
||||||
|
type CalendarValues = {
|
||||||
|
d?: string
|
||||||
|
title: string
|
||||||
|
content: unknown
|
||||||
|
location: string
|
||||||
|
start: number | undefined
|
||||||
|
end: number | undefined
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
h?: string
|
h?: string
|
||||||
header: Snippet
|
header: Snippet
|
||||||
initialValues?: {
|
initialValues?: CalendarValues
|
||||||
d: string
|
|
||||||
title: string
|
|
||||||
content: string
|
|
||||||
location: string
|
|
||||||
start: number
|
|
||||||
end: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url, h, header, initialValues}: Props = $props()
|
let {url, h, header, initialValues}: Props = $props()
|
||||||
|
|
||||||
|
const draftKey = new DraftKey<CalendarValues>(`calendar:${url}:${h ?? ""}`)
|
||||||
|
const draft = draftKey.get()
|
||||||
|
if (!initialValues) {
|
||||||
|
initialValues = draft
|
||||||
|
}
|
||||||
|
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
@@ -95,17 +104,33 @@
|
|||||||
|
|
||||||
pushToast({message: "Your event has been saved!"})
|
pushToast({message: "Your event has been saved!"})
|
||||||
publishThunk({event, relays: [url]})
|
publishThunk({event, relays: [url]})
|
||||||
|
draftKey.clear()
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = initialValues?.content || ""
|
let editorContent = $state<unknown>(initialValues?.content ?? "")
|
||||||
const editor = makeEditor({url, submit, uploading, content})
|
|
||||||
|
|
||||||
let title = $state(initialValues?.title || "")
|
const onChange = (json: unknown) => {
|
||||||
let location = $state(initialValues?.location || "")
|
editorContent = json
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = makeEditor({
|
||||||
|
url,
|
||||||
|
submit,
|
||||||
|
uploading,
|
||||||
|
onChange,
|
||||||
|
content: initialValues?.content ?? "",
|
||||||
|
})
|
||||||
|
|
||||||
|
let title = $state(initialValues?.title ?? "")
|
||||||
|
let location = $state(initialValues?.location ?? "")
|
||||||
let start: number | undefined = $state(initialValues?.start)
|
let start: number | undefined = $state(initialValues?.start)
|
||||||
let end: number | undefined = $state(initialValues?.end)
|
let end: number | undefined = $state(initialValues?.end)
|
||||||
let endDirty = Boolean(initialValues?.end)
|
let endDirty = $state(Boolean(initialValues?.end))
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
draftKey.set({d: initialValues?.d, title, location, start, end, content: editorContent})
|
||||||
|
})
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!endDirty && start) {
|
if (!endDirty && start) {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@
|
|||||||
import ThunkToast from "@app/components/ThunkToast.svelte"
|
import ThunkToast from "@app/components/ThunkToast.svelte"
|
||||||
import {userSettingsValues, deriveChat} from "@app/core/state"
|
import {userSettingsValues, deriveChat} from "@app/core/state"
|
||||||
import {pushModal} from "@app/util/modal"
|
import {pushModal} from "@app/util/modal"
|
||||||
|
import {DraftKey} from "@app/util/drafts"
|
||||||
import {makeDelete, prependParent} from "@app/core/commands"
|
import {makeDelete, prependParent} from "@app/core/commands"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@
|
|||||||
const {pubkeys, info}: Props = $props()
|
const {pubkeys, info}: Props = $props()
|
||||||
|
|
||||||
const chat = deriveChat(pubkeys)
|
const chat = deriveChat(pubkeys)
|
||||||
|
const draftKey = new DraftKey<{content?: unknown}>(`dm:${$chat?.id}`)
|
||||||
const others = remove($pubkey!, pubkeys)
|
const others = remove($pubkey!, pubkeys)
|
||||||
const missingRelayLists = $derived(others.filter(pk => !$messagingRelayListsByPubkey.has(pk)))
|
const missingRelayLists = $derived(others.filter(pk => !$messagingRelayListsByPubkey.has(pk)))
|
||||||
|
|
||||||
@@ -337,6 +339,7 @@
|
|||||||
{onEscape}
|
{onEscape}
|
||||||
{onEditPrevious}
|
{onEditPrevious}
|
||||||
content={eventToEdit?.content}
|
content={eventToEdit?.content}
|
||||||
|
draftKey={eventToEdit ? undefined : draftKey}
|
||||||
disabled={Boolean(missingRelayLists.length)} />
|
disabled={Boolean(missingRelayLists.length)} />
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,16 +10,20 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
|
import {type DraftKey} from "@app/util/drafts"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
content?: string
|
content?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
draftKey?: DraftKey<{content?: unknown}>
|
||||||
onEscape?: () => void
|
onEscape?: () => void
|
||||||
onEditPrevious?: () => void
|
onEditPrevious?: () => void
|
||||||
onSubmit: (event: EventContent) => void
|
onSubmit: (event: EventContent) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const {content, disabled = false, onEscape, onEditPrevious, onSubmit}: Props = $props()
|
const {content, disabled = false, draftKey, onEscape, onEditPrevious, onSubmit}: Props = $props()
|
||||||
|
|
||||||
|
const draft = draftKey?.get()
|
||||||
|
|
||||||
const autofocus = !isMobile && !disabled
|
const autofocus = !isMobile && !disabled
|
||||||
|
|
||||||
@@ -59,14 +63,20 @@
|
|||||||
|
|
||||||
onSubmit({content, tags})
|
onSubmit({content, tags})
|
||||||
|
|
||||||
|
draftKey?.clear()
|
||||||
ed.chain().clearContent().run()
|
ed.chain().clearContent().run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onChange = (json: unknown) => {
|
||||||
|
draftKey?.set({content: json})
|
||||||
|
}
|
||||||
|
|
||||||
const editor = makeEditor({
|
const editor = makeEditor({
|
||||||
content,
|
content: content ?? (draft?.content as string | object | undefined),
|
||||||
autofocus,
|
autofocus,
|
||||||
submit,
|
submit,
|
||||||
uploading,
|
uploading,
|
||||||
|
onChange,
|
||||||
aggressive: true,
|
aggressive: true,
|
||||||
encryptFiles: true,
|
encryptFiles: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {PROTECTED} from "@app/core/state"
|
import {PROTECTED} from "@app/core/state"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
|
import {DraftKey} from "@app/util/drafts"
|
||||||
import {canEnforceNip70, uploadFile} from "@app/core/commands"
|
import {canEnforceNip70, uploadFile} from "@app/core/commands"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -32,13 +33,31 @@
|
|||||||
content?: string
|
content?: string
|
||||||
price?: number
|
price?: number
|
||||||
currency?: string
|
currency?: string
|
||||||
images?: string[]
|
images?: (string | File)[]
|
||||||
status?: string
|
status?: string
|
||||||
topics?: string[]
|
topics?: string[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url, h, header, initialValues}: Props = $props()
|
let {url, h, header, initialValues}: Props = $props()
|
||||||
|
|
||||||
|
type ClassifiedDraft = {
|
||||||
|
editorContent?: unknown
|
||||||
|
d?: string
|
||||||
|
title?: string
|
||||||
|
content?: string
|
||||||
|
price?: number
|
||||||
|
currency?: string
|
||||||
|
images?: (string | File)[]
|
||||||
|
status?: string
|
||||||
|
topics?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const draftKey = new DraftKey<ClassifiedDraft>(`classified:${url}:${h ?? ""}`)
|
||||||
|
const draft = draftKey.get()
|
||||||
|
if (!initialValues) {
|
||||||
|
initialValues = draft
|
||||||
|
}
|
||||||
|
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
@@ -110,22 +129,35 @@
|
|||||||
event: makeEvent(CLASSIFIED, {content, tags}),
|
event: makeEvent(CLASSIFIED, {content, tags}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
draftKey.clear()
|
||||||
history.back()
|
history.back()
|
||||||
} finally {
|
} finally {
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = initialValues?.content || ""
|
const initialContent = initialValues?.content || ""
|
||||||
const editor = makeEditor({url, submit, content})
|
const onChange = (json: unknown) => {
|
||||||
|
draftKey.set({editorContent: json, title, price, currency, topics, status, images})
|
||||||
|
}
|
||||||
|
const editor = makeEditor({
|
||||||
|
url,
|
||||||
|
submit,
|
||||||
|
onChange,
|
||||||
|
content: draft?.editorContent ?? initialContent,
|
||||||
|
})
|
||||||
|
|
||||||
let loading = $state(false)
|
let loading = $state(false)
|
||||||
let title = $state(initialValues?.title || "")
|
let title = $state(initialValues?.title ?? "")
|
||||||
let status = $state(initialValues?.status || "active")
|
let status = $state(initialValues?.status ?? "active")
|
||||||
let price = $state(Number(initialValues?.price || 0))
|
let price = $state(Number(initialValues?.price ?? 0))
|
||||||
let currency = $state(initialValues?.currency || "SAT")
|
let currency = $state(initialValues?.currency ?? "SAT")
|
||||||
let images = $state<(string | File)[]>(initialValues?.images || [])
|
let images = $state<(string | File)[]>(initialValues?.images ?? [])
|
||||||
let topics = $state(uniq(removeUndefined((initialValues?.topics || []).map(normalizeTopic))))
|
let topics = $state(uniq(removeUndefined((initialValues?.topics ?? []).map(normalizeTopic))))
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
draftKey.set({...draftKey.get(), title, price, currency, topics, status, images})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
||||||
|
|||||||
@@ -10,10 +10,14 @@
|
|||||||
import {publishComment, canEnforceNip70} from "@app/core/commands"
|
import {publishComment, canEnforceNip70} from "@app/core/commands"
|
||||||
import {PROTECTED} from "@app/core/state"
|
import {PROTECTED} from "@app/core/state"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
|
import {DraftKey} from "@app/util/drafts"
|
||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
|
|
||||||
const {url, event, onClose, onSubmit} = $props()
|
const {url, event, onClose, onSubmit} = $props()
|
||||||
|
|
||||||
|
const draftKey = new DraftKey<{content?: unknown}>(`reply:${event.id}`)
|
||||||
|
const draft = draftKey.get()
|
||||||
|
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
@@ -38,10 +42,20 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
draftKey.clear()
|
||||||
onSubmit(publishComment({event, content, tags, relays: [url]}))
|
onSubmit(publishComment({event, content, tags, relays: [url]}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = makeEditor({url, submit, uploading, autofocus: !isMobile})
|
const onChange = (json: unknown) => draftKey.set({content: json})
|
||||||
|
|
||||||
|
const editor = makeEditor({
|
||||||
|
url,
|
||||||
|
submit,
|
||||||
|
uploading,
|
||||||
|
autofocus: !isMobile,
|
||||||
|
content: draft?.content as string | object | undefined,
|
||||||
|
onChange,
|
||||||
|
})
|
||||||
|
|
||||||
let form: HTMLElement
|
let form: HTMLElement
|
||||||
let spacer: HTMLElement
|
let spacer: HTMLElement
|
||||||
|
|||||||
@@ -20,14 +20,31 @@
|
|||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {PROTECTED} from "@app/core/state"
|
import {PROTECTED} from "@app/core/state"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
|
import {DraftKey} from "@app/util/drafts"
|
||||||
import {canEnforceNip70} from "@app/core/commands"
|
import {canEnforceNip70} from "@app/core/commands"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
h?: string
|
h?: string
|
||||||
|
initialValues?: {
|
||||||
|
content?: string
|
||||||
|
amount?: number
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {url, h}: Props = $props()
|
let {url, h, initialValues}: Props = $props()
|
||||||
|
|
||||||
|
type GoalDraft = {
|
||||||
|
editorContent?: unknown
|
||||||
|
content?: string
|
||||||
|
amount?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const draftKey = new DraftKey<GoalDraft>(`goal:${url}:${h ?? ""}`)
|
||||||
|
const draft = draftKey.get()
|
||||||
|
if (!initialValues) {
|
||||||
|
initialValues = draft
|
||||||
|
}
|
||||||
|
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
@@ -77,13 +94,27 @@
|
|||||||
event: makeEvent(ZAP_GOAL, {content, tags}),
|
event: makeEvent(ZAP_GOAL, {content, tags}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
draftKey.clear()
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = makeEditor({url, submit, uploading, placeholder: "What's on your mind?"})
|
const onChange = (json: unknown) => draftKey.update({editorContent: json})
|
||||||
|
|
||||||
let content = $state("")
|
const editor = makeEditor({
|
||||||
let amount = $state(1000)
|
url,
|
||||||
|
submit,
|
||||||
|
uploading,
|
||||||
|
onChange,
|
||||||
|
placeholder: "What's on your mind?",
|
||||||
|
content: draft?.editorContent as string | object | undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
let content = $state(initialValues?.content ?? "")
|
||||||
|
let amount = $state(initialValues?.amount ?? 1000)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
draftKey.update({content, amount})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
||||||
|
|||||||
@@ -22,8 +22,21 @@
|
|||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {PROTECTED} from "@app/core/state"
|
import {PROTECTED} from "@app/core/state"
|
||||||
import {canEnforceNip70} from "@app/core/commands"
|
import {canEnforceNip70} from "@app/core/commands"
|
||||||
|
import {DraftKey} from "@app/util/drafts"
|
||||||
import type {PollType} from "@app/util/polls"
|
import type {PollType} from "@app/util/polls"
|
||||||
|
|
||||||
|
type DraftOption = {
|
||||||
|
id: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PollDraft = {
|
||||||
|
title?: string
|
||||||
|
pollType?: PollType
|
||||||
|
endsAt?: number
|
||||||
|
options?: DraftOption[]
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string
|
url: string
|
||||||
h?: string
|
h?: string
|
||||||
@@ -31,12 +44,10 @@
|
|||||||
|
|
||||||
const {url, h}: Props = $props()
|
const {url, h}: Props = $props()
|
||||||
|
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const draftKey = new DraftKey<PollDraft>(`poll:${url}:${h ?? ""}`)
|
||||||
|
const draft = draftKey.get()
|
||||||
|
|
||||||
type DraftOption = {
|
const shouldProtect = canEnforceNip70(url)
|
||||||
id: string
|
|
||||||
value: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const back = () => history.back()
|
const back = () => history.back()
|
||||||
|
|
||||||
@@ -129,17 +140,24 @@
|
|||||||
event: makeEvent(Poll, {content: title.trim(), tags}),
|
event: makeEvent(Poll, {content: title.trim(), tags}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
draftKey.clear()
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = $state("")
|
let title = $state(draft?.title ?? "")
|
||||||
let pollType = $state<PollType>("singlechoice")
|
let pollType = $state<PollType>(draft?.pollType ?? "singlechoice")
|
||||||
let endsAt = $state<number | undefined>()
|
let endsAt = $state<number | undefined>(draft?.endsAt)
|
||||||
let options = $state<DraftOption[]>([
|
let options = $state<DraftOption[]>(
|
||||||
{id: randomId(), value: "Yes"},
|
draft?.options ?? [
|
||||||
{id: randomId(), value: "No"},
|
{id: randomId(), value: "Yes"},
|
||||||
])
|
{id: randomId(), value: "No"},
|
||||||
|
],
|
||||||
|
)
|
||||||
let draggedOptionId = $state<string | undefined>()
|
let draggedOptionId = $state<string | undefined>()
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
draftKey.set({title, pollType, endsAt, options})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
import ComposeMenu from "@app/components/ComposeMenu.svelte"
|
import ComposeMenu from "@app/components/ComposeMenu.svelte"
|
||||||
import EditorContent from "@app/editor/EditorContent.svelte"
|
import EditorContent from "@app/editor/EditorContent.svelte"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
|
import {DraftKey} from "@app/util/drafts"
|
||||||
import {onDestroy, onMount} from "svelte"
|
import {onDestroy, onMount} from "svelte"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -25,6 +26,11 @@
|
|||||||
|
|
||||||
const {url, h, content, onEscape, onEditPrevious, onSubmit}: Props = $props()
|
const {url, h, content, onEscape, onEditPrevious, onSubmit}: Props = $props()
|
||||||
|
|
||||||
|
type ComposeDraft = {content?: unknown}
|
||||||
|
|
||||||
|
const draftKey = url || h ? new DraftKey<ComposeDraft>(`room:${url ?? ""}:${h ?? ""}`) : undefined
|
||||||
|
const draft = draftKey?.get()
|
||||||
|
|
||||||
const autofocus = !isMobile
|
const autofocus = !isMobile
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
@@ -61,10 +67,23 @@
|
|||||||
|
|
||||||
onSubmit({content, tags})
|
onSubmit({content, tags})
|
||||||
|
|
||||||
|
draftKey?.clear()
|
||||||
ed.chain().clearContent().run()
|
ed.chain().clearContent().run()
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = makeEditor({url, content, autofocus, submit, uploading, aggressive: true})
|
const onChange = (json: unknown) => {
|
||||||
|
draftKey?.set({content: json})
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = makeEditor({
|
||||||
|
url,
|
||||||
|
content: content ?? (draft?.content as string | object | undefined),
|
||||||
|
autofocus,
|
||||||
|
submit,
|
||||||
|
uploading,
|
||||||
|
onChange,
|
||||||
|
aggressive: true,
|
||||||
|
})
|
||||||
|
|
||||||
let popover: Instance | undefined = $state()
|
let popover: Instance | undefined = $state()
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
import {pushToast} from "@app/util/toast"
|
import {pushToast} from "@app/util/toast"
|
||||||
import {PROTECTED} from "@app/core/state"
|
import {PROTECTED} from "@app/core/state"
|
||||||
import {makeEditor} from "@app/editor"
|
import {makeEditor} from "@app/editor"
|
||||||
|
import {DraftKey} from "@app/util/drafts"
|
||||||
import {canEnforceNip70} from "@app/core/commands"
|
import {canEnforceNip70} from "@app/core/commands"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -27,6 +28,9 @@
|
|||||||
|
|
||||||
const {url, h}: Props = $props()
|
const {url, h}: Props = $props()
|
||||||
|
|
||||||
|
const draftKey = new DraftKey<{content?: unknown; title?: string}>(`thread:${url}:${h ?? ""}`)
|
||||||
|
const draft = draftKey.get()
|
||||||
|
|
||||||
const shouldProtect = canEnforceNip70(url)
|
const shouldProtect = canEnforceNip70(url)
|
||||||
|
|
||||||
const uploading = writable(false)
|
const uploading = writable(false)
|
||||||
@@ -70,12 +74,26 @@
|
|||||||
event: makeEvent(THREAD, {content, tags}),
|
event: makeEvent(THREAD, {content, tags}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
draftKey.clear()
|
||||||
history.back()
|
history.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = makeEditor({url, submit, uploading, placeholder: "What's on your mind?"})
|
const onChange = (json: unknown) => draftKey.update({content: json})
|
||||||
|
|
||||||
let title: string = $state("")
|
const editor = makeEditor({
|
||||||
|
url,
|
||||||
|
submit,
|
||||||
|
uploading,
|
||||||
|
onChange,
|
||||||
|
placeholder: "What's on your mind?",
|
||||||
|
content: draft?.content as string | object | undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
let title: string = $state(draft?.title ?? "")
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
draftKey.update({title})
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
<Modal tag="form" onsubmit={preventDefault(submit)}>
|
||||||
|
|||||||
@@ -11,13 +11,17 @@
|
|||||||
let element: HTMLElement
|
let element: HTMLElement
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.then(({options}) => {
|
editor.then(ed => {
|
||||||
if (options.element) {
|
if (ed.options.element) {
|
||||||
element?.append(options.element)
|
element?.append(ed.options.element)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.autofocus) {
|
if ((ed as any)._shouldAutofocus) {
|
||||||
;(element?.querySelector("[contenteditable]") as HTMLElement)?.focus()
|
const hasContent = ed.getText().trim().length > 0
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
ed.commands.focus(hasContent ? "end" : "start")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
+11
-4
@@ -28,6 +28,7 @@ export const makeEditor = async ({
|
|||||||
autofocus = false,
|
autofocus = false,
|
||||||
charCount,
|
charCount,
|
||||||
content = "",
|
content = "",
|
||||||
|
onChange,
|
||||||
placeholder = "",
|
placeholder = "",
|
||||||
url,
|
url,
|
||||||
submit,
|
submit,
|
||||||
@@ -38,7 +39,8 @@ export const makeEditor = async ({
|
|||||||
aggressive?: boolean
|
aggressive?: boolean
|
||||||
autofocus?: boolean
|
autofocus?: boolean
|
||||||
charCount?: Writable<number>
|
charCount?: Writable<number>
|
||||||
content?: string
|
content?: string | object
|
||||||
|
onChange?: (json: unknown) => void
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
url?: string
|
url?: string
|
||||||
submit: () => void
|
submit: () => void
|
||||||
@@ -82,9 +84,9 @@ export const makeEditor = async ({
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return new Editor({
|
const ed = new Editor({
|
||||||
content: escapeHtml(content),
|
content: typeof content === "string" ? escapeHtml(content) : content,
|
||||||
autofocus,
|
autofocus: false,
|
||||||
editorProps,
|
editorProps,
|
||||||
element: document.createElement("div"),
|
element: document.createElement("div"),
|
||||||
extensions: [
|
extensions: [
|
||||||
@@ -142,6 +144,11 @@ export const makeEditor = async ({
|
|||||||
onUpdate({editor}) {
|
onUpdate({editor}) {
|
||||||
wordCount?.set(editor.storage.wordCount.words)
|
wordCount?.set(editor.storage.wordCount.words)
|
||||||
charCount?.set(editor.storage.wordCount.chars)
|
charCount?.set(editor.storage.wordCount.chars)
|
||||||
|
onChange?.(editor.getJSON())
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
;(ed as any)._shouldAutofocus = autofocus
|
||||||
|
|
||||||
|
return ed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const store = new Map<string, unknown>()
|
||||||
|
|
||||||
|
export class DraftKey<T> {
|
||||||
|
constructor(private key: string) {}
|
||||||
|
|
||||||
|
get(): T | undefined {
|
||||||
|
return store.get(this.key) as T | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
set(value: T): void {
|
||||||
|
store.set(this.key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
update(value: Partial<T>): void {
|
||||||
|
this.set({...this.get(), ...value} as T)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(): void {
|
||||||
|
store.delete(this.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user