From 54556104327967bb33c1e8130d65646b4b92b703 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 28 Nov 2024 23:47:24 +0000 Subject: [PATCH] feat(#362): add conversation quote style --- lib/shortcodes.js | 58 +++++++++++++++++++++++ src/_styles/_root.css | 104 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/lib/shortcodes.js b/lib/shortcodes.js index 5fa37c3b..cc4d11a8 100644 --- a/lib/shortcodes.js +++ b/lib/shortcodes.js @@ -19,6 +19,7 @@ export const figure = (image, caption, alt, className) => { return `${imgMarkup}${captionMarkup}`; }; +// TODO: Update this to wrap the blockquote in a figure export const blockquote = (content, cite, via, url) => { let footer = ''; @@ -109,9 +110,66 @@ export const buttonBoard = (items) => { return ``; } +/** + * The format for a conversation is as follows: + * + * > a: Person A is saying these words + * > b: Person B is saying these words + * > Person B is also saying these words + * + * The conversation always begins with person A unless set by the `b:` prefix. Any lines that do not begin with a `>` + * will be ignored. + * + * @param {string} content + * @return {string} + */ +export const conversation = (content) => { + const {lines} = [...content.trim().matchAll(/> (([ab]:) (.+)|(.+))/gm)] + .reduce((carry, match) => { + // match[2] is the voice (either a: or b:) + // match[3] contains the line if voice is set else match[4] + if (typeof match[2] !== 'undefined') carry.voice = match[2]; + const line = (typeof match[2] === 'undefined') + ? match[4] + : match[3]; + + const classList= carry.voice === 'a:' + ? ['from-me'] + : ['from-them']; + + carry.lines.push({ + classList, + voice: carry.voice, + line: markdown().renderInline(line).trim(), + }); + + return carry; + }, {lines: [], voice: 'a:'}); + + for (let i = 0; i < lines.length; i++) { + const pLine = lines[i-1] ?? undefined; + const line = lines[i]; + + if (typeof pLine === 'undefined') continue; + + if (pLine.voice === line.voice) { + // If the previous line has the same voice as this line, remove its tail. Only the last + // message by the same voice has a tail. + pLine.classList.push('no-tail'); + } + } + + const innerHtml = lines.map(({line, classList}) => { + return `

${line}

` + }).join(' '); + + return `
${innerHtml}
`; +}; + export const registerShortcodes = (eleventyConfig) => { eleventyConfig.addShortcode('figure', figure); eleventyConfig.addAsyncShortcode('image', async () => 'TODO'); eleventyConfig.addPairedShortcode('blockquote', blockquote); + eleventyConfig.addPairedShortcode('conversation', conversation); eleventyConfig.addShortcode('buttonBoard', buttonBoard); }; diff --git a/src/_styles/_root.css b/src/_styles/_root.css index ec0b54b5..1c8b3e54 100644 --- a/src/_styles/_root.css +++ b/src/_styles/_root.css @@ -97,6 +97,110 @@ figure blockquote + figcaption { margin-inline-end: 2.5em; } +figure.conversation { + background-color: #fff; + border: 1px solid #e5e5ea; + border-radius: 0.25rem; + display: flex; + flex-direction: column; + font-size: 1.25rem; + margin: 0 auto 1rem; + max-width: 83%; + padding: 0.5rem 1.5rem; + + p { + border-radius: 1.15rem; + line-height: 1.25; + max-width: 75%; + padding: 0.5rem .875rem; + position: relative; + word-wrap: break-word; + font-family: sans-serif; + font-size: 1rem; + } + p::before, + p::after { + bottom: -0.1rem; + content: ""; + height: 1rem; + position: absolute; + } + + p.from-me { + align-self: flex-end; + background-color: #248bf5; + color: #fff; + } + + p.from-me::before { + border-bottom-left-radius: 0.8rem 0.7rem; + border-right: 1rem solid #248bf5; + right: -0.35rem; + transform: translate(0, -0.1rem); + } + + p.from-me::after { + background-color: #fff; + border-bottom-left-radius: 0.5rem; + right: -40px; + transform: translate(-30px, -2px); + width: 10px; + } + + p[class^="from-"] { + margin: 0.5rem 0; + width: fit-content; + } + + p[class^="from-"].no-tail { + margin-bottom: 0; + } + + p.from-me ~ p.from-me, + p.from-them ~ p.from-them { + margin: 0.25rem 0 0; + } + + p.from-me ~ p.from-me:not(:last-child), + p.from-them ~ p.from-them:not(:last-child){ + margin: 0.25rem 0 0; + } + + p.from-me ~ p.from-me:last-child, + p.from-them ~ p.from-them:last-child{ + margin-bottom: 0.5rem; + } + + p.from-them { + align-items: flex-start; + background-color: #e5e5ea; + color: #000; + } + + p.from-them:before { + border-bottom-right-radius: 0.8rem 0.7rem; + border-left: 1rem solid #e5e5ea; + left: -0.35rem; + transform: translate(0, -0.1rem); + } + + p.from-them::after { + background-color: #fff; + border-bottom-right-radius: 0.5rem; + left: 20px; + transform: translate(-30px, -2px); + width: 10px; + } + + .no-tail::before { + display: none; + } + + p.from-me + p.from-them{ + margin-top: 1rem !important; + } +} + blockquote { display: block; margin-block-start: 1em;