diff --git a/server/utils/sanity/blocks.ts b/server/utils/sanity/blocks.ts index 4c735348..e747c9e4 100644 --- a/server/utils/sanity/blocks.ts +++ b/server/utils/sanity/blocks.ts @@ -69,6 +69,11 @@ export function resolveTextWithFacets (blocks: PortableTextBlock[], footer?: str continue } + // Add separator if this isn't the first block + if (fullText) { + fullText += '\n\n' + } + let blockText = '' for (const child of block.children.filter(child => child._type === 'span')) { let text = child.text || '' @@ -127,9 +132,6 @@ export function resolveTextWithFacets (blocks: PortableTextBlock[], footer?: str blockText += text } - if (fullText && blockText) { - fullText += '\n\n' - } fullText += blockText } diff --git a/test/unit/sanity.spec.ts b/test/unit/sanity.spec.ts index fec964dd..19622682 100644 --- a/test/unit/sanity.spec.ts +++ b/test/unit/sanity.spec.ts @@ -99,6 +99,55 @@ const linkTestBlocks = [ }, ] +const emojiLinkTestBlocks = [ + { + _key: 'emoji-block', + _type: 'block', + children: [ + { + _key: 'text-with-emoji', + _type: 'span', + marks: [], + text: 'it\'s not difficult to clean up the _very few_ messages I get that aren\'t genuine 🤷', + }, + ], + markDefs: [], + style: 'normal', + }, + { + _key: 'link-block', + _type: 'block', + children: [ + { + _key: 'text-before-link', + _type: 'span', + marks: [], + text: 'you can see the code on ', + }, + { + _key: 'link-text', + _type: 'span', + marks: ['link-key'], + text: 'my website', + }, + { + _key: 'text-after-link', + _type: 'span', + marks: [], + text: ' ....and even make a PR if you have an idea of a better approach...', + }, + ], + markDefs: [ + { + _key: 'link-key', + _type: 'link', + href: 'https://github.com/danielroe/roe.dev/blob/main/server/api/question.ts', + }, + ], + style: 'normal', + }, +] + describe('mentions', () => { const testCases = { bluesky: '@nuxt.com', @@ -237,4 +286,43 @@ describe('link handling for different platforms', () => { }], }) }) + + it('should handle emojis correctly in byte offset calculation', () => { + const result = resolveTextWithFacets(emojiLinkTestBlocks) + + // The text should be: "it's not difficult to clean up the _very few_ messages I get that aren't genuine 🤷\n\nyou can see the code on my website ....and even make a PR if you have an idea of a better approach..." + // The emoji 🤷 is 4 bytes in UTF-8 + // First block: "it's not difficult to clean up the _very few_ messages I get that aren't genuine 🤷" (83 bytes) + // Then "\n\n" (2 bytes) + // Second block starts at byte 85 + // "you can see the code on " (25 bytes) + // Link "my website" should start at byte 85 + 25 = 110 + const expectedText = 'it\'s not difficult to clean up the _very few_ messages I get that aren\'t genuine 🤷\n\nyou can see the code on my website ....and even make a PR if you have an idea of a better approach...' + + expect(result.text).toEqual(expectedText) + expect(result.facets).toHaveLength(1) + + // Calculate expected byte positions + const firstBlockText = 'it\'s not difficult to clean up the _very few_ messages I get that aren\'t genuine 🤷' + const firstBlockBytes = Buffer.byteLength(firstBlockText, 'utf8') + const newlineBytes = Buffer.byteLength('\n\n', 'utf8') + const beforeLinkText = 'you can see the code on ' + const beforeLinkBytes = Buffer.byteLength(beforeLinkText, 'utf8') + const linkText = 'my website' + const linkBytes = Buffer.byteLength(linkText, 'utf8') + + const expectedByteStart = firstBlockBytes + newlineBytes + beforeLinkBytes + const expectedByteEnd = expectedByteStart + linkBytes + + expect(result.facets[0]).toEqual({ + index: { + byteStart: expectedByteStart, + byteEnd: expectedByteEnd, + }, + features: [{ + $type: 'app.bsky.richtext.facet#link', + uri: 'https://github.com/danielroe/roe.dev/blob/main/server/api/question.ts', + }], + }) + }) })