Last active
September 19, 2023 11:41
-
-
Save iamandrewluca/d95d508baf1565b14c2c7d23dd231332 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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