72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
|
|
/**
|
||
|
|
* TipTap content validation helpers shared by the admin template editor
|
||
|
|
* and the document-template service. Surfaces unsupported node types so
|
||
|
|
* the admin gets a clear validation error before saving, and exports the
|
||
|
|
* `TEMPLATE_VARIABLES` catalog so the editor can offer token suggestions.
|
||
|
|
*
|
||
|
|
* Note: this file was the remaining sliver of the legacy 571-line
|
||
|
|
* `tiptap-to-pdfme.ts` bridge. The pdfme rendering path it once fed has
|
||
|
|
* been removed (see the PDF stack overhaul spec); only the validation +
|
||
|
|
* variable catalog survive because they're still useful for the
|
||
|
|
* Documenso-template-body editor UX.
|
||
|
|
*/
|
||
|
|
|
||
|
|
export interface TipTapMark {
|
||
|
|
type: string;
|
||
|
|
attrs?: Record<string, unknown>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface TipTapNode {
|
||
|
|
type: string;
|
||
|
|
content?: TipTapNode[];
|
||
|
|
text?: string;
|
||
|
|
marks?: TipTapMark[];
|
||
|
|
attrs?: Record<string, unknown>;
|
||
|
|
}
|
||
|
|
|
||
|
|
const UNSUPPORTED_NODES = new Set([
|
||
|
|
'blockquote',
|
||
|
|
'codeBlock',
|
||
|
|
'horizontalRule',
|
||
|
|
'taskList',
|
||
|
|
'taskItem',
|
||
|
|
]);
|
||
|
|
|
||
|
|
export const TEMPLATE_VARIABLES: Array<{ key: string; label: string; example: string }> = [
|
||
|
|
{ key: 'client.name', label: 'Client Full Name', example: 'John Smith' },
|
||
|
|
{ key: 'client.company', label: 'Company Name', example: 'Smith Holdings' },
|
||
|
|
{ key: 'client.email', label: 'Client Email', example: 'john@smithholdings.com' },
|
||
|
|
{ key: 'client.phone', label: 'Client Phone', example: '+61 400 000 000' },
|
||
|
|
{ key: 'interest.stage', label: 'Pipeline Stage', example: 'Signed EOI/NDA' },
|
||
|
|
{ key: 'interest.berthNumber', label: 'Berth Number (from interest)', example: 'A23' },
|
||
|
|
{ key: 'berth.mooring_number', label: 'Berth Number', example: 'A23' },
|
||
|
|
{ key: 'berth.price', label: 'Berth Price', example: '$45,000' },
|
||
|
|
{ key: 'berth.tenure_type', label: 'Tenure Type', example: 'Freehold' },
|
||
|
|
{ key: 'port.name', label: 'Port Name', example: 'Port Nimara' },
|
||
|
|
{ key: 'port.currency', label: 'Port Currency', example: 'AUD' },
|
||
|
|
{ key: 'date.today', label: "Today's Date", example: '2026-03-15' },
|
||
|
|
{ key: 'date.year', label: 'Current Year', example: '2026' },
|
||
|
|
];
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Recursively walks a TipTap node tree and collects any unsupported node types.
|
||
|
|
* Returns an array of unsupported type names found, or empty array if valid.
|
||
|
|
*/
|
||
|
|
export function validateTipTapDocument(doc: TipTapNode): string[] {
|
||
|
|
const found = new Set<string>();
|
||
|
|
|
||
|
|
function walk(node: TipTapNode): void {
|
||
|
|
if (UNSUPPORTED_NODES.has(node.type)) {
|
||
|
|
found.add(node.type);
|
||
|
|
}
|
||
|
|
if (node.content) {
|
||
|
|
for (const child of node.content) {
|
||
|
|
walk(child);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
walk(doc);
|
||
|
|
return Array.from(found);
|
||
|
|
}
|