From b56b47df823bcc1191cf14ab398813c1e512da7d Mon Sep 17 00:00:00 2001 From: Shibo Lyu Date: Wed, 16 Apr 2025 00:46:34 +0800 Subject: [PATCH] fix: Add initialDoc prop and BlahRichText conversion utility Fix typo in schema ("paragragh" -> "paragraph") and implement bidirectional conversion between BlahRichText and ProseMirror formats. Update profile page to use new initialDoc prop instead of children. --- .../RichTextInput/ClientInput.svelte | 17 ++++++++++-- src/lib/components/RichTextInput/schema.ts | 2 +- src/lib/richText.ts | 27 ++++++++++++++++++- .../settings/account/profile/+page.svelte | 14 +++++----- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/lib/components/RichTextInput/ClientInput.svelte b/src/lib/components/RichTextInput/ClientInput.svelte index de69343..3af773e 100644 --- a/src/lib/components/RichTextInput/ClientInput.svelte +++ b/src/lib/components/RichTextInput/ClientInput.svelte @@ -7,10 +7,22 @@ export interface Props extends Omit { onDocChange?: (doc: Node) => void; placeholder?: string; + /** + * The initial content in editor. + * + * This is higher priority than `children`. + */ + initialDoc?: Node | null; children?: import('svelte').Snippet; } - const { onDocChange, placeholder = '', children, ...stateConfiguration }: Props = $props(); + const { + onDocChange, + placeholder = '', + children, + initialDoc: initialDocProp, + ...stateConfiguration + }: Props = $props(); let domEl: HTMLDivElement; let editorView: EditorView; @@ -18,7 +30,8 @@ let isEmpty = $state(!children); $effect(() => { - const initialDoc = DOMParser.fromSchema(stateConfiguration.schema).parse(domEl); + const initialDoc = + initialDocProp ?? DOMParser.fromSchema(stateConfiguration.schema).parse(domEl); domEl.replaceChildren(); onDocChange?.(initialDoc); isEmpty = initialDoc.textContent.length === 0; diff --git a/src/lib/components/RichTextInput/schema.ts b/src/lib/components/RichTextInput/schema.ts index 60aa200..fb9507a 100644 --- a/src/lib/components/RichTextInput/schema.ts +++ b/src/lib/components/RichTextInput/schema.ts @@ -4,7 +4,7 @@ import { nodes as basicNodes, marks as basicMarks } from 'prosemirror-schema-bas export const messageSchema = new Schema({ nodes: { doc: { content: 'block+' }, - paragragh: { + paragraph: { content: 'inline*', ...basicNodes.paragraph }, diff --git a/src/lib/richText.ts b/src/lib/richText.ts index 1ddf6b3..d631a33 100644 --- a/src/lib/richText.ts +++ b/src/lib/richText.ts @@ -1,5 +1,5 @@ import type { BlahRichText, BlahRichTextSpanAttributes } from '@blah-im/core/richText'; -import type { Node } from 'prosemirror-model'; +import type { Node, Schema } from 'prosemirror-model'; function isObjectEmpty(obj: object) { for (const _ in obj) return false; @@ -58,3 +58,28 @@ export function proseMirrorDocToBlahRichText(doc: Node): BlahRichText { return spans; } + +export function blahRichTextToProseMirrorDoc(richText: BlahRichText, schema: Schema): Node { + console.log(schema); + const paragraphs = richText.flatMap((span) => { + if (typeof span === 'string') { + if (!span.trim().length) return []; + return [schema.nodes.paragraph.create({}, schema.text(span))]; + } else { + const [text, attributes] = span; + const marks = []; + if (attributes.b) marks.push(schema.marks.strong.create()); + if (attributes.i) marks.push(schema.marks.em.create()); + if (attributes.m) marks.push(schema.marks.code.create()); + if (attributes.link) marks.push(schema.marks.link.create({ href: attributes.link })); + if (attributes.u) marks.push(schema.marks.underline.create()); + if (attributes.s) marks.push(schema.marks.strikethrough.create()); + if (attributes.tag) marks.push(schema.marks.tag.create()); + if (attributes.spoiler) marks.push(schema.marks.spoiler.create()); + + return [schema.nodes.paragraph.create({}, schema.text(text, marks))]; + } + }); + + return schema.nodes.doc.create({}, paragraphs); +} diff --git a/src/routes/(app)/settings/account/profile/+page.svelte b/src/routes/(app)/settings/account/profile/+page.svelte index 607461a..c23138d 100644 --- a/src/routes/(app)/settings/account/profile/+page.svelte +++ b/src/routes/(app)/settings/account/profile/+page.svelte @@ -13,9 +13,12 @@ import Button from '$lib/components/Button.svelte'; import RichTextInput from '$lib/components/RichTextInput.svelte'; import { messageSchema } from '$lib/components/RichTextInput/schema'; + import { blahRichTextToProseMirrorDoc } from '$lib/richText'; + import type { Node } from 'prosemirror-model'; const currentAccount = $derived(accountsManager.currentAccount); let profile: BlahProfile | null = $state(null); + let initialBio: Node | null = $state(null); let isBusy: boolean = $state(false); @@ -23,21 +26,17 @@ if (currentAccount) { const snapshot = $state.snapshot(currentAccount.profile.signee.payload); profile = snapshot; - console.log('Reloaded'); + initialBio = blahRichTextToProseMirrorDoc([snapshot.bio ?? ''], messageSchema); } }); - $inspect(profile); - async function saveProfile() { if (!currentAccount || !profile) return; - console.log('Saving profile', $state.snapshot(profile)); isBusy = true; const identity = await accountsManager.identityForAccount(currentAccount); await identity.updateProfile(profile); await accountsManager.saveIdentity(identity); - console.log('Profile saved', identity.generateIdentityDescription()); isBusy = false; } @@ -67,9 +66,8 @@ schema={messageSchema} onDocChange={(doc) => profile && (profile.bio = doc.textContent)} placeholder="a 25 yo. artist from Paris." - > - {profile.bio ?? ''} - + initialDoc={initialBio} + /> {/if}