From f00ed4eaf1386e03126a61a2a23e1c96bb6b2a90 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 29 Dec 2023 18:39:08 -0400 Subject: html: metatag('blockwrap') --- src/util/html.js | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) (limited to 'src/util/html.js') diff --git a/src/util/html.js b/src/util/html.js index 7a73e567..08d03bc7 100644 --- a/src/util/html.js +++ b/src/util/html.js @@ -44,6 +44,15 @@ export const joinChildren = Symbol(); // or when there are multiple children. export const noEdgeWhitespace = Symbol(); +// Don't pass this directly, use html.metatag('blockwrap') instead. +// Causes *following* content (past the metatag) to be placed inside a span +// which is styled 'inline-block', which ensures that the words inside the +// metatag all stay together, line-breaking only if needed, and following +// text is displayed immediately after the last character of the last line of +// the metatag (provided there's room on that line for the following word or +// character). +export const blockwrap = Symbol(); + // Note: This is only guaranteed to return true for blanks (as returned by // html.blank()) and false for Tags and Templates (regardless of contents or // other properties). Don't depend on this to match any other values. @@ -182,6 +191,31 @@ export function tags(content, attributes = null) { return new Tag(null, attributes, content); } +export function metatag(identifier, ...args) { + let content; + let opts = {}; + + if ( + typeof args[0] === 'object' && + !(Array.isArray(args[0]) || + args[0] instanceof Tag || + args[0] instanceof Template) + ) { + opts = args[0]; + content = args[1]; + } else { + content = args[0]; + } + + switch (identifier) { + case 'blockwrap': + return new Tag(null, {[blockwrap]: true}, content); + + default: + throw new Error(`Unknown metatag "${identifier}"`); + } +} + export function normalize(content) { return Tag.normalize(content); } @@ -285,7 +319,10 @@ export class Tag { } get contentOnly() { - return this.tagName === '' && this.attributes.blank; + if (this.tagName !== '') return false; + if (!this.attributes.blank) return false; + if (this.blockwrap) return false; + return true; } #setAttributeFlag(attribute, value) { @@ -346,6 +383,14 @@ export class Tag { return this.#getAttributeFlag(noEdgeWhitespace); } + set blockwrap(value) { + this.#setAttributeFlag(blockwrap, value); + } + + get blockwrap() { + return this.#getAttributeFlag(blockwrap); + } + toString() { const attributesString = this.attributes.toString(); const contentString = this.content.toString(); @@ -407,6 +452,7 @@ export class Tag { : `\n${this.joinChildren}\n`); let content = ''; + let blockwrapClosers = ''; for (const [index, item] of this.content.entries()) { let itemContent; @@ -429,9 +475,19 @@ export class Tag { content += joiner; } + // Blockwraps only apply if they actually contain some content whose + // words should be kept together, so it's okay to put them beneath the + // itemContent check. + if (item instanceof Tag && item.blockwrap) { + content += ``; + blockwrapClosers += ``; + } + content += itemContent; } + content += blockwrapClosers; + return content; } -- cgit 1.3.0-6-gf8a5