Skip to content

Commit

Permalink
Merge pull request #7 from dashborg/dev-0.3.3
Browse files Browse the repository at this point in the history
Hibiki v0.3.3
  • Loading branch information
sawka authored Mar 5, 2022
2 parents 2efd2c1 + ff65460 commit e5cc5db
Show file tree
Hide file tree
Showing 19 changed files with 213 additions and 116 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ no build tools to download and run.
Just add one script tag to the top of your page or template:

```
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.2/hibiki-prod.min.js"></script>
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.3/hibiki-prod.min.js"></script>
```

Wrap any portion of your content with a &lt;template hibiki&gt; tag and you have your first
Expand Down
7 changes: 7 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Change Log

## v0.3.3

* HibikiGlobalConfig preRenderHook and postRenderHook (receives state and DOM node)
* Added getStateName() to HibikiState (set from 'name' attribute on hibiki template element)
* Allow a 'script' tag with type="text/hibiki-html" to be used in place of a 'template' tag (SEO concerns)
* The top-level Hibiki tag (&lt;template>, &lt;script>, or &lt;hibiki>) can now take two optional attributes. "src"/"hibikisrc" will load the Hibiki HTML src from the given URL. "datasrc" will load the initial HTML data from the given URL.

## v0.3.2

* new array functions: fn:filter(), fn:map(), fn:find(), fn:findindex(), fn:reduce(), fn:reverse(), fn:every(), fn:some(), fn:concat(), fn:join(), fn:shift(), fn:unshift()
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hibiki",
"version": "0.3.2",
"version": "0.3.3",
"description": "Hibiki HTML",
"main": "dist/hibiki.js",
"repository": "https://github.com/sawka/hibiki",
Expand All @@ -16,7 +16,7 @@
"@babel/preset-react": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
"@types/classnames": "2.3.1",
"@types/node": "^16.11.6",
"@types/node": "^16",
"@types/react": "^17.0.34",
"@types/uuid": "^8.3.1",
"babel-loader": "^8.2.3",
Expand Down
2 changes: 1 addition & 1 deletion playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=1200">
<title>Hibiki HTML Playground</title>
<link rel="preconnect" href="https://fonts.gstatic.com">
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.2/hibiki-prod.min.js"></script>
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.3/hibiki-prod.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="/playground.css">
Expand Down
2 changes: 1 addition & 1 deletion playground/playground-iframe-bulma.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>Hibiki Playground Preview</title>
<link rel="preconnect" href="https://fonts.gstatic.com">
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.2/hibiki-prod.min.js"></script>
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.3/hibiki-prod.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
Expand Down
2 changes: 1 addition & 1 deletion playground/playground-iframe-clean.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>Hibiki Playground Preview</title>
<link rel="preconnect" href="https://fonts.gstatic.com">
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.2/hibiki-prod.min.js"></script>
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.3/hibiki-prod.min.js"></script>
</head>
<body>
%%TEXT%%
Expand Down
2 changes: 1 addition & 1 deletion playground/tutorial/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<meta name="viewport" content="width=1200">
<title>Hibiki HTML Tutorial</title>
<link rel="preconnect" href="https://fonts.gstatic.com">
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.2/hibiki-prod.min.js"></script>
<script src="https://cdn.hibikihtml.com/hibiki/v0.3.3/hibiki-prod.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="/playground.css">
Expand Down
4 changes: 2 additions & 2 deletions playground/tutorial/t-gs.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ <h4 class="title is-4">Getting Started</h4>
You can create a Hibiki HTML app on any page in two steps. First add the Hibiki HTML JavaScript tag to
your page:
</p>
<pre class="codeblock">&lt;script src="https://cdn.hibikihtml.com/hibiki/v0.3.2/hibiki-prod.min.js"&gt;&lt;/script&gt;</pre>
<pre class="codeblock">&lt;script src="https://cdn.hibikihtml.com/hibiki/v0.3.3/hibiki-prod.min.js"&gt;&lt;/script&gt;</pre>
<p>
Next add a Hibiki template block to your page where you want the application to display (you can also wrap
your existing HTML to instantly convert it to a Hibiki app):
Expand Down Expand Up @@ -50,7 +50,7 @@ <h6 class="title is-6 mb-2">How the preview and tutorial was built</h6>
<a href="https://bulma.io/" target="_blank">Bulma</a> CSS library, and
<a href="https://fontawesome.com/v4.7/" target="_blank">FontAwesome v4.7</a>.
</p>
<pre class="codeblock">&lt;script src="https://cdn.hibikihtml.com/hibiki/v0.3.2/hibiki-prod.min.js">&lt;/script>
<pre class="codeblock">&lt;script src="https://cdn.hibikihtml.com/hibiki/v0.3.3/hibiki-prod.min.js">&lt;/script>
&lt;link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
&lt;link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css"></pre>

Expand Down
2 changes: 1 addition & 1 deletion playground/tutorial/t-intro.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ <h4 class="title is-4">Easy to Get Started</h4>
<p>
<a class="content-anchor" name="section1"></a>
Add one script tag to the top of your page:<br>
<code>https://cdn.hibikihtml.com/hibiki/v0.3.2/hibiki-prod.min.js</code>
<code>https://cdn.hibikihtml.com/hibiki/v0.3.3/hibiki-prod.min.js</code>
</p>
<p>
Wrap any portion of your content with a &lt;template&nbsp;hibiki&gt; tag and you have your first
Expand Down
110 changes: 89 additions & 21 deletions src/hibiki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {parseHtml} from "./html-parser";
import {HibikiState, DataEnvironment} from "./state";
import * as ReactDOM from "react-dom";
import {HibikiRootNode, CORE_LIBRARY} from "./nodes";
import {deepTextContent, evalDeepTextContent, isObject, bindLibContext} from "./utils";
import {deepTextContent, evalDeepTextContent, isObject, bindLibContext, callHook} from "./utils";
import merge from "lodash/merge";
import type {HibikiConfig, Hibiki, HibikiExtState, ReactClass, LibraryType, HibikiGlobalConfig} from "./types";
import type {HibikiNode} from "./html-parser";
Expand All @@ -33,12 +33,15 @@ function errorWithCause(message : string, cause : Error) {
throw new Error(message, {cause: cause}); // ES6 error with cause
}


function getGlobalConfig() : HibikiGlobalConfig {
let rtn : HibikiGlobalConfig = {
noUsagePing: false,
noWelcomeMessage: false,
libraryRoot: DEFAULT_LIBRARY_ROOT,
useDevLibraryBuilds: false,
preRenderHook: null,
postRenderHook: null,
};
if (window.HibikiGlobalConfig != null && typeof(window.HibikiGlobalConfig) === "object") {
rtn = Object.assign(rtn, window.HibikiGlobalConfig);
Expand Down Expand Up @@ -88,6 +91,9 @@ function readHibikiConfigFromOuterHtml(htmlElem : string | HTMLElement) : Hibiki
if (htmlElem.hasAttribute("nodatamergefromhtml")) {
rtn.noDataMergeFromHtml = true;
}
if (htmlElem.hasAttribute("name")) {
rtn.stateName = htmlElem.getAttribute("name");
}
return rtn;
}

Expand All @@ -99,38 +105,49 @@ let createState = function createState(config : HibikiConfig, html : string | HT
config = config || {};
initialData = initialData || {};
let htmlObj : HibikiNode = null;
if (html != null) {
if (config.htmlSrc != null) {
htmlObj = parseHtml(config.htmlSrc);
bindLibContext(htmlObj, "main");
}
else if (html != null) {
htmlObj = parseHtml(html);
bindLibContext(htmlObj, "main");
}
state.setHtml(htmlObj);
let configFromOuterHtml = readHibikiConfigFromOuterHtml(html);
config = merge(config, configFromOuterHtml);
config = merge({}, config, configFromOuterHtml);
let hibikiOpts = readHibikiOptsFromHtml(htmlObj);
if (!config.noConfigMergeFromHtml) {
config = merge((hibikiOpts.config ?? {}), config);
config = merge({}, (hibikiOpts.config ?? {}), config);
}
state.setConfig(config);
if (!config.noDataMergeFromHtml) {
initialData = merge((hibikiOpts.initialData ?? {}), initialData);
initialData = merge({}, (config.initialData ?? {}), (hibikiOpts.initialData ?? {}), initialData);
}
state.setGlobalData(initialData);
if (state.getStateName() != null) {
window.Hibiki.States[state.getStateName()] = state;
}
return state.getExtState();
}
createState = mobx.action(createState);

function render(elem : HTMLElement, state : HibikiExtState) {
let parentHtmlTag = elem.parentElement.tagName.toLowerCase();
let props = {hibikiState: state, parentHtmlTag: parentHtmlTag};
let props = {hibikiState: state, parentHtmlTag: parentHtmlTag, htmlElem: elem};
let reactElem = React.createElement(HibikiRootNode, props, null);
let gc = getGlobalConfig();
if (gc.preRenderHook != null) {
callHook("preRenderHook", gc.preRenderHook, state, elem);
}
state.setInitCallback(() => {
ReactDOM.render(reactElem, elem);
elem.classList.remove("hibiki-cloak");
});
state.initialize(false);
}

function loadTag(elem : HTMLElement) : HibikiExtState {
async function loadTag(elem : HTMLElement) : Promise<HibikiExtState> {
if (elem.hasAttribute("loaded")) {
console.log("Hibiki tag already loaded", elem);
return null;
Expand All @@ -140,9 +157,10 @@ function loadTag(elem : HTMLElement) : HibikiExtState {
console.log("Hibiki cannot render directly into <body> tag, create a tag under <body> to render to");
return null;
}
if (elem.tagName.toLowerCase() === "template") {

let renderNode : HTMLElement = null;
if (elem.tagName.toLowerCase() === "template" || elem.tagName.toLowerCase() === "script") {
let forElemId = elem.getAttribute("for");
let renderNode = null;
if (forElemId != null) {
renderNode = document.getElementById(forElemId);
}
Expand All @@ -161,30 +179,80 @@ function loadTag(elem : HTMLElement) : HibikiExtState {
elem.parentNode.insertBefore(renderNode, elem.nextSibling); // insert after elem
}
}
let state = createState({}, elem, null);
render(renderNode, state);
return state;
}
else {
let state = createState({}, elem, null);
render(elem, state);
return state;
renderNode = elem;
}
let config : HibikiConfig = {};
let psrcs = fetchRemoteSrcs(config, elem);
if (psrcs != null) {
await psrcs;
}
let state = createState(config, elem, null);
render(renderNode, state);
return state;
}

function fetchRemoteSrcs(config : HibikiConfig, elem : HTMLElement) : Promise<any> {
let srcAttr = elem.getAttribute("hibikisrc") ?? elem.getAttribute("src");
let dataAttr = elem.getAttribute("datasrc");
if (srcAttr == null && dataAttr == null) {
return null;
}
let parr : Promise<any>[] = [];
if (srcAttr != null) {
let psrc = fetch(srcAttr).then((resp) => {
if (!resp.ok) {
throw new Error(sprintf("Bad fetch response for hibiki src url '%s': %d %s", srcAttr, resp.status, resp.statusText));
}
let ctype = resp.headers.get("Content-Type");
if (!ctype.startsWith("text/")) {
throw new Error(sprintf("Bad fetch response for hibiki src url '%s', non-text mime-type: '%s'", srcAttr, ctype));
}
return resp.text();
}).then((text) => {
config.htmlSrc = text;
});
parr.push(psrc);
}
if (dataAttr != null) {
let pdata = fetch(dataAttr).then((resp) => {
if (!resp.ok) {
throw new Error(sprintf("Bad fetch response for hibiki data url '%s': %d %s", dataAttr, resp.status, resp.statusText));
}
let ctype = resp.headers.get("Content-Type");
if (!ctype.startsWith("application/json")) {
throw new Error(sprintf("Bad fetch response for hibiki data url '%s', non 'application/json' type: '%s'", dataAttr, ctype));
}
return resp.json();
}).then((data) => {
config.initialData = data;
});
parr.push(pdata);
}
return Promise.all(parr);
}

function autoloadTags() : void {
let elems = document.querySelectorAll("hibiki, template[hibiki]");
async function autoloadTagsAsync() : Promise<void> {
let elems = document.querySelectorAll("hibiki, template[hibiki], script[type='text/hibiki-html']");
for (let i=0; i<elems.length; i++) {
let elem : HTMLElement = elems[i] as HTMLElement;
if (elem.hasAttribute("noautoload")) {
continue;
}
let state = loadTag(elem);
if (elem.hasAttribute("name")) {
window.Hibiki.States[elem.getAttribute("name")] = state;
}
let state = await loadTag(elem);
window.HibikiState = state;
}
return;
}

function autoloadTags() : Promise<boolean> {
let p = autoloadTagsAsync()
return p.then(() => { return true; })
.catch((e) => {
console.log("ERROR calling Hibiki.autoloadTags", e);
return false;
});
}

let LocalHandlers : Record<string, (HibikiRequest) => any> = {};
Expand Down
6 changes: 5 additions & 1 deletion src/html-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,11 @@ class HtmlParser {

parseHtml(input : string | HTMLElement, sourceName? : string) : HibikiNode {
let elem = null;
if (input instanceof HTMLElement) {
if (input instanceof HTMLElement && input.tagName.toLowerCase() === "script") {
elem = document.createElement("div");
elem.innerHTML = input.textContent;
}
else if (input instanceof HTMLElement) {
elem = input;
}
else {
Expand Down
Loading

0 comments on commit e5cc5db

Please sign in to comment.