jsx working
This commit is contained in:
commit
ec4241ea88
6 changed files with 273 additions and 0 deletions
29
DialogueTree.hs
Normal file
29
DialogueTree.hs
Normal 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
12
html/deno.json
Normal 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
146
html/html.ts
Normal 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("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'");
|
||||
};
|
||||
|
||||
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
7
html/jsx/index.d.ts
vendored
Normal 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
47
html/jsx/jsx-runtime.ts
Normal 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
32
html/main.tsx
Normal 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 {};
|
Loading…
Reference in a new issue