mirror of
https://github.com/larvit/adf-codec.git
synced 2026-11-12 19:58:28 +01:00
Answer principles round 3: 0 🔴, 1 🟡 (0 misread); the carry walk's entries hold their code block, the linked image its own function, item 143 filed
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { isBlockNodeType, shownText, shownUrl } from './shown-text.ts'
|
||||
|
||||
test('shows a date as its UTC day, one before 1970 included', () => {
|
||||
assert.equal(shownText({ attrs: { timestamp: '1700000000000' }, type: 'date' }), '2023-11-14')
|
||||
assert.equal(shownText({ attrs: { timestamp: '-86400000' }, type: 'date' }), '1969-12-31')
|
||||
})
|
||||
|
||||
test('shows an emoji by its text, else by its short name where the text is blank', () => {
|
||||
assert.equal(shownText({ attrs: { shortName: ':smile:', text: '😄' }, type: 'emoji' }), '😄')
|
||||
assert.equal(shownText({ attrs: { shortName: ':smile:', text: ' ' }, type: 'emoji' }), ':smile:')
|
||||
})
|
||||
|
||||
test('shows a mention by its text, else by its id, and nothing for an empty id', () => {
|
||||
assert.equal(shownText({ attrs: { id: '5b10a2' }, type: 'mention' }), '@5b10a2')
|
||||
assert.equal(shownText({ attrs: { id: '' }, type: 'mention' }), undefined)
|
||||
})
|
||||
|
||||
test('shows an expand or a nested expand by its title', () => {
|
||||
assert.equal(shownText({ attrs: { title: 'Details' }, type: 'expand' }), 'Details')
|
||||
assert.equal(shownText({ attrs: { title: 'Details' }, type: 'nestedExpand' }), 'Details')
|
||||
})
|
||||
|
||||
test('links an external image to its URL, and a stored one to nothing', () => {
|
||||
assert.equal(shownUrl({ attrs: { type: 'external', url: 'https://example.com/a.png' }, type: 'media' }), 'https://example.com/a.png')
|
||||
assert.equal(shownUrl({ attrs: { type: 'file', url: 'https://example.com/a.png' }, type: 'mediaInline' }), undefined)
|
||||
})
|
||||
|
||||
test('counts a card standing apart as a block, as a block node is', () => {
|
||||
assert.equal(isBlockNodeType('blockCard'), true)
|
||||
assert.equal(isBlockNodeType('embedCard'), true)
|
||||
assert.equal(isBlockNodeType('paragraph'), true)
|
||||
assert.equal(isBlockNodeType('inlineCard'), false)
|
||||
})
|
||||
@@ -29,44 +29,32 @@ export function carryElement(tag: string, json: string, content: string): string
|
||||
// What a carry element shows, block by block, as the writer panels picked it (docs/decisions.md §The HTML dialect); a reader of the element reads its JSON alone.
|
||||
function shownBlocks(root: AdfNode, codeLineFeed: string): ShownBlock[] {
|
||||
const shown: ShownBlock[] = []
|
||||
// The blocks the walk is inside, innermost last; a `null` on `pending` closes the innermost.
|
||||
const blocks: AdfNode[] = []
|
||||
let html = ''
|
||||
const pending: (AdfNode | null)[] = [root]
|
||||
let current: ShownBlock = { code: undefined, html: '' }
|
||||
// Each entry holds the code block it stands in; a `null` node ends a block, after which the content stands in `code` again.
|
||||
const pending: { code: AdfNode | undefined; node: AdfNode | null }[] = [{ code: undefined, node: root }]
|
||||
for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
|
||||
const parts = next === null || partsShownText(next.type)
|
||||
if (parts && html !== '') shown.push({ code: innermostCode(blocks), html })
|
||||
if (parts) html = ''
|
||||
if (next === null) {
|
||||
blocks.pop()
|
||||
continue
|
||||
const { node } = next
|
||||
const code = node?.type === 'codeBlock' ? node : next.code
|
||||
if (node === null || partsShownText(node.type)) {
|
||||
if (current.html !== '') shown.push(current)
|
||||
current = { code, html: '' }
|
||||
}
|
||||
if (parts) {
|
||||
blocks.push(next)
|
||||
pending.push(null)
|
||||
}
|
||||
html += shownPiece(next, innermostCode(blocks) === undefined ? '<br>' : codeLineFeed)
|
||||
for (const child of [...nodeContent(next)].reverse()) pending.push(child)
|
||||
if (node === null) continue
|
||||
current = { ...current, html: current.html + shownPiece(node, current.code === undefined ? '<br>' : codeLineFeed) }
|
||||
if (partsShownText(node.type)) pending.push({ code: next.code, node: null })
|
||||
for (const child of [...nodeContent(node)].reverse()) pending.push({ code, node: child })
|
||||
}
|
||||
if (html !== '') shown.push({ code: undefined, html })
|
||||
if (current.html !== '') shown.push(current)
|
||||
return shown
|
||||
}
|
||||
|
||||
function innermostCode(blocks: readonly AdfNode[]): AdfNode | undefined {
|
||||
const innermost = blocks.at(-1)
|
||||
return innermost?.type === 'codeBlock' ? innermost : undefined
|
||||
}
|
||||
|
||||
// The node's own part of what its carry element shows: its text, what it shows in its place, or a note naming what it references, linked to its link mark's target, else to the URL a card or an image links to; an image a link to another target marks is followed by a link to it.
|
||||
function shownPiece(node: AdfNode, lineFeed: string): string {
|
||||
if (node.type === 'hardBreak') return '<br>'
|
||||
const mark = nodeMarks(node).find((held) => held.type === 'link')
|
||||
const target = mark === undefined ? undefined : linkAttrs(nodeAttrs(mark))
|
||||
const url = shownUrl(node)
|
||||
if (['media', 'mediaInline'].includes(node.type) && target !== undefined && url !== undefined && target.href !== url) {
|
||||
const linkText = visibleText(nodeAttrs(node)['alt']) ?? visibleText(target.href) ?? url
|
||||
return `${linked(shownValue(linkText, lineFeed), target)} (${linked('image', { href: url })})`
|
||||
}
|
||||
if (['media', 'mediaInline'].includes(node.type) && target !== undefined && url !== undefined && target.href !== url) return linkedImage(node, target, url, lineFeed)
|
||||
const link = target ?? (url === undefined ? undefined : { href: url })
|
||||
const text = node.text ?? shownText(node)
|
||||
if (text !== undefined) return linked(shownValue(text, lineFeed), link)
|
||||
@@ -74,6 +62,12 @@ function shownPiece(node: AdfNode, lineFeed: string): string {
|
||||
return name === undefined ? '' : linked(`<em>(${shownValue(name, lineFeed)})</em>`, link)
|
||||
}
|
||||
|
||||
// The image's alt, else the link's target, else its URL, linked to the target, then a link to the image, as portable markdown writes it inline.
|
||||
function linkedImage(media: AdfNode, target: LinkAttrs, url: string, lineFeed: string): string {
|
||||
const text = visibleText(nodeAttrs(media)['alt']) ?? visibleText(target.href) ?? url
|
||||
return `${linked(shownValue(text, lineFeed), target)} (${linked('image', { href: url })})`
|
||||
}
|
||||
|
||||
// The shown HTML inside an `<a>` where it links and HTML can hold the link.
|
||||
function linked(shown: string, link: LinkAttrs | undefined): string {
|
||||
const anchor = link === undefined ? undefined : anchorOpener(link)
|
||||
|
||||
Reference in New Issue
Block a user