"
-
- 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
- desc = nil if desc == ''
-
- 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',
- cost: 0.5
- transform: loadwith load or loadstring
- }
- {
- inp: 'mmm/tpl -> (.+)',
- out: '%1',
- cost: 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',
- cost: 0.5
- 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',
- cost: 1
- 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',
- cost: 1
- 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',
- cost: 1
- transform: (src, fileder) => img :src
- }
- {
- inp: 'URL -> video/.+',
- out: 'mmm/dom',
- cost: 1
- transform: (src) =>
- -- @TODO: add parsed MIME type
- video (source :src), controls: true, loop: true
- }
- {
- inp: 'text/plain',
- out: 'mmm/dom',
- cost: 2
- transform: (val) => span val
- }
- {
- inp: 'alpha',
- out: 'mmm/dom',
- cost: 2
- transform: single code
- }
- -- this one needs a higher cost
- -- {
- -- inp: 'URL -> .+',
- -- out: 'mmm/dom',
- -- transform: single code
- -- }
- {
- inp: '(.+)',
- out: 'URL -> %1',
- cost: 5
- transform: (_, fileder, key) => "#{fileder.path}/#{key.name}:#{@from}"
- }
- {
- inp: 'table',
- out: 'text/json',
- cost: 2
- transform: do
- tojson = (obj) ->
- switch type obj
- when 'string'
- string.format '%q', obj
- when 'table'
- if obj[1] or not next obj
- "[#{table.concat [tojson c for c in *obj], ','}]"
- else
- "{#{table.concat ["#{tojson k}: #{tojson v}" for k,v in pairs obj], ', '}}"
- when 'number'
- tostring obj
- when 'boolean'
- tostring obj
- when 'nil'
- 'null'
- else
- error "unknown type '#{type obj}'"
-
- (val) => tojson val
- }
- {
- inp: 'table',
- out: 'mmm/dom',
- cost: 5
- transform: do
- deep_tostring = (tbl, space='') ->
- buf = space .. tostring tbl
-
- return buf unless 'table' == type tbl
-
- buf = buf .. ' {\n'
- for k,v in pairs tbl
- buf = buf .. "#{space} [#{k}]: #{deep_tostring v, space .. ' '}\n"
- buf = buf .. "#{space}}"
- buf
-
- (tbl) => pre code deep_tostring tbl
- }
- code_hl 'javascript'
- code_hl 'moonscript'
- code_hl 'lua'
- code_hl 'markdown'
- code_hl 'css'
-}
-
-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',
- cost: 1
- transform: loadwith moon.load or moon.loadstring
- }
-
- table.insert converts, {
- inp: 'text/moonscript -> (.+)',
- out: 'text/lua -> %1',
- cost: 2
- transform: single moon.to_lua
- }
-else
- table.insert converts, {
- inp: 'text/javascript -> (.+)',
- out: '%1',
- cost: 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',
- cost: 1
- transform: (md) => "#{markdown md}
"
- }
-
- table.insert converts, {
- inp: 'text/markdown%+span',
- out: 'mmm/dom',
- cost: 1
- transform: if MODE == 'SERVER'
- (source) =>
- html = markdown source
- html = html\gsub '^$', '/span>'
- else
- (source) =>
- html = markdown source
- html = html\gsub '^%s*
%s*', ''
- html = html\gsub '%s*
%s*$', ''
- with document\createElement 'span'
- .innerHTML = html
- }
-
-if MODE == 'CLIENT' and window.mermaid
- window.mermaid\initialize {
- startOnLoad: false
- fontFamily: 'monospace'
- }
-
- id_counter = 1
- table.insert converts, {
- inp: 'text/mermaid-graph'
- out: 'mmm/dom'
- cost: 1
- transform: (source, fileder, key) =>
- id_counter += 1
- id = "mermaid-#{id_counter}"
- with container = document\createElement 'div'
- cb = (svg) =>
- .innerHTML = svg
- .firstElementChild.style.width = '100%'
- .firstElementChild.style.height = 'auto'
-
- window\setImmediate (_) ->
- window.mermaid\render id, source, cb, container
- }
-
-converts
diff --git a/mmm/mmmfs/init.moon b/mmm/mmmfs/init.moon
index 8aba86a..fc89d7f 100644
--- a/mmm/mmmfs/init.moon
+++ b/mmm/mmmfs/init.moon
@@ -1,9 +1,4 @@
-require = relative ...
-import Key, Fileder from require '.fileder'
-import Browser from require '.browser'
+require = relative ..., 0
-{
- :Key
- :Fileder
- :Browser
-}
+export ^
+PLUGINS = require '.plugins'
diff --git a/mmm/mmmfs/plugins/code.moon b/mmm/mmmfs/plugins/code.moon
new file mode 100644
index 0000000..ec22f71
--- /dev/null
+++ b/mmm/mmmfs/plugins/code.moon
@@ -0,0 +1,16 @@
+import pre from require 'mmm.dom'
+import languages from require 'mmm.highlighting'
+
+-- syntax-highlighted code
+{
+ converts: {
+ {
+ inp: 'text/([^ ]*).*'
+ out: 'mmm/dom'
+ cost: 5
+ transform: (val) =>
+ lang = @from\match @convert.inp
+ pre languages[lang] val
+ }
+ }
+}
diff --git a/mmm/mmmfs/plugins/init.moon b/mmm/mmmfs/plugins/init.moon
new file mode 100644
index 0000000..af4a2aa
--- /dev/null
+++ b/mmm/mmmfs/plugins/init.moon
@@ -0,0 +1,292 @@
+require = relative ..., 1
+import div, pre, code, img, video, span, source 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'
+
+keep = (var) ->
+ last = var\get!
+ var\map (val) ->
+ last = val or last
+ last
+
+-- 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.
+-- * cost - conversion cost
+-- * transform - function (val: inp, fileder) => val: out
+-- @convert, @from, @to contain the convert and the concrete types
+converts = {
+ {
+ inp: 'fn -> (.+)',
+ out: '%1',
+ cost: 1
+ transform: (val, fileder) => val fileder
+ }
+ {
+ inp: 'mmm/component',
+ out: 'mmm/dom',
+ cost: 3
+ transform: single tohtml
+ }
+ {
+ inp: 'mmm/dom',
+ out: 'text/html+frag',
+ cost: 3
+ transform: (node) => if MODE == 'SERVER' then node else node.outerHTML
+ }
+ {
+ -- inp: 'text/html%+frag',
+ -- @TODO: this doesn't feel right... maybe mmm/dom has to go?
+ inp: 'mmm/dom',
+ out: 'text/html',
+ cost: 3
+ transform: (html, fileder) => render html, fileder
+ }
+ {
+ inp: 'text/html%+frag',
+ out: 'mmm/dom',
+ cost: 1
+ transform: if MODE == 'SERVER'
+ (html, fileder) =>
+ html = html\gsub '(.-)', (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 "
+
+ link_to path, text, fileder
+
+ html = html\gsub '(.-)', (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 "
+
+ 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
+ desc = nil if desc == ''
+
+ 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',
+ cost: 0.5
+ transform: loadwith load or loadstring
+ }
+ {
+ inp: 'mmm/tpl -> (.+)',
+ out: '%1',
+ cost: 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',
+ cost: 0.5
+ 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 -> image/.+',
+ out: 'mmm/dom',
+ cost: 1
+ transform: (src, fileder) => img :src
+ }
+ {
+ inp: 'URL -> video/.+',
+ out: 'mmm/dom',
+ cost: 1
+ transform: (src) =>
+ -- @TODO: add parsed MIME type
+ video (source :src), controls: true, loop: true
+ }
+ {
+ inp: 'text/plain',
+ out: 'mmm/dom',
+ cost: 2
+ transform: (val) => span val
+ }
+ {
+ inp: 'alpha',
+ out: 'mmm/dom',
+ cost: 2
+ transform: single code
+ }
+ -- this one needs a higher cost
+ -- {
+ -- inp: 'URL -> .+',
+ -- out: 'mmm/dom',
+ -- transform: single code
+ -- }
+ {
+ inp: '(.+)',
+ out: 'URL -> %1',
+ cost: 5
+ transform: (_, fileder, key) => "#{fileder.path}/#{key.name}:#{@from}"
+ }
+ {
+ inp: 'table',
+ out: 'text/json',
+ cost: 2
+ transform: do
+ tojson = (obj) ->
+ switch type obj
+ when 'string'
+ string.format '%q', obj
+ when 'table'
+ if obj[1] or not next obj
+ "[#{table.concat [tojson c for c in *obj], ','}]"
+ else
+ "{#{table.concat ["#{tojson k}: #{tojson v}" for k,v in pairs obj], ', '}}"
+ when 'number'
+ tostring obj
+ when 'boolean'
+ tostring obj
+ when 'nil'
+ 'null'
+ else
+ error "unknown type '#{type obj}'"
+
+ (val) => tojson val
+ }
+ {
+ inp: 'table',
+ out: 'mmm/dom',
+ cost: 5
+ transform: do
+ deep_tostring = (tbl, space='') ->
+ buf = space .. tostring tbl
+
+ return buf unless 'table' == type tbl
+
+ buf = buf .. ' {\n'
+ for k,v in pairs tbl
+ buf = buf .. "#{space} [#{k}]: #{deep_tostring v, space .. ' '}\n"
+ buf = buf .. "#{space}}"
+ buf
+
+ (tbl) => pre code deep_tostring tbl
+ }
+}
+
+add_converts = (module) ->
+ ok, plugin = pcall require, ".plugins.#{module}"
+
+ if not ok
+ print "[Plugins] couldn't load plugins.#{module}: #{plugin}"
+ return
+
+ print "[Plugins] loaded plugins.#{module}"
+
+ if plugin.converts
+ for convert in *plugin.converts
+ table.insert converts, convert
+
+add_converts 'code'
+add_converts 'markdown'
+add_converts 'mermaid'
+add_converts 'twitter'
+add_converts 'youtube'
+
+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',
+ cost: 1
+ transform: loadwith moon.load or moon.loadstring
+ }
+
+ table.insert converts, {
+ inp: 'text/moonscript -> (.+)',
+ out: 'text/lua -> %1',
+ cost: 2
+ transform: single moon.to_lua
+ }
+else
+ table.insert converts, {
+ inp: 'text/javascript -> (.+)',
+ out: '%1',
+ cost: 1
+ transform: (source) =>
+ f = js.new window.Function, source
+ f!
+ }
+
+:converts
diff --git a/mmm/mmmfs/plugins/markdown.moon b/mmm/mmmfs/plugins/markdown.moon
new file mode 100644
index 0000000..5024320
--- /dev/null
+++ b/mmm/mmmfs/plugins/markdown.moon
@@ -0,0 +1,33 @@
+markdown = if MODE == 'SERVER'
+ success, discount = pcall require, 'discount'
+ assert success, "couldn't require 'discount'"
+
+ (md) ->
+ res = assert discount.compile md, 'githubtags'
+ res.body
+else
+ assert window and window.marked, "marked.js not found"
+ window\marked
+
+assert markdown, "no markdown implementation found"
+
+{
+ converts: {
+ {
+ inp: 'text/markdown'
+ out: 'text/html+frag'
+ cost: 1
+ transform: (md) => "#{markdown md}
"
+ }
+ {
+ inp: 'text/markdown%+span'
+ out: 'text/html+frag'
+ cost: 1
+ transform: (source) =>
+ html = markdown source
+ html = html\gsub '^%s*%s*', ''
+ html = html\gsub '%s*
%s*$', ''
+ html
+ }
+ }
+}
diff --git a/mmm/mmmfs/plugins/mermaid.moon b/mmm/mmmfs/plugins/mermaid.moon
new file mode 100644
index 0000000..ae34afa
--- /dev/null
+++ b/mmm/mmmfs/plugins/mermaid.moon
@@ -0,0 +1,29 @@
+assert window and window.mermaid, "mermaid.js not found"
+
+window.mermaid\initialize {
+ startOnLoad: false
+ fontFamily: 'monospace'
+}
+
+id_counter = 1
+
+{
+ converts: {
+ {
+ inp: 'text/mermaid-graph'
+ out: 'mmm/dom'
+ cost: 1
+ transform: (source, fileder, key) =>
+ id_counter += 1
+ id = "mermaid-#{id_counter}"
+ with container = document\createElement 'div'
+ cb = (svg) =>
+ .innerHTML = svg
+ .firstElementChild.style.width = '100%'
+ .firstElementChild.style.height = 'auto'
+
+ window\setImmediate (_) ->
+ window.mermaid\render id, source, cb, container
+ }
+ }
+}
diff --git a/mmm/mmmfs/plugins/twitter.moon b/mmm/mmmfs/plugins/twitter.moon
new file mode 100644
index 0000000..b6e1adc
--- /dev/null
+++ b/mmm/mmmfs/plugins/twitter.moon
@@ -0,0 +1,22 @@
+import div, blockquote, a from require 'mmm.dom'
+
+{
+ converts: {
+ {
+ inp: 'URL -> twitter/tweet'
+ out: 'mmm/dom'
+ cost: 1
+ 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
+ }
+ }
+ }
+}
diff --git a/mmm/mmmfs/plugins/youtube.moon b/mmm/mmmfs/plugins/youtube.moon
new file mode 100644
index 0000000..99cf995
--- /dev/null
+++ b/mmm/mmmfs/plugins/youtube.moon
@@ -0,0 +1,27 @@
+import iframe from require 'mmm.dom'
+
+{
+ converts: {
+ {
+ inp: 'URL -> youtube/video'
+ out: 'mmm/dom'
+ cost: 1
+ 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}"
+ }
+ }
+ }
+}