add hooks
This commit is contained in:
parent
c3014d690b
commit
0a70e34a13
2 changed files with 34 additions and 10 deletions
|
@ -1,6 +1,7 @@
|
|||
import { Nilable } from "./utils.ts";
|
||||
|
||||
export type Attrs = Record<string, string>;
|
||||
export type AttrEntry = [key: string, value: string];
|
||||
export type AnyNode = Fragment | FragmentNode;
|
||||
|
||||
export function isTextNode(val: unknown): val is TextNode {
|
||||
|
|
43
ren/str.ts
43
ren/str.ts
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
AnyNode,
|
||||
AttrEntry,
|
||||
Attrs,
|
||||
Elem,
|
||||
Fragment,
|
||||
|
@ -15,17 +16,31 @@ interface StrRendererOpts {
|
|||
doctype?: string;
|
||||
forceRenderDoctype?: boolean;
|
||||
wrapNode?: (node: AnyNode) => AnyNode;
|
||||
onVisitAttr?: (entry: AttrEntry, params: OnVisitAttrParams) => AttrEntry;
|
||||
}
|
||||
|
||||
interface StrRendererHooks {
|
||||
onVisitAttr: (entry: AttrEntry, params: OnVisitAttrParams) => AttrEntry;
|
||||
}
|
||||
|
||||
export interface OnVisitAttrParams {
|
||||
readonly tagName: string;
|
||||
readonly attrs: Attrs;
|
||||
}
|
||||
|
||||
export class StrRenderer implements Renderer<string> {
|
||||
#doctype: string;
|
||||
#forceRenderDoctype: boolean;
|
||||
#wrapNode: (node: AnyNode) => AnyNode;
|
||||
#hooks: StrRendererHooks;
|
||||
|
||||
constructor(opts?: StrRendererOpts) {
|
||||
this.#doctype = opts?.doctype ?? "html";
|
||||
this.#forceRenderDoctype = opts?.forceRenderDoctype ?? false;
|
||||
this.#wrapNode = opts?.wrapNode ?? identity;
|
||||
this.#hooks = {
|
||||
onVisitAttr: opts?.onVisitAttr ?? identity,
|
||||
};
|
||||
}
|
||||
|
||||
render(node: AnyNode): string {
|
||||
|
@ -34,7 +49,7 @@ export class StrRenderer implements Renderer<string> {
|
|||
(isElem(wrappedNode) && wrappedNode.tagName === "html");
|
||||
return concat([
|
||||
shouldRenderDoctype && encodeDoctype(this.#doctype),
|
||||
encodeAnyNode(wrappedNode),
|
||||
encodeAnyNode(wrappedNode, this.#hooks),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -47,36 +62,44 @@ function encodeDoctype(value: string): string {
|
|||
return `<!doctype ${value}>`;
|
||||
}
|
||||
|
||||
function encodeAnyNode(node: AnyNode): string {
|
||||
function encodeAnyNode(node: AnyNode, hooks: StrRendererHooks): string {
|
||||
return isTextNode(node)
|
||||
? encodeTextNode(node)
|
||||
: isFragment(node)
|
||||
? encodeHtmlFragment(node)
|
||||
: encodeHtmlElement(node);
|
||||
? encodeHtmlFragment(node, hooks)
|
||||
: encodeHtmlElement(node, hooks);
|
||||
}
|
||||
|
||||
function encodeTextNode(node: TextNode): string {
|
||||
return node.innerText;
|
||||
}
|
||||
|
||||
function encodeHtmlFragment(node: Fragment): string {
|
||||
return concat(node.children.map(encodeAnyNode));
|
||||
function encodeHtmlFragment(node: Fragment, hooks: StrRendererHooks): string {
|
||||
return concat(node.children.map((ch) => encodeAnyNode(ch, hooks)));
|
||||
}
|
||||
|
||||
function encodeHtmlElement(
|
||||
{ tagName, attrs, children }: Elem,
|
||||
hooks: StrRendererHooks,
|
||||
): string {
|
||||
const open = `<${join(" ", [tagName, encodeAttrs(attrs)])}>`;
|
||||
const open = `<${join(" ", [tagName, encodeAttrs(tagName, attrs, hooks)])}>`;
|
||||
if (isSelfClosedTagName(tagName)) return open;
|
||||
|
||||
const encodedChildren = children.map(encodeAnyNode);
|
||||
const encodedChildren = children.map((ch) => encodeAnyNode(ch, hooks));
|
||||
return `${open}${concat(encodedChildren)}</${tagName}>`;
|
||||
}
|
||||
|
||||
function encodeAttrs(attrs: Attrs): string {
|
||||
function encodeAttrs(
|
||||
tagName: string,
|
||||
attrs: Attrs,
|
||||
hooks: StrRendererHooks,
|
||||
): string {
|
||||
return join(
|
||||
" ",
|
||||
Object.entries(attrs).map(([key, value]) => encodeAttr(key, value)),
|
||||
Object.entries(attrs).map((entry) => {
|
||||
const [key, value] = hooks.onVisitAttr(entry, { tagName, attrs });
|
||||
return encodeAttr(key, value);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Reference in a new issue