aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s-ol@users.noreply.github.com>2019-10-07 17:49:16 +0000
committers-ol <s-ol@users.noreply.github.com>2019-10-07 17:49:16 +0000
commitad26c7c4e374f66a978f9946bbb083377f2224a6 (patch)
tree7bdee1e299cb2a907294b115b577e14ccbee4986
parentadd simple HTTP server (diff)
downloadmmm-ad26c7c4e374f66a978f9946bbb083377f2224a6.tar.gz
mmm-ad26c7c4e374f66a978f9946bbb083377f2224a6.zip
allow server to render with layout
-rw-r--r--build/layout.moon108
-rw-r--r--build/render_all.moon71
-rw-r--r--build/server.moon3
-rw-r--r--mmm/mmmfs/conversion.moon262
-rw-r--r--mmm/mmmfs/converts.moon269
-rw-r--r--mmm/mmmfs/layout.moon166
6 files changed, 449 insertions, 430 deletions
diff --git a/build/layout.moon b/build/layout.moon
deleted file mode 100644
index 61f93cb..0000000
--- a/build/layout.moon
+++ /dev/null
@@ -1,108 +0,0 @@
-import header, aside, footer, div, svg, script, g, circle, h1, span, b, a, img from require 'mmm.dom'
-import navigate_to from (require 'mmm.mmmfs.util') require 'mmm.dom'
-
-pick = (...) ->
- num = select '#', ...
- i = math.ceil math.random! * num
- select i, ...
-
-iconlink = (href, src, alt, style) -> a {
- class: 'iconlink',
- target: '_blank',
- rel: 'me',
- :href,
- img :src, :alt, :style
-}
-
-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' }
- }
-}
-
-{
- header: header {
- div {
- h1 {
- logo
- span {
- span 'mmm', class: 'bold'
- '&#8203;'
- '.s&#8209;ol.nu'
- }
- }
- span "fun stuff with code and wires"
- -- pick 'fun', 'cool', 'weird', 'interesting', 'new'
- -- pick 'stuff', 'things', 'projects', 'experiments', 'news'
- -- "with"
- -- pick 'mostly code', 'code and wires', 'silicon', 'electronics'
- }
- 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 "
- var l = document.currentScript.parentElement;
- l.href = l.href.replace('%20[at]%20', '@');
- l.href = l.href.replace('%20[removethis]', '') + '?subject=Hey there :)';
- "
- }
- }
- }
- footer: footer {
- span {
- 'made with \xe2\x98\xbd by '
- a 's-ol', href: 'https://twitter.com/S0lll0s'
- ", #{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' }
- }
- }
- get_meta: =>
- title = (@get 'title: text/plain') or @gett 'name: alpha'
-
- l = (str) ->
- str = str\gsub '[%s\\n]+$', ''
- str\gsub '\\n', ' '
- e = (str) -> string.format '%q', l str
-
- meta = "
- <meta charset=\"UTF-8\">
- <title>#{l title}</title>
- "
-
- if page_meta = @get '_meta: mmm/dom'
- meta ..= page_meta
- else
- meta ..= "
- <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
-
- <meta property=\"og:title\" content=#{e title} />
- <meta property=\"og:type\" content=\"website\" />
- <meta property=\"og:url\" content=\"https://mmm.s-ol.nu#{@path}/\" />
- <meta property=\"og:site_name\" content=\"mmm\" />"
-
- if desc = @get 'description: text/plain'
- meta ..= "
- <meta property=\"og:description\" content=#{e desc} />"
-
- meta
-}
diff --git a/build/render_all.moon b/build/render_all.moon
index 14e4cce..4fe827b 100644
--- a/build/render_all.moon
+++ b/build/render_all.moon
@@ -10,73 +10,24 @@ add '?/init.server'
require 'mmm'
import tohtml from require 'mmm.component'
import Browser from require 'mmm.mmmfs.browser'
-import get_meta, header, footer from require 'build.layout'
+import render from require 'mmm.mmmfs.layout'
+import SQLStore from require 'mmm.mmmfs.drivers.sql'
+import load_tree from require 'build.util'
-- usage:
--- moon render_all.moon [db.sqlite3]
-{ file } = arg
+-- moon render_all.moon [db.sqlite3] [startpath]
+{ file, startpath } = arg
export BROWSER
-render = (fileder, output) ->
- BROWSER = Browser fileder
-
- with io.open output, 'w'
- \write [[
- <!DOCTYPE html>
- <html>
- <head>
- <link rel="stylesheet" type="text/css" href="/main.css" />
- <!--
- <link rel="preload" as="fetch" href="/mmm/dom/init.lua" />
- <link rel="preload" as="fetch" href="/mmm/component/init.lua" />
- <link rel="preload" as="fetch" href="/mmm/mmmfs/init.lua" />
- <link rel="preload" as="fetch" href="/mmm/mmmfs/fileder.lua" />
- <link rel="preload" as="fetch" href="/mmm/mmmfs/browser.lua" />
- -->
-
- <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400" rel="stylesheet">
- ]]
- \write "
- #{get_meta fileder}
- </head>
- <body>
- #{header}
-
- #{assert (tohtml BROWSER), "couldn't render BROWSER"}
-
- #{footer}
- "
- \write [[
- <script src="/highlight.pack.js"></script>
- <script src="//cdnjs.cloudflare.com/ajax/libs/marked/0.5.1/marked.min.js"></script>
- <script src="//cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.6/svg.min.js"></script>
- <script src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
- <script src="/fengari-web.js"></script>
- <script type="application/lua" src="/mmm.bundle.lua"></script>
- <script type="application/lua">require 'mmm'</script>
- ]]
- \write "
- <script type=\"application/lua\">
- on_load = on_load or {}
- table.insert(on_load, function()
- local path = #{string.format '%q', path}
- local browser = require 'mmm.mmmfs.browser'
- local root = dofile '/$bundle.lua'
- root:mount('', true)
-
- BROWSER = browser.Browser(root, path, true)
- end)
- </script>
- </body>
- </html>
- "
- \close!
-
-
tree = load_tree SQLStore :name
+tree = tree\walk startpath if startpath
for fileder in coroutine.wrap tree\iterate
print "rendering '#{fileder.path}'..."
os.execute "mkdir -p 'out/#{fileder.path}'"
- render fileder, "out/#{fileder.path}/index.html"
+
+ BROWSER = Browser fileder
+ with io.open "out/#{fileder.path}/index.html", 'w'
+ \write render (tohtml BROWSER), fileder
+ \close!
diff --git a/build/server.moon b/build/server.moon
index dd491d3..b178636 100644
--- a/build/server.moon
+++ b/build/server.moon
@@ -82,7 +82,6 @@ class Server
path = req\get ':path'
path, facet = dir_base path
- print "'#{path}', '#{facet}'"
facet = if #facet > 0
facet = '' if facet == ':'
accept = req\get 'mmm-accept'
@@ -95,7 +94,7 @@ class Server
res = headers.new!
response_type = if status > 299 then 'text/plain'
else if facet then facet.type
- else 'text/plain'
+ else 'text/json'
res\append ':status', tostring status
res\append 'content-type', response_type
diff --git a/mmm/mmmfs/conversion.moon b/mmm/mmmfs/conversion.moon
index f6ca8c0..54cd680 100644
--- a/mmm/mmmfs/conversion.moon
+++ b/mmm/mmmfs/conversion.moon
@@ -1,263 +1,5 @@
-import div, code, img, video, blockquote, a, span, source, iframe from require 'mmm.dom'
-import find_fileder, link_to, embed from (require 'mmm.mmmfs.util') require 'mmm.dom'
-import tohtml from require 'mmm.component'
-
--- fix JS null values
-js_fix = if MODE == 'CLIENT'
- (arg) ->
- return if arg == js.null
- arg
-
--- limit function to one argument
-single = (func) -> (val) -> func val
-
--- load a chunk using a specific 'load'er
-loadwith = (_load) -> (val, fileder, key) ->
- func = assert _load val, "#{fileder}##{key}"
- func!
-
--- list of converts
--- converts each have
--- * inp - input type. can capture subtypes using `(.+)`
--- * out - output type. can substitute subtypes from inp with %1, %2 etc.
--- * transform - function (val: inp, fileder) -> val: out
-converts = {
- {
- inp: 'fn -> (.+)',
- out: '%1',
- transform: (val, fileder) -> val fileder
- },
- {
- inp: 'mmm/component',
- out: 'mmm/dom',
- transform: single tohtml
- },
- {
- inp: 'mmm/dom',
- out: 'text/html',
- transform: (node) -> if MODE == 'SERVER' then node else node.outerHTML
- },
- {
- inp: 'text/html',
- out: 'mmm/dom',
- transform: if MODE == 'SERVER'
- (html, fileder) ->
- html = html\gsub '<mmm%-link%s+(.-)>(.-)</mmm%-link>', (attrs, text) ->
- text = nil if #text == 0
- path = ''
- while attrs and attrs != ''
- key, val, _attrs = attrs\match '^(%w+)="([^"]-)"%s*(.*)'
- if not key
- key, _attrs = attrs\match '^(%w+)%s*(.*)$'
- val = true
-
- attrs = _attrs
-
- switch key
- when 'path' then path = val
- else warn "unkown attribute '#{key}=\"#{val}\"' in <mmm-link>"
-
- link_to path, text, fileder
-
- html = html\gsub '<mmm%-embed%s+(.-)>(.-)</mmm%-embed>', (attrs, desc) ->
- path, facet = '', ''
- opts = {}
- if #desc != 0
- opts.desc = desc
-
- while attrs and attrs != ''
- key, val, _attrs = attrs\match '^(%w+)="([^"]-)"%s*(.*)'
- if not key
- key, _attrs = attrs\match '^(%w+)%s*(.*)$'
- val = true
-
- attrs = _attrs
-
- switch key
- when 'path' then path = val
- when 'facet' then facet = val
- when 'nolink' then opts.nolink = true
- when 'inline' then opts.inline = true
- else warn "unkown attribute '#{key}=\"#{val}\"' in <mmm-embed>"
-
- embed path, facet, fileder, opts
-
- html
- else
- (html, fileder) ->
- parent = with document\createElement 'div'
- .innerHTML = html
-
- -- copy to iterate safely, HTMLCollections update when nodes are GC'ed
- embeds = \getElementsByTagName 'mmm-embed'
- embeds = [embeds[i] for i=0, embeds.length - 1]
- for element in *embeds
- path = js_fix element\getAttribute 'path'
- facet = js_fix element\getAttribute 'facet'
- nolink = js_fix element\getAttribute 'nolink'
- inline = js_fix element\getAttribute 'inline'
- desc = js_fix element.innerText
-
- element\replaceWith embed path or '', facet or '', fileder, { :nolink, :inline, :desc }
-
- embeds = \getElementsByTagName 'mmm-link'
- embeds = [embeds[i] for i=0, embeds.length - 1]
- for element in *embeds
- text = js_fix element.innerText
- path = js_fix element\getAttribute 'path'
-
- element\replaceWith link_to path or '', text, fileder
-
- assert 1 == parent.childElementCount, "text/html with more than one child!"
- parent.firstElementChild
- },
- {
- inp: 'text/lua -> (.+)',
- out: '%1',
- transform: loadwith load or loadstring
- },
- {
- inp: 'mmm/tpl -> (.+)',
- out: '%1',
- transform: (source, fileder) ->
- source\gsub '{{(.-)}}', (expr) ->
- path, facet = expr\match '^([%w%-_%./]*)%+(.*)'
- assert path, "couldn't match TPL expression '#{expr}'"
-
- (find_fileder path, fileder)\gett facet
- },
- {
- inp: 'time/iso8601-date',
- out: 'time/unix',
- transform: (val) ->
- 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: '#{val}'"
- os.time :year, :month, :day
- },
- {
- inp: 'URL -> twitter/tweet',
- out: 'mmm/dom',
- transform: (href) ->
- id = assert (href\match 'twitter.com/[^/]-/status/(%d*)'), "couldn't parse twitter/tweet URL: '#{href}'"
- if MODE == 'CLIENT'
- with parent = div!
- window.twttr.widgets\createTweet id, parent
- else
- div blockquote {
- class: 'twitter-tweet'
- 'data-lang': 'en'
- a '(linked tweet)', :href
- }
- },
- {
- inp: 'URL -> youtube/video',
- out: 'mmm/dom',
- transform: (link) ->
- id = link\match 'youtu%.be/([^/]+)'
- id or= link\match 'youtube.com/watch.*[?&]v=([^&]+)'
- id or= link\match 'youtube.com/[ev]/([^/]+)'
- id or= link\match 'youtube.com/embed/([^/]+)'
-
- assert id, "couldn't parse youtube URL: '#{link}'"
-
- iframe {
- width: 560
- height: 315
- frameborder: 0
- allowfullscreen: true
- frameBorder: 0
- src: "//www.youtube.com/embed/#{id}"
- }
- },
- {
- inp: 'URL -> image/.+',
- out: 'mmm/dom',
- transform: (src, fileder) -> img :src
- },
- {
- inp: 'URL -> video/.+',
- out: 'mmm/dom',
- transform: (src) ->
- -- @TODO: add parsed MIME type
- video (source :src), controls: true, loop: true
- },
- {
- inp: 'text/plain',
- out: 'mmm/dom',
- transform: (val) -> span val
- },
- {
- inp: 'alpha',
- out: 'mmm/dom',
- transform: single code
- },
- {
- inp: 'URL -> .*',
- out: 'mmm/dom',
- transform: single code
- },
-}
-
-if MODE == 'SERVER'
- ok, moon = pcall require, 'moonscript.base'
- if ok
- _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
- }
-else
- table.insert converts, {
- inp: 'text/javascript -> (.+)',
- out: '%1',
- transform: (source) ->
- f = js.new window.Function, source
- f!
- }
-
-do
- local markdown
- if MODE == 'SERVER'
- success, discount = pcall require, 'discount'
- if not success
- warn "NO MARKDOWN SUPPORT!", discount
-
- markdown = success and (md) ->
- res = assert discount.compile md, 'githubtags'
- res.body
- else
- markdown = window and window.marked and window\marked
-
- if markdown
- table.insert converts, {
- inp: 'text/markdown',
- out: 'text/html',
- transform: (md) -> "<div class=\"markdown\">#{markdown md}</div>"
- }
-
- table.insert converts, {
- inp: 'text/markdown%+span',
- out: 'mmm/dom',
- transform: if MODE == 'SERVER'
- (source) ->
- html = markdown source
- html = html\gsub '^<p', '<span'
- html\gsub '/p>$', '/span>'
- else
- (source) ->
- html = markdown source
- html = html\gsub '^%s*<p>%s*', ''
- html = html\gsub '%s*</p>%s*$', ''
- with document\createElement 'span'
- .innerHTML = html
- }
+require = relative ..., 1
+converts = require '.converts'
count = (base, pattern='->') -> select 2, base\gsub pattern, ''
escape_pattern = (inp) -> "^#{inp\gsub '([-/])', '%%%1'}$"
diff --git a/mmm/mmmfs/converts.moon b/mmm/mmmfs/converts.moon
new file mode 100644
index 0000000..f8aad9f
--- /dev/null
+++ b/mmm/mmmfs/converts.moon
@@ -0,0 +1,269 @@
+require = relative ..., 1
+import div, code, img, video, blockquote, a, span, source, iframe from require 'mmm.dom'
+import find_fileder, link_to, embed from (require 'mmm.mmmfs.util') require 'mmm.dom'
+import render from require '.layout'
+import tohtml from require 'mmm.component'
+
+-- fix JS null values
+js_fix = if MODE == 'CLIENT'
+ (arg) ->
+ return if arg == js.null
+ arg
+
+-- limit function to one argument
+single = (func) -> (val) -> func val
+
+-- load a chunk using a specific 'load'er
+loadwith = (_load) -> (val, fileder, key) ->
+ func = assert _load val, "#{fileder}##{key}"
+ func!
+
+-- list of converts
+-- converts each have
+-- * inp - input type. can capture subtypes using `(.+)`
+-- * out - output type. can substitute subtypes from inp with %1, %2 etc.
+-- * transform - function (val: inp, fileder) -> val: out
+converts = {
+ {
+ inp: 'fn -> (.+)',
+ out: '%1',
+ transform: (val, fileder) -> val fileder
+ },
+ {
+ inp: 'mmm/component',
+ out: 'mmm/dom',
+ transform: single tohtml
+ },
+ {
+ inp: 'mmm/dom',
+ out: 'text/html+frag',
+ transform: (node) -> if MODE == 'SERVER' then node else node.outerHTML
+ },
+ {
+ inp: 'text/html%+frag',
+ out: 'text/html',
+ transform: (html, fileder) -> render html, fileder
+ },
+ {
+ inp: 'text/html%+frag',
+ out: 'mmm/dom',
+ transform: if MODE == 'SERVER'
+ (html, fileder) ->
+ html = html\gsub '<mmm%-link%s+(.-)>(.-)</mmm%-link>', (attrs, text) ->
+ text = nil if #text == 0
+ path = ''
+ while attrs and attrs != ''
+ key, val, _attrs = attrs\match '^(%w+)="([^"]-)"%s*(.*)'
+ if not key
+ key, _attrs = attrs\match '^(%w+)%s*(.*)$'
+ val = true
+
+ attrs = _attrs
+
+ switch key
+ when 'path' then path = val
+ else warn "unkown attribute '#{key}=\"#{val}\"' in <mmm-link>"
+
+ link_to path, text, fileder
+
+ html = html\gsub '<mmm%-embed%s+(.-)>(.-)</mmm%-embed>', (attrs, desc) ->
+ path, facet = '', ''
+ opts = {}
+ if #desc != 0
+ opts.desc = desc
+
+ while attrs and attrs != ''
+ key, val, _attrs = attrs\match '^(%w+)="([^"]-)"%s*(.*)'
+ if not key
+ key, _attrs = attrs\match '^(%w+)%s*(.*)$'
+ val = true
+
+ attrs = _attrs
+
+ switch key
+ when 'path' then path = val
+ when 'facet' then facet = val
+ when 'nolink' then opts.nolink = true
+ when 'inline' then opts.inline = true
+ else warn "unkown attribute '#{key}=\"#{val}\"' in <mmm-embed>"
+
+ embed path, facet, fileder, opts
+
+ html
+ else
+ (html, fileder) ->
+ parent = with document\createElement 'div'
+ .innerHTML = html
+
+ -- copy to iterate safely, HTMLCollections update when nodes are GC'ed
+ embeds = \getElementsByTagName 'mmm-embed'
+ embeds = [embeds[i] for i=0, embeds.length - 1]
+ for element in *embeds
+ path = js_fix element\getAttribute 'path'
+ facet = js_fix element\getAttribute 'facet'
+ nolink = js_fix element\getAttribute 'nolink'
+ inline = js_fix element\getAttribute 'inline'
+ desc = js_fix element.innerText
+
+ element\replaceWith embed path or '', facet or '', fileder, { :nolink, :inline, :desc }
+
+ embeds = \getElementsByTagName 'mmm-link'
+ embeds = [embeds[i] for i=0, embeds.length - 1]
+ for element in *embeds
+ text = js_fix element.innerText
+ path = js_fix element\getAttribute 'path'
+
+ element\replaceWith link_to path or '', text, fileder
+
+ assert 1 == parent.childElementCount, "text/html with more than one child!"
+ parent.firstElementChild
+ },
+ {
+ inp: 'text/lua -> (.+)',
+ out: '%1',
+ transform: loadwith load or loadstring
+ },
+ {
+ inp: 'mmm/tpl -> (.+)',
+ out: '%1',
+ transform: (source, fileder) ->
+ source\gsub '{{(.-)}}', (expr) ->
+ path, facet = expr\match '^([%w%-_%./]*)%+(.*)'
+ assert path, "couldn't match TPL expression '#{expr}'"
+
+ (find_fileder path, fileder)\gett facet
+ },
+ {
+ inp: 'time/iso8601-date',
+ out: 'time/unix',
+ transform: (val) ->
+ 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: '#{val}'"
+ os.time :year, :month, :day
+ },
+ {
+ inp: 'URL -> twitter/tweet',
+ out: 'mmm/dom',
+ transform: (href) ->
+ id = assert (href\match 'twitter.com/[^/]-/status/(%d*)'), "couldn't parse twitter/tweet URL: '#{href}'"
+ if MODE == 'CLIENT'
+ with parent = div!
+ window.twttr.widgets\createTweet id, parent
+ else
+ div blockquote {
+ class: 'twitter-tweet'
+ 'data-lang': 'en'
+ a '(linked tweet)', :href
+ }
+ },
+ {
+ inp: 'URL -> youtube/video',
+ out: 'mmm/dom',
+ transform: (link) ->
+ id = link\match 'youtu%.be/([^/]+)'
+ id or= link\match 'youtube.com/watch.*[?&]v=([^&]+)'
+ id or= link\match 'youtube.com/[ev]/([^/]+)'
+ id or= link\match 'youtube.com/embed/([^/]+)'
+
+ assert id, "couldn't parse youtube URL: '#{link}'"
+
+ iframe {
+ width: 560
+ height: 315
+ frameborder: 0
+ allowfullscreen: true
+ frameBorder: 0
+ src: "//www.youtube.com/embed/#{id}"
+ }
+ },
+ {
+ inp: 'URL -> image/.+',
+ out: 'mmm/dom',
+ transform: (src, fileder) -> img :src
+ },
+ {
+ inp: 'URL -> video/.+',
+ out: 'mmm/dom',
+ transform: (src) ->
+ -- @TODO: add parsed MIME type
+ video (source :src), controls: true, loop: true
+ },
+ {
+ inp: 'text/plain',
+ out: 'mmm/dom',
+ transform: (val) -> span val
+ },
+ {
+ inp: 'alpha',
+ out: 'mmm/dom',
+ transform: single code
+ },
+ {
+ inp: 'URL -> .*',
+ out: 'mmm/dom',
+ transform: single code
+ },
+}
+
+if MODE == 'SERVER'
+ ok, moon = pcall require, 'moonscript.base'
+ if ok
+ _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
+ }
+else
+ table.insert converts, {
+ inp: 'text/javascript -> (.+)',
+ out: '%1',
+ transform: (source) ->
+ f = js.new window.Function, source
+ f!
+ }
+
+do
+ local markdown
+ if MODE == 'SERVER'
+ success, discount = pcall require, 'discount'
+ if not success
+ warn "NO MARKDOWN SUPPORT!", discount
+
+ markdown = success and (md) ->
+ res = assert discount.compile md, 'githubtags'
+ res.body
+ else
+ markdown = window and window.marked and window\marked
+
+ if markdown
+ table.insert converts, {
+ inp: 'text/markdown',
+ out: 'text/html+frag',
+ transform: (md) -> "<div class=\"markdown\">#{markdown md}</div>"
+ }
+
+ table.insert converts, {
+ inp: 'text/markdown%+span',
+ out: 'mmm/dom',
+ transform: if MODE == 'SERVER'
+ (source) ->
+ html = markdown source
+ html = html\gsub '^<p', '<span'
+ html\gsub '/p>$', '/span>'
+ else
+ (source) ->
+ html = markdown source
+ html = html\gsub '^%s*<p>%s*', ''
+ html = html\gsub '%s*</p>%s*$', ''
+ with document\createElement 'span'
+ .innerHTML = html
+ }
+
+converts
diff --git a/mmm/mmmfs/layout.moon b/mmm/mmmfs/layout.moon
new file mode 100644
index 0000000..47e511b
--- /dev/null
+++ b/mmm/mmmfs/layout.moon
@@ -0,0 +1,166 @@
+require = relative ..., 1
+import header, aside, footer, div, svg, script, g, circle, h1, span, b, a, img from require 'mmm.dom'
+import navigate_to from (require 'mmm.mmmfs.util') require 'mmm.dom'
+
+pick = (...) ->
+ num = select '#', ...
+ i = math.ceil math.random! * num
+ select i, ...
+
+iconlink = (href, src, alt, style) -> a {
+ class: 'iconlink',
+ target: '_blank',
+ rel: 'me',
+ :href,
+ img :src, :alt, :style
+}
+
+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' }
+ }
+}
+
+header = header {
+ div {
+ h1 {
+ logo
+ span {
+ span 'mmm', class: 'bold'
+ '&#8203;'
+ '.s&#8209;ol.nu'
+ }
+ }
+ span "fun stuff with code and wires"
+ -- pick 'fun', 'cool', 'weird', 'interesting', 'new'
+ -- pick 'stuff', 'things', 'projects', 'experiments', 'news'
+ -- "with"
+ -- pick 'mostly code', 'code and wires', 'silicon', 'electronics'
+ }
+ 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 "
+ var l = document.currentScript.parentElement;
+ l.href = l.href.replace('%20[at]%20', '@');
+ l.href = l.href.replace('%20[removethis]', '') + '?subject=Hey there :)';
+ "
+ }
+ }
+}
+
+footer = footer {
+ span {
+ 'made with \xe2\x98\xbd by '
+ a 's-ol', href: 'https://twitter.com/S0lll0s'
+ ", #{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' }
+ }
+}
+
+get_meta = =>
+ title = (@get 'title: text/plain') or @gett 'name: alpha'
+
+ l = (str) ->
+ str = str\gsub '[%s\\n]+$', ''
+ str\gsub '\\n', ' '
+ e = (str) -> string.format '%q', l str
+
+ meta = "
+ <meta charset=\"UTF-8\">
+ <title>#{l title}</title>
+ "
+
+ if page_meta = @get '_meta: mmm/dom'
+ meta ..= page_meta
+ else
+ meta ..= "
+ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
+
+ <meta property=\"og:title\" content=#{e title} />
+ <meta property=\"og:type\" content=\"website\" />
+ <meta property=\"og:url\" content=\"https://mmm.s-ol.nu#{@path}/\" />
+ <meta property=\"og:site_name\" content=\"mmm\" />"
+
+ if desc = @get 'description: text/plain'
+ meta ..= "
+ <meta property=\"og:description\" content=#{e desc} />"
+
+ meta
+
+render = (content, fileder) ->
+ buf = [[
+<!DOCTYPE html>
+<html>
+ <head>
+ <link rel="stylesheet" type="text/css" href="/main.css" />
+ <!--
+ <link rel="preload" as="fetch" href="/mmm/dom/init.lua" />
+ <link rel="preload" as="fetch" href="/mmm/component/init.lua" />
+ <link rel="preload" as="fetch" href="/mmm/mmmfs/init.lua" />
+ <link rel="preload" as="fetch" href="/mmm/mmmfs/fileder.lua" />
+ <link rel="preload" as="fetch" href="/mmm/mmmfs/browser.lua" />
+ -->
+
+ <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,400" rel="stylesheet">
+ ]]
+ buf ..= "
+ #{get_meta fileder}
+ </head>
+ <body>
+ #{header}
+
+ #{content}
+
+ #{footer}
+ "
+ buf ..= [[
+ <script src="/highlight.pack.js"></script>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/marked/0.5.1/marked.min.js"></script>
+ <script src="//cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.6/svg.min.js"></script>
+ <script src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
+ <script src="/fengari-web.js"></script>
+ <script type="application/lua" src="/mmm.bundle.lua"></script>
+ <script type="application/lua">require 'mmm'</script>
+ ]]
+ buf ..= "
+ <script type=\"application/lua\">
+ on_load = on_load or {}
+ table.insert(on_load, function()
+ local path = #{string.format '%q', path}
+ local browser = require 'mmm.mmmfs.browser'
+ local root = dofile '/$bundle.lua'
+ root:mount('', true)
+
+ BROWSER = browser.Browser(root, path, true)
+ end)
+ </script>
+ </body>
+</html>
+ "
+
+ buf
+
+{
+ :render
+}