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'> {
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;

View file

@ -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
},

View file

@ -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);
}

View file

@ -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;
}
</script>
@ -67,9 +66,8 @@
schema={messageSchema}
onDocChange={(doc) => profile && (profile.bio = doc.textContent)}
placeholder="a 25 yo. artist from Paris."
>
{profile.bio ?? ''}
</RichTextInput>
initialDoc={initialBio}
/>
</GroupedListSection>
</GroupedListContainer>
{/if}