UNPKG

abi.js

Version:

[![typescript-icon]][typescript-link] [![license-icon]][license-link] [![status-icon]][status-link] [![ci-icon]][ci-link] [![twitter-icon]][twitter-link]

473 lines (468 loc) 13.8 kB
"use strict"; // src/view.test.ts var import_bun_test = require("bun:test"); // src/parser.ts function parse_str(val) { val = val.trim(); const res = /^('|")(.*?)\1$/gms.exec(val); if (res) { val = res[2]; } return val; } // src/view.ts var Doc = class { constructor(root, type = "html", charset = "UTF-8", version, mode) { this.root = root; this.type = type; this.charset = charset; if (version === void 0) { this.version = this.type === "html" ? 5 : 1; } else { this.version = version; } if (mode === void 0) { this.mode = "strict"; } else { this.mode = mode; } } version; mode; render(locale) { let str = ""; switch (this.type) { case "xml": str += `<?xml version="${this.version.toFixed(1)}" encoding="${this.charset}"?> `; break; case "xhtml": if (this.version === 1.1) { str += `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">`; } else if (this.version === 1) { switch (this.mode) { case "strict": str += `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">`; break; case "frameset": str += `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">`; break; default: str += `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">`; } } break; case "html": if (this.version >= 5) { str += "<!DOCTYPE html>"; } else if (this.version === 4.01) { switch (this.mode) { case "strict": str += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'; break; case "frameset": str += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'; break; default: str += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'; } } else if (this.version === 4) { switch (this.mode) { case "strict": str += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'; break; case "frameset": str += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">'; break; default: str += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">'; } } else if (this.version === 3.2) { str += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">'; } else if (this.version === 2) { str += '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'; } else if (this.version === 1) { str += '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 1.0//EN">'; } break; default: throw new Error(`Unsupported document type: ${this.type}`); } return str + this.root.render(locale); } }; var Node = class { }; var Tag = class extends Node { constructor(name, ...nodes) { super(); this.name = name; this.addNodes(nodes); } nodes = []; addNodes(nodes) { for (const node of nodes) { this.addNode(node); } return this; } addNode(node) { this.nodes.push(node); return this; } getNodes() { return this.nodes; } getTexts() { const texts = []; for (const node of this.nodes) { if (node instanceof Text) { texts.push(node); } } return texts; } getElements() { const elements = []; for (const node of this.nodes) { if (node instanceof Element) { elements.push(node); } } return elements; } get slot() { return new Slot(this.nodes); } get is_empty() { return this.slot.is_empty; } render(locale) { return this.open() + this.renderSlot(locale) + this.close(); } renderSlot(locale) { return this.slot.render(locale); } }; var Component = class extends Tag { constructor(name, props, ...nodes) { super(name, ...nodes); this.props = props; } open() { return `<${this.name}${this.renderProps()}${this.slot.is_empty ? "" : ">"}`; } close() { return this.slot.is_empty ? "/>" : `</${this.name}>`; } renderProps() { let props = ""; for (const prop of Object.entries(this.props)) { props += ` ${prop[0]}="${prop[1].toString()}"`; } return props; } }; var Element = class _Element extends Tag { constructor(name, attrs = {}, ...nodes) { super(name, ...nodes); this.attrs = attrs; } static ORPHAN = [ "area", "base", "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "meta", "param", "source", "track", "wbr" ]; static INLINE = [ "a", "abbr", "acronym", "b", "bdi", "bdo", "big", "br", "cite", "code", "data", "del", "dfn", "em", "font", "i", "img", "ins", "kbd", "map", "mark", "object", "q", "rp", "rt", "rtc", "ruby", "s", "samp", "small", "span", "strike", "strong", "sub", "sup", "time", "tt", "u", "var" ]; renderAttrs() { let attrs = ""; for (const attr of Object.entries(this.attrs)) { attrs += ` ${attr[0]}="${attr[1]}"`; } return attrs; } open() { return `<${this.name}${this.renderAttrs()}>`; } close() { return this.is_orphan ? "" : `</${this.name}>`; } get is_orphan() { return _Element.ORPHAN.includes(this.name); } get is_paired() { return !this.is_orphan; } get is_inline() { return _Element.INLINE.includes(this.name); } get is_block() { return !this.is_inline; } get is_custom() { return this.name.includes("-"); } }; var Slot = class extends Node { constructor(nodes) { super(); this.nodes = nodes; } get is_empty() { return this.nodes.length === 0; } render(locale) { let str = ""; for (const node of this.nodes) { str += node.render(locale); } return str; } }; var Text = class _Text extends Node { constructor(value, translations = {}) { super(); this.value = value; _Text.setTranslations(value, translations); } static locale = "en_US"; static dictionnary = {}; static setTranslations(value, translations) { for (const translation of Object.entries(translations)) { _Text.setTranslation(value, translation[0], translation[1]); } } static setTranslation(value, locale, translation) { if (_Text.dictionnary[value] === void 0) { _Text.dictionnary[value] = {}; } _Text.dictionnary[value][locale] = translation; } static getTranslation(value, locale) { return _Text.getTranslations(value)[locale]; } static getTranslations(value) { const translations = _Text.dictionnary[value]; if (!translations) { for (const [defaultValue, translations2] of Object.entries( _Text.dictionnary )) { for (const translation of Object.values(translations2)) { if (value === translation) { translations2[_Text.locale] = defaultValue; return translations2; } } } } return translations; } static translate(value) { const translations = _Text.getTranslations(value); return Translate.from(translations); } translateTo(locale) { return _Text.getTranslation(this.value, locale) ?? this.value; } render(locale) { return locale ? this.translateTo(locale) : this.value; } }; var Translate = class _Translate { constructor(translations) { this.translations = translations; } static from(translations) { return new _Translate(translations); } to(locale) { return this.translations[locale]; } }; var Template = class { constructor(content) { this.content = content; } render(locale) { const content = this.content.toString(); const ID = "[a-zA-Z]+[a-zA-Z0-9-_]*"; const SINGLE_QUOTE_STR = "'(?:\\'|[^'])*'"; const DOUBLE_QUOTE_STR = '"(?:\\"|[^"])*"'; const STR = `${SINGLE_QUOTE_STR}|${DOUBLE_QUOTE_STR}`; const ATTR = `(${ID})\\s*=\\s*(${STR})\\s*`; const ATTRS = `${ATTR}(?:\\s*${ATTR})*`; const ATTRS_BLOCK = `\\[(${ATTRS})\\]`; const ELT = `(${ID})\\s*${ATTRS_BLOCK}`; const ELT_BLOCK = `${ELT}\\s*\\{\\s*(.*?)\\s*\\}`; const elt_m = RegExp(ELT_BLOCK).exec(content); if (elt_m) { const attrs = {}; let attrs_str = elt_m[2] || ""; let attrs_m = RegExp(ATTR, "gm").exec(attrs_str); while (attrs_m) { attrs[attrs_m[1]] = parse_str(attrs_m[2]); attrs_str = attrs_str.replace(attrs_m[0], ""); attrs_m = RegExp(ATTR, "gm").exec(attrs_str); } const elt = element(elt_m[1], attrs, text(elt_m[7])); return elt.render(locale); } return content; } }; function doc(root, type = "html", charset = "UTF-8", version, mode) { return new Doc(root, type, charset, version, mode); } function text(value, translations = {}) { return new Text(value, translations); } function component(name, props = {}, ...nodes) { return new Component(name, props, ...nodes); } function element(name, attrs = {}, ...nodes) { return new Element(name, attrs, ...nodes); } function template(content) { return new Template(content); } // src/view.test.ts (0, import_bun_test.test)("Test doc", () => { const _doc = doc(element("html"), "html"); (0, import_bun_test.expect)(_doc).toBeInstanceOf(Doc); (0, import_bun_test.expect)(_doc.root).toBeInstanceOf(Node); (0, import_bun_test.expect)(_doc.render()).toEqual("<!DOCTYPE html><html></html>"); }); (0, import_bun_test.test)("Test text", () => { const txt = text("Hello", { fr_CI: "Salut" }); (0, import_bun_test.expect)(txt).toBeInstanceOf(Text); (0, import_bun_test.expect)(txt).toBeInstanceOf(Node); (0, import_bun_test.expect)(txt.value).toEqual("Hello"); (0, import_bun_test.expect)(txt.render()).toEqual("Hello"); (0, import_bun_test.expect)(txt.render("fr_CI")).toEqual("Salut"); (0, import_bun_test.expect)(txt.translateTo("fr_CI")).toEqual("Salut"); (0, import_bun_test.expect)(Text.translate("Salut").to("en_US")).toEqual("Hello"); (0, import_bun_test.expect)(Text.translate("Salut").to("fr_CI")).toEqual("Salut"); }); (0, import_bun_test.test)("Test component", () => { const cmp = component( "MyHello", { name: "Sigui", age: 27 }, element("p", {}, text("Hello"), element("b", {}, text("Sigui"))), element("p", {}, text("Age"), element("b", {}, text("25"))) ); (0, import_bun_test.expect)(cmp).toBeInstanceOf(Component); (0, import_bun_test.expect)(cmp).toBeInstanceOf(Node); (0, import_bun_test.expect)(cmp).toBeInstanceOf(Tag); (0, import_bun_test.expect)(cmp.slot).toBeInstanceOf(Slot); (0, import_bun_test.expect)(cmp.slot).toBeInstanceOf(Node); (0, import_bun_test.expect)(cmp.is_empty).toBeFalse(); (0, import_bun_test.expect)(cmp.slot.render()).toEqual( "<p>Hello<b>Sigui</b></p><p>Age<b>25</b></p>" ); (0, import_bun_test.expect)(cmp.render()).toEqual( '<MyHello name="Sigui" age="27"><p>Hello<b>Sigui</b></p><p>Age<b>25</b></p></MyHello>' ); }); (0, import_bun_test.test)("Test element", () => { const elt = element( "div", { id: "MyDiv" }, text("Hello"), element("b", {}, text("World")) ); (0, import_bun_test.expect)(elt).toBeInstanceOf(Element); (0, import_bun_test.expect)(elt).toBeInstanceOf(Node); (0, import_bun_test.expect)(elt).toBeInstanceOf(Tag); (0, import_bun_test.expect)(elt.slot).toBeInstanceOf(Slot); (0, import_bun_test.expect)(elt.slot).toBeInstanceOf(Node); (0, import_bun_test.expect)(elt.is_empty).toBeFalse(); (0, import_bun_test.expect)(elt.slot.render()).toEqual("Hello<b>World</b>"); (0, import_bun_test.expect)(elt.render()).toEqual('<div id="MyDiv">Hello<b>World</b></div>'); }); (0, import_bun_test.test)("Test inline element", () => { const elt = element("br"); const c_elt = element("br", {}, text("content")); (0, import_bun_test.expect)(elt.is_inline).toBeTrue(); (0, import_bun_test.expect)(c_elt.is_inline).toBeTrue(); (0, import_bun_test.expect)(elt.is_empty).toBeTrue(); (0, import_bun_test.expect)(c_elt.is_empty).toBeFalse(); (0, import_bun_test.expect)(elt.is_paired).toBeFalse(); (0, import_bun_test.expect)(c_elt.is_paired).toBeFalse(); (0, import_bun_test.expect)(elt.is_custom).toBeFalse(); }); (0, import_bun_test.test)("Test paired element", () => { const elt = element("p"); (0, import_bun_test.expect)(elt.is_inline).toBeFalse(); (0, import_bun_test.expect)(elt.is_empty).toBeTrue(); (0, import_bun_test.expect)(elt.is_paired).toBeTrue(); (0, import_bun_test.expect)(elt.is_custom).toBeFalse(); }); (0, import_bun_test.test)("Test template", () => { const tpl = template`p[id="myP" class="my-4 mx-8" title='It\'s a Hello World'] { Bonjour le monde }`; const r = `<p id="myP" class="my-4 mx-8" title="It's a Hello World">Bonjour le monde</p>`; (0, import_bun_test.expect)(tpl).toBeInstanceOf(Template); (0, import_bun_test.expect)(tpl.render()).toEqual(r); });