feat: entry list at /content/:key

This commit is contained in:
Florian Sylvain committed 2026-10-20 21:05:03 +02:00
1 parent b4cbcb0395
commit 91d916fb74
44 files changed
+4984 -32

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
import { formatNumber, t } from '@/i18n'
import type { BuiltinField } from '@/lib/contentTypes'
import { formatAbsoluteDate, formatAbsoluteDateFromIso } from '@/lib/relativeDate'
export type CellKind = 'empty' | 'text' | 'richtext' | 'number' | 'boolean' | 'date' | 'choice'
export interface CellView {
kind: CellKind
text: string
yes?: boolean
}
export const EMPTY_TEXT = '–'
const EMPTY: CellView = { kind: 'empty', text: '' }
const ISO_DAY = /^(\d{4})-(\d{2})-(\d{2})/
const LINE_PREFIX = /^\s{0,3}(?:#{1,6}(?:\s+|$)|>\s?|[-*+]\s+|\d+[.)]\s+)+/
const IMAGE_OR_LINK = /!?\[([^\]]*)\]\([^)]*\)/g
const EMPHASIS = /(\*\*|__|\*|`|~~)/g
export function richTextFirstLine(markdown: string) {
for (const line of markdown.split(/\r?\n/)) {
const plain = line
.replace(LINE_PREFIX, '')
.replace(IMAGE_OR_LINK, '$1')
.replace(EMPHASIS, '')
.trim()
if (plain) {
return plain
}
}
return ''
}
export function formatDayValue(value: string) {
const match = ISO_DAY.exec(value)
if (!match) {
return value
}
return formatAbsoluteDate(new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])))
}
export function describeCell(field: BuiltinField, value: unknown): CellView {
if (value === undefined || value === null || value === '') {
return EMPTY
}
switch (field.type) {
case 'boolean':
return { kind: 'boolean', text: '', yes: value === true }
case 'number':
return typeof value === 'number'
? { kind: 'number', text: formatNumber(value) }
: { kind: 'number', text: String(value) }
case 'date':
return { kind: 'date', text: formatDayValue(String(value)) }
case 'select':
return { kind: 'choice', text: String(value) }
case 'richtext': {
const line = richTextFirstLine(String(value))
return line ? { kind: 'richtext', text: line } : EMPTY
}
default:
return { kind: 'text', text: String(value) }
}
}
export function formatEntryDate(iso: string) {
return formatAbsoluteDateFromIso(iso)
}
export function isEmptyCell(cell: CellView) {
return cell.kind === 'empty'
}
export function plainCellText(cell: CellView) {
if (cell.kind === 'empty') {
return EMPTY_TEXT
}
if (cell.kind === 'boolean') {
return cell.yes ? t('Yes') : t('No')
}
return cell.text
}
export interface CardFields {
first?: BuiltinField
second?: BuiltinField
choice?: BuiltinField
}
export function cardFields(fields: BuiltinField[]): CardFields {
const [first, second] = fields
const choice = fields.find((field) => field.type === 'select')
return { first, second, choice: choice === first ? undefined : choice }
}