Files
RenewCMS/web/src/lib/entryCells.ts
T

177 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }
}