Skip to content

Commit c0ea46a

Browse files
committed
Migrate kg-lexical-html-renderer to TypeScript
no issue This commit moves kg-lexical-html-renderer from lib/ to src/, migrates its build and tests to TypeScript/ESM, and fixes the migration fallout in published artifacts, runtime loading, and callback typing. It also tightens the shared exportDOM and Koenig card typing in kg-default-nodes because this renderer migration depends on those cross-package contracts compiling cleanly against Lexical.
1 parent 16bbc11 commit c0ea46a

57 files changed

Lines changed: 563 additions & 296 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/kg-default-nodes/src/KoenigDecoratorNode.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* c8 ignore start */
22
import {DecoratorNode} from 'lexical';
3+
import type {ExportDOMOptions, ExportDOMOutput} from './export-dom.js';
34

45
export class KoenigDecoratorNode extends DecoratorNode<unknown> {
56
static transform() {
@@ -11,7 +12,23 @@ export class KoenigDecoratorNode extends DecoratorNode<unknown> {
1112
}
1213
}
1314

14-
export function $isKoenigCard(node: unknown): node is KoenigDecoratorNode {
15-
return node instanceof KoenigDecoratorNode;
15+
export type KoenigCard = KoenigDecoratorNode & {
16+
isKoenigCard(): true;
17+
exportDOM(options: ExportDOMOptions): ExportDOMOutput;
18+
hasDynamicData(): boolean;
19+
getDynamicData?(options: ExportDOMOptions): Promise<{key: number; data: unknown}>;
20+
};
21+
22+
export function $isKoenigCard(node: unknown): node is KoenigCard {
23+
if (!(node instanceof KoenigDecoratorNode)) {
24+
return false;
25+
}
26+
27+
const card = node as Partial<KoenigCard>;
28+
29+
return typeof card.isKoenigCard === 'function' &&
30+
card.isKoenigCard() === true &&
31+
typeof card.exportDOM === 'function' &&
32+
typeof card.hasDynamicData === 'function';
1633
}
1734
/* c8 ignore end */

packages/kg-default-nodes/src/export-dom.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import type {DOMExportOutput as LexicalDOMExportOutput} from 'lexical';
2+
13
export type ExportDOMOutputType = 'inner' | 'outer' | 'value' | 'html';
4+
export type ExportDOMElement = LexicalDOMExportOutput['element'];
25

3-
export interface ExportDOMOutput<TElement extends Element | null = Element | null, TType extends ExportDOMOutputType = ExportDOMOutputType> {
4-
element: TElement;
6+
export type ExportDOMOutput<
7+
TType extends ExportDOMOutputType = ExportDOMOutputType
8+
> = LexicalDOMExportOutput & {
59
type: TType;
6-
}
10+
};
711

812
export interface ExportDOMFeatureOptions {
913
emailCustomization?: boolean;

packages/kg-default-nodes/src/generate-decorator-node.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ export class GeneratedDecoratorNodeBase<TDataset extends Record<string, unknown>
114114
return null;
115115
}
116116

117+
isKoenigCard(): true {
118+
return true;
119+
}
120+
117121
hasDynamicData(): boolean {
118122
return false;
119123
}
@@ -191,6 +195,10 @@ export function generateDecoratorNode<
191195
return nodeType;
192196
}
193197

198+
isKoenigCard(): true {
199+
return true;
200+
}
201+
194202
/**
195203
* Creates a copy of an existing node with all its properties
196204
* @extends DecoratorNode

packages/kg-default-nodes/src/nodes/audio/audio-renderer.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ interface DefaultAudioRenderOptions extends RenderOptions {
2323

2424
type AudioRenderOptions = EmailAudioRenderOptions | DefaultAudioRenderOptions;
2525

26-
export type AudioExportDOMOutput =
27-
| ExportDOMOutput<HTMLDivElement>
28-
| ExportDOMOutput<HTMLTableElement>
29-
| ExportDOMOutput<HTMLSpanElement, 'inner'>;
26+
export type AudioExportDOMOutput = ExportDOMOutput;
3027

3128
export function renderAudioNode(node: AudioNodeData, options: AudioRenderOptions = {}): AudioExportDOMOutput {
3229
addCreateDocumentOption(options);

packages/kg-default-nodes/src/nodes/call-to-action/calltoaction-renderer.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {addCreateDocumentOption} from '../../utils/add-create-document-option.js';
22
import type {ExportDOMOptions} from '../../export-dom.js';
3-
import {renderWithVisibility, type RenderOutput, type Visibility} from '../../utils/visibility.js';
3+
import {renderWithVisibility, type Visibility} from '../../utils/visibility.js';
44
import {getResizedImageDimensions} from '../../utils/get-resized-image-dimensions.js';
55
import {isLocalContentImage} from '../../utils/is-local-content-image.js';
66
import {buildCleanBasicHtmlForElement} from '../../utils/build-clean-basic-html-for-element.js';
7+
import {getFirstHtmlElement} from '../../utils/get-first-html-element.js';
78

89
interface CTADataset {
910
layout: string;
@@ -401,7 +402,7 @@ export function renderCallToActionNode(node: CTANodeData, options: CTARenderOpti
401402

402403
emailDiv.innerHTML = emailCTATemplate(dataset, options);
403404

404-
return renderWithVisibility({element: emailDiv.firstElementChild as RenderOutput['element'], type: 'outer' as const}, node.visibility, options);
405+
return renderWithVisibility({element: getFirstHtmlElement(emailDiv, 'renderCallToActionNode email'), type: 'outer' as const}, node.visibility, options);
405406
}
406407

407408
if (dataset.hasSponsorLabel) {
@@ -415,5 +416,5 @@ export function renderCallToActionNode(node: CTANodeData, options: CTARenderOpti
415416

416417
element.innerHTML = htmlString?.trim();
417418

418-
return renderWithVisibility({element: element.firstElementChild as RenderOutput['element'], type: 'outer' as const}, node.visibility, options);
419+
return renderWithVisibility({element: getFirstHtmlElement(element, 'renderCallToActionNode'), type: 'outer' as const}, node.visibility, options);
419420
}

packages/kg-default-nodes/src/nodes/email/email-renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ interface EmailNodeData {
1010

1111
interface RenderOptions extends ExportDOMOptions {}
1212

13-
export function renderEmailNode(node: EmailNodeData, options: RenderOptions = {}): EmptyContainerOutput | ExportDOMOutput<HTMLDivElement, 'inner'> {
13+
export function renderEmailNode(node: EmailNodeData, options: RenderOptions = {}): EmptyContainerOutput | ExportDOMOutput<'inner'> {
1414
addCreateDocumentOption(options);
1515
const document = options.createDocument!();
1616

packages/kg-default-nodes/src/nodes/file/file-renderer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {addCreateDocumentOption} from '../../utils/add-create-document-option.js
22
import type {ExportDOMOptions} from '../../export-dom.js';
33
import {renderEmptyContainer} from '../../utils/render-empty-container.js';
44
import {escapeHtml} from '../../utils/escape-html.js';
5+
import {getFirstHtmlElement} from '../../utils/get-first-html-element.js';
56
import {bytesToSize} from '../../utils/size-byte-converter.js';
67

78
interface FileNodeData {
@@ -86,7 +87,7 @@ function emailTemplate(node: FileNodeData, document: Document, options: RenderOp
8687
const container = document.createElement('div');
8788
container.innerHTML = html.trim();
8889

89-
return {element: container.firstElementChild, type: 'outer' as const};
90+
return {element: getFirstHtmlElement(container, 'renderFileNode emailTemplate'), type: 'outer' as const};
9091
}
9192

9293
function cardTemplate(node: FileNodeData, document: Document) {

packages/kg-default-nodes/src/nodes/header/renderers/v2/header-renderer.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {addCreateDocumentOption} from '../../../../utils/add-create-document-option.js';
22
import type {ExportDOMOptions} from '../../../../export-dom.js';
3+
import {getFirstHtmlElement} from '../../../../utils/get-first-html-element.js';
34
import {slugify} from '../../../../utils/slugify.js';
45
import {getSrcsetAttribute, type ImageRenderOptions} from '../../../../utils/srcset-attribute.js';
56

@@ -242,7 +243,7 @@ export function renderHeaderNodeV2(dataset: HeaderV2DatasetNode, options: Header
242243

243244
emailDiv.innerHTML = emailTemplate(node, options)?.trim();
244245

245-
return {element: emailDiv.firstElementChild as HTMLDivElement, type: 'outer' as const};
246+
return {element: getFirstHtmlElement(emailDiv, 'renderHeaderV2Node email') as HTMLDivElement, type: 'outer' as const};
246247
}
247248

248249
const htmlString = cardTemplate(node, options);
@@ -264,7 +265,7 @@ export function renderHeaderNodeV2(dataset: HeaderV2DatasetNode, options: Header
264265
}
265266
}
266267

267-
return {element: element.firstElementChild as HTMLDivElement, type: 'outer' as const};
268+
return {element: getFirstHtmlElement(element, 'renderHeaderV2Node') as HTMLDivElement, type: 'outer' as const};
268269
}
269270

270271
export function getCardClasses(nodeData: HeaderV2NodeData) {

packages/kg-default-nodes/src/nodes/html/html-renderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ interface HtmlNodeData {
1111
interface RenderOptions extends ExportDOMOptions {}
1212

1313
export type HtmlExportDOMOutput =
14-
ExportDOMOutput<Element, 'inner' | 'value' | 'html'>;
14+
ExportDOMOutput<'inner' | 'value' | 'html'>;
1515

1616
export function renderHtmlNode(node: HtmlNodeData, options: RenderOptions = {}): HtmlExportDOMOutput {
1717
addCreateDocumentOption(options);
@@ -29,7 +29,7 @@ export function renderHtmlNode(node: HtmlNodeData, options: RenderOptions = {}):
2929
textarea.value = wrappedHtml;
3030

3131
if (node.visibility) {
32-
const renderOutput: ExportDOMOutput<HTMLTextAreaElement, 'value'> = {element: textarea, type: 'value'};
32+
const renderOutput: ExportDOMOutput<'value'> = {element: textarea, type: 'value'};
3333
return renderWithVisibility(renderOutput, node.visibility, options) as HtmlExportDOMOutput;
3434
}
3535

packages/kg-default-nodes/src/nodes/markdown/markdown-renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interface MarkdownNodeData {
88

99
interface MarkdownRenderOptions extends ExportDOMOptions {}
1010

11-
export function renderMarkdownNode(node: MarkdownNodeData, options: MarkdownRenderOptions = {}): ExportDOMOutput<HTMLDivElement, 'inner'> {
11+
export function renderMarkdownNode(node: MarkdownNodeData, options: MarkdownRenderOptions = {}): ExportDOMOutput<'inner'> {
1212
addCreateDocumentOption(options);
1313
if (typeof options.createDocument !== 'function') {
1414
throw new TypeError('renderMarkdownNode requires options.createDocument to be a function');

0 commit comments

Comments
 (0)