commit 36f24a1800b6000eaf6f1634c0e4048d604e718b parent 861009e8f6898a4dc5ae125e717351a0ff19e9d3 Author: weetat <wtenison@protonmail.com> Date: Fri, 11 Apr 2025 23:39:55 +0000 Flatten web/global and remember theme choice Move templates, scripts, styles, the favicon and fmd.wasm back to the top of web/global. The theme toggle is saved in sessionStorage, and links point at test.wtenison.com for now. Diffstat:
20 files changed, 669 insertions(+), 656 deletions(-)
diff --git a/src/network/routes.go b/src/network/routes.go @@ -21,7 +21,7 @@ func makeServerRoutes() { mux.Handle("/data/", http.StripPrefix("/data/", http.FileServer(http.Dir(mfs.Dirs["data"])))) mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, filepath.Join(mfs.Dirs["web"], "/global/images/favicon.ico")) + http.ServeFile(w, r, filepath.Join(mfs.Dirs["web"], "/global/favicon.ico")) }) mux.HandleFunc("/home", handlePageHome) diff --git a/web/edit/default.fmd b/web/edit/default.fmd @@ -1,7 +1,7 @@ _(class:"--hide"):{Click on the 'Render' button to see rendered the HTML output of this text} h1:{- Welcome -} _(class:"--set-size_5-4-font"):{OVERVIEW} sup:{This is a info page for the b:{FMD interpreter}.} -The b:{FMD interpreter} is being developed for the a(link:"https://miki.wtenison.com/home"):{Miki Project} to provide a convenient Markdown language that is easy to parse, straightforward to read, and able to work with HTML. This is a application being developed for education and personal use. FMD runs on the browser via a WASM module. +The b:{FMD interpreter} is being developed for the a(link:"https://test.wtenison.com/home"):{Miki Project} to provide a convenient Markdown language that is easy to parse, straightforward to read, and able to work with HTML. This is a application being developed for education and personal use. FMD runs on the browser via a WASM module. Using the b:{Text Pad} sup:{(click b:RETURN above if in b:OUTPUT view)}, you may input almost any character without stirring the ire of the interpreter. The symbols you need to look out are: code:{ : "{ }" ( ) } diff --git a/web/edit/legend.fmd b/web/edit/legend.fmd @@ -1,6 +1,6 @@ _(class:"--set-size_5-4-font"):{OVERVIEW} sup:{This is a info page for the b:{FMD interpreter}.} -The b:{FMD interpreter} is being developed for the a(link:"https://miki.wtenison.com/home"):{Miki Project} to provide a convenient Markdown language that is easy to parse, straightforward to read, and able to work with HTML. This is a application being developed for education and personal use. FMD runs on the browser via a WASM module. +The b:{FMD interpreter} is being developed for the a(link:"https://test.wtenison.com/home"):{Miki Project} to provide a convenient Markdown language that is easy to parse, straightforward to read, and able to work with HTML. This is a application being developed for education and personal use. FMD runs on the browser via a WASM module. Using the b:{Text Pad} sup:{(click b:RETURN above if in b:OUTPUT view)}, you may input almost any character without stirring the ire of the interpreter. The symbols you need to look out are: code:{ : "{ }" ( ) } @@ -40,7 +40,7 @@ code:{a} : b:{Link} - Creates a link. sm:i:{Example:} code:{"a(link:"https://info.wtenison.com"):{Some Guy's Website}"} code:{img} : b:{Image} - Creates an image link. -sm:i:{Example:} code:{"img(link:"https://miki.wtenison.com/my-image.png"):{My Image}"} +sm:i:{Example:} code:{"img(link:"https://test.wtenison.com/my-image.png"):{My Image}"} code:{col} : b:{Column} - Creates a block that will contain column elements. sm:i:{Example:} code:{"col:{ _:{column A content} _:{column B content}}"} diff --git a/web/edit/script.js b/web/edit/script.js @@ -12,7 +12,9 @@ WebAssembly.instantiateStreaming(fetch("web/global/fmd/fmd.wasm"), go.importObje .then((result) => { go.run(result.instance); - fmdViewDescriptor.innerHTML = interpretFMD("span(class:\"__descriptor\"):{b:{TEXT PAD}<br>span:{Input fmd text here ▾}}") + makeFMDState(0) + + fmdViewDescriptor.innerHTML = interpretFMD(0, "span(class:\"__descriptor\"):{b:{TEXT PAD}<br>span:{Input fmd text here ▾}}") loadFMDFileRaw("https://test.wtenison.com/web/edit/default.fmd", fmdViewPanelSet["text-editor"].content) diff --git a/web/global/images/favicon.ico b/web/global/favicon.ico Binary files differ. diff --git a/web/global/fmd.tmpl.html b/web/global/fmd.tmpl.html @@ -0,0 +1,3 @@ +{{define "fmd"}} +<script src="/web/global/wasm_exec.js" defer></script> +{{end}} +\ No newline at end of file diff --git a/web/global/fmd.wasm b/web/global/fmd.wasm Binary files differ. diff --git a/web/global/fmd/fmd.wasm b/web/global/fmd/fmd.wasm Binary files differ. diff --git a/web/global/fmd/wasm_exec.js b/web/global/fmd/wasm_exec.js @@ -1,573 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. -// -// This file has been modified for use by the TinyGo compiler. - -(() => { - // Map multiple JavaScript environments to a single common API, - // preferring web standards over Node.js API. - // - // Environments considered: - // - Browsers - // - Node.js - // - Electron - // - Parcel - - if (typeof global !== "undefined") { - // global already exists - } else if (typeof window !== "undefined") { - window.global = window; - } else if (typeof self !== "undefined") { - self.global = self; - } else { - throw new Error("cannot export Go (neither global, window nor self is defined)"); - } - - if (!global.require && typeof require !== "undefined") { - global.require = require; - } - - if (!global.fs && global.require) { - global.fs = require("node:fs"); - } - - const enosys = () => { - const err = new Error("not implemented"); - err.code = "ENOSYS"; - return err; - }; - - if (!global.fs) { - let outputBuf = ""; - global.fs = { - constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused - writeSync(fd, buf) { - outputBuf += decoder.decode(buf); - const nl = outputBuf.lastIndexOf("\n"); - if (nl != -1) { - console.log(outputBuf.substr(0, nl)); - outputBuf = outputBuf.substr(nl + 1); - } - return buf.length; - }, - write(fd, buf, offset, length, position, callback) { - if (offset !== 0 || length !== buf.length || position !== null) { - callback(enosys()); - return; - } - const n = this.writeSync(fd, buf); - callback(null, n); - }, - chmod(path, mode, callback) { callback(enosys()); }, - chown(path, uid, gid, callback) { callback(enosys()); }, - close(fd, callback) { callback(enosys()); }, - fchmod(fd, mode, callback) { callback(enosys()); }, - fchown(fd, uid, gid, callback) { callback(enosys()); }, - fstat(fd, callback) { callback(enosys()); }, - fsync(fd, callback) { callback(null); }, - ftruncate(fd, length, callback) { callback(enosys()); }, - lchown(path, uid, gid, callback) { callback(enosys()); }, - link(path, link, callback) { callback(enosys()); }, - lstat(path, callback) { callback(enosys()); }, - mkdir(path, perm, callback) { callback(enosys()); }, - open(path, flags, mode, callback) { callback(enosys()); }, - read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, - readdir(path, callback) { callback(enosys()); }, - readlink(path, callback) { callback(enosys()); }, - rename(from, to, callback) { callback(enosys()); }, - rmdir(path, callback) { callback(enosys()); }, - stat(path, callback) { callback(enosys()); }, - symlink(path, link, callback) { callback(enosys()); }, - truncate(path, length, callback) { callback(enosys()); }, - unlink(path, callback) { callback(enosys()); }, - utimes(path, atime, mtime, callback) { callback(enosys()); }, - }; - } - - if (!global.process) { - global.process = { - getuid() { return -1; }, - getgid() { return -1; }, - geteuid() { return -1; }, - getegid() { return -1; }, - getgroups() { throw enosys(); }, - pid: -1, - ppid: -1, - umask() { throw enosys(); }, - cwd() { throw enosys(); }, - chdir() { throw enosys(); }, - } - } - - if (!global.crypto) { - const nodeCrypto = require("node:crypto"); - global.crypto = { - getRandomValues(b) { - nodeCrypto.randomFillSync(b); - }, - }; - } - - if (!global.performance) { - global.performance = { - now() { - const [sec, nsec] = process.hrtime(); - return sec * 1000 + nsec / 1000000; - }, - }; - } - - if (!global.TextEncoder) { - global.TextEncoder = require("node:util").TextEncoder; - } - - if (!global.TextDecoder) { - global.TextDecoder = require("node:util").TextDecoder; - } - - // End of polyfills for common API. - - const encoder = new TextEncoder("utf-8"); - const decoder = new TextDecoder("utf-8"); - let reinterpretBuf = new DataView(new ArrayBuffer(8)); - var logLine = []; - const wasmExit = {}; // thrown to exit via proc_exit (not an error) - - global.Go = class { - constructor() { - this._callbackTimeouts = new Map(); - this._nextCallbackTimeoutID = 1; - - const mem = () => { - // The buffer may change when requesting more memory. - return new DataView(this._inst.exports.memory.buffer); - } - - const unboxValue = (v_ref) => { - reinterpretBuf.setBigInt64(0, v_ref, true); - const f = reinterpretBuf.getFloat64(0, true); - if (f === 0) { - return undefined; - } - if (!isNaN(f)) { - return f; - } - - const id = v_ref & 0xffffffffn; - return this._values[id]; - } - - - const loadValue = (addr) => { - let v_ref = mem().getBigUint64(addr, true); - return unboxValue(v_ref); - } - - const boxValue = (v) => { - const nanHead = 0x7FF80000n; - - if (typeof v === "number") { - if (isNaN(v)) { - return nanHead << 32n; - } - if (v === 0) { - return (nanHead << 32n) | 1n; - } - reinterpretBuf.setFloat64(0, v, true); - return reinterpretBuf.getBigInt64(0, true); - } - - switch (v) { - case undefined: - return 0n; - case null: - return (nanHead << 32n) | 2n; - case true: - return (nanHead << 32n) | 3n; - case false: - return (nanHead << 32n) | 4n; - } - - let id = this._ids.get(v); - if (id === undefined) { - id = this._idPool.pop(); - if (id === undefined) { - id = BigInt(this._values.length); - } - this._values[id] = v; - this._goRefCounts[id] = 0; - this._ids.set(v, id); - } - this._goRefCounts[id]++; - let typeFlag = 1n; - switch (typeof v) { - case "string": - typeFlag = 2n; - break; - case "symbol": - typeFlag = 3n; - break; - case "function": - typeFlag = 4n; - break; - } - return id | ((nanHead | typeFlag) << 32n); - } - - const storeValue = (addr, v) => { - let v_ref = boxValue(v); - mem().setBigUint64(addr, v_ref, true); - } - - const loadSlice = (array, len, cap) => { - return new Uint8Array(this._inst.exports.memory.buffer, array, len); - } - - const loadSliceOfValues = (array, len, cap) => { - const a = new Array(len); - for (let i = 0; i < len; i++) { - a[i] = loadValue(array + i * 8); - } - return a; - } - - const loadString = (ptr, len) => { - return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); - } - - const timeOrigin = Date.now() - performance.now(); - this.importObject = { - wasi_snapshot_preview1: { - // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write - fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) { - let nwritten = 0; - if (fd == 1) { - for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) { - let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32 - let ptr = mem().getUint32(iov_ptr + 0, true); - let len = mem().getUint32(iov_ptr + 4, true); - nwritten += len; - for (let i=0; i<len; i++) { - let c = mem().getUint8(ptr+i); - if (c == 13) { // CR - // ignore - } else if (c == 10) { // LF - // write line - let line = decoder.decode(new Uint8Array(logLine)); - logLine = []; - console.log(line); - } else { - logLine.push(c); - } - } - } - } else { - console.error('invalid file descriptor:', fd); - } - mem().setUint32(nwritten_ptr, nwritten, true); - return 0; - }, - fd_close: () => 0, // dummy - fd_fdstat_get: () => 0, // dummy - fd_seek: () => 0, // dummy - proc_exit: (code) => { - this.exited = true; - this.exitCode = code; - this._resolveExitPromise(); - throw wasmExit; - }, - random_get: (bufPtr, bufLen) => { - crypto.getRandomValues(loadSlice(bufPtr, bufLen)); - return 0; - }, - }, - gojs: { - // func ticks() float64 - "runtime.ticks": () => { - return timeOrigin + performance.now(); - }, - - // func sleepTicks(timeout float64) - "runtime.sleepTicks": (timeout) => { - // Do not sleep, only reactivate scheduler after the given timeout. - setTimeout(() => { - if (this.exited) return; - try { - this._inst.exports.go_scheduler(); - } catch (e) { - if (e !== wasmExit) throw e; - } - }, timeout); - }, - - // func finalizeRef(v ref) - "syscall/js.finalizeRef": (v_ref) => { - // Note: TinyGo does not support finalizers so this should never be - // called. - // console.error('syscall/js.finalizeRef not implemented'); - }, - - // func stringVal(value string) ref - "syscall/js.stringVal": (value_ptr, value_len) => { - const s = loadString(value_ptr, value_len); - return boxValue(s); - }, - - // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { - let prop = loadString(p_ptr, p_len); - let v = unboxValue(v_ref); - let result = Reflect.get(v, prop); - return boxValue(result); - }, - - // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { - const v = unboxValue(v_ref); - const p = loadString(p_ptr, p_len); - const x = unboxValue(x_ref); - Reflect.set(v, p, x); - }, - - // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { - const v = unboxValue(v_ref); - const p = loadString(p_ptr, p_len); - Reflect.deleteProperty(v, p); - }, - - // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (v_ref, i) => { - return boxValue(Reflect.get(unboxValue(v_ref), i)); - }, - - // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (v_ref, i, x_ref) => { - Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); - }, - - // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { - const v = unboxValue(v_ref); - const name = loadString(m_ptr, m_len); - const args = loadSliceOfValues(args_ptr, args_len, args_cap); - try { - const m = Reflect.get(v, name); - storeValue(ret_addr, Reflect.apply(m, v, args)); - mem().setUint8(ret_addr + 8, 1); - } catch (err) { - storeValue(ret_addr, err); - mem().setUint8(ret_addr + 8, 0); - } - }, - - // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { - try { - const v = unboxValue(v_ref); - const args = loadSliceOfValues(args_ptr, args_len, args_cap); - storeValue(ret_addr, Reflect.apply(v, undefined, args)); - mem().setUint8(ret_addr + 8, 1); - } catch (err) { - storeValue(ret_addr, err); - mem().setUint8(ret_addr + 8, 0); - } - }, - - // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { - const v = unboxValue(v_ref); - const args = loadSliceOfValues(args_ptr, args_len, args_cap); - try { - storeValue(ret_addr, Reflect.construct(v, args)); - mem().setUint8(ret_addr + 8, 1); - } catch (err) { - storeValue(ret_addr, err); - mem().setUint8(ret_addr+ 8, 0); - } - }, - - // func valueLength(v ref) int - "syscall/js.valueLength": (v_ref) => { - return unboxValue(v_ref).length; - }, - - // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (ret_addr, v_ref) => { - const s = String(unboxValue(v_ref)); - const str = encoder.encode(s); - storeValue(ret_addr, str); - mem().setInt32(ret_addr + 8, str.length, true); - }, - - // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { - const str = unboxValue(v_ref); - loadSlice(slice_ptr, slice_len, slice_cap).set(str); - }, - - // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (v_ref, t_ref) => { - return unboxValue(v_ref) instanceof unboxValue(t_ref); - }, - - // func copyBytesToGo(dst []byte, src ref) (int, bool) - "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { - let num_bytes_copied_addr = ret_addr; - let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable - - const dst = loadSlice(dest_addr, dest_len); - const src = unboxValue(src_ref); - if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { - mem().setUint8(returned_status_addr, 0); // Return "not ok" status - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - mem().setUint32(num_bytes_copied_addr, toCopy.length, true); - mem().setUint8(returned_status_addr, 1); // Return "ok" status - }, - - // copyBytesToJS(dst ref, src []byte) (int, bool) - // Originally copied from upstream Go project, then modified: - // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 - "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { - let num_bytes_copied_addr = ret_addr; - let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable - - const dst = unboxValue(dst_ref); - const src = loadSlice(src_addr, src_len); - if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { - mem().setUint8(returned_status_addr, 0); // Return "not ok" status - return; - } - const toCopy = src.subarray(0, dst.length); - dst.set(toCopy); - mem().setUint32(num_bytes_copied_addr, toCopy.length, true); - mem().setUint8(returned_status_addr, 1); // Return "ok" status - }, - } - }; - - // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. - // For compatibility, we use both as long as Go 1.20 is supported. - this.importObject.env = this.importObject.gojs; - } - - async run(instance) { - this._inst = instance; - this._values = [ // JS values that Go currently has references to, indexed by reference id - NaN, - 0, - null, - true, - false, - global, - this, - ]; - this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id - this._ids = new Map(); // mapping from JS values to reference ids - this._idPool = []; // unused ids that have been garbage collected - this.exited = false; // whether the Go program has exited - this.exitCode = 0; - - if (this._inst.exports._start) { - let exitPromise = new Promise((resolve, reject) => { - this._resolveExitPromise = resolve; - }); - - // Run program, but catch the wasmExit exception that's thrown - // to return back here. - try { - this._inst.exports._start(); - } catch (e) { - if (e !== wasmExit) throw e; - } - - await exitPromise; - return this.exitCode; - } else { - this._inst.exports._initialize(); - } - } - - _resume() { - if (this.exited) { - throw new Error("Go program has already exited"); - } - try { - this._inst.exports.resume(); - } catch (e) { - if (e !== wasmExit) throw e; - } - if (this.exited) { - this._resolveExitPromise(); - } - } - - _makeFuncWrapper(id) { - const go = this; - return function () { - const event = { id: id, this: this, args: arguments }; - go._pendingEvent = event; - go._resume(); - return event.result; - }; - } - } - - if ( - global.require && - global.require.main === module && - global.process && - global.process.versions && - !global.process.versions.electron - ) { - if (process.argv.length != 3) { - console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); - process.exit(1); - } - - const go = new Go(); - WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => { - let exitCode = await go.run(result.instance); - process.exit(exitCode); - }).catch((err) => { - console.error(err); - process.exit(1); - }); - } -})(); - - -function loadFMDFileRaw(url, container) { - return fetch(url) - .then(response => response.text()) - .then(text => { - // Load the text into the text editor & buffer - _textBUffer = text - }) - .then(() => { - // If FMD is valid, render to HTML - container.innerHTML = _textBUffer - }) -} - -function loadFMDFileHTML(url, container) { - return fetch(url) - .then(response => response.text()) - .then(text => { - // Load the text into the text editor & buffer - _textBUffer = text - }). - then(() => { - // If FMD is valid, render to HTML - if (isValidFMD(_textBUffer)) { - container.innerHTML = interpretFMD(_textBUffer) - } else { - container.innerHTML = "FMD Parsing Failed" - } - }) - .catch(err => console.error('_ERROR:fetch:failed to load FMD url:', err)); -} diff --git a/web/global/header.tmpl.html b/web/global/header.tmpl.html @@ -0,0 +1,40 @@ +{{define "header"}} +<header class="--center-on-body"> + + <div id="header-title" class="_content_"><span id="site-name"><i>miki.wtenison</i></span> + <span id="site-page" class="--set-size_font-to-screen">{{.}}</span> + </div> + + <nav id="header-nav-view" class="__view __mcol"> + + <div id="main-menu-set" class="__collapsible"> + <button type="button" class="_collapsible-toggle_">MENU</button> + <div class="_collapsible-content-view_"> + <div class="__mlist"> + <a class="_content_ __border __bttn --bg_theme" href="https://test.wtenison.com/home">Home</a> + <a class="_content_ __border __bttn --bg_theme" href="https://wtenison.com">Portal + <span class="__descriptor --set-size_half-font">(<b>WTENISON.COM</b>) + </span> + </a> + <div class="_content_ __border --bg_theme"> + <s><b>SEARCH</b></s> + </div> + </div> + </div> + </div> + + <div id="options-set" class="__collapsible"> + <button type="button" class="_collapsible-toggle_">OPTIONS</button> + <div class="_collapsible-content-view_"> + <div class="__mlist"> + <button type="button" id="swap-theme-bttn"><b>SWAP THEME</b></button> + </div> + </div> + </div> + </div> + </nav> + + <hr> + +</header> +{{end}} +\ No newline at end of file diff --git a/web/global/meta.tmpl.html b/web/global/meta.tmpl.html @@ -0,0 +1,5 @@ +{{define "meta"}} +<meta name="viewport" charset="utf-8" content="width=device-width, initial-scale=1.0" /> +<link rel="stylesheet" type="text/css" href="/web/global/styles.css" /> +<script src="/web/global/script.js" defer></script> +{{end}} +\ No newline at end of file diff --git a/web/global/script.js b/web/global/script.js @@ -0,0 +1,32 @@ +// GLOBAL SETTINGS + +window.onload = () => { + if (sessionStorage.getItem("theme") != null) { + document.body.style.colorScheme = sessionStorage.getItem("theme") + } +} + +// COLLAPSIBLE // + +_collapsibleToggles = document.getElementsByClassName("_collapsible-toggle_") +for (let i = 0; i < _collapsibleToggles.length; i++) { + _collapsibleToggles[i].addEventListener("click", function () { + _collapsibleToggles[i].classList.toggle("--open") + _collapsibleContent = _collapsibleToggles[i].nextElementSibling + + if (_collapsibleToggles[i].classList.contains("--open")) { + _collapsibleContent.style.height = _collapsibleContent.scrollHeight + "px" + } else { + _collapsibleContent.style.height = "0px" + } + }) +} + +// SWAP THEME // + +document.getElementById("swap-theme-bttn").onclick = (event) => { + document.body.style.colorScheme = + (document.body.style.colorScheme == "light" || + document.body.style.colorScheme == "") ? "dark" : "light" + sessionStorage.setItem("theme", document.body.style.colorScheme) +} +\ No newline at end of file diff --git a/web/global/scripts/script.js b/web/global/scripts/script.js @@ -1,23 +0,0 @@ -// COLLAPSIBLE // - -_collapsibleToggles = document.getElementsByClassName("_collapsible-toggle_") -for (let i = 0; i < _collapsibleToggles.length; i++) { - _collapsibleToggles[i].addEventListener("click", function () { - _collapsibleToggles[i].classList.toggle("--open") - _collapsibleContent = _collapsibleToggles[i].nextElementSibling - - if (_collapsibleToggles[i].classList.contains("--open")) { - _collapsibleContent.style.height = _collapsibleContent.scrollHeight + "px" - } else { - _collapsibleContent.style.height = "0px" - } - }) -} - -// SWAP THEME // - -document.getElementById("swap-theme-bttn").onclick = (event) => { - document.body.style.colorScheme = - (document.body.style.colorScheme == "light" || - document.body.style.colorScheme == "") ? "dark" : "light" -} -\ No newline at end of file diff --git a/web/global/styles/styles.css b/web/global/styles.css diff --git a/web/global/templates/fmd.tmpl.html b/web/global/templates/fmd.tmpl.html @@ -1,3 +0,0 @@ -{{define "fmd"}} -<script src="/web/global/fmd/wasm_exec.js" defer></script> -{{end}} -\ No newline at end of file diff --git a/web/global/templates/header.tmpl.html b/web/global/templates/header.tmpl.html @@ -1,40 +0,0 @@ -{{define "header"}} -<header class="--center-on-body"> - - <div id="header-title" class="_content_"><span id="site-name"><i>miki.wtenison</i></span> - <span id="site-page" class="--set-size_font-to-screen">{{.}}</span> - </div> - - <nav id="header-nav-view" class="__view __mcol"> - - <div id="main-menu-set" class="__collapsible"> - <button type="button" class="_collapsible-toggle_">MENU</button> - <div class="_collapsible-content-view_"> - <div class="__mlist"> - <a class="_content_ __border __bttn --bg_theme" href="https://miki.wtenison.com/home">Home</a> - <a class="_content_ __border __bttn --bg_theme" href="https://wtenison.com">Portal - <span class="__descriptor --set-size_half-font">(<b>WTENISON.COM</b>) - </span> - </a> - <div class="_content_ __border --bg_theme"> - <s><b>SEARCH</b></s> - </div> - </div> - </div> - </div> - - <div id="options-set" class="__collapsible"> - <button type="button" class="_collapsible-toggle_">OPTIONS</button> - <div class="_collapsible-content-view_"> - <div class="__mlist"> - <button type="button" id="swap-theme-bttn"><b>SWAP THEME</b></button> - </div> - </div> - </div> - </div> - </nav> - - <hr> - -</header> -{{end}} -\ No newline at end of file diff --git a/web/global/templates/meta.tmpl.html b/web/global/templates/meta.tmpl.html @@ -1,5 +0,0 @@ -{{define "meta"}} -<meta name="viewport" charset="utf-8" content="width=device-width, initial-scale=1.0" /> -<link rel="stylesheet" type="text/css" href="/web/global/styles/styles.css" /> -<script src="/web/global/scripts/script.js" defer></script> -{{end}} -\ No newline at end of file diff --git a/web/global/wasm_exec.js b/web/global/wasm_exec.js @@ -0,0 +1,572 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// This file has been modified for use by the TinyGo compiler. + +(() => { + // Map multiple JavaScript environments to a single common API, + // preferring web standards over Node.js API. + // + // Environments considered: + // - Browsers + // - Node.js + // - Electron + // - Parcel + + if (typeof global !== "undefined") { + // global already exists + } else if (typeof window !== "undefined") { + window.global = window; + } else if (typeof self !== "undefined") { + self.global = self; + } else { + throw new Error("cannot export Go (neither global, window nor self is defined)"); + } + + if (!global.require && typeof require !== "undefined") { + global.require = require; + } + + if (!global.fs && global.require) { + global.fs = require("node:fs"); + } + + const enosys = () => { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + return err; + }; + + if (!global.fs) { + let outputBuf = ""; + global.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substr(0, nl)); + outputBuf = outputBuf.substr(nl + 1); + } + return buf.length; + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + const n = this.writeSync(fd, buf); + callback(null, n); + }, + chmod(path, mode, callback) { callback(enosys()); }, + chown(path, uid, gid, callback) { callback(enosys()); }, + close(fd, callback) { callback(enosys()); }, + fchmod(fd, mode, callback) { callback(enosys()); }, + fchown(fd, uid, gid, callback) { callback(enosys()); }, + fstat(fd, callback) { callback(enosys()); }, + fsync(fd, callback) { callback(null); }, + ftruncate(fd, length, callback) { callback(enosys()); }, + lchown(path, uid, gid, callback) { callback(enosys()); }, + link(path, link, callback) { callback(enosys()); }, + lstat(path, callback) { callback(enosys()); }, + mkdir(path, perm, callback) { callback(enosys()); }, + open(path, flags, mode, callback) { callback(enosys()); }, + read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, + readdir(path, callback) { callback(enosys()); }, + readlink(path, callback) { callback(enosys()); }, + rename(from, to, callback) { callback(enosys()); }, + rmdir(path, callback) { callback(enosys()); }, + stat(path, callback) { callback(enosys()); }, + symlink(path, link, callback) { callback(enosys()); }, + truncate(path, length, callback) { callback(enosys()); }, + unlink(path, callback) { callback(enosys()); }, + utimes(path, atime, mtime, callback) { callback(enosys()); }, + }; + } + + if (!global.process) { + global.process = { + getuid() { return -1; }, + getgid() { return -1; }, + geteuid() { return -1; }, + getegid() { return -1; }, + getgroups() { throw enosys(); }, + pid: -1, + ppid: -1, + umask() { throw enosys(); }, + cwd() { throw enosys(); }, + chdir() { throw enosys(); }, + } + } + + if (!global.crypto) { + const nodeCrypto = require("node:crypto"); + global.crypto = { + getRandomValues(b) { + nodeCrypto.randomFillSync(b); + }, + }; + } + + if (!global.performance) { + global.performance = { + now() { + const [sec, nsec] = process.hrtime(); + return sec * 1000 + nsec / 1000000; + }, + }; + } + + if (!global.TextEncoder) { + global.TextEncoder = require("node:util").TextEncoder; + } + + if (!global.TextDecoder) { + global.TextDecoder = require("node:util").TextDecoder; + } + + // End of polyfills for common API. + + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + let reinterpretBuf = new DataView(new ArrayBuffer(8)); + var logLine = []; + const wasmExit = {}; // thrown to exit via proc_exit (not an error) + + global.Go = class { + constructor() { + this._callbackTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const mem = () => { + // The buffer may change when requesting more memory. + return new DataView(this._inst.exports.memory.buffer); + } + + const unboxValue = (v_ref) => { + reinterpretBuf.setBigInt64(0, v_ref, true); + const f = reinterpretBuf.getFloat64(0, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = v_ref & 0xffffffffn; + return this._values[id]; + } + + + const loadValue = (addr) => { + let v_ref = mem().getBigUint64(addr, true); + return unboxValue(v_ref); + } + + const boxValue = (v) => { + const nanHead = 0x7FF80000n; + + if (typeof v === "number") { + if (isNaN(v)) { + return nanHead << 32n; + } + if (v === 0) { + return (nanHead << 32n) | 1n; + } + reinterpretBuf.setFloat64(0, v, true); + return reinterpretBuf.getBigInt64(0, true); + } + + switch (v) { + case undefined: + return 0n; + case null: + return (nanHead << 32n) | 2n; + case true: + return (nanHead << 32n) | 3n; + case false: + return (nanHead << 32n) | 4n; + } + + let id = this._ids.get(v); + if (id === undefined) { + id = this._idPool.pop(); + if (id === undefined) { + id = BigInt(this._values.length); + } + this._values[id] = v; + this._goRefCounts[id] = 0; + this._ids.set(v, id); + } + this._goRefCounts[id]++; + let typeFlag = 1n; + switch (typeof v) { + case "string": + typeFlag = 2n; + break; + case "symbol": + typeFlag = 3n; + break; + case "function": + typeFlag = 4n; + break; + } + return id | ((nanHead | typeFlag) << 32n); + } + + const storeValue = (addr, v) => { + let v_ref = boxValue(v); + mem().setBigUint64(addr, v_ref, true); + } + + const loadSlice = (array, len, cap) => { + return new Uint8Array(this._inst.exports.memory.buffer, array, len); + } + + const loadSliceOfValues = (array, len, cap) => { + const a = new Array(len); + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8); + } + return a; + } + + const loadString = (ptr, len) => { + return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); + } + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + wasi_snapshot_preview1: { + // https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write + fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) { + let nwritten = 0; + if (fd == 1) { + for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) { + let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32 + let ptr = mem().getUint32(iov_ptr + 0, true); + let len = mem().getUint32(iov_ptr + 4, true); + nwritten += len; + for (let i=0; i<len; i++) { + let c = mem().getUint8(ptr+i); + if (c == 13) { // CR + // ignore + } else if (c == 10) { // LF + // write line + let line = decoder.decode(new Uint8Array(logLine)); + logLine = []; + console.log(line); + } else { + logLine.push(c); + } + } + } + } else { + console.error('invalid file descriptor:', fd); + } + mem().setUint32(nwritten_ptr, nwritten, true); + return 0; + }, + fd_close: () => 0, // dummy + fd_fdstat_get: () => 0, // dummy + fd_seek: () => 0, // dummy + proc_exit: (code) => { + this.exited = true; + this.exitCode = code; + this._resolveExitPromise(); + throw wasmExit; + }, + random_get: (bufPtr, bufLen) => { + crypto.getRandomValues(loadSlice(bufPtr, bufLen)); + return 0; + }, + }, + gojs: { + // func ticks() float64 + "runtime.ticks": () => { + return timeOrigin + performance.now(); + }, + + // func sleepTicks(timeout float64) + "runtime.sleepTicks": (timeout) => { + // Do not sleep, only reactivate scheduler after the given timeout. + setTimeout(() => { + if (this.exited) return; + try { + this._inst.exports.go_scheduler(); + } catch (e) { + if (e !== wasmExit) throw e; + } + }, timeout); + }, + + // func finalizeRef(v ref) + "syscall/js.finalizeRef": (v_ref) => { + // Note: TinyGo does not support finalizers so this should never be + // called. + // console.error('syscall/js.finalizeRef not implemented'); + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (value_ptr, value_len) => { + const s = loadString(value_ptr, value_len); + return boxValue(s); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { + let prop = loadString(p_ptr, p_len); + let v = unboxValue(v_ref); + let result = Reflect.get(v, prop); + return boxValue(result); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { + const v = unboxValue(v_ref); + const p = loadString(p_ptr, p_len); + const x = unboxValue(x_ref); + Reflect.set(v, p, x); + }, + + // func valueDelete(v ref, p string) + "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { + const v = unboxValue(v_ref); + const p = loadString(p_ptr, p_len); + Reflect.deleteProperty(v, p); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (v_ref, i) => { + return boxValue(Reflect.get(unboxValue(v_ref), i)); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (v_ref, i, x_ref) => { + Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { + const v = unboxValue(v_ref); + const name = loadString(m_ptr, m_len); + const args = loadSliceOfValues(args_ptr, args_len, args_cap); + try { + const m = Reflect.get(v, name); + storeValue(ret_addr, Reflect.apply(m, v, args)); + mem().setUint8(ret_addr + 8, 1); + } catch (err) { + storeValue(ret_addr, err); + mem().setUint8(ret_addr + 8, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { + try { + const v = unboxValue(v_ref); + const args = loadSliceOfValues(args_ptr, args_len, args_cap); + storeValue(ret_addr, Reflect.apply(v, undefined, args)); + mem().setUint8(ret_addr + 8, 1); + } catch (err) { + storeValue(ret_addr, err); + mem().setUint8(ret_addr + 8, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { + const v = unboxValue(v_ref); + const args = loadSliceOfValues(args_ptr, args_len, args_cap); + try { + storeValue(ret_addr, Reflect.construct(v, args)); + mem().setUint8(ret_addr + 8, 1); + } catch (err) { + storeValue(ret_addr, err); + mem().setUint8(ret_addr+ 8, 0); + } + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (v_ref) => { + return unboxValue(v_ref).length; + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (ret_addr, v_ref) => { + const s = String(unboxValue(v_ref)); + const str = encoder.encode(s); + storeValue(ret_addr, str); + mem().setInt32(ret_addr + 8, str.length, true); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { + const str = unboxValue(v_ref); + loadSlice(slice_ptr, slice_len, slice_cap).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + "syscall/js.valueInstanceOf": (v_ref, t_ref) => { + return unboxValue(v_ref) instanceof unboxValue(t_ref); + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { + let num_bytes_copied_addr = ret_addr; + let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable + + const dst = loadSlice(dest_addr, dest_len); + const src = unboxValue(src_ref); + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + mem().setUint8(returned_status_addr, 0); // Return "not ok" status + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + mem().setUint32(num_bytes_copied_addr, toCopy.length, true); + mem().setUint8(returned_status_addr, 1); // Return "ok" status + }, + + // copyBytesToJS(dst ref, src []byte) (int, bool) + // Originally copied from upstream Go project, then modified: + // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 + "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { + let num_bytes_copied_addr = ret_addr; + let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable + + const dst = unboxValue(dst_ref); + const src = loadSlice(src_addr, src_len); + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + mem().setUint8(returned_status_addr, 0); // Return "not ok" status + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + mem().setUint32(num_bytes_copied_addr, toCopy.length, true); + mem().setUint8(returned_status_addr, 1); // Return "ok" status + }, + } + }; + + // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. + // For compatibility, we use both as long as Go 1.20 is supported. + this.importObject.env = this.importObject.gojs; + } + + async run(instance) { + this._inst = instance; + this._values = [ // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + global, + this, + ]; + this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map(); // mapping from JS values to reference ids + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + this.exitCode = 0; + + if (this._inst.exports._start) { + let exitPromise = new Promise((resolve, reject) => { + this._resolveExitPromise = resolve; + }); + + // Run program, but catch the wasmExit exception that's thrown + // to return back here. + try { + this._inst.exports._start(); + } catch (e) { + if (e !== wasmExit) throw e; + } + + await exitPromise; + return this.exitCode; + } else { + this._inst.exports._initialize(); + } + } + + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + try { + this._inst.exports.resume(); + } catch (e) { + if (e !== wasmExit) throw e; + } + if (this.exited) { + this._resolveExitPromise(); + } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } + } + + if ( + global.require && + global.require.main === module && + global.process && + global.process.versions && + !global.process.versions.electron + ) { + if (process.argv.length != 3) { + console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); + process.exit(1); + } + + const go = new Go(); + WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => { + let exitCode = await go.run(result.instance); + process.exit(exitCode); + }).catch((err) => { + console.error(err); + process.exit(1); + }); + } +})(); + +function loadFMDFileRaw(url, container) { + return fetch(url) + .then(response => response.text()) + .then(text => { + // Load the text into the text editor & buffer + _textBUffer = text + }) + .then(() => { + // If FMD is valid, render to HTML + container.innerHTML = _textBUffer + }) +} + +function loadFMDFileHTML(url, container) { + return fetch(url) + .then(response => response.text()) + .then(text => { + // Load the text into the text editor & buffer + _textBUffer = text + }). + then(() => { + // If FMD is valid, render to HTML + if (isFMDValid(0, _textBUffer)) { + container.innerHTML = interpretFMD(0, _textBUffer) + } else { + container.innerHTML = "FMD Parsing Failed" + } + }) + .catch(err => console.error('_ERROR:fetch:failed to load FMD url:', err)); +} diff --git a/web/home/script.js b/web/home/script.js @@ -2,13 +2,14 @@ const go = new global.Go(); -WebAssembly.instantiateStreaming(fetch("web/global/fmd/fmd.wasm"), go.importObject) +WebAssembly.instantiateStreaming(fetch("web/global/fmd.wasm"), go.importObject) .then((result) => { go.run(result.instance); + makeFMDState(0) _homePageContent = document.getElementById("home-page-view").getElementsByClassName("_content_")[0] - loadFMDFileHTML("https://miki.wtenison.com/data/fmd/home.fmd", _homePageContent) + loadFMDFileHTML("https://test.wtenison.com/data/fmd/home.fmd", _homePageContent) }) .catch(err => console.error('_ERROR:instantiateStreaming:failed to load WASM:', err)); \ No newline at end of file diff --git a/web/subject/script.js b/web/subject/script.js @@ -2,10 +2,12 @@ const go = new global.Go(); -WebAssembly.instantiateStreaming(fetch("web/global/fmd/fmd.wasm"), go.importObject) +WebAssembly.instantiateStreaming(fetch("web/global/fmd.wasm"), go.importObject) .then((result) => { go.run(result.instance); + makeFMDState(0) + _pageSubjectTitle = document.getElementById("site-page") _subjectView = document.getElementById("subject-view")