fix: rich text rendering

This commit is contained in:
Shibo Lyu 2024-09-02 01:20:01 +08:00
parent 1bbc149536
commit 29fa1988d4
4 changed files with 54 additions and 37 deletions

View file

@ -2,6 +2,7 @@
import type { BlahRichText } from '$lib/richText'; import type { BlahRichText } from '$lib/richText';
import { tw } from '$lib/tw'; import { tw } from '$lib/tw';
import RichTextSpan from './RichTextRenderer/RichTextSpan.svelte'; import RichTextSpan from './RichTextRenderer/RichTextSpan.svelte';
import PlainTextRenderer from './RichTextRenderer/PlainTextRenderer.svelte';
export let content: BlahRichText; export let content: BlahRichText;
let className = ''; let className = '';
@ -9,20 +10,14 @@
</script> </script>
<div class={tw('rich-text', className)}> <div class={tw('rich-text', className)}>
{#each content as block} <p>
<p> {#each content as span}
{#each block as span} {#if typeof span === 'string'}
{#if typeof span === 'string'} <PlainTextRenderer text={span} />
{#if span === ''} {:else}
<br /> {@const [text, attributes] = span}
{:else} <RichTextSpan {text} {attributes} />
{span} {/if}
{/if} {/each}
{:else} </p>
{@const [text, attributes] = span}
<RichTextSpan {text} {attributes} />
{/if}
{/each}
</p>
{/each}
</div> </div>

View file

@ -0,0 +1,10 @@
<script lang="ts">
export let text: string;
</script>
{#each text.split('\n') as segment, idx}
{#if idx > 0}
<br />
{/if}
{segment}
{/each}

View file

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { BlahRichTextSpanAttributes } from '$lib/richText'; import type { BlahRichTextSpanAttributes } from '$lib/richText';
import PlainTextRenderer from './PlainTextRenderer.svelte';
// From outside to inside, better align this with the RichTextInput // From outside to inside, better align this with the RichTextInput
const renderOrder: (keyof BlahRichTextSpanAttributes)[] = [ const renderOrder: (keyof BlahRichTextSpanAttributes)[] = [
@ -30,8 +31,10 @@
const nextAttribute = attribute ? (renderOrder[renderOrder.indexOf(attribute) + 1] ?? '') : null; const nextAttribute = attribute ? (renderOrder[renderOrder.indexOf(attribute) + 1] ?? '') : null;
</script> </script>
{#if attribute === '' || !attributes[attribute]} {#if attribute === ''}
{text} <PlainTextRenderer {text} />
{:else if !attributes[attribute]}
<svelte:self {...$$props} attribute={nextAttribute} />
{:else if attribute === 'link'} {:else if attribute === 'link'}
<a href={attributes.link} target="_blank"> <a href={attributes.link} target="_blank">
<svelte:self {...$$props} attribute={nextAttribute} /> <svelte:self {...$$props} attribute={nextAttribute} />

View file

@ -1,3 +1,4 @@
import canonicalize from 'canonicalize';
import type { AttributeMap, Delta } from 'typewriter-editor'; import type { AttributeMap, Delta } from 'typewriter-editor';
import { z } from 'zod'; import { z } from 'zod';
@ -18,10 +19,7 @@ export const blahRichTextSpanSchema = z.union([
]); ]);
export type BlahRichTextSpan = z.input<typeof blahRichTextSpanSchema>; export type BlahRichTextSpan = z.input<typeof blahRichTextSpanSchema>;
export const blahRichTextBlockSchema = z.array(blahRichTextSpanSchema); export const blahRichTextSchema = z.array(blahRichTextSpanSchema);
export type BlahRichTextBlock = z.input<typeof blahRichTextBlockSchema>;
export const blahRichTextSchema = z.array(blahRichTextBlockSchema);
export type BlahRichText = z.input<typeof blahRichTextSchema>; export type BlahRichText = z.input<typeof blahRichTextSchema>;
function isObjectEmpty(obj: object) { function isObjectEmpty(obj: object) {
@ -48,23 +46,34 @@ function deltaAttributesToBlahRichTextSpanAttributes(
} }
export function deltaToBlahRichText(delta: Delta): BlahRichText { export function deltaToBlahRichText(delta: Delta): BlahRichText {
const blocks: BlahRichText = []; const spans: BlahRichText = [];
let block: BlahRichTextBlock = []; let lastText = '';
for (const op of delta.ops) { let lastAttributes: BlahRichTextSpanAttributes | null = null;
const lines = op.insert?.split('\n'); let canonicalizedLastAttributes: string = 'null';
if (!lines) continue;
const attributes = deltaAttributesToBlahRichTextSpanAttributes(op.attributes);
const line = lines.shift(); function commitSpan() {
block.push(attributes ? [line, attributes] : line); spans.push(lastAttributes === null ? lastText : [lastText, lastAttributes]);
for (const line of lines) {
blocks.push(block);
block = [];
block.push(attributes ? [line, attributes] : line);
}
} }
return blocks; for (const op of delta.ops) {
// Not sure in what cases op.insert would not be a string, but let's be safe
if (typeof op.insert !== 'string') continue;
const attributes = deltaAttributesToBlahRichTextSpanAttributes(op.attributes);
const canonicalizedAttributes = canonicalize(attributes) ?? 'null';
if (canonicalizedAttributes === canonicalizedLastAttributes) {
lastText += op.insert;
continue;
}
commitSpan();
lastText = op.insert;
lastAttributes = attributes;
canonicalizedLastAttributes = canonicalizedAttributes;
}
commitSpan();
return spans;
} }