aboutsummaryrefslogtreecommitdiffstats
path: root/root/static
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2019-10-10 16:42:07 +0000
committers-ol <s-ol@users.noreply.github.com>2019-10-10 16:42:07 +0000
commit9632233c16a26f017c648faf36a6b26833e62f2e (patch)
treeadb41885dfaebfc303b92ead87c8122f0d8083cc /root/static
parentadd $interactive facet with Browser (diff)
downloadmmm-9632233c16a26f017c648faf36a6b26833e62f2e.tar.gz
mmm-9632233c16a26f017c648faf36a6b26833e62f2e.zip
make fileders load just-in-time
Diffstat (limited to 'root/static')
-rw-r--r--root/static/mmm/application$lua.lua2701
-rw-r--r--root/static/style/text$css.css411
2 files changed, 3112 insertions, 0 deletions
diff --git a/root/static/mmm/application$lua.lua b/root/static/mmm/application$lua.lua
new file mode 100644
index 0000000..5a6dc53
--- /dev/null
+++ b/root/static/mmm/application$lua.lua
@@ -0,0 +1,2701 @@
+local p = package.preload
+if not p["mmm.canvasapp"] then p["mmm.canvasapp"] = load("local window = js.global\
+local js = require('js')\
+local a, canvas, div, button, script\
+do\
+ local _obj_0 = require('mmm.dom')\
+ a, canvas, div, button, script = _obj_0.a, _obj_0.canvas, _obj_0.div, _obj_0.button, _obj_0.script\
+end\
+local CanvasApp\
+do\
+ local _class_0\
+ local _base_0 = {\
+ width = 500,\
+ height = 400,\
+ update = function(self, dt)\
+ self.time = self.time + dt\
+ if self.length and self.time > self.length then\
+ self.time = self.time - self.length\
+ return true\
+ end\
+ end,\
+ render = function(self, fps)\
+ if fps == nil then\
+ fps = 60\
+ end\
+ assert(self.length, 'cannot render CanvasApp without length set')\
+ self.paused = true\
+ local actual_render\
+ actual_render = function()\
+ local writer = js.new(window.Whammy.Video, fps)\
+ local doFrame\
+ doFrame = function()\
+ local done = self:update(1 / fps)\
+ self.ctx:resetTransform()\
+ self:draw()\
+ writer:add(self.canvas)\
+ if done or self.time >= self.length then\
+ local blob = writer:compile()\
+ local name = tostring(self.__class.__name) .. \"_\" .. tostring(fps) .. \"fps.webm\"\
+ return self.node.lastChild:appendChild(a(name, {\
+ download = name,\
+ href = window.URL:createObjectURL(blob)\
+ }))\
+ else\
+ return window:setTimeout(doFrame)\
+ end\
+ end\
+ self.time = 0\
+ return doFrame()\
+ end\
+ if window.Whammy then\
+ return actual_render()\
+ else\
+ window.global = window.global or window\
+ return document.body:appendChild(script({\
+ onload = actual_render,\
+ src = 'https://cdn.jsdelivr.net/npm/whammy@0.0.1/whammy.min.js'\
+ }))\
+ end\
+ end\
+ }\
+ _base_0.__index = _base_0\
+ _class_0 = setmetatable({\
+ __init = function(self, show_menu, paused)\
+ if show_menu == nil then\
+ show_menu = false\
+ end\
+ self.paused = paused\
+ self.canvas = canvas({\
+ width = self.width,\
+ height = self.height\
+ })\
+ self.ctx = self.canvas:getContext('2d')\
+ self.time = 0\
+ self.canvas.tabIndex = 0\
+ self.canvas:addEventListener('click', function(_, e)\
+ return self.click and self:click(e.offsetX, e.offsetY, e.button)\
+ end)\
+ self.canvas:addEventListener('keydown', function(_, e)\
+ return self.keydown and self:keydown(e.key, e.code)\
+ end)\
+ local lastMillis = window.performance:now()\
+ local animationFrame\
+ animationFrame = function(_, millis)\
+ self:update((millis - lastMillis) / 1000)\
+ self.ctx:resetTransform()\
+ self:draw()\
+ lastMillis = millis\
+ if not self.paused then\
+ return window:setTimeout((function()\
+ return window:requestAnimationFrame(animationFrame)\
+ end), 0)\
+ end\
+ end\
+ window:requestAnimationFrame(animationFrame)\
+ if show_menu then\
+ self.node = div({\
+ className = 'canvas_app',\
+ self.canvas,\
+ div({\
+ className = 'overlay',\
+ button('render 30fps', {\
+ onclick = function()\
+ return self:render(30)\
+ end\
+ }),\
+ button('render 60fps', {\
+ onclick = function()\
+ return self:render(60)\
+ end\
+ })\
+ })\
+ })\
+ else\
+ self.node = self.canvas\
+ end\
+ end,\
+ __base = _base_0,\
+ __name = \"CanvasApp\"\
+ }, {\
+ __index = _base_0,\
+ __call = function(cls, ...)\
+ local _self_0 = setmetatable({}, _base_0)\
+ cls.__init(_self_0, ...)\
+ return _self_0\
+ end\
+ })\
+ _base_0.__class = _class_0\
+ CanvasApp = _class_0\
+end\
+return {\
+ CanvasApp = CanvasApp\
+}\
+", "mmm/canvasapp.client.lua") end
+if not p["mmm.color"] then p["mmm.color"] = load("local rgb\
+rgb = function(r, g, b)\
+ if 'table' == type(r) then\
+ r, g, b = table.unpack(r)\
+ end\
+ return \"rgb(\" .. tostring(r * 255) .. \", \" .. tostring(g * 255) .. \", \" .. tostring(b * 255) .. \")\"\
+end\
+local rgba\
+rgba = function(r, g, b, a)\
+ if 'table' == type(r) then\
+ r, g, b, a = table.unpack(r)\
+ end\
+ return \"rgba(\" .. tostring(r * 255) .. \", \" .. tostring(g * 255) .. \", \" .. tostring(b * 255) .. \", \" .. tostring(a or 1) .. \")\"\
+end\
+local hsl\
+hsl = function(h, s, l)\
+ if 'table' == type(h) then\
+ h, s, l = table.unpack(h)\
+ end\
+ return \"hsl(\" .. tostring(h * 360) .. \", \" .. tostring(s * 100) .. \"%, \" .. tostring(l * 100) .. \"%)\"\
+end\
+local hsla\
+hsla = function(h, s, l, a)\
+ if 'table' == type(h) then\
+ h, s, l, a = table.unpack(h)\
+ end\
+ return \"hsla(\" .. tostring(h * 360) .. \", \" .. tostring(s * 100) .. \"%, \" .. tostring(l * 100) .. \"%, \" .. tostring(a or 1) .. \")\"\
+end\
+return {\
+ rgb = rgb,\
+ rgba = rgba,\
+ hsl = hsl,\
+ hsla = hsla\
+}\
+", "mmm/color.lua") end
+if not p["mmm.highlighting"] then p["mmm.highlighting"] = load("local code\
+code = require('mmm.dom').code\
+local highlight\
+local trim\
+trim = function(str)\
+ return str:match('^ *(..-) *$')\
+end\
+if MODE == 'SERVER' then\
+ highlight = function(lang, str)\
+ assert(str, 'no string to highlight')\
+ return code((trim(str)), {\
+ class = \"hljs lang-\" .. tostring(lang)\
+ })\
+ end\
+else\
+ highlight = function(lang, str)\
+ assert(str, 'no string to highlight')\
+ local result = window.hljs:highlight(lang, (trim(str)), true)\
+ do\
+ local _with_0 = code({\
+ class = \"hljs lang-\" .. tostring(lang)\
+ })\
+ _with_0.innerHTML = result.value\
+ return _with_0\
+ end\
+ end\
+end\
+local languages = setmetatable({ }, {\
+ __index = function(self, name)\
+ do\
+ local val\
+ val = function(str)\
+ return highlight(name, str)\
+ end\
+ self[name] = val\
+ return val\
+ end\
+ end\
+})\
+return {\
+ highlight = highlight,\
+ languages = languages\
+}\
+", "mmm/highlighting.lua") end
+if not p["mmm"] then p["mmm"] = load("window = js.global\
+local console\
+document, console = window.document, window.console\
+MODE = 'CLIENT'\
+local deep_tostring\
+deep_tostring = function(tbl, space)\
+ if space == nil then\
+ space = ''\
+ end\
+ if 'userdata' == type(tbl) then\
+ return tbl\
+ end\
+ local buf = space .. tostring(tbl)\
+ if not ('table' == type(tbl)) then\
+ return buf\
+ end\
+ buf = buf .. ' {\\n'\
+ for k, v in pairs(tbl) do\
+ buf = buf .. tostring(space) .. \" [\" .. tostring(k) .. \"]: \" .. tostring(deep_tostring(v, space .. ' ')) .. \"\\n\"\
+ end\
+ buf = buf .. tostring(space) .. \"}\"\
+ return buf\
+end\
+print = function(...)\
+ local contents\
+ do\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ local _list_0 = {\
+ ...\
+ }\
+ for _index_0 = 1, #_list_0 do\
+ local v = _list_0[_index_0]\
+ _accum_0[_len_0] = deep_tostring(v)\
+ _len_0 = _len_0 + 1\
+ end\
+ contents = _accum_0\
+ end\
+ return console:log(table.unpack(contents))\
+end\
+warn = function(...)\
+ local contents\
+ do\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ local _list_0 = {\
+ ...\
+ }\
+ for _index_0 = 1, #_list_0 do\
+ local v = _list_0[_index_0]\
+ _accum_0[_len_0] = deep_tostring(v)\
+ _len_0 = _len_0 + 1\
+ end\
+ contents = _accum_0\
+ end\
+ return console:warn(table.unpack(contents))\
+end\
+package.path = '/?.lua;/?/init.lua'\
+do\
+ local _require = require\
+ relative = function(base, sub)\
+ if not ('number' == type(sub)) then\
+ sub = 0\
+ end\
+ for i = 1, sub do\
+ base = base:match('^(.*)%.%w+$')\
+ end\
+ return function(name, x)\
+ if '.' == name:sub(1, 1) then\
+ name = base .. name\
+ end\
+ return _require(name)\
+ end\
+ end\
+end\
+if on_load then\
+ for _index_0 = 1, #on_load do\
+ local f = on_load[_index_0]\
+ f()\
+ end\
+end\
+on_load = setmetatable({ }, {\
+ __newindex = function(t, k, v)\
+ rawset(t, k, v)\
+ return v()\
+ end\
+})\
+", "mmm/init.client.lua") end
+if not p["mmm.ordered"] then p["mmm.ordered"] = load("local sort\
+sort = function(t, order_fn, only_strings)\
+ do\
+ local index\
+ do\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ for k, v in pairs(t) do\
+ if (not only_strings) or 'string' == type(k) then\
+ _accum_0[_len_0] = k\
+ _len_0 = _len_0 + 1\
+ end\
+ end\
+ index = _accum_0\
+ end\
+ table.sort(index, order_fn)\
+ return index\
+ end\
+end\
+local onext\
+onext = function(state, key)\
+ state.i = state.i + state.step\
+ local t, index, i\
+ t, index, i = state.t, state.index, state.i\
+ do\
+ key = index[i]\
+ if key then\
+ return key, t[key]\
+ end\
+ end\
+end\
+local opairs\
+opairs = function(t, order_fn, only_strings)\
+ if only_strings == nil then\
+ only_strings = false\
+ end\
+ local state = {\
+ t = t,\
+ i = 0,\
+ step = 1,\
+ index = sort(t, order_fn, only_strings)\
+ }\
+ return onext, state, nil\
+end\
+local ropairs\
+ropairs = function(t, order_fn, only_strings)\
+ if only_strings == nil then\
+ only_strings = false\
+ end\
+ local index = sort(t, order_fn, only_strings)\
+ local state = {\
+ t = t,\
+ index = index,\
+ i = #index + 1,\
+ step = -1\
+ }\
+ return onext, state, nil\
+end\
+return {\
+ onext = onext,\
+ opairs = opairs,\
+ ropairs = ropairs\
+}\
+", "mmm/ordered.lua") end
+if not p["mmm.mmmfs.browser"] then p["mmm.mmmfs.browser"] = load("local require = relative(..., 1)\
+local Key\
+Key = require('.fileder').Key\
+local converts, get_conversions, apply_conversions\
+do\
+ local _obj_0 = require('.conversion')\
+ converts, get_conversions, apply_conversions = _obj_0.converts, _obj_0.get_conversions, _obj_0.apply_conversions\
+end\
+local ReactiveVar, get_or_create, text, elements\
+do\
+ local _obj_0 = require('mmm.component')\
+ ReactiveVar, get_or_create, text, elements = _obj_0.ReactiveVar, _obj_0.get_or_create, _obj_0.text, _obj_0.elements\
+end\
+local pre, div, nav, span, button, a, code, select, option\
+pre, div, nav, span, button, a, code, select, option = elements.pre, elements.div, elements.nav, elements.span, elements.button, elements.a, elements.code, elements.select, elements.option\
+local languages\
+languages = require('mmm.highlighting').languages\
+local keep\
+keep = function(var)\
+ local last = var:get()\
+ return var:map(function(val)\
+ last = val or last\
+ return last\
+ end)\
+end\
+local code_cast\
+code_cast = function(lang)\
+ return {\
+ inp = \"text/\" .. tostring(lang) .. \".*\",\
+ out = 'mmm/dom',\
+ transform = function(val)\
+ return languages[lang](val)\
+ end\
+ }\
+end\
+local casts = {\
+ code_cast('javascript'),\
+ code_cast('moonscript'),\
+ code_cast('lua'),\
+ code_cast('markdown'),\
+ code_cast('html'),\
+ {\
+ inp = 'text/plain',\
+ out = 'mmm/dom',\
+ transform = function(val)\
+ return text(val)\
+ end\
+ },\
+ {\
+ inp = 'URL.*',\
+ out = 'mmm/dom',\
+ transform = function(href)\
+ return span(a((code(href)), {\
+ href = href\
+ }))\
+ end\
+ }\
+}\
+for _index_0 = 1, #converts do\
+ local convert = converts[_index_0]\
+ table.insert(casts, convert)\
+end\
+local Browser\
+do\
+ local _class_0\
+ local err_and_trace, default_convert\
+ local _base_0 = {\
+ get_content = function(self, prop, err, convert)\
+ if err == nil then\
+ err = self.error\
+ end\
+ if convert == nil then\
+ convert = default_convert\
+ end\
+ local clear_error\
+ clear_error = function()\
+ if MODE == 'CLIENT' then\
+ return err:set()\
+ end\
+ end\
+ local disp_error\
+ disp_error = function(msg)\
+ if MODE == 'CLIENT' then\
+ err:set(pre(msg))\
+ end\
+ warn(\"ERROR rendering content: \" .. tostring(msg))\
+ return nil\
+ end\
+ local active = self.active:get()\
+ if not (active) then\
+ return disp_error(\"fileder not found!\")\
+ end\
+ if not (prop) then\
+ return disp_error(\"facet not found!\")\
+ end\
+ local ok, res = xpcall(convert, err_and_trace, active, prop)\
+ if MODE == 'CLIENT' then\
+ document.body.classList:remove('loading')\
+ end\
+ if ok and res then\
+ clear_error()\
+ return res\
+ elseif ok then\
+ return div(\"[no conversion path to \" .. tostring(prop.type) .. \"]\")\
+ elseif res and res.msg.match and res.msg:match('%[nossr%]$') then\
+ return div(\"[this page could not be pre-rendered on the server]\")\
+ else\
+ res = tostring(res.msg) .. \"\\n\" .. tostring(res.trace)\
+ return disp_error(res)\
+ end\
+ end,\
+ get_inspector = function(self)\
+ self.inspect_prop = self.facet:map(function(prop)\
+ local active = self.active:get()\
+ local key = active and active:find(prop)\
+ if key and key.original then\
+ key = key.original\
+ end\
+ return key\
+ end)\
+ self.inspect_err = ReactiveVar()\
+ do\
+ local _with_0 = div({\
+ class = 'view inspector'\
+ })\
+ _with_0:append(nav({\
+ span('inspector'),\
+ self.inspect_prop:map(function(current)\
+ current = current and current:tostring()\
+ local fileder = self.active:get()\
+ local onchange\
+ onchange = function(_, e)\
+ if e.target.value == '' then\
+ return \
+ end\
+ local name\
+ name = self.facet:get().name\
+ return self.inspect_prop:set(Key(e.target.value))\
+ end\
+ do\
+ local _with_1 = select({\
+ onchange = onchange\
+ })\
+ _with_1:append(option('(none)', {\
+ value = '',\
+ disabled = true,\
+ selected = not value\
+ }))\
+ if fileder then\
+ for key, _ in pairs(fileder.facets) do\
+ local value = key:tostring()\
+ _with_1:append(option(value, {\
+ value = value,\
+ selected = value == current\
+ }))\
+ end\
+ end\
+ return _with_1\
+ end\
+ end),\
+ self.inspect:map(function(enabled)\
+ if enabled then\
+ return button('close', {\
+ onclick = function(_, e)\
+ return self.inspect:set(false)\
+ end\
+ })\
+ end\
+ end)\
+ }))\
+ _with_0:append((function()\
+ do\
+ local _with_1 = div({\
+ class = self.inspect_err:map(function(e)\
+ if e then\
+ return 'error-wrap'\
+ else\
+ return 'error-wrap empty'\
+ end\
+ end)\
+ })\
+ _with_1:append(span(\"an error occured while rendering this view:\"))\
+ _with_1:append(self.inspect_err)\
+ return _with_1\
+ end\
+ end)())\
+ _with_0:append((function()\
+ do\
+ local _with_1 = pre({\
+ class = 'content'\
+ })\
+ _with_1:append(keep(self.inspect_prop:map(function(prop, old)\
+ return self:get_content(prop, self.inspect_err, function(self, prop)\
+ local value, key = self:get(prop)\
+ assert(key, \"couldn't @get \" .. tostring(prop))\
+ local conversions = get_conversions('mmm/dom', key.type, casts)\
+ assert(conversions, \"cannot cast '\" .. tostring(key.type) .. \"'\")\
+ return apply_conversions(conversions, value, self, prop)\
+ end)\
+ end)))\
+ return _with_1\
+ end\
+ end)())\
+ return _with_0\
+ end\
+ end,\
+ navigate = function(self, new)\
+ return self.path:set(new)\
+ end\
+ }\
+ _base_0.__index = _base_0\
+ _class_0 = setmetatable({\
+ __init = function(self, root, path, rehydrate)\
+ if rehydrate == nil then\
+ rehydrate = false\
+ end\
+ self.root = root\
+ assert(self.root, 'root fileder is nil')\
+ self.path = ReactiveVar(path or '')\
+ if MODE == 'CLIENT' then\
+ local logo = document:querySelector('header h1 > svg')\
+ local spin\
+ spin = function()\
+ logo.classList:add('spin')\
+ local _ = logo.parentElement.offsetWidth\
+ return logo.classList:remove('spin')\
+ end\
+ self.path:subscribe(function(path)\
+ document.body.classList:add('loading')\
+ spin()\
+ if self.skip then\
+ return \
+ end\
+ local vis_path = path .. ((function()\
+ if '/' == path:sub(-1) then\
+ return ''\
+ else\
+ return '/'\
+ end\
+ end)())\
+ return window.history:pushState(path, '', vis_path)\
+ end)\
+ window.onpopstate = function(_, event)\
+ if event.state and not event.state == js.null then\
+ self.skip = true\
+ self.path:set(event.state)\
+ self.skip = nil\
+ end\
+ end\
+ end\
+ self.active = self.path:map((function()\
+ local _base_1 = self.root\
+ local _fn_0 = _base_1.walk\
+ return function(...)\
+ return _fn_0(_base_1, ...)\
+ end\
+ end)())\
+ self.facet = self.active:map(function(fileder)\
+ if not (fileder) then\
+ return \
+ end\
+ local last = self.facet and self.facet:get()\
+ return Key((function()\
+ if last then\
+ return last.type\
+ else\
+ return 'mmm/dom'\
+ end\
+ end)())\
+ end)\
+ self.inspect = ReactiveVar((MODE == 'CLIENT' and window.location.search:match('[?&]inspect')))\
+ local main = get_or_create('div', 'browser-root', {\
+ class = 'main view'\
+ })\
+ if MODE == 'SERVER' then\
+ main:append(nav({\
+ id = 'browser-navbar',\
+ span('please stand by... interactivity loading :)')\
+ }))\
+ else\
+ main:prepend((function()\
+ do\
+ local _with_0 = get_or_create('nav', 'browser-navbar')\
+ _with_0.node.innerHTML = ''\
+ _with_0:append(span('path: ', self.path:map(function(path)\
+ do\
+ local _with_1 = div({\
+ class = 'path',\
+ style = {\
+ display = 'inline-block'\
+ }\
+ })\
+ local path_segment\
+ path_segment = function(name, href)\
+ return a(name, {\
+ href = href,\
+ onclick = function(_, e)\
+ e:preventDefault()\
+ return self:navigate(href)\
+ end\
+ })\
+ end\
+ local href = ''\
+ path = path:match('^/(.*)')\
+ _with_1:append(path_segment('root', ''))\
+ while path do\
+ local name, rest = path:match('^([%w%-_%.]+)/(.*)')\
+ if not name then\
+ name = path\
+ end\
+ path = rest\
+ href = tostring(href) .. \"/\" .. tostring(name)\
+ _with_1:append('/')\
+ _with_1:append(path_segment(name, href))\
+ end\
+ return _with_1\
+ end\
+ end)))\
+ _with_0:append(span('view facet:', {\
+ style = {\
+ ['margin-right'] = '0'\
+ }\
+ }))\
+ _with_0:append(self.active:map(function(fileder)\
+ local onchange\
+ onchange = function(_, e)\
+ local type\
+ type = self.facet:get().type\
+ return self.facet:set(Key({\
+ name = e.target.value,\
+ type = type\
+ }))\
+ end\
+ local current = self.facet:get()\
+ current = current and current.name\
+ do\
+ local _with_1 = select({\
+ onchange = onchange,\
+ disabled = not fileder\
+ })\
+ local has_main = fileder and fileder:find(current.name, '.*')\
+ _with_1:append(option('(main)', {\
+ value = '',\
+ disabled = not has_main,\
+ selected = current == ''\
+ }))\
+ if fileder then\
+ for i, value in ipairs(fileder:get_facets()) do\
+ local _continue_0 = false\
+ repeat\
+ if value == '' then\
+ _continue_0 = true\
+ break\
+ end\
+ _with_1:append(option(value, {\
+ value = value,\
+ selected = value == current\
+ }))\
+ _continue_0 = true\
+ until true\
+ if not _continue_0 then\
+ break\
+ end\
+ end\
+ end\
+ return _with_1\
+ end\
+ end))\
+ _with_0:append(self.inspect:map(function(enabled)\
+ if not enabled then\
+ return button('inspect', {\
+ onclick = function(_, e)\
+ return self.inspect:set(true)\
+ end\
+ })\
+ end\
+ end))\
+ return _with_0\
+ end\
+ end)())\
+ end\
+ self.error = ReactiveVar()\
+ main:append((function()\
+ do\
+ local _with_0 = get_or_create('div', 'browser-error', {\
+ class = self.error:map(function(e)\
+ if e then\
+ return 'error-wrap'\
+ else\
+ return 'error-wrap empty'\
+ end\
+ end)\
+ })\
+ _with_0:append((span(\"an error occured while rendering this view:\")), (rehydrate and _with_0.node.firstChild))\
+ _with_0:append(self.error)\
+ return _with_0\
+ end\
+ end)())\
+ main:append((function()\
+ do\
+ local _with_0 = get_or_create('div', 'browser-content', {\
+ class = 'content'\
+ })\
+ local content = ReactiveVar((function()\
+ if rehydrate then\
+ return _with_0.node.lastChild\
+ else\
+ return self:get_content(self.facet:get())\
+ end\
+ end)())\
+ _with_0:append(keep(content))\
+ if MODE == 'CLIENT' then\
+ self.facet:subscribe(function(p)\
+ return window:setTimeout((function()\
+ return content:set(self:get_content(p))\
+ end), 150)\
+ end)\
+ end\
+ return _with_0\
+ end\
+ end)())\
+ if rehydrate then\
+ self.facet:set(self.facet:get())\
+ end\
+ local inspector = self.inspect:map(function(enabled)\
+ if enabled then\
+ return self:get_inspector()\
+ end\
+ end)\
+ local wrapper = get_or_create('div', 'browser-wrapper', main, inspector, {\
+ class = 'browser'\
+ })\
+ self.node = wrapper.node\
+ do\
+ local _base_1 = wrapper\
+ local _fn_0 = _base_1.render\
+ self.render = function(...)\
+ return _fn_0(_base_1, ...)\
+ end\
+ end\
+ end,\
+ __base = _base_0,\
+ __name = \"Browser\"\
+ }, {\
+ __index = _base_0,\
+ __call = function(cls, ...)\
+ local _self_0 = setmetatable({}, _base_0)\
+ cls.__init(_self_0, ...)\
+ return _self_0\
+ end\
+ })\
+ _base_0.__class = _class_0\
+ local self = _class_0\
+ err_and_trace = function(msg)\
+ return {\
+ msg = msg,\
+ trace = debug.traceback()\
+ }\
+ end\
+ default_convert = function(self, key)\
+ return self:get(key.name, 'mmm/dom')\
+ end\
+ default_convert = function(self, key)\
+ return self:get(key.name, 'mmm/dom')\
+ end\
+ Browser = _class_0\
+end\
+return {\
+ Browser = Browser\
+}\
+", "mmm/mmmfs/browser.lua") end
+if not p["mmm.mmmfs.conversion"] then p["mmm.mmmfs.conversion"] = load("local require = relative(..., 1)\
+local converts = require('.converts')\
+local count\
+count = function(base, pattern)\
+ if pattern == nil then\
+ pattern = '->'\
+ end\
+ return select(2, base:gsub(pattern, ''))\
+end\
+local escape_pattern\
+escape_pattern = function(inp)\
+ return \"^\" .. tostring(inp:gsub('([^%w])', '%%%1')) .. \"$\"\
+end\
+local escape_inp\
+escape_inp = function(inp)\
+ return \"^\" .. tostring(inp:gsub('([-/])', '%%%1')) .. \"$\"\
+end\
+local get_conversions\
+get_conversions = function(want, have, _converts, limit)\
+ if _converts == nil then\
+ _converts = converts\
+ end\
+ if limit == nil then\
+ limit = 5\
+ end\
+ assert(have, 'need starting type(s)')\
+ if 'string' == type(have) then\
+ have = {\
+ have\
+ }\
+ end\
+ assert(#have > 0, 'need starting type(s) (list was empty)')\
+ want = escape_pattern(want)\
+ local iterations = limit + math.max(table.unpack((function()\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ for _index_0 = 1, #have do\
+ local type = have[_index_0]\
+ _accum_0[_len_0] = count(type)\
+ _len_0 = _len_0 + 1\
+ end\
+ return _accum_0\
+ end)()))\
+ do\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ for _index_0 = 1, #have do\
+ local start = have[_index_0]\
+ _accum_0[_len_0] = {\
+ start = start,\
+ rest = start,\
+ conversions = { }\
+ }\
+ _len_0 = _len_0 + 1\
+ end\
+ have = _accum_0\
+ end\
+ for i = 1, iterations do\
+ local next_have, c = { }, 1\
+ for _index_0 = 1, #have do\
+ local _des_0 = have[_index_0]\
+ local start, rest, conversions\
+ start, rest, conversions = _des_0.start, _des_0.rest, _des_0.conversions\
+ if rest:match(want) then\
+ return conversions, start\
+ else\
+ for _index_1 = 1, #_converts do\
+ local _continue_0 = false\
+ repeat\
+ local convert = _converts[_index_1]\
+ local inp = escape_inp(convert.inp)\
+ if not (rest:match(inp)) then\
+ _continue_0 = true\
+ break\
+ end\
+ local result = rest:gsub(inp, convert.out)\
+ if result then\
+ next_have[c] = {\
+ start = start,\
+ rest = result,\
+ conversions = {\
+ {\
+ convert = convert,\
+ from = rest,\
+ to = result\
+ },\
+ table.unpack(conversions)\
+ }\
+ }\
+ c = c + 1\
+ end\
+ _continue_0 = true\
+ until true\
+ if not _continue_0 then\
+ break\
+ end\
+ end\
+ end\
+ end\
+ have = next_have\
+ if not (#have > 0) then\
+ return \
+ end\
+ end\
+end\
+local apply_conversions\
+apply_conversions = function(conversions, value, ...)\
+ for i = #conversions, 1, -1 do\
+ local step = conversions[i]\
+ value = step.convert.transform(step, value, ...)\
+ end\
+ return value\
+end\
+return {\
+ converts = converts,\
+ get_conversions = get_conversions,\
+ apply_conversions = apply_conversions\
+}\
+", "mmm/mmmfs/conversion.lua") end
+if not p["mmm.mmmfs.fileder"] then p["mmm.mmmfs.fileder"] = load("local require = relative(..., 1)\
+local get_conversions, apply_conversions\
+do\
+ local _obj_0 = require('.conversion')\
+ get_conversions, apply_conversions = _obj_0.get_conversions, _obj_0.apply_conversions\
+end\
+local Key\
+do\
+ local _class_0\
+ local _base_0 = {\
+ tostring = function(self)\
+ if self.name == '' then\
+ return self.type\
+ else\
+ return tostring(self.name) .. \": \" .. tostring(self.type)\
+ end\
+ end,\
+ __tostring = function(self)\
+ return self:tostring()\
+ end\
+ }\
+ _base_0.__index = _base_0\
+ _class_0 = setmetatable({\
+ __init = function(self, opts, second)\
+ if 'string' == type(second) then\
+ self.name, self.type = (opts or ''), second\
+ elseif 'string' == type(opts) then\
+ self.name, self.type = opts:match('^([%w-_]+): *(.+)$')\
+ if not self.name then\
+ self.name = ''\
+ self.type = opts\
+ end\
+ elseif 'table' == type(opts) then\
+ self.name = opts.name\
+ self.type = opts.type\
+ self.original = opts.original\
+ self.filename = opts.filename\
+ else\
+ return error(\"wrong argument type: \" .. tostring(type(opts)) .. \", \" .. tostring(type(second)))\
+ end\
+ end,\
+ __base = _base_0,\
+ __name = \"Key\"\
+ }, {\
+ __index = _base_0,\
+ __call = function(cls, ...)\
+ local _self_0 = setmetatable({}, _base_0)\
+ cls.__init(_self_0, ...)\
+ return _self_0\
+ end\
+ })\
+ _base_0.__class = _class_0\
+ Key = _class_0\
+end\
+local Fileder\
+do\
+ local _class_0\
+ local _base_0 = {\
+ walk = function(self, path)\
+ if path == '' then\
+ return self\
+ end\
+ if '/' ~= path:sub(1, 1) then\
+ path = tostring(self.path) .. \"/\" .. tostring(path)\
+ end\
+ if not (self.path == path:sub(1, #self.path)) then\
+ return \
+ end\
+ if #path == #self.path then\
+ return self\
+ end\
+ local _list_0 = self.children\
+ for _index_0 = 1, #_list_0 do\
+ local child = _list_0[_index_0]\
+ do\
+ local match = child:walk(path)\
+ if match then\
+ return match\
+ end\
+ end\
+ end\
+ end,\
+ mount = function(self, path, mount_as)\
+ if not mount_as then\
+ path = path .. self:gett('name: alpha')\
+ end\
+ assert(not self.path or self.path == path, \"mounted twice: \" .. tostring(self.path) .. \" and now \" .. tostring(path))\
+ self.path = path\
+ local _list_0 = self.children\
+ for _index_0 = 1, #_list_0 do\
+ local child = _list_0[_index_0]\
+ child:mount(self.path .. '/')\
+ end\
+ end,\
+ iterate = function(self, depth)\
+ if depth == nil then\
+ depth = 0\
+ end\
+ coroutine.yield(self)\
+ if depth == 1 then\
+ return \
+ end\
+ local _list_0 = self.children\
+ for _index_0 = 1, #_list_0 do\
+ local child = _list_0[_index_0]\
+ child:iterate(depth - 1)\
+ end\
+ end,\
+ get_facets = function(self)\
+ local names = { }\
+ for key in pairs(self.facets) do\
+ names[key.name] = true\
+ end\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ for name in pairs(names) do\
+ _accum_0[_len_0] = name\
+ _len_0 = _len_0 + 1\
+ end\
+ return _accum_0\
+ end,\
+ has = function(self, ...)\
+ local want = Key(...)\
+ for key in pairs(self.facets) do\
+ local _continue_0 = false\
+ repeat\
+ if key.original then\
+ _continue_0 = true\
+ break\
+ end\
+ if key.name == want.name and key.type == want.type then\
+ return key\
+ end\
+ _continue_0 = true\
+ until true\
+ if not _continue_0 then\
+ break\
+ end\
+ end\
+ end,\
+ has_facet = function(self, want)\
+ for key in pairs(self.facets) do\
+ local _continue_0 = false\
+ repeat\
+ if key.original then\
+ _continue_0 = true\
+ break\
+ end\
+ if key.name == want then\
+ return key\
+ end\
+ _continue_0 = true\
+ until true\
+ if not _continue_0 then\
+ break\
+ end\
+ end\
+ end,\
+ find = function(self, ...)\
+ local want = Key(...)\
+ local matching\
+ do\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ for key in pairs(self.facets) do\
+ if key.name == want.name then\
+ _accum_0[_len_0] = key\
+ _len_0 = _len_0 + 1\
+ end\
+ end\
+ matching = _accum_0\
+ end\
+ if not (#matching > 0) then\
+ return \
+ end\
+ local shortest_path, start = get_conversions(want.type, (function()\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ for _index_0 = 1, #matching do\
+ local key = matching[_index_0]\
+ _accum_0[_len_0] = key.type\
+ _len_0 = _len_0 + 1\
+ end\
+ return _accum_0\
+ end)())\
+ if start then\
+ for _index_0 = 1, #matching do\
+ local key = matching[_index_0]\
+ if key.type == start then\
+ return key, shortest_path\
+ end\
+ end\
+ return error(\"couldn't find key after resolution?\")\
+ end\
+ end,\
+ get = function(self, ...)\
+ local want = Key(...)\
+ local key, conversions = self:find(want)\
+ if key then\
+ local value = apply_conversions(conversions, self.facets[key], self, key)\
+ return value, key\
+ end\
+ end,\
+ gett = function(self, ...)\
+ local want = Key(...)\
+ local value, key = self:get(want)\
+ assert(value, tostring(self) .. \" doesn't have value for '\" .. tostring(want) .. \"'\")\
+ return value, key\
+ end,\
+ __tostring = function(self)\
+ return \"Fileder:\" .. tostring(self.path)\
+ end\
+ }\
+ _base_0.__index = _base_0\
+ _class_0 = setmetatable({\
+ __init = function(self, facets, children)\
+ if not children then\
+ do\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ for i, child in ipairs(facets) do\
+ facets[i] = nil\
+ local _value_0 = child\
+ _accum_0[_len_0] = _value_0\
+ _len_0 = _len_0 + 1\
+ end\
+ children = _accum_0\
+ end\
+ end\
+ self.children = setmetatable({ }, {\
+ __index = function(t, k)\
+ if not ('string' == type(k)) then\
+ return rawget(t, k)\
+ end\
+ return self:walk(tostring(self.path) .. \"/\" .. tostring(k))\
+ end,\
+ __newindex = function(t, k, child)\
+ rawset(t, k, child)\
+ if self.path == '/' then\
+ return child:mount('/')\
+ elseif self.path then\
+ return child:mount(self.path .. '/')\
+ end\
+ end\
+ })\
+ for i, child in ipairs(children) do\
+ self.children[i] = child\
+ end\
+ self.facets = setmetatable({ }, {\
+ __newindex = function(t, key, v)\
+ return rawset(t, (Key(key)), v)\
+ end\
+ })\
+ for k, v in pairs(facets) do\
+ self.facets[k] = v\
+ end\
+ end,\
+ __base = _base_0,\
+ __name = \"Fileder\"\
+ }, {\
+ __index = _base_0,\
+ __call = function(cls, ...)\
+ local _self_0 = setmetatable({}, _base_0)\
+ cls.__init(_self_0, ...)\
+ return _self_0\
+ end\
+ })\
+ _base_0.__class = _class_0\
+ Fileder = _class_0\
+end\
+local dir_base\
+dir_base = function(path)\
+ local dir, base = path:match('(.-)([^/]-)$')\
+ if dir and #dir > 0 then\
+ dir = dir:sub(1, #dir - 1)\
+ end\
+ return dir, base\
+end\
+local load_tree\
+load_tree = function(store, root)\
+ if root == nil then\
+ root = ''\
+ end\
+ local fileders = setmetatable({ }, {\
+ __index = function(self, path)\
+ do\
+ local val = Fileder({ })\
+ val.path = path\
+ rawset(self, path, val)\
+ return val\
+ end\
+ end\
+ })\
+ root = fileders[root]\
+ root.facets['name: alpha'] = ''\
+ for fn, ft in store:list_facets(root.path) do\
+ local val = store:load_facet(root.path, fn, ft)\
+ root.facets[Key(fn, ft)] = val\
+ end\
+ for path in store:list_all_fileders(root.path) do\
+ local fileder = fileders[path]\
+ local parent, name = dir_base(path)\
+ fileder.facets['name: alpha'] = name\
+ table.insert(fileders[parent].children, fileder)\
+ for fn, ft in store:list_facets(path) do\
+ local val = store:load_facet(path, fn, ft)\
+ fileder.facets[Key(fn, ft)] = val\
+ end\
+ end\
+ return root\
+end\
+return {\
+ Key = Key,\
+ Fileder = Fileder,\
+ dir_base = dir_base,\
+ load_tree = load_tree\
+}\
+", "mmm/mmmfs/fileder.lua") end
+if not p["mmm.mmmfs"] then p["mmm.mmmfs"] = load("local require = relative(...)\
+local Key, Fileder\
+do\
+ local _obj_0 = require('.fileder')\
+ Key, Fileder = _obj_0.Key, _obj_0.Fileder\
+end\
+local Browser\
+Browser = require('.browser').Browser\
+return {\
+ Key = Key,\
+ Fileder = Fileder,\
+ Browser = Browser\
+}\
+", "mmm/mmmfs/init.lua") end
+if not p["mmm.mmmfs.util"] then p["mmm.mmmfs.util"] = load("local merge\
+merge = function(orig, extra)\
+ if orig == nil then\
+ orig = { }\
+ end\
+ do\
+ local attr\
+ do\
+ local _tbl_0 = { }\
+ for k, v in pairs(orig) do\
+ _tbl_0[k] = v\
+ end\
+ attr = _tbl_0\
+ end\
+ for k, v in pairs(extra) do\
+ attr[k] = v\
+ end\
+ return attr\
+ end\
+end\
+local tourl\
+tourl = function(path)\
+ if STATIC then\
+ return path .. '/'\
+ else\
+ return path .. '/'\
+ end\
+end\
+return function(elements)\
+ local a, div, pre\
+ a, div, pre = elements.a, elements.div, elements.pre\
+ local find_fileder\
+ find_fileder = function(fileder, origin)\
+ if 'string' == type(fileder) then\
+ assert(origin, \"cannot resolve path '\" .. tostring(fileder) .. \"' without origin!\")\
+ return assert((origin:walk(fileder)), \"couldn't resolve path '\" .. tostring(fileder) .. \"' from \" .. tostring(origin))\
+ else\
+ return assert(fileder, \"no fileder passed.\")\
+ end\
+ end\
+ local navigate_to\
+ navigate_to = function(path, name, opts)\
+ if opts == nil then\
+ opts = { }\
+ end\
+ opts.href = tourl(path)\
+ if MODE == 'CLIENT' then\
+ opts.onclick = function(self, e)\
+ e:preventDefault()\
+ return BROWSER:navigate(path)\
+ end\
+ end\
+ return a(name, opts)\
+ end\
+ local link_to\
+ link_to = function(fileder, name, origin, attr)\
+ fileder = find_fileder(fileder, origin)\
+ name = name or fileder:get('title: mmm/dom')\
+ name = name or fileder:gett('name: alpha')\
+ do\
+ local href = fileder:get('link: URL.*')\
+ if href then\
+ return a(name, merge(attr, {\
+ href = href,\
+ target = '_blank'\
+ }))\
+ else\
+ return a(name, merge(attr, {\
+ href = tourl(fileder.path),\
+ onclick = (function()\
+ if MODE == 'CLIENT' then\
+ return function(self, e)\
+ e:preventDefault()\
+ return BROWSER:navigate(fileder.path)\
+ end\
+ end\
+ end)()\
+ }))\
+ end\
+ end\
+ end\
+ local embed\
+ embed = function(fileder, name, origin, opts)\
+ if name == nil then\
+ name = ''\
+ end\
+ if opts == nil then\
+ opts = { }\
+ end\
+ fileder = find_fileder(fileder, origin)\
+ local ok, node = pcall(fileder.gett, fileder, name, 'mmm/dom')\
+ if not ok then\
+ return div(\"couldn't embed \" .. tostring(fileder) .. \" \" .. tostring(name), (pre(node)), {\
+ style = {\
+ background = 'var(--gray-fail)',\
+ padding = '1em'\
+ }\
+ })\
+ end\
+ local klass = 'embed'\
+ if opts.desc then\
+ klass = klass .. ' desc'\
+ end\
+ if opts.inline then\
+ klass = klass .. ' inline'\
+ end\
+ node = div({\
+ class = klass,\
+ node,\
+ (function()\
+ if opts.desc then\
+ return div(opts.desc, {\
+ class = 'description'\
+ })\
+ end\
+ end)()\
+ })\
+ if opts.nolink then\
+ return node\
+ end\
+ return link_to(fileder, node, nil, opts.attr)\
+ end\
+ return {\
+ find_fileder = find_fileder,\
+ link_to = link_to,\
+ navigate_to = navigate_to,\
+ embed = embed\
+ }\
+end\
+", "mmm/mmmfs/util.lua") end
+if not p["mmm.mmmfs.converts"] then p["mmm.mmmfs.converts"] = load("local require = relative(..., 1)\
+local div, code, img, video, blockquote, a, span, source, iframe\
+do\
+ local _obj_0 = require('mmm.dom')\
+ div, code, img, video, blockquote, a, span, source, iframe = _obj_0.div, _obj_0.code, _obj_0.img, _obj_0.video, _obj_0.blockquote, _obj_0.a, _obj_0.span, _obj_0.source, _obj_0.iframe\
+end\
+local find_fileder, link_to, embed\
+do\
+ local _obj_0 = (require('mmm.mmmfs.util'))(require('mmm.dom'))\
+ find_fileder, link_to, embed = _obj_0.find_fileder, _obj_0.link_to, _obj_0.embed\
+end\
+local render\
+render = require('.layout').render\
+local tohtml\
+tohtml = require('mmm.component').tohtml\
+local js_fix\
+if MODE == 'CLIENT' then\
+ js_fix = function(arg)\
+ if arg == js.null then\
+ return \
+ end\
+ return arg\
+ end\
+end\
+local single\
+single = function(func)\
+ return function(self, val)\
+ return func(val)\
+ end\
+end\
+local loadwith\
+loadwith = function(_load)\
+ return function(self, val, fileder, key)\
+ local func = assert(_load(val, tostring(fileder) .. \"#\" .. tostring(key)))\
+ return func()\
+ end\
+end\
+local converts = {\
+ {\
+ inp = 'fn -> (.+)',\
+ out = '%1',\
+ transform = function(self, val, fileder)\
+ return val(fileder)\
+ end\
+ },\
+ {\
+ inp = 'mmm/component',\
+ out = 'mmm/dom',\
+ transform = single(tohtml)\
+ },\
+ {\
+ inp = 'mmm/dom',\
+ out = 'text/html+frag',\
+ transform = function(self, node)\
+ if MODE == 'SERVER' then\
+ return node\
+ else\
+ return node.outerHTML\
+ end\
+ end\
+ },\
+ {\
+ inp = 'mmm/dom',\
+ out = 'text/html',\
+ transform = function(self, html, fileder)\
+ return render(html, fileder)\
+ end\
+ },\
+ {\
+ inp = 'text/html%+frag',\
+ out = 'mmm/dom',\
+ transform = (function()\
+ if MODE == 'SERVER' then\
+ return function(self, html, fileder)\
+ html = html:gsub('<mmm%-link%s+(.-)>(.-)</mmm%-link>', function(attrs, text)\
+ if #text == 0 then\
+ text = nil\
+ end\
+ local path = ''\
+ while attrs and attrs ~= '' do\
+ local key, val, _attrs = attrs:match('^(%w+)=\"([^\"]-)\"%s*(.*)')\
+ if not key then\
+ key, _attrs = attrs:match('^(%w+)%s*(.*)$')\
+ val = true\
+ end\
+ attrs = _attrs\
+ local _exp_0 = key\
+ if 'path' == _exp_0 then\
+ path = val\
+ else\
+ warn(\"unkown attribute '\" .. tostring(key) .. \"=\\\"\" .. tostring(val) .. \"\\\"' in <mmm-link>\")\
+ end\
+ end\
+ return link_to(path, text, fileder)\
+ end)\
+ html = html:gsub('<mmm%-embed%s+(.-)>(.-)</mmm%-embed>', function(attrs, desc)\
+ local path, facet = '', ''\
+ local opts = { }\
+ if #desc ~= 0 then\
+ opts.desc = desc\
+ end\
+ while attrs and attrs ~= '' do\
+ local key, val, _attrs = attrs:match('^(%w+)=\"([^\"]-)\"%s*(.*)')\
+ if not key then\
+ key, _attrs = attrs:match('^(%w+)%s*(.*)$')\
+ val = true\
+ end\
+ attrs = _attrs\
+ local _exp_0 = key\
+ if 'path' == _exp_0 then\
+ path = val\
+ elseif 'facet' == _exp_0 then\
+ facet = val\
+ elseif 'nolink' == _exp_0 then\
+ opts.nolink = true\
+ elseif 'inline' == _exp_0 then\
+ opts.inline = true\
+ else\
+ warn(\"unkown attribute '\" .. tostring(key) .. \"=\\\"\" .. tostring(val) .. \"\\\"' in <mmm-embed>\")\
+ end\
+ end\
+ return embed(path, facet, fileder, opts)\
+ end)\
+ return html\
+ end\
+ else\
+ return function(self, html, fileder)\
+ local parent\
+ do\
+ local _with_0 = document:createElement('div')\
+ _with_0.innerHTML = html\
+ local embeds = _with_0:getElementsByTagName('mmm-embed')\
+ do\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ for i = 0, embeds.length - 1 do\
+ _accum_0[_len_0] = embeds[i]\
+ _len_0 = _len_0 + 1\
+ end\
+ embeds = _accum_0\
+ end\
+ for _index_0 = 1, #embeds do\
+ local element = embeds[_index_0]\
+ local path = js_fix(element:getAttribute('path'))\
+ local facet = js_fix(element:getAttribute('facet'))\
+ local nolink = js_fix(element:getAttribute('nolink'))\
+ local inline = js_fix(element:getAttribute('inline'))\
+ local desc = js_fix(element.innerText)\
+ element:replaceWith(embed(path or '', facet or '', fileder, {\
+ nolink = nolink,\
+ inline = inline,\
+ desc = desc\
+ }))\
+ end\
+ embeds = _with_0:getElementsByTagName('mmm-link')\
+ do\
+ local _accum_0 = { }\
+ local _len_0 = 1\
+ for i = 0, embeds.length - 1 do\
+ _accum_0[_len_0] = embeds[i]\
+ _len_0 = _len_0 + 1\
+ end\
+ embeds = _accum_0\
+ end\
+ for _index_0 = 1, #embeds do\
+ local element = embeds[_index_0]\
+ local text = js_fix(element.innerText)\
+ local path = js_fix(element:getAttribute('path'))\
+ element:replaceWith(link_to(path or '', text, fileder))\
+ end\
+ parent = _with_0\
+ end\
+ assert(1 == parent.childElementCount, \"text/html with more than one child!\")\
+ return parent.firstElementChild\
+ end\
+ end\
+ end)()\
+ },\
+ {\
+ inp = 'text/lua -> (.+)',\
+ out = '%1',\
+ transform = loadwith(load or loadstring)\
+ },\
+ {\
+ inp = 'mmm/tpl -> (.+)',\
+ out = '%1',\
+ transform = function(self, source, fileder)\
+ return source:gsub('{{(.-)}}', function(expr)\
+ local path, facet = expr:match('^([%w%-_%./]*)%+(.*)')\
+ assert(path, \"couldn't match TPL expression '\" .. tostring(expr) .. \"'\")\
+ return (find_fileder(path, fileder)):gett(facet)\
+ end)\
+ end\
+ },\
+ {\
+ inp = 'time/iso8601-date',\
+ out = 'time/unix',\
+ transform = function(self, val)\
+ local year, _, month, day = val:match('^%s*(%d%d%d%d)(%-?)([01]%d)%2([0-3]%d)%s*$')\
+ assert(year, \"failed to parse ISO 8601 date: '\" .. tostring(val) .. \"'\")\
+ return os.time({\
+ year = year,\
+ month = month,\
+ day = day\
+ })\
+ end\
+ },\
+ {\
+ inp = 'URL -> twitter/tweet',\
+ out = 'mmm/dom',\
+ transform = function(self, href)\
+ local id = assert((href:match('twitter.com/[^/]-/status/(%d*)')), \"couldn't parse twitter/tweet URL: '\" .. tostring(href) .. \"'\")\
+ if MODE == 'CLIENT' then\
+ do\
+ local parent = div()\
+ window.twttr.widgets:createTweet(id, parent)\
+ return parent\
+ end\
+ else\
+ return div(blockquote({\
+ class = 'twitter-tweet',\
+ ['data-lang'] = 'en',\
+ a('(linked tweet)', {\
+ href = href\
+ })\
+ }))\
+ end\
+ end\
+ },\
+ {\
+ inp = 'URL -> youtube/video',\
+ out = 'mmm/dom',\
+ transform = function(self, link)\
+ local id = link:match('youtu%.be/([^/]+)')\
+ id = id or link:match('youtube.com/watch.*[?&]v=([^&]+)')\
+ id = id or link:match('youtube.com/[ev]/([^/]+)')\
+ id = id or link:match('youtube.com/embed/([^/]+)')\
+ assert(id, \"couldn't parse youtube URL: '\" .. tostring(link) .. \"'\")\
+ return iframe({\
+ width = 560,\
+ height = 315,\
+ frameborder = 0,\
+ allowfullscreen = true,\
+ frameBorder = 0,\
+ src = \"//www.youtube.com/embed/\" .. tostring(id)\
+ })\
+ end\
+ },\
+ {\
+ inp = 'URL -> image/.+',\
+ out = 'mmm/dom',\
+ transform = function(self, src, fileder)\
+ return img({\
+ src = src\
+ })\
+ end\
+ },\
+ {\
+ inp = 'URL -> video/.+',\
+ out = 'mmm/dom',\
+ transform = function(self, src)\
+ return video((source({\
+ src = src\
+ })), {\
+ controls = true,\
+ loop = true\
+ })\
+ end\
+ },\
+ {\
+ inp = 'text/plain',\
+ out = 'mmm/dom',\
+ transform = function(self, val)\
+ return span(val)\
+ end\
+ },\
+ {\
+ inp = 'alpha',\
+ out = 'mmm/dom',\
+ transform = single(code)\
+ },\
+ {\
+ inp = '(.+)',\
+ out = 'URL -> %1',\
+ transform = function(self, _, fileder, key)\
+ return tostring(fileder.path) .. \"/\" .. tostring(key.name) .. \":\" .. tostring(self.from)\
+ end\
+ }\
+}\
+if MODE == 'SERVER' then\
+ local ok, moon = pcall(require, 'moonscript.base')\
+ if ok then\
+ local _load = moon.load or moon.loadstring\
+ table.insert(converts, {\
+ inp = 'text/moonscript -> (.+)',\
+ out = '%1',\
+ transform = loadwith(moon.load or moon.loadstring)\
+ })\
+ table.insert(converts, {\
+ inp = 'text/moonscript -> (.+)',\
+ out = 'text/lua -> %1',\
+ transform = single(moon.to_lua)\
+ })\
+ end\
+else\
+ table.insert(converts, {\
+ inp = 'text/javascript -> (.+)',\
+ out = '%1',\
+ transform = function(self, source)\
+ local f = js.new(window.Function, source)\
+ return f()\
+ end\
+ })\
+end\
+do\
+ local markdown\
+ if MODE == 'SERVER' then\
+ local success, discount = pcall(require, 'discount')\
+ if not success then\
+ warn(\"NO MARKDOWN SUPPORT!\", discount)\
+ end\
+ markdown = success and function(md)\
+ local res = assert(discount.compile(md, 'githubtags'))\
+ return res.body\
+ end\
+ else\
+ markdown = window and window.marked and (function()\
+ local _base_0 = window\
+ local _fn_0 = _base_0.marked\
+ return function(...)\
+ return _fn_0(_base_0, ...)\
+ end\
+ end)()\
+ end\
+ if markdown then\
+ table.insert(converts, {\
+ inp = 'text/markdown',\
+ out = 'text/html+frag',\
+ transform = function(self, md)\
+ return \"<div class=\\\"markdown\\\">\" .. tostring(markdown(md)) .. \"</div>\"\
+ end\
+ })\
+ table.insert(converts, {\
+ inp = 'text/markdown%+span',\
+ out = 'mmm/dom',\
+ transform = (function()\
+ if MODE == 'SERVER' then\
+ return function(self, source)\
+ local html = markdown(source)\
+ html = html:gsub('^<p', '<span')\
+ return html:gsub('/p>$', '/span>')\
+ end\
+ else\
+ return function(self, source)\
+ local html = markdown(source)\
+ html = html:gsub('^%s*<p>%s*', '')\
+ html = html:gsub('%s*</p>%s*$', '')\
+ do\
+ local _with_0 = document:createElement('span')\
+ _with_0.innerHTML = html\
+ return _with_0\
+ end\
+ end\
+ end\
+ end)()\
+ })\
+ end\
+end\
+return converts\
+", "mmm/mmmfs/converts.lua") end
+if not p["mmm.mmmfs.layout"] then p["mmm.mmmfs.layout"] = load("local require = relative(..., 1)\
+local header, aside, footer, div, svg, script, g, circle, h1, span, b, a, img\
+do\
+ local _obj_0 = require('mmm.dom')\
+ header, aside, footer, div, svg, script, g, circle, h1, span, b, a, img = _obj_0.header, _obj_0.aside, _obj_0.footer, _obj_0.div, _obj_0.svg, _obj_0.script, _obj_0.g, _obj_0.circle, _obj_0.h1, _obj_0.span, _obj_0.b, _obj_0.a, _obj_0.img\
+end\
+local navigate_to\
+navigate_to = (require('mmm.mmmfs.util'))(require('mmm.dom')).navigate_to\
+local pick\
+pick = function(...)\
+ local num = select('#', ...)\
+ local i = math.ceil(math.random() * num)\
+ return (select(i, ...))\
+end\
+local iconlink\
+iconlink = function(href, src, alt, style)\
+ return a({\
+ class = 'iconlink',\
+ target = '_blank',\
+ rel = 'me',\
+ href = href,\
+ img({\
+ src = src,\
+ alt = alt,\
+ style = style\
+ })\
+ })\
+end\
+local logo = svg({\
+ class = 'sun',\
+ viewBox = '-0.75 -1 1.5 2',\
+ xmlns = 'http://www.w3.org/2000/svg',\
+ baseProfile = 'full',\
+ version = '1.1',\
+ g({\
+ transform = 'translate(0 .18)',\
+ g({\
+ class = 'circle out',\
+ circle({\
+ r = '.6',\
+ fill = 'none',\
+ ['stroke-width'] = '.12'\
+ })\
+ }),\
+ g({\
+ class = 'circle in',\
+ circle({\
+ r = '.2',\
+ stroke = 'none'\
+ })\
+ })\
+ })\
+})\
+local gen_header\
+gen_header = function()\
+ return header({\
+ div({\
+ h1({\
+ navigate_to('', logo),\
+ span({\
+ span('mmm', {\
+ class = 'bold'\
+ }),\
+ '&#8203;',\
+ '.s&#8209;ol.nu'\
+ })\
+ }),\
+ table.concat({\
+ pick('fun', 'cool', 'weird', 'interesting', 'new', 'pleasant'),\
+ pick('stuff', 'things', 'projects', 'experiments', 'visuals', 'ideas'),\
+ pick(\"with\", 'and'),\
+ pick('mostly code', 'code and wires', 'silicon', 'electronics', 'shaders', 'oscilloscopes', 'interfaces', 'hardware', 'FPGAs')\
+ }, ' ')\
+ }),\
+ aside({\
+ navigate_to('/about', 'about me'),\
+ navigate_to('/games', 'games'),\
+ navigate_to('/projects', 'other'),\
+ a({\
+ href = 'mailto:s%20[removethis]%20[at]%20s-ol.nu',\
+ 'contact',\
+ script(\"\\n var l = document.currentScript.parentElement;\\n l.href = l.href.replace('%20[at]%20', '@');\\n l.href = l.href.replace('%20[removethis]', '') + '?subject=Hey there :)';\\n \")\
+ })\
+ })\
+ })\
+end\
+footer = footer({\
+ span({\
+ 'made with \\xe2\\x98\\xbd by ',\
+ a('s-ol', {\
+ href = 'https://twitter.com/S0lll0s'\
+ }),\
+ \", \" .. tostring(os.date('%Y'))\
+ }),\
+ div({\
+ class = 'icons',\
+ iconlink('https://github.com/s-ol', 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/github.svg', 'github'),\
+ iconlink('https://merveilles.town/@s_ol', 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/mastodon.svg', 'mastodon'),\
+ iconlink('https://twitter.com/S0lll0s', 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/twitter.svg', 'twitter'),\
+ iconlink('https://webring.xxiivv.com/#random', 'https://webring.xxiivv.com/icon.black.svg', 'webring', {\
+ height = '1.3em',\
+ ['margin-left'] = '.3em',\
+ ['margin-top'] = '-0.12em'\
+ })\
+ })\
+})\
+local get_meta\
+get_meta = function(self)\
+ local title = (self:get('title: text/plain')) or self:gett('name: alpha')\
+ local l\
+ l = function(str)\
+ str = str:gsub('[%s\\\\n]+$', '')\
+ return str:gsub('\\\\n', ' ')\
+ end\
+ local e\
+ e = function(str)\
+ return string.format('%q', l(str))\
+ end\
+ local meta = \"\\n <meta charset=\\\"UTF-8\\\">\\n <title>\" .. tostring(l(title)) .. \"</title>\\n \"\
+ do\
+ local page_meta = self:get('_meta: mmm/dom')\
+ if page_meta then\
+ meta = meta .. page_meta\
+ else\
+ meta = meta .. \"\\n <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1\\\">\\n\\n <meta property=\\\"og:title\\\" content=\" .. tostring(e(title)) .. \" />\\n <meta property=\\\"og:type\\\" content=\\\"website\\\" />\\n <meta property=\\\"og:url\\\" content=\\\"https://mmm.s-ol.nu\" .. tostring(self.path) .. \"/\\\" />\\n <meta property=\\\"og:site_name\\\" content=\\\"mmm\\\" />\"\
+ do\
+ local desc = self:get('description: text/plain')\
+ if desc then\
+ meta = meta .. \"\\n <meta property=\\\"og:description\\\" content=\" .. tostring(e(desc)) .. \" />\"\
+ end\
+ end\
+ end\
+ end\
+ return meta\
+end\
+local render\
+render = function(content, fileder, opts)\
+ if opts == nil then\
+ opts = { }\
+ end\
+ opts.meta = opts.meta or get_meta(fileder)\
+ opts.scripts = opts.scripts or ''\
+ if not (opts.noview) then\
+ content = [[ <div class=\"view main\">\
+ <div class=\"content\">\
+ ]] .. content .. [[ </div>\
+ </div>\
+ ]]\
+ end\
+ local buf = [[<!DOCTYPE html>\
+<html>\
+ <head>\
+ <link rel=\"stylesheet\" type=\"text/css\" href=\"/static/style/:text/css\" />\
+ <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400,600\" />\
+ ]]\
+ buf = buf .. \"\\n \" .. tostring(get_meta(fileder)) .. \"\\n </head>\\n <body>\\n \" .. tostring(gen_header()) .. \"\\n\\n \" .. tostring(content) .. \"\\n\\n \" .. tostring(footer) .. \"\\n \"\
+ buf = buf .. [[ <script type=\"application/javascript\" src=\"/static/highlight-pack/:application/javascript\"></script>\
+ <script type=\"application/javascript\" src=\"//cdnjs.cloudflare.com/ajax/libs/marked/0.5.1/marked.min.js\"></script>\
+ <script type=\"application/javascript\" src=\"//cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.6/svg.min.js\"></script>\
+ <script type=\"application/javascript\" src=\"//platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\
+ <script type=\"application/javascript\" src=\"/static/fengari-web/:application/javascript\"></script>\
+ <script type=\"application/lua\" src=\"/static/mmm/:application/lua\"></script>\
+ <script type=\"application/lua\">require 'mmm'</script>\
+ ]]\
+ buf = buf .. opts.scripts\
+ buf = buf .. \"\\n </body>\\n</html>\\n \"\
+ return buf\
+end\
+return {\
+ render = render\
+}\
+", "mmm/mmmfs/layout.lua") end
+if not p["mmm.mmmfs.stores.fs"] then p["mmm.mmmfs.stores.fs"] = load("local lfs = require('lfs')\
+local dir_base\
+dir_base = function(path)\
+ local dir, base = path:match('(.-)([^/]-)$')\
+ if dir and #dir > 0 then\
+ dir = dir:sub(1, #dir - 1)\
+ end\
+ return dir, base\
+end\
+local FSStore\
+do\
+ local _class_0\
+ local _base_0 = {\
+ log = function(self, ...)\
+ return print(\"[DB]\", ...)\
+ end,\
+ list_fileders_in = function(self, path)\
+ if path == nil then\
+ path = ''\
+ end\
+ return coroutine.wrap(function()\
+ for entry_name in lfs.dir(self.root .. path) do\
+ local _continue_0 = false\
+ repeat\
+ if '.' == entry_name:sub(1, 1) then\
+ _continue_0 = true\
+ break\
+ end\
+ local entry_path = self.root .. tostring(path) .. \"/\" .. tostring(entry_name)\
+ if 'directory' == lfs.attributes(entry_path, 'mode') then\
+ coroutine.yield(tostring(path) .. \"/\" .. tostring(entry_name))\
+ end\
+ _continue_0 = true\
+ until true\
+ if not _continue_0 then\
+ break\
+ end\
+ end\
+ end)\
+ end,\
+ list_all_fileders = function(self, path)\
+ if path == nil then\
+ path = ''\
+ end\
+ return coroutine.wrap(function()\
+ for path in self:list_fileders_in(path) do\
+ coroutine.yield(path)\
+ for p in self:list_all_fileders(path) do\
+ coroutine.yield(p)\
+ end\
+ end\
+ end)\
+ end,\
+ create_fileder = function(self, parent, name)\
+ self:log(\"creating fileder \" .. tostring(path))\
+ local path = tostring(parent) .. \"/\" .. tostring(name)\
+ assert(lfs.mkdir(self.root .. path))\
+ return path\
+ end,\
+ remove_fileder = function(self, path)\
+ self:log(\"removing fileder \" .. tostring(path))\
+ local rmdir\
+ rmdir = function(path)\
+ for file in lfs.dir(path) do\
+ local _continue_0 = false\
+ repeat\
+ if '.' == file:sub(1, 1) then\
+ _continue_0 = true\
+ break\
+ end\
+ local file_path = tostring(path) .. \"/\" .. tostring(file)\
+ local _exp_0 = lfs.attributes(file_path, 'mode')\
+ if 'file' == _exp_0 then\
+ assert(os.remove(file_path))\
+ elseif 'directory' == _exp_0 then\
+ assert(rmdir(file_path))\
+ end\
+ _continue_0 = true\
+ until true\
+ if not _continue_0 then\
+ break\
+ end\
+ end\
+ return lfs.rmdir(path)\
+ end\
+ return rmdir(self.root .. path)\
+ end,\
+ rename_fileder = function(self, path, next_name)\
+ self:log(\"renaming fileder \" .. tostring(path) .. \" -> '\" .. tostring(next_name) .. \"'\")\
+ local parent, name = dir_base(path)\
+ return assert(os.rename(path, self.root .. tostring(parent) .. \"/\" .. tostring(next_name)))\
+ end,\
+ move_fileder = function(self, path, next_parent)\
+ self:log(\"moving fileder \" .. tostring(path) .. \" -> \" .. tostring(next_parent) .. \"/\")\
+ local parent, name = dir_base(path)\
+ return assert(os.rename(self.root .. path, self.root .. tostring(next_parent) .. \"/\" .. tostring(name)))\
+ end,\
+ list_facets = function(self, path)\
+ return coroutine.wrap(function()\
+ for entry_name in lfs.dir(self.root .. path) do\
+ local entry_path = tostring(self.root .. path) .. \"/\" .. tostring(entry_name)\
+ if 'file' == lfs.attributes(entry_path, 'mode') then\
+ entry_name = (entry_name:match('(.*)%.%w+')) or entry_name\
+ entry_name = entry_name:gsub('%$', '/')\
+ local name, type = entry_name:match('([%w-_]+): *(.+)')\
+ if not name then\
+ name = ''\
+ type = entry_name\
+ end\
+ coroutine.yield(name, type)\
+ end\
+ end\
+ end)\
+ end,\
+ tofp = function(self, path, name, type)\
+ if #name > 0 then\
+ type = tostring(name) .. \": \" .. tostring(type)\
+ end\
+ type = type:gsub('%/', '$')\
+ return self.root .. tostring(path) .. \"/\" .. tostring(type)\
+ end,\
+ locate = function(self, path, name, type)\
+ if not (lfs.attributes(self.root .. path, 'mode')) then\
+ return \
+ end\
+ type = type:gsub('%/', '$')\
+ if #name > 0 then\
+ name = tostring(name) .. \": \"\
+ end\
+ name = name .. type\
+ name = name:gsub('([^%w])', '%%%1')\
+ local file_name\
+ for entry_name in lfs.dir(self.root .. path) do\
+ if (entry_name:match(\"^\" .. tostring(name) .. \"$\")) or entry_name:match(\"^\" .. tostring(name) .. \"%.%w+$\") then\
+ if file_name then\
+ error(\"two files match \" .. tostring(name) .. \": \" .. tostring(file_name) .. \" and \" .. tostring(entry_name) .. \"!\")\
+ end\
+ file_name = entry_name\
+ end\
+ end\
+ return file_name and self.root .. tostring(path) .. \"/\" .. tostring(file_name)\
+ end,\
+ load_facet = function(self, path, name, type)\
+ local filepath = self:locate(path, name, type)\
+ if not (filepath) then\
+ return \
+ end\
+ local file = assert((io.open(filepath, 'rb')), \"couldn't open facet file '\" .. tostring(filepath) .. \"'\")\
+ do\
+ local _with_0 = file:read('*all')\
+ file:close()\
+ return _with_0\
+ end\
+ end,\
+ create_facet = function(self, path, name, type, blob)\
+ self:log(\"creating facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\
+ assert(blob, \"cant create facet without value!\")\
+ local filepath = self:tofp(path, name, type)\
+ if lfs.attributes(filepath, 'mode') then\
+ error(\"facet file already exists!\")\
+ end\
+ local file = assert((io.open(filepath, 'wb')), \"couldn't open facet file '\" .. tostring(filepath) .. \"'\")\
+ file:write(blob)\
+ return file:close()\
+ end,\
+ remove_facet = function(self, path, name, type)\
+ self:log(\"removing facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\
+ local filepath = self:locate(path, name, type)\
+ assert(filepath, \"couldn't locate facet!\")\
+ return assert(os.remove(filepath))\
+ end,\
+ rename_facet = function(self, path, name, type, next_name)\
+ self:log(\"renaming facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type) .. \" -> \" .. tostring(next_name))\
+ local filepath = self:locate(path, name, type)\
+ assert(filepath, \"couldn't locate facet!\")\
+ return assert(os.rename(filepath, self:tofp(path, next_name, type)))\
+ end,\
+ update_facet = function(self, path, name, type, blob)\
+ self:log(\"updating facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\
+ local filepath = self:locate(path, name, type)\
+ assert(filepath, \"couldn't locate facet!\")\
+ local file = assert((io.open(filepath, 'wb')), \"couldn't open facet file '\" .. tostring(filepath) .. \"'\")\
+ file:write(blob)\
+ return file:close()\
+ end\
+ }\
+ _base_0.__index = _base_0\
+ _class_0 = setmetatable({\
+ __init = function(self, opts)\
+ if opts == nil then\
+ opts = { }\
+ end\
+ opts.root = opts.root or 'root'\
+ opts.verbose = opts.verbose or false\
+ if not opts.verbose then\
+ self.log = function() end\
+ end\
+ self.root = opts.root:match('^(.-)/?$')\
+ return self:log(\"opening '\" .. tostring(opts.root) .. \"'...\")\
+ end,\
+ __base = _base_0,\
+ __name = \"FSStore\"\
+ }, {\
+ __index = _base_0,\
+ __call = function(cls, ...)\
+ local _self_0 = setmetatable({}, _base_0)\
+ cls.__init(_self_0, ...)\
+ return _self_0\
+ end\
+ })\
+ _base_0.__class = _class_0\
+ FSStore = _class_0\
+end\
+return {\
+ FSStore = FSStore\
+}\
+", "mmm/mmmfs/stores/fs.lua") end
+if not p["mmm.mmmfs.stores"] then p["mmm.mmmfs.stores"] = load("local require = relative(..., 0)\
+local get_store\
+get_store = function(args, opts)\
+ if args == nil then\
+ args = 'sql'\
+ end\
+ if opts == nil then\
+ opts = {\
+ verbose = true\
+ }\
+ end\
+ local type, arg = args:match('(%w+):(.*)')\
+ if not (type) then\
+ type = args\
+ end\
+ local _exp_0 = type:lower()\
+ if 'sql' == _exp_0 then\
+ local SQLStore\
+ SQLStore = require('.sql').SQLStore\
+ if arg == 'MEMORY' then\
+ opts.memory = true\
+ else\
+ opts.file = arg\
+ end\
+ return SQLStore(opts)\
+ elseif 'fs' == _exp_0 then\
+ local FSStore\
+ FSStore = require('.fs').FSStore\
+ opts.root = arg\
+ return FSStore(opts)\
+ else\
+ warn(\"unknown or missing value for STORE: valid types values are sql, fs\")\
+ return os.exit(1)\
+ end\
+end\
+return {\
+ get_store = get_store\
+}\
+", "mmm/mmmfs/stores/init.lua") end
+if not p["mmm.mmmfs.stores.sql"] then p["mmm.mmmfs.stores.sql"] = load("local sqlite = require('sqlite3')\
+local root = os.tmpname()\
+local SQLStore\
+do\
+ local _class_0\
+ local _base_0 = {\
+ log = function(self, ...)\
+ return print(\"[DB]\", ...)\
+ end,\
+ close = function(self)\
+ return self.db:close()\
+ end,\
+ fetch = function(self, q, ...)\
+ local stmt = assert(self.db:prepare(q))\
+ if 0 < select('#', ...) then\
+ stmt:bind(...)\
+ end\
+ return stmt:irows()\
+ end,\
+ fetch_one = function(self, q, ...)\
+ local stmt = assert(self.db:prepare(q))\
+ if 0 < select('#', ...) then\
+ stmt:bind(...)\
+ end\
+ return stmt:first_irow()\
+ end,\
+ exec = function(self, q, ...)\
+ local stmt = assert(self.db:prepare(q))\
+ if 0 < select('#', ...) then\
+ stmt:bind(...)\
+ end\
+ local res = assert(stmt:exec())\
+ end,\
+ list_fileders_in = function(self, path)\
+ if path == nil then\
+ path = ''\
+ end\
+ return coroutine.wrap(function()\
+ for _des_0 in self:fetch('SELECT path\\n FROM fileder WHERE parent IS ?', path) do\
+ path = _des_0[1]\
+ coroutine.yield(path)\
+ end\
+ end)\
+ end,\
+ list_all_fileders = function(self, path)\
+ if path == nil then\
+ path = ''\
+ end\
+ return coroutine.wrap(function()\
+ for path in self:list_fileders_in(path) do\
+ coroutine.yield(path)\
+ for p in self:list_all_fileders(path) do\
+ coroutine.yield(p)\
+ end\
+ end\
+ end)\
+ end,\
+ create_fileder = function(self, parent, name)\
+ local path = tostring(parent) .. \"/\" .. tostring(name)\
+ self:log(\"creating fileder \" .. tostring(path))\
+ self:exec('INSERT INTO fileder (path, parent)\\n VALUES (:path, :parent)', {\
+ path = path,\
+ parent = parent\
+ })\
+ local changes = self:fetch_one('SELECT changes()')\
+ assert(changes[1] == 1, \"couldn't create fileder - parent missing?\")\
+ return path\
+ end,\
+ remove_fileder = function(self, path)\
+ self:log(\"removing fileder \" .. tostring(path))\
+ return self:exec('DELETE FROM fileder\\n WHERE path LIKE :path || \"/%\"\\n OR path = :path', path)\
+ end,\
+ rename_fileder = function(self, path, next_name)\
+ self:log(\"renaming fileder \" .. tostring(path) .. \" -> '\" .. tostring(next_name) .. \"'\")\
+ error('not implemented')\
+ return self:exec('UPDATE fileder\\n SET path = parent || \"/\" || :next_name\\n WHERE path = :path', {\
+ path = path,\
+ next_name = next_name\
+ })\
+ end,\
+ move_fileder = function(self, path, next_parent)\
+ self:log(\"moving fileder \" .. tostring(path) .. \" -> \" .. tostring(next_parent) .. \"/\")\
+ return error('not implemented')\
+ end,\
+ list_facets = function(self, path)\
+ return coroutine.wrap(function()\
+ for _des_0 in self:fetch('SELECT facet.name, facet.type\\n FROM facet\\n INNER JOIN fileder ON facet.fileder_id = fileder.id\\n WHERE fileder.path = ?', path) do\
+ local name, type\
+ name, type = _des_0[1], _des_0[2]\
+ coroutine.yield(name, type)\
+ end\
+ end)\
+ end,\
+ load_facet = function(self, path, name, type)\
+ local v = self:fetch_one('SELECT facet.value\\n FROM facet\\n INNER JOIN fileder ON facet.fileder_id = fileder.id\\n WHERE fileder.path = :path\\n AND facet.name = :name\\n AND facet.type = :type', {\
+ path = path,\
+ name = name,\
+ type = type\
+ })\
+ return v and v[1]\
+ end,\
+ create_facet = function(self, path, name, type, blob)\
+ self:log(\"creating facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\
+ self:exec('INSERT INTO facet (fileder_id, name, type, value)\\n SELECT id, :name, :type, :blob\\n FROM fileder\\n WHERE fileder.path = :path', {\
+ path = path,\
+ name = name,\
+ type = type,\
+ blob = blob\
+ })\
+ local changes = self:fetch_one('SELECT changes()')\
+ return assert(changes[1] == 1, \"couldn't create facet - fileder missing?\")\
+ end,\
+ remove_facet = function(self, path, name, type)\
+ self:log(\"removing facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\
+ self:exec('DELETE FROM facet\\n WHERE name = :name\\n AND type = :type\\n AND fileder_id = (SELECT id FROM fileder WHERE path = :path)', {\
+ path = path,\
+ name = name,\
+ type = type\
+ })\
+ local changes = self:fetch_one('SELECT changes()')\
+ return assert(changes[1] == 1, \"no such facet\")\
+ end,\
+ rename_facet = function(self, path, name, type, next_name)\
+ self:log(\"renaming facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type) .. \" -> \" .. tostring(next_name))\
+ self:exec('UPDATE facet\\n SET name = :next_name\\n WHERE name = :name\\n AND type = :type\\n AND fileder_id = (SELECT id FROM fileder WHERE path = :path)', {\
+ path = path,\
+ name = name,\
+ next_name = next_name,\
+ type = type\
+ })\
+ local changes = self:fetch_one('SELECT changes()')\
+ return assert(changes[1] == 1, \"no such facet\")\
+ end,\
+ update_facet = function(self, path, name, type, blob)\
+ self:log(\"updating facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\
+ self:exec('UPDATE facet\\n SET value = :blob\\n WHERE facet.name = :name\\n AND facet.type = :type\\n AND facet.fileder_id = (SELECT id FROM fileder WHERE path = :path)', {\
+ path = path,\
+ name = name,\
+ type = type,\
+ blob = blob\
+ })\
+ local changes = self:fetch_one('SELECT changes()')\
+ return assert(changes[1] == 1, \"no such facet\")\
+ end\
+ }\
+ _base_0.__index = _base_0\
+ _class_0 = setmetatable({\
+ __init = function(self, opts)\
+ if opts == nil then\
+ opts = { }\
+ end\
+ opts.file = opts.file or 'db.sqlite3'\
+ opts.verbose = opts.verbose or false\
+ opts.memory = opts.memory or false\
+ if not opts.verbose then\
+ self.log = function() end\
+ end\
+ if opts.memory then\
+ self:log(\"opening in-memory DB...\")\
+ self.db = sqlite.open_memory()\
+ else\
+ self:log(\"opening '\" .. tostring(opts.file) .. \"'...\")\
+ self.db = sqlite.open(opts.file)\
+ end\
+ return assert(self.db:exec([[ PRAGMA foreign_keys = ON;\
+ PRAGMA case_sensitive_like = ON;\
+ CREATE TABLE IF NOT EXISTS fileder (\
+ id INTEGER NOT NULL PRIMARY KEY,\
+ path TEXT NOT NULL UNIQUE,\
+ parent TEXT REFERENCES fileder(path)\
+ ON DELETE CASCADE\
+ ON UPDATE CASCADE\
+ );\
+ INSERT OR IGNORE INTO fileder (path, parent) VALUES (\"\", NULL);\
+\
+ CREATE TABLE IF NOT EXISTS facet (\
+ fileder_id INTEGER NOT NULL\
+ REFERENCES fileder\
+ ON UPDATE CASCADE\
+ ON DELETE CASCADE,\
+ name TEXT NOT NULL,\
+ type TEXT NOT NULL,\
+ value BLOB NOT NULL,\
+ PRIMARY KEY (fileder_id, name, type)\
+ );\
+ CREATE INDEX IF NOT EXISTS facet_fileder_id ON facet(fileder_id);\
+ CREATE INDEX IF NOT EXISTS facet_name ON facet(name);\
+ ]]))\
+ end,\
+ __base = _base_0,\
+ __name = \"SQLStore\"\
+ }, {\
+ __index = _base_0,\
+ __call = function(cls, ...)\
+ local _self_0 = setmetatable({}, _base_0)\
+ cls.__init(_self_0, ...)\
+ return _self_0\
+ end\
+ })\
+ _base_0.__class = _class_0\
+ SQLStore = _class_0\
+end\
+return {\
+ SQLStore = SQLStore\
+}\
+", "mmm/mmmfs/stores/sql.lua") end
+if not p["mmm.dom"] then p["mmm.dom"] = load("local element\
+element = function(element)\
+ return function(...)\
+ local children = {\
+ ...\
+ }\
+ local attributes = children[#children]\
+ if 'table' == (type(attributes)) and not attributes.node then\
+ table.remove(children)\
+ else\
+ attributes = { }\
+ end\
+ do\
+ local e = document:createElement(element)\
+ for k, v in pairs(attributes) do\
+ if k == 'class' then\
+ k = 'className'\
+ end\
+ if k == 'style' and 'table' == type(v) then\
+ for kk, vv in pairs(v) do\
+ e.style[kk] = vv\
+ end\
+ elseif 'string' == type(k) then\
+ e[k] = v\
+ end\
+ end\
+ if #children == 0 then\
+ children = attributes\
+ end\
+ for _index_0 = 1, #children do\
+ local child = children[_index_0]\
+ if 'string' == type(child) then\
+ child = document:createTextNode(child)\
+ end\
+ e:appendChild(child)\
+ end\
+ return e\
+ end\
+ end\
+end\
+return setmetatable({ }, {\
+ __index = function(self, name)\
+ do\
+ local val = element(name)\
+ self[name] = val\
+ return val\
+ end\
+ end\
+})\
+", "mmm/dom/init.client.lua") end
+if not p["mmm.component"] then p["mmm.component"] = load("local tohtml\
+tohtml = function(val)\
+ if 'string' == type(val) then\
+ return document:createTextNode(val)\
+ end\
+ if 'table' == type(val) then\
+ assert(val.node, \"Table doesn't have .node\")\
+ val = val.node\
+ end\
+ if 'userdata' == type(val) then\
+ assert((js.instanceof(val, js.global.Node)), \"userdata is not a Node\")\
+ return val\
+ else\
+ return error(\"not a Node: \" .. tostring(val) .. \", \" .. tostring(type(val)))\
+ end\
+end\
+local text\
+text = function(str)\
+ return document:createTextNode(tostring(str))\
+end\
+local ReactiveVar\
+do\
+ local _class_0\
+ local _base_0 = {\
+ set = function(self, value)\
+ local old = self.value\
+ self.value = value\
+ for k, callback in pairs(self.listeners) do\
+ callback(self.value, old)\
+ end\
+ end,\
+ get = function(self)\
+ return self.value\
+ end,\
+ transform = function(self, transform)\
+ return self:set(transform(self:get()))\
+ end,\
+ subscribe = function(self, callback)\
+ do\
+ local _with_0\
+ _with_0 = function()\
+ self.listeners[callback] = nil\
+ end\
+ self.listeners[callback] = callback\
+ return _with_0\
+ end\
+ end,\
+ map = function(self, transform)\
+ do\
+ local _with_0 = ReactiveVar(transform(self.value))\
+ _with_0.upstream = self:subscribe(function(...)\
+ return _with_0:set(transform(...))\
+ end)\
+ return _with_0\
+ end\
+ end\
+ }\
+ _base_0.__index = _base_0\
+ _class_0 = setmetatable({\
+ __init = function(self, value)\
+ self.value = value\
+ self.listeners = setmetatable({ }, {\
+ __mode = 'kv'\
+ })\
+ end,\
+ __base = _base_0,\
+ __name = \"ReactiveVar\"\
+ }, {\
+ __index = _base_0,\
+ __call = function(cls, ...)\
+ local _self_0 = setmetatable({}, _base_0)\
+ cls.__init(_self_0, ...)\
+ return _self_0\
+ end\
+ })\
+ _base_0.__class = _class_0\
+ local self = _class_0\
+ self.isinstance = function(val)\
+ return 'table' == (type(val)) and val.subscribe\
+ end\
+ ReactiveVar = _class_0\
+end\
+local ReactiveElement\
+do\
+ local _class_0\
+ local _base_0 = {\
+ destroy = function(self)\
+ local _list_0 = self._subscriptions\
+ for _index_0 = 1, #_list_0 do\
+ local unsub = _list_0[_index_0]\
+ unsub()\
+ end\
+ end,\
+ set = function(self, attr, value)\
+ if attr == 'class' then\
+ attr = 'className'\
+ end\
+ if 'table' == (type(value)) and ReactiveVar.isinstance(value) then\
+ table.insert(self._subscriptions, value:subscribe(function(...)\
+ return self:set(attr, ...)\
+ end))\
+ value = value:get()\
+ end\
+ if attr == 'style' and 'table' == type(value) then\
+ for k, v in pairs(value) do\
+ self.node.style[k] = v\
+ end\
+ return \
+ end\
+ self.node[attr] = value\
+ end,\
+ prepend = function(self, child, last)\
+ return self:append(child, last, 'prepend')\
+ end,\
+ append = function(self, child, last, mode)\
+ if mode == nil then\
+ mode = 'append'\
+ end\
+ if ReactiveVar.isinstance(child) then\
+ table.insert(self._subscriptions, child:subscribe(function(...)\
+ return self:append(...)\
+ end))\
+ child = child:get()\
+ end\
+ if 'string' == type(last) then\
+ error('cannot replace string node')\
+ end\
+ if child == nil then\
+ if last then\
+ self:remove(last)\
+ end\
+ return \
+ end\
+ child = tohtml(child)\
+ if last then\
+ return self.node:replaceChild(child, tohtml(last))\
+ else\
+ local _exp_0 = mode\
+ if 'append' == _exp_0 then\
+ return self.node:appendChild(child)\
+ elseif 'prepend' == _exp_0 then\
+ return self.node:insertBefore(child, self.node.firstChild)\
+ end\
+ end\
+ end,\
+ remove = function(self, child)\
+ self.node:removeChild(tohtml(child))\
+ if 'table' == (type(child)) and child.destroy then\
+ return child:destroy()\
+ end\
+ end\
+ }\
+ _base_0.__index = _base_0\
+ _class_0 = setmetatable({\
+ __init = function(self, element, ...)\
+ if 'userdata' == type(element) then\
+ self.node = element\
+ else\
+ self.node = document:createElement(element)\
+ end\
+ self._subscriptions = { }\
+ local children = {\
+ ...\
+ }\
+ local attributes = children[#children]\
+ if 'table' == (type(attributes)) and (not ReactiveElement.isinstance(attributes)) and (not ReactiveVar.isinstance(attributes)) then\
+ table.remove(children)\
+ else\
+ attributes = { }\
+ end\
+ for k, v in pairs(attributes) do\
+ if 'string' == type(k) then\
+ self:set(k, v)\
+ end\
+ end\
+ if #children == 0 then\
+ children = attributes\
+ end\
+ for _index_0 = 1, #children do\
+ local child = children[_index_0]\
+ self:append(child)\
+ end\
+ end,\
+ __base = _base_0,\
+ __name = \"ReactiveElement\"\
+ }, {\
+ __index = _base_0,\
+ __call = function(cls, ...)\
+ local _self_0 = setmetatable({}, _base_0)\
+ cls.__init(_self_0, ...)\
+ return _self_0\
+ end\
+ })\
+ _base_0.__class = _class_0\
+ local self = _class_0\
+ self.isinstance = function(val)\
+ return 'table' == (type(val)) and val.node\
+ end\
+ ReactiveElement = _class_0\
+end\
+local get_or_create\
+get_or_create = function(elem, id, ...)\
+ elem = (document:getElementById(id)) or elem\
+ do\
+ local _with_0 = ReactiveElement(elem, ...)\
+ _with_0:set('id', id)\
+ return _with_0\
+ end\
+end\
+local elements = setmetatable({ }, {\
+ __index = function(self, name)\
+ do\
+ local val\
+ val = function(...)\
+ return ReactiveElement(name, ...)\
+ end\
+ self[name] = val\
+ return val\
+ end\
+ end\
+})\
+return {\
+ ReactiveVar = ReactiveVar,\
+ ReactiveElement = ReactiveElement,\
+ get_or_create = get_or_create,\
+ tohtml = tohtml,\
+ text = text,\
+ elements = elements\
+}\
+", "mmm/component/init.client.lua") end
diff --git a/root/static/style/text$css.css b/root/static/style/text$css.css
new file mode 100644
index 0000000..c0dc484
--- /dev/null
+++ b/root/static/style/text$css.css
@@ -0,0 +1,411 @@
+:root {
+ --gray-bright: #eeeeee;
+ --gray-dark: #303336;
+ --gray-darker: #1d1f21;
+ --gray-neutral: #b9bdc1;
+ --gray-success: #bdddc1;
+ --gray-fail: #ddbdc1;
+ --margin-wide: 2rem; }
+ @media only screen and (max-width: 425px) {
+ :root {
+ --margin-wide: 0.5rem; } }
+
+* {
+ font-weight: inherit;
+ font-style: inherit;
+ font-family: inherit;
+ color: inherit;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale; }
+
+body, html, h1, h2, h3, h4, h5, h6 {
+ margin: 0;
+ padding: 0; }
+
+h1, h2, h3, h4, h5, h6 {
+ font-weight: bold; }
+
+input, select, button {
+ color: initial; }
+
+tt, code, kbd, samp {
+ font-family: monospace; }
+
+code {
+ font-size: 0.8em; }
+
+b, strong {
+ font-weight: bold; }
+
+em, i {
+ font-style: italic; }
+
+a {
+ font-size: 1em;
+ cursor: pointer; }
+
+hr {
+ clear: both; }
+
+ul {
+ margin: 0; }
+
+body {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ background: #ffffff;
+ font-family: sans-serif;
+ min-height: 100vh; }
+
+a {
+ display: inline-block;
+ font-weight: bold;
+ text-decoration: underline;
+ text-decoration-color: transparent;
+ transition: filter 500ms, text-decoration-color 500ms; }
+ a:hover {
+ filter: invert(40%);
+ text-decoration-color: currentColor; }
+
+::selection {
+ background: #303336;
+ color: #eeeeee;
+ padding: 1em; }
+
+/*
+
+vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid)
+
+*/
+/*background color*/
+.hljs {
+ background: #1d1f21;
+ border-radius: 6px;
+ padding: 0.2em 0.5em;
+ white-space: nowrap; }
+
+pre > .hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 1em;
+ white-space: pre-wrap;
+ margin: 0 2em; }
+
+/*selection color*/
+.hljs::selection,
+.hljs span::selection {
+ background: #373b41; }
+
+.hljs::-moz-selection,
+.hljs span::-moz-selection {
+ background: #373b41; }
+
+/*foreground color*/
+.hljs {
+ color: #c5c8c6; }
+
+/*color: fg_yellow*/
+.hljs-title,
+.hljs-name {
+ color: #f0c674; }
+
+/*color: fg_comment*/
+.hljs-comment,
+.hljs-meta,
+.hljs-meta .hljs-keyword {
+ color: #707880; }
+
+/*color: fg_red*/
+.hljs-number,
+.hljs-symbol,
+.hljs-literal,
+.hljs-deletion,
+.hljs-link {
+ color: #cc6666; }
+
+/*color: fg_green*/
+.hljs-string,
+.hljs-doctag,
+.hljs-addition,
+.hljs-regexp,
+.hljs-selector-attr,
+.hljs-selector-pseudo {
+ color: #b5bd68; }
+
+/*color: fg_purple*/
+.hljs-attribute,
+.hljs-code,
+.hljs-selector-id {
+ color: #b294bb; }
+
+/*color: fg_blue*/
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-bullet,
+.hljs-tag {
+ color: #81a2be; }
+
+/*color: fg_aqua*/
+.hljs-subst,
+.hljs-variable,
+.hljs-template-tag,
+.hljs-template-variable {
+ color: #8abeb7; }
+
+/*color: fg_orange*/
+.hljs-type,
+.hljs-built_in,
+.hljs-builtin-name,
+.hljs-quote,
+.hljs-section,
+.hljs-selector-class {
+ color: #de935f; }
+
+.hljs-emphasis {
+ font-style: italic; }
+
+.hljs-strong {
+ font-weight: bold; }
+
+header > div {
+ font-family: 'Source Sans Pro', sans-serif;
+ padding: 1rem 2rem;
+ text-align: right;
+ color: #eeeeee;
+ background: #303336; }
+ header > div h1 {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ flex-wrap: wrap;
+ margin: 0;
+ line-height: 5rem;
+ font-size: 4rem;
+ font-weight: 200; }
+ header > div h1 > span {
+ margin-left: auto; }
+ header > div h1 .bold {
+ font-weight: 400; }
+ header > div h1 > a {
+ height: 5rem; }
+ header > div h1 .sun {
+ height: 100%;
+ stroke: currentColor;
+ fill: currentColor; }
+ header > div h1 .sun .out, header > div h1 .sun .in {
+ animation: spin 2s;
+ animation-iteration-count: 1;
+ transform: scale(1); }
+ header > div h1 .sun .in {
+ animation: size 2s; }
+ header > div h1 .sun.spin .circle {
+ animation: none; }
+
+header aside {
+ margin: 0;
+ padding: 0 2rem;
+ color: #eeeeee;
+ background: #1d1f21;
+ line-height: 3em; }
+ header aside > a {
+ margin-right: 2em; }
+
+@keyframes spin {
+ 0% {
+ transform: rotate3d(0.5, 1, 0, 0turn); }
+ 100% {
+ transform: rotate3d(0.5, 1, 0, 1turn); } }
+
+@keyframes size {
+ 0%, 100% {
+ transform: scale(1); }
+ 40%, 60% {
+ transform: scale(1.8); } }
+
+footer {
+ display: flex;
+ padding: 1rem 2rem;
+ position: sticky;
+ bottom: 0;
+ color: #eeeeee;
+ background: #303336; }
+ footer .icons {
+ flex: 1;
+ display: flex;
+ justify-content: flex-end; }
+ footer .icons .iconlink {
+ filter: invert(0.7); }
+ footer .icons .iconlink:hover {
+ filter: invert(1); }
+ footer .icons .iconlink > img {
+ height: 1em;
+ margin-left: 0.5em;
+ vertical-align: middle; }
+
+.browser {
+ display: flex;
+ flex: 1;
+ border-top: 0.2rem solid #eeeeee; }
+
+.view {
+ display: flex;
+ flex-direction: column;
+ flex: 1 1 0;
+ min-width: 0; }
+ .view.inspector {
+ top: 0;
+ position: sticky;
+ max-height: 100vh;
+ color: #c5c8c6;
+ border-style: solid;
+ border-width: 0 0 0 0.6rem;
+ border-radius: 0 6px 6px 0;
+ border-color: #303336;
+ background: #303336; }
+ .view.inspector nav {
+ background: inherit; }
+ .view.inspector .content {
+ margin: 0;
+ padding: 0;
+ display: flex;
+ background: #1d1f21; }
+ .view.inspector .content > * {
+ display: block;
+ margin: 0;
+ padding: 1em;
+ border-radius: 0;
+ border: 0;
+ flex: 1; }
+ .view nav {
+ position: sticky;
+ top: 0;
+ display: flex;
+ flex: 0 0 auto;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ background: #eeeeee;
+ z-index: 1000; }
+ .view nav > span:first-of-type {
+ flex: 1 0 auto; }
+ .view nav > * {
+ margin: 1em;
+ white-space: nowrap; }
+ @media only screen and (max-width: 425px) {
+ .view nav > .inspect-btn {
+ display: none; } }
+ .view .error-wrap {
+ flex: 0 0 auto;
+ padding: 1em 2em;
+ overflow: hidden;
+ background: #ddbdc1; }
+ .view .error-wrap.empty {
+ display: none; }
+ .view .error-wrap > span {
+ display: inline-block;
+ margin-bottom: 0.5em;
+ font-weight: bold;
+ color: #f00; }
+ .view .error-wrap > pre {
+ margin: 0; }
+ .view .content {
+ flex: 1 1 auto;
+ overflow: auto;
+ padding: 1em 2em;
+ position: relative; }
+
+.content {
+ opacity: 1;
+ transition: opacity 150ms; }
+ body.loading .content {
+ opacity: 0; }
+
+.content img, .content video {
+ width: inherit;
+ height: inherit; }
+
+.content .markdown > img, .content .markdown > video,
+.content .markdown > p > img,
+.content .markdown > p > video,
+.content .markdown > p > a > img,
+.content .markdown > p > a > video,
+.content .markdown > a > img,
+.content .markdown > a > video {
+ display: block;
+ max-width: 100%;
+ max-height: 50vh;
+ padding: 0 2em;
+ box-sizing: border-box; }
+
+.content .embed {
+ width: inherit;
+ height: inherit; }
+ .content .embed .description {
+ text-align: center; }
+ .content .embed.inline {
+ display: inline-block; }
+ .content .embed.desc {
+ display: inline-block;
+ padding: 0.5em;
+ margin: 0.2em;
+ background: #eeeeee; }
+
+.content pre > code {
+ border-style: solid;
+ border-width: 0 0 0 0.6rem;
+ border-radius: 0 6px 6px 0;
+ border-color: #303336;
+ display: block;
+ border-radius: 6px;
+ margin: 0 var(--margin-wide);
+ padding: 1em;
+ white-space: pre-wrap;
+ overflow-x: auto;
+ background: #1d1f21;
+ color: #c5c8c6; }
+
+.content pre.dual-code {
+ display: flex;
+ justify-content: space-between;
+ padding: 0 2rem; }
+ .content pre.dual-code > code {
+ border-style: solid;
+ border-width: 0 0.3rem 0 0;
+ border-radius: 6px 0 0 6px;
+ flex: 1;
+ margin: 0; }
+ .content pre.dual-code > code + code {
+ border-style: solid;
+ border-width: 0 0 0 0.3rem;
+ border-radius: 0 6px 6px 0; }
+
+.content .example, .content .well {
+ border-style: solid;
+ border-width: 0 0 0 0.6rem;
+ border-radius: 0 6px 6px 0;
+ margin: 1rem var(--margin-wide);
+ padding: 1rem;
+ background: #eeeeee;
+ border-color: #b9bdc1; }
+
+.canvas_app {
+ position: relative;
+ display: inline-block; }
+ .canvas_app .overlay {
+ position: absolute;
+ padding: 1rem;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ opacity: 0;
+ background: rgba(0, 0, 0, 0.7);
+ transition: opacity 300ms; }
+ .canvas_app .overlay:hover {
+ opacity: 1; }
+ .canvas_app .overlay > * {
+ margin: 0.5em; }
+ .canvas_app .overlay a {
+ display: block;
+ color: #eeeeee;
+ font-family: inherit; }