jsx working

This commit is contained in:
mehbark 2023-07-09 02:34:35 -04:00
commit ec4241ea88
6 changed files with 273 additions and 0 deletions

29
DialogueTree.hs Normal file
View file

@ -0,0 +1,29 @@
module DialogueTree where
import Text.Blaze.Html.Renderer.String
import Text.Blaze.Html5
data DTree
= T Html
| O Html [(Html, DTree)]
ht :: (ToMarkup a) => a -> Html
ht = preEscapedToHtml
normalizeOption :: (Html, DTree) -> Html
normalizeOption (h, t) =
details . ht $
[summary h, normalize t]
normalize :: DTree -> Html
normalize (T s) = s
normalize (O response things) =
details . ht $
summary response
: (normalizeOption <$> things)
render :: DTree -> String
render = renderHtml . normalize
put :: DTree -> IO ()
put = putStrLn . render

12
html/deno.json Normal file
View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxFactory": "create_element",
"jsxFragmentFactory": "create_fragment",
"jsxImportSource": "./jsx"
},
"imports": {
"html": "./html.ts",
"./jsx/jsx-runtime": "./jsx/jsx-runtime.ts"
}
}

146
html/html.ts Normal file
View file

@ -0,0 +1,146 @@
export type Prop = string | number | boolean;
export type Attributes = Record<string, Prop>;
export type Html = string | NonText;
export type NonText = {
tag: string;
attributes: Attributes;
children: Html[];
};
export const Fragment = "Fragment";
function is_string(elem: Html): elem is string {
return typeof elem == "string";
}
export function fr(...children: Html[]): Html {
return { tag: Fragment, attributes: {}, children };
}
function div(...children: Html[]): Html {
return { tag: "div", attributes: {}, children };
}
function expand_fragments_in_list(children: Html[]): Html[] {
let out = [];
for (const child of children) {
if (is_string(child)) {
out.push(child);
} else if (child.tag == Fragment) {
out.push(...child.children.map(expand_fragments));
} else {
out.push(child);
}
}
return out;
}
// NOTE: if you pass a fragment to this, it won't be expanded (which makes sense if you think about it)
function expand_fragments(elem: Html): Html {
if (is_string(elem)) {
return elem;
} else {
const children = expand_fragments_in_list(elem.children);
return { ...elem, children };
}
}
const attr = (attrs: Attributes) => (elem: Html): Html => {
if (is_string(elem)) {
return elem;
} else {
const { tag, attributes, children } = elem;
return { tag, attributes: { ...attributes, ...attrs }, children };
}
};
const escape = (unsafe: string): string => {
return unsafe
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
};
function render_attributes(attrs: Attributes): string {
return (
(Object.keys(attrs).length > 0 ? " " : "") +
Object.entries(attrs)
.filter(([_attr, val]) => typeof val != "undefined")
.map(([attr, val]) => `${attr}="${escape((val ?? "").toString())}"`)
.join(" ")
);
}
function indent(str: string, amount = 4, char = " "): string {
const ind = char.repeat(4);
return str
.split("\n")
.map((l) => ind + l)
.join("\n");
}
function render_elem(
{ tag, attributes, children }: NonText,
mini = false,
): string {
if (children.length == 0) {
return (
`<${tag}${render_attributes(attributes)}` + (mini ? "/>" : " />")
);
} else {
let inner = "";
let string_last = false;
for (let i = 0; i < children.length; i++) {
let child = children[i];
const rendered = render(child, mini);
if (is_string(child)) {
mini && string_last && (inner += " ");
inner += rendered;
string_last = true;
} else {
inner += rendered;
string_last = false;
}
if (!mini && i + 1 < children.length) inner += "\n";
}
return [
`<${tag}${render_attributes(attributes)}>`,
mini ? inner : indent(inner),
`</${tag}>`,
].join(mini ? "" : "\n");
}
}
// old:
// joining with " " is inefficient, but necessary for correct string behavior
// i've decided that joining with "" is worth the size savings
// THIS MEANS MINIFICATION IS SEMANTICALLY DIFFERENT
// JK I DID IT THE HARD WAY :]
export function render(elem: Html, mini = true): string {
if (is_string(elem)) {
return escape(elem);
} else if (elem.tag == Fragment) {
// mimics react's behavior with fragments, ehhh nvm different aims
return elem.children.map((elem) => render(elem, mini)).join("\n");
} else {
const { tag, attributes } = elem;
const expanded = expand_fragments(elem);
if (is_string(expanded)) {
throw "impossible";
}
return render_elem(
{ tag, attributes, children: expanded.children },
mini,
);
}
}

7
html/jsx/index.d.ts vendored Normal file
View file

@ -0,0 +1,7 @@
declare namespace JSX {
export interface IntrinsicElements {
[elemName: string]: any;
}
}
declare var React: never;

47
html/jsx/jsx-runtime.ts Normal file
View file

@ -0,0 +1,47 @@
import { Attributes, fr, Html, Prop } from "../html.ts";
type SadProp = Prop | undefined | Html | Html[];
export type Component = (
attributes: Attributes,
children: Html[],
) => Html;
interface Props {
[key: string]: typeof key extends "children" ? never : SadProp;
children: undefined | Html | Html[];
}
export function create_element(
type: Component | string,
props_?: Props,
): Html {
console.log(arguments);
const props: Props = props_ ?? { children: [] };
const children: Html[] = [props.children ?? []].flat();
const attributes: Attributes = {};
for (const [key, val] of Object.entries(props)) {
if (
key == "children" || typeof val == "undefined" || typeof val == "object"
) continue;
attributes[key] = val;
}
console.log(attributes, children);
if (typeof type == "string") {
return { tag: type, attributes, children };
} else {
return type(attributes, children);
}
}
export function Fragment(
_: Record<string | number | symbol, never>,
children: Html[],
) {
return fr(...children);
}
export const jsx = create_element;
export const jsxs = create_element;

32
html/main.tsx Normal file
View file

@ -0,0 +1,32 @@
import { Html, render } from "./html.ts";
import { Component } from "./jsx/jsx-runtime.ts";
const Homestuck: Component = (attrs, children) => (
<div {...attrs} homestuck={true}>
{...children}
</div>
);
const bla: Html = (
<Homestuck>
<Homestuck awesome={true} />
<a href="https://bambosh.dev/unofficial-homestuck-collection">text</a>
<div class="has-fragments">
<>
homestuck
<em>is</em>
cool
</>
<>
vast error is
<em>also</em>
cool
</>
</div>
</Homestuck>
);
console.log(bla);
console.log(render(bla, false));
console.log(render(bla, true));
export {};