import type { Kysely } from 'kysely'; import type { Database, FieldRow } from '../db/schema.js'; /** One social account. `platform` is a label, not an enum — see `SOCIAL_PRESETS`. */ export interface SocialProfile { platform: string; url: string; } export interface SiteSettings { name: string | null; titleTemplate: string | null; seoDescription: string | null; seoOgImageId: string | null; seoNoIndex: boolean; social: SocialProfile[]; /** Definitions in `repeaterSubField`'s shape, ready for `synthesiseFields`. */ extraFields: unknown[]; /** One row of answers to those definitions, keyed by `api_id`. */ extraValues: Record; } /** * The platforms the screen offers by name, in the order it offers them. * * **Suggestions, not a vocabulary.** Storage is `[{ platform, url }]`, so an institution on * something nobody listed adds it by typing a name — the same arrangement the `embed` field's host * presets have, where the presets save typing and the allowlist is still whatever was chosen. A * fixed enum here would date the schema, and dating it would mean a migration to say a college is on * a new service. */ export declare const SOCIAL_PRESETS: readonly ["Instagram", "Facebook", "X", "YouTube", "LinkedIn", "TikTok", "Bluesky", "Threads"]; export declare const MAX_SITE_NAME = 120; export declare const MAX_TITLE_TEMPLATE = 200; export declare const MAX_SEO_DESCRIPTION = 500; /** Enough for every platform anybody lists and a bound on what the row will carry. */ export declare const MAX_SOCIAL_PROFILES = 20; /** What every column reads as before anybody has been to the screen. */ export declare const EMPTY_SITE_SETTINGS: SiteSettings; export declare function getSiteSettings(db: Kysely): Promise; /** The admin-defined fields as `FieldRow`s, ready for `FieldControl` and `validateItemData`. */ export declare function siteExtraFields(settings: SiteSettings): FieldRow[]; export declare class SiteSettingsError extends Error { } export interface SiteSettingsInput { name?: string | null; titleTemplate?: string | null; seoDescription?: string | null; seoOgImageId?: string | null; seoNoIndex?: boolean; social?: SocialProfile[]; extraFields?: unknown[]; extraValues?: Record; } /** * Write the site-wide half of the one settings row. * * An upsert rather than a read-then-branch, for the reason `updateBranding` records: two requests * arriving together would both find no row and both insert, and the loser of that race is a * primary-key violation on a screen that was doing nothing unusual. * * Every key is optional and absent means "leave it", so a screen sending only what it renders cannot * blank what it does not — the same `undefined`/`null` distinction `updateItem` makes for * `publish_at`, and for the same reason: `??` collapses them and silently ignores a request to clear. */ export declare function updateSiteSettings(db: Kysely, input: SiteSettingsInput, actorId: string | null): Promise; /** * A page's title with the site's template applied. * * Exported because both `resolveSeo` and the settings screen's live preview need it, and a second * copy of "where does `%s` go" is how a preview starts lying about what a visitor will see. * * A template with no `%s` is treated as having none at all rather than as a title of its own: an * admin halfway through typing one must not blank every page's title while they think about it. */ export declare function applyTitleTemplate(title: string, template: string | null | undefined): string;