You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/components/words/engine/serialize-html.ts

82 lines
2.3 KiB

fix(password-field): align input chrome with system + add Form/SIUM demo Style fix — input chrome was hand-rolled with raw color tokens (`--color-neutral-border`, `--ring-width`) instead of consuming the recipe layer like every other text input. Now matches search-field and number-field exactly: - Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts` with the same surface as `search-field` (height, px, gap, font-size per size; border, focus, invalid, bg, color, focus-shadow tokens). - Regenerated `src/uix/eidos/generated/base.css`. - Rewrote `password-field.css` to consume the new tokens — same focus ring (`focus-shadow`), same border colors, same disabled opacity, same transition tokens. VisibilityTrigger gets its own border/bg/ color-on-hover scheme aligned with search-field's clear button. - Added per-size icon + caps font-size tokens so the trigger and caps pill scale with the input. Form + SIUM demo — answers "how does PasswordField validate?": Added a new subsection in the Live tab with three working forms wired through `<Form>` + `<Field>` and SIUM schemas: 1. Login (purpose='current', non-empty password) 2. Registration (purpose='new', complexity rules via regex pipes — uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter as a companion to the schema) 3. Password change (3 fields with a cross-field `refine()` so new === confirm; demonstrates SIUM's refine pattern) The section explicitly documents that PasswordField has no built-in complexity rules — validation is schema-first. The schema decides what's "correct"; PasswordField's flags propagate via Field's OR-merge of `formFieldState.isInvalid`. svelte-check: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import {
getInlineText,
type WordsBlock,
type WordsDocument,
type WordsInline,
type WordsMark
} from './document';
import { sanitizeWordsUrl } from './normalize';
const MARK_TAGS = {
bold: 'strong',
italic: 'em',
underline: 'u',
strike: 's',
code: 'code'
} as const satisfies Record<WordsMark, string>;
export function serializeWordsHtml(document: WordsDocument): string {
return document.children.map(serializeBlockHtml).join('');
}
function serializeBlockHtml(block: WordsBlock): string {
if (block.type === 'list') {
const tag = block.kind === 'ordered' ? 'ol' : 'ul';
const children = block.children
.map((item) => {
const checkbox =
block.kind === 'check'
? `<input type="checkbox"${item.checked === true ? ' checked' : ''} disabled> `
: '';
return `<li>${checkbox}${serializeInlinesHtml(item.children)}</li>`;
})
.join('');
return `<${tag}>${children}</${tag}>`;
}
if (block.type === 'heading') {
return `<h${block.level}>${serializeInlinesHtml(block.children)}</h${block.level}>`;
}
if (block.type === 'quote') {
return `<blockquote>${serializeInlinesHtml(block.children)}</blockquote>`;
}
return `<p>${serializeInlinesHtml(block.children)}</p>`;
}
function serializeInlinesHtml(inlines: readonly WordsInline[]): string {
return inlines.map(serializeInlineHtml).join('');
}
function serializeInlineHtml(inline: WordsInline): string {
if (inline.type === 'link') {
const href = sanitizeWordsUrl(inline.href);
if (!href) return escapeHtml(inline.children.map(getInlineText).join(''));
const title = inline.title ? ` title="${escapeHtmlAttr(inline.title)}"` : '';
return `<a href="${escapeHtmlAttr(href)}"${title}>${serializeInlinesHtml(inline.children)}</a>`;
}
return wrapMarks(escapeHtml(inline.text), inline.marks ?? []);
}
function wrapMarks(value: string, marks: readonly WordsMark[]): string {
return marks.reduce((html, mark) => {
const tag = MARK_TAGS[mark];
return `<${tag}>${html}</${tag}>`;
}, value);
}
function escapeHtml(value: string): string {
return value
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function escapeHtmlAttr(value: string): string {
return escapeHtml(value).replace(/`/g, '&#96;');
}

Powered by TurnKey Linux.