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.
This commit is contained in:
Shibo Lyu 2025-04-16 00:46:34 +08:00
parent aaedd69889
commit b56b47df82
4 changed files with 48 additions and 12 deletions

View file

@ -7,10 +7,22 @@
export interface Props extends Omit<EditorStateConfiguration, 'initialDoc'> { export interface Props extends Omit<EditorStateConfiguration, 'initialDoc'> {
onDocChange?: (doc: Node) => void; onDocChange?: (doc: Node) => void;
placeholder?: string; placeholder?: string;
/**
* The initial content in editor.
*
* This is higher priority than `children`.
*/
initialDoc?: Node | null;
children?: import('svelte').Snippet; children?: import('svelte').Snippet;
} }
const { onDocChange, placeholder = '', children, ...stateConfiguration }: Props = $props(); const {
onDocChange,
placeholder = '',
children,
initialDoc: initialDocProp,
...stateConfiguration
}: Props = $props();
let domEl: HTMLDivElement; let domEl: HTMLDivElement;
let editorView: EditorView; let editorView: EditorView;
@ -18,7 +30,8 @@
let isEmpty = $state(!children); let isEmpty = $state(!children);
$effect(() => { $effect(() => {
const initialDoc = DOMParser.fromSchema(stateConfiguration.schema).parse(domEl); const initialDoc =
initialDocProp ?? DOMParser.fromSchema(stateConfiguration.schema).parse(domEl);
domEl.replaceChildren(); domEl.replaceChildren();
onDocChange?.(initialDoc); onDocChange?.(initialDoc);
isEmpty = initialDoc.textContent.length === 0; isEmpty = initialDoc.textContent.length === 0;

View file

@ -4,7 +4,7 @@ import { nodes as basicNodes, marks as basicMarks } from 'prosemirror-schema-bas
export const messageSchema = new Schema({ export const messageSchema = new Schema({
nodes: { nodes: {
doc: { content: 'block+' }, doc: { content: 'block+' },
paragragh: { paragraph: {
content: 'inline*', content: 'inline*',
...basicNodes.paragraph ...basicNodes.paragraph
}, },

View file

@ -1,5 +1,5 @@
import type { BlahRichText, BlahRichTextSpanAttributes } from '@blah-im/core/richText'; 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) { function isObjectEmpty(obj: object) {
for (const _ in obj) return false; for (const _ in obj) return false;
@ -58,3 +58,28 @@ export function proseMirrorDocToBlahRichText(doc: Node): BlahRichText {
return spans; 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);
}

View file

@ -13,9 +13,12 @@
import Button from '$lib/components/Button.svelte'; import Button from '$lib/components/Button.svelte';
import RichTextInput from '$lib/components/RichTextInput.svelte'; import RichTextInput from '$lib/components/RichTextInput.svelte';
import { messageSchema } from '$lib/components/RichTextInput/schema'; import { messageSchema } from '$lib/components/RichTextInput/schema';
import { blahRichTextToProseMirrorDoc } from '$lib/richText';
import type { Node } from 'prosemirror-model';
const currentAccount = $derived(accountsManager.currentAccount); const currentAccount = $derived(accountsManager.currentAccount);
let profile: BlahProfile | null = $state(null); let profile: BlahProfile | null = $state(null);
let initialBio: Node | null = $state(null);
let isBusy: boolean = $state(false); let isBusy: boolean = $state(false);
@ -23,21 +26,17 @@
if (currentAccount) { if (currentAccount) {
const snapshot = $state.snapshot(currentAccount.profile.signee.payload); const snapshot = $state.snapshot(currentAccount.profile.signee.payload);
profile = snapshot; profile = snapshot;
console.log('Reloaded'); initialBio = blahRichTextToProseMirrorDoc([snapshot.bio ?? ''], messageSchema);
} }
}); });
$inspect(profile);
async function saveProfile() { async function saveProfile() {
if (!currentAccount || !profile) return; if (!currentAccount || !profile) return;
console.log('Saving profile', $state.snapshot(profile));
isBusy = true; isBusy = true;
const identity = await accountsManager.identityForAccount(currentAccount); const identity = await accountsManager.identityForAccount(currentAccount);
await identity.updateProfile(profile); await identity.updateProfile(profile);
await accountsManager.saveIdentity(identity); await accountsManager.saveIdentity(identity);
console.log('Profile saved', identity.generateIdentityDescription());
isBusy = false; isBusy = false;
} }
</script> </script>
@ -67,9 +66,8 @@
schema={messageSchema} schema={messageSchema}
onDocChange={(doc) => profile && (profile.bio = doc.textContent)} onDocChange={(doc) => profile && (profile.bio = doc.textContent)}
placeholder="a 25 yo. artist from Paris." placeholder="a 25 yo. artist from Paris."
> initialDoc={initialBio}
{profile.bio ?? ''} />
</RichTextInput>
</GroupedListSection> </GroupedListSection>
</GroupedListContainer> </GroupedListContainer>
{/if} {/if}