import { formatNumber, t } from '@/i18n' import type { BuiltinField } from '@/lib/contentTypes' import { fieldTypeSpec, type FieldCellKind } from '@/lib/fieldTypes' import { formatDayValue } from '@/lib/entryValue' import { isMissingImage, type MediaSummary } from '@/lib/mediaField' import { isMissingRelation, relationIdsOf, type RelationSummary } from '@/lib/relationField' import { formatAbsoluteDateFromIso } from '@/lib/relativeDate' export { formatDayValue } export type CellKind = 'empty' | FieldCellKind export interface RelationChip { id: number label: string missing: boolean } export interface CellView { kind: CellKind text: string yes?: boolean image?: MediaSummary chips?: RelationChip[] more?: number } export const RELATION_CHIPS_SHOWN = 2 export const EMPTY_TEXT = '–' const EMPTY: CellView = { kind: 'empty', text: '' } const LINE_PREFIX = /^\s{0,3}(?:#{1,6}(?:\s+|$)|>\s?|[-*+]\s+|\d+[.)]\s+)+/ const IMAGE_OR_LINK = /!?\[([^\]]*)\]\([^)]*\)/g const EMPHASIS = /(\*\*|__|\*|`|~~)/g const UNDERSCORE_EMPHASIS = /(^|[\s(])_(?=\S)([^_]*?\S)_(?=$|[\s).,;:!?])/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, '') .replace(UNDERSCORE_EMPHASIS, '$1$2') .trim() if (plain) { return plain } } return '' } function thumbnailCell(value: unknown, image: MediaSummary | undefined): CellView { const shown = image ?? { id: Number(value), missing: true as const } return { kind: 'thumbnail', text: isMissingImage(shown) ? '' : shown.filename, image: shown, } } function summaryField(first: { key: string; type: string }): BuiltinField { return { key: first.key, label: '', type: first.type, required: false, unique: false, default: null, min: null, max: null, options: null, } } export function relationLabel(summary: RelationSummary) { if (isMissingRelation(summary)) { return t('Entry no longer exists') } const first = summary.first_field const cell = describeCell(summaryField(first), first.value) return cell.kind === 'empty' || cell.kind === 'thumbnail' || cell.kind === 'relation' ? t('Entry {0}', [summary.id]) : plainCellText(cell) } function relationCell(value: unknown, summaries: RelationSummary[]): CellView { const ids = relationIdsOf(value) if (ids.length === 0) { return EMPTY } const chips = ids.map((id): RelationChip => { const summary = summaries.find((candidate) => candidate.id === id) ?? { id, missing: true as const, } return { id, label: relationLabel(summary), missing: isMissingRelation(summary) } }) return { kind: 'relation', text: chips.map((chip) => chip.label).join(', '), chips: chips.slice(0, RELATION_CHIPS_SHOWN), more: Math.max(0, chips.length - RELATION_CHIPS_SHOWN), } } export function describeCell( field: BuiltinField, value: unknown, image?: MediaSummary, relations: RelationSummary[] = [], ): CellView { if (value === undefined || value === null || value === '') { return EMPTY } const kind = fieldTypeSpec(field.type).cell switch (kind) { case 'thumbnail': return thumbnailCell(value, image) case 'relation': return relationCell(value, relations) case 'boolean': return { kind, text: '', yes: value === true } case 'number': return { kind, text: typeof value === 'number' ? formatNumber(value) : String(value) } case 'date': return { kind, text: formatDayValue(String(value)) } case 'choice': return { kind, text: String(value) } case 'richtext': { const line = richTextFirstLine(String(value)) return line ? { kind, text: line } : EMPTY } default: return { kind, 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 || EMPTY_TEXT } export interface CardFields { first?: BuiltinField second?: BuiltinField choice?: BuiltinField } function isThumbnail(field: BuiltinField) { return fieldTypeSpec(field.type).cell === 'thumbnail' } export function thumbnailColumn(columns: BuiltinField[]) { return columns.find(isThumbnail) } export function cardFields(fields: BuiltinField[]): CardFields { const [first, ...rest] = fields const second = rest.find((field) => !isThumbnail(field)) const choice = fields.find((field) => field.type === 'select') return { first, second, choice: choice === first ? undefined : choice } }