diff options
-rw-r--r-- | src/static/site6.css | 4 | ||||
-rw-r--r-- | src/util/html.js | 58 |
2 files changed, 61 insertions, 1 deletions
diff --git a/src/static/site6.css b/src/static/site6.css index c7587971..bf9bc2cc 100644 --- a/src/static/site6.css +++ b/src/static/site6.css @@ -473,6 +473,10 @@ a:not([href]):hover { white-space: nowrap; } +.blockwrap { + display: inline-block; +} + .contribution.has-tooltip, .datetimestamp.has-tooltip { position: relative; 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 += `<span class="blockwrap">`; + blockwrapClosers += `</span>`; + } + content += itemContent; } + content += blockwrapClosers; + return content; } |