Skip to content

Instantly share code, notes, and snippets.

@iamandrewluca
Last active September 19, 2023 11:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iamandrewluca/d95d508baf1565b14c2c7d23dd231332 to your computer and use it in GitHub Desktop.
Save iamandrewluca/d95d508baf1565b14c2c7d23dd231332 to your computer and use it in GitHub Desktop.
import { FunctionalComponent, isVNode, VNode, VNodeNormalizedChildren, createTextVNode, render, createVNode, createCommentVNode } from "vue";
function visit(nodes: VNodeNormalizedChildren, exits: Map<string, VNode>): VNodeNormalizedChildren {
if (!Array.isArray(nodes)) return nodes
return nodes.map(node => {
if (!isVNode(node)) return node
if (node.type !== HTMLCommentExit) {
node.children = visit(node.children, exits)
return node
}
const id = Math.random().toString()
exits.set(id, node)
return createTextVNode(id)
})
}
export const HTMLCommentEnter: FunctionalComponent<{ disabled?: boolean }> = (props, { slots }) => {
const { disabled = false } = props
if (!slots.default) return
const vnodes = slots.default()
if (disabled) return vnodes
const exits = new Map<string, VNode>()
const div = document.createElement('div')
render(createVNode(() => visit(vnodes, exits)), div)
let text = div.innerHTML
let newNodes = []
for (const [id, vnode] of exits) {
const [head, tail] = text.split(id)
newNodes.push(createCommentVNode(head), vnode)
text = tail
}
return newNodes
}
export const HTMLCommentExit: FunctionalComponent = (_, { slots }) => {
if (!slots.default) return
const vnodes = slots.default()
return vnodes
}
import { PropsWithChildren, useLayoutEffect, useRef } from "react";
export function HTMLComment(
props: PropsWithChildren<{
prepend?: string;
append?: string;
disabled?: boolean;
}>
) {
const { children, prepend = "", append = "", disabled = false } = props;
const ref = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
if (!ref.current) return;
const exits = Array.from(
ref.current.querySelectorAll(":scope [data-exit]")
).reverse();
if (disabled) {
exits.forEach((exit) => exit.replaceWith(...Array.from(exit.childNodes)));
ref.current.replaceWith(...Array.from(ref.current.childNodes));
return;
}
if (exits.length === 0) {
const data = `${prepend}${ref.current.innerHTML}${append}`;
ref.current.replaceWith(new Comment(data));
return;
}
console.log(exits[0]?.innerHTML);
const id = Math.random().toString();
exits.forEach((exit) => exit.replaceWith(id));
const parts = ref.current.innerHTML
.split(id)
.reverse()
.map((part) => `${prepend}${part}${append}`)
.map((data) => new Comment(data));
function isNode(node: unknown): node is Element | Comment {
return node instanceof Node;
}
const items: Node[] = [];
while (parts.length > 0 || exits.length > 0) {
const partBefore = parts.pop();
const exit = Array.from(exits.pop()?.childNodes ?? []);
const toAdd = [partBefore, ...exit].filter(isNode);
items.push(...toAdd);
}
ref.current.replaceWith(...items);
});
return <div hidden ref={ref} children={children} />;
}
export function HTMLCommentExit(props: PropsWithChildren<unknown>) {
const { children } = props;
return (
<div hidden data-exit>
{children}
</div>
);
}
import { HTMLComment, HTMLCommentExit } from "./comment";
import { render } from "react-dom";
import { Fragment, PropsWithChildren, useEffect } from "react";
function OutlookComment({
children,
disabled
}: PropsWithChildren<{ disabled?: boolean }>) {
return (
<HTMLComment prepend="[if mso 10]>" append="<![endif]" disabled={disabled}>
{children}
</HTMLComment>
);
}
export default function App() {
return (
<div>
<OutlookComment>
<table style={{ backgroundColor: "#1c3" }}>
<tbody>
<tr>
<td>
<HTMLCommentExit>
First Exit
<OutlookComment>
<div>666</div>
<HTMLCommentExit>
<div>42</div>
</HTMLCommentExit>
</OutlookComment>
</HTMLCommentExit>
</td>
</tr>
<tr>
<td>
<HTMLCommentExit>
<div>Second exit</div>
</HTMLCommentExit>
</td>
</tr>
</tbody>
</table>
</OutlookComment>
</div>
);
}
const root = document.querySelector("#root");
function Done({ children, callback }: any) {
useEffect(callback);
return <Fragment children={children} />;
}
render(
<Done callback={() => console.log(root?.innerHTML)}>
<App />
</Done>,
root
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment