facet editing with CodeMirror!
s-ol
3 years ago
63 | 63 | <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.6/svg.min.js"></script> |
64 | 64 | <script type="text/javascript" src="//unpkg.com/mermaid@8.4.0/dist/mermaid.min.js"></script> |
65 | 65 | <script type="text/javascript" src="//unpkg.com/marked@0.7.0/marked.min.js"></script> |
66 | <link rel="stylesheet" type="text/css" href="//unpkg.com/codemirror@5.49.2/lib/codemirror.css" /> | |
67 | <script type="text/javascript" src="//unpkg.com/codemirror@5.49.2/lib/codemirror.js"></script> | |
68 | <script type="text/javascript" src="//unpkg.com/codemirror@5.49.2/mode/lua/lua.js"></script> | |
69 | <script type="text/javascript" src="//unpkg.com/codemirror@5.49.2/mode/markdown/markdown.js"></script> | |
70 | <script type="text/javascript" src="//unpkg.com/codemirror@5.49.2/addon/display/autorefresh.js"></script> | |
66 | 71 | <script type="text/javascript" src="/static/fengari-web/:text/javascript"></script> |
67 | 72 | <script type="text/lua" src="/static/mmm/:text/lua"></script> |
68 | 73 | <script type="text/lua">require 'mmm'; require 'mmm.mmmfs'</script> |
0 | 0 | require = relative ..., 1 |
1 | 1 | import Key from require '.fileder' |
2 | import converts from require '.plugins' | |
2 | import converts, editors from require '.plugins' | |
3 | 3 | import get_conversions, apply_conversions from require '.conversion' |
4 | 4 | import ReactiveVar, get_or_create, text, elements, tohtml from require 'mmm.component' |
5 | import pre, div, nav, span, button, a, code, select, option from elements | |
5 | import pre, div, nav, span, button, a, code, option from elements | |
6 | 6 | import languages from require 'mmm.highlighting' |
7 | 7 | |
8 | 8 | keep = (var) -> |
11 | 11 | last = val or last |
12 | 12 | last |
13 | 13 | |
14 | combine = (...) -> | |
15 | res = {} | |
16 | lists = {...} | |
17 | for list in *lists | |
18 | for val in *list | |
19 | table.insert res, val | |
20 | ||
21 | res | |
22 | ||
14 | 23 | casts = { |
15 | { | |
16 | inp: 'text/.*', | |
17 | out: 'mmm/dom', | |
18 | cost: 0 | |
19 | transform: (val) => | |
20 | lang = @from\match 'text/(.*)' | |
21 | languages[lang] val | |
22 | } | |
23 | 24 | { |
24 | 25 | inp: 'URL.*' |
25 | 26 | out: 'mmm/dom' |
27 | 28 | transform: (href) => span a (code href), :href |
28 | 29 | } |
29 | 30 | } |
30 | ||
31 | for convert in *converts | |
32 | table.insert casts, convert | |
31 | casts = combine casts, converts, editors | |
33 | 32 | |
34 | 33 | export BROWSER |
35 | 34 | class Browser |
127 | 126 | |
128 | 127 | current = @facet\get! |
129 | 128 | current = current and current.name |
130 | with select :onchange, disabled: not fileder, value: @facet\map (f) -> f and f.name | |
129 | with elements.select :onchange, disabled: not fileder, value: @facet\map (f) -> f and f.name | |
131 | 130 | has_main = fileder and fileder\has_facet '' |
132 | 131 | \append option '(main)', value: '', disabled: not has_main, selected: current == '' |
133 | 132 | if fileder |
145 | 144 | |
146 | 145 | -- append or patch #browser-content |
147 | 146 | main\append with get_or_create 'div', 'browser-content', class: 'content' |
148 | content = ReactiveVar if rehydrate then .node.lastChild else @get_content @facet\get! | |
149 | \append keep content | |
147 | @content = ReactiveVar if rehydrate then .node.lastChild else @get_content @facet\get! | |
148 | \append keep @content | |
150 | 149 | if MODE == 'CLIENT' |
151 | 150 | @facet\subscribe (p) -> |
152 | window\setTimeout (-> content\set @get_content p), 150 | |
151 | window\setTimeout (-> @refresh p), 150 | |
153 | 152 | |
154 | 153 | if rehydrate |
155 | 154 | -- force one rerender to set onclick handlers etc |
164 | 163 | |
165 | 164 | err_and_trace = (msg) -> debug.traceback msg, 2 |
166 | 165 | default_convert = (key) => @get key.name, 'mmm/dom' |
166 | ||
167 | -- rerender main content | |
168 | refresh: (facet=@facet\get!) => | |
169 | @content\set @get_content facet | |
167 | 170 | |
168 | 171 | -- render #browser-content |
169 | 172 | get_content: (prop, err=@error, convert=default_convert) => |
217 | 220 | { :name } = @facet\get! |
218 | 221 | @inspect_prop\set Key e.target.value |
219 | 222 | |
220 | with select :onchange | |
223 | with elements.select :onchange | |
221 | 224 | \append option '(none)', value: '', disabled: true, selected: not value |
222 | 225 | if fileder |
223 | 226 | for value in pairs fileder.facet_keys |
133 | 133 | |
134 | 134 | rawset t, k, v |
135 | 135 | |
136 | v = k unless v == nil | |
137 | @facet_keys[k] = v | |
136 | if not v | |
137 | @facet_keys[k] = nil | |
138 | 138 | } |
139 | 139 | |
140 | 140 | -- this fails with JS objects from JSON.parse |
0 | import pre from require 'mmm.dom' | |
0 | import div from require 'mmm.dom' | |
1 | 1 | import languages from require 'mmm.highlighting' |
2 | ||
3 | class Editor | |
4 | o = do | |
5 | mkobj = window\eval "(function () { return {}; })" | |
6 | (tbl) -> | |
7 | with obj = mkobj! | |
8 | for k,v in pairs(tbl) | |
9 | obj[k] = v | |
10 | ||
11 | new: (value, mode, @fileder, @key) => | |
12 | @node = div class: 'editor' | |
13 | @cm = window\CodeMirror @node, o { | |
14 | :value | |
15 | :mode | |
16 | lineNumber: true | |
17 | lineWrapping: true | |
18 | autoRefresh: true | |
19 | theme: 'hybrid' | |
20 | } | |
21 | ||
22 | @cm\on 'changes', (_, mirr) -> | |
23 | window\clearTimeout @timeout if @timeout | |
24 | @timeout = window\setTimeout (-> @change!), 300 | |
25 | ||
26 | change: => | |
27 | @timeout = nil | |
28 | doc = @cm\getDoc! | |
29 | if @lastState and doc\isClean @lastState | |
30 | -- no changes since last event | |
31 | return | |
32 | ||
33 | @lastState = doc\changeGeneration true | |
34 | value = doc\getValue! | |
35 | ||
36 | @fileder.facets[@key] = value | |
37 | BROWSER\refresh! | |
2 | 38 | |
3 | 39 | -- syntax-highlighted code |
4 | 40 | { |
12 | 48 | pre languages[lang] val |
13 | 49 | } |
14 | 50 | } |
51 | editors: if MODE == 'CLIENT' then { | |
52 | { | |
53 | inp: 'text/([^ ]*).*' | |
54 | out: 'mmm/dom' | |
55 | cost: 0 | |
56 | transform: (value, fileder, key) => | |
57 | mode = @from\match @convert.inp | |
58 | Editor value, mode, fileder, key | |
59 | } | |
60 | } | |
15 | 61 | } |
3 | 3 | import render from require '.layout' |
4 | 4 | import tohtml from require 'mmm.component' |
5 | 5 | |
6 | keep = (var) -> | |
7 | last = var\get! | |
8 | var\map (val) -> | |
9 | last = val or last | |
10 | last | |
11 | ||
12 | 6 | -- fix JS null values |
13 | 7 | js_fix = if MODE == 'CLIENT' |
14 | 8 | (arg) -> |
23 | 17 | func = assert _load val, "#{fileder}##{key}" |
24 | 18 | func! |
25 | 19 | |
26 | -- list of converts | |
20 | -- list of converts, editors | |
27 | 21 | -- converts each have |
28 | 22 | -- * inp - input type. can capture subtypes using `(.+)` |
29 | 23 | -- * out - output type. can substitute subtypes from inp with %1, %2 etc. |
30 | 24 | -- * cost - conversion cost |
31 | 25 | -- * transform - function (val: inp, fileder) => val: out |
32 | 26 | -- @convert, @from, @to contain the convert and the concrete types |
27 | editors = {} | |
33 | 28 | converts = { |
34 | 29 | { |
35 | 30 | inp: 'fn -> (.+)', |
255 | 250 | for convert in *plugin.converts |
256 | 251 | table.insert converts, convert |
257 | 252 | |
253 | if plugin.editors | |
254 | for editor in *plugin.editors | |
255 | table.insert editors, editor | |
256 | ||
258 | 257 | add_converts 'code' |
259 | 258 | add_converts 'markdown' |
260 | 259 | add_converts 'mermaid' |
288 | 287 | f! |
289 | 288 | } |
290 | 289 | |
291 | :converts | |
290 | { | |
291 | :converts | |
292 | :editors | |
293 | } |
14 | 14 | &.inspector { |
15 | 15 | top: 0; |
16 | 16 | position: sticky; |
17 | max-height: 100vh; | |
17 | max-height: calc(100vh - 3rem); | |
18 | 18 | color: #c5c8c6; |
19 | 19 | |
20 | 20 | @include left-border; |
0 | 0 | footer { |
1 | 1 | display: flex; |
2 | 2 | padding: 1rem 2rem; |
3 | height: 1rem; | |
3 | 4 | |
4 | 5 | position: sticky; |
5 | 6 | bottom: 0; |
0 | /* | |
1 | vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid) | |
2 | */ | |
3 | ||
4 | /*background color*/ | |
5 | .CodeMirror.cm-s-hybrid { | |
6 | color: #c5c8c6; | |
7 | background: #1d1f21; | |
8 | ||
9 | height: auto; | |
10 | ||
11 | /*selection color*/ | |
12 | .CodeMirror-selected, | |
13 | .CodeMirror-line::selection, | |
14 | .CodeMirror-line > span::selection, | |
15 | .CodeMirror-line > span > span::selection, { | |
16 | background: #373b41; | |
17 | } | |
18 | ||
19 | /*foreground color*/ | |
20 | .CodeMirror-cursor { | |
21 | border-color: #c5c8c6; | |
22 | } | |
23 | ||
24 | /*color: fg_yellow*/ | |
25 | .cm-keyword { | |
26 | color: #f0c674; | |
27 | } | |
28 | ||
29 | /*color: fg_comment*/ | |
30 | .cm-comment, | |
31 | .cm-meta { | |
32 | color: #707880; | |
33 | } | |
34 | ||
35 | /*color: fg_red*/ | |
36 | .cm-number, | |
37 | .cm-atom { | |
38 | color: #cc6666 | |
39 | } | |
40 | ||
41 | /*color: fg_green*/ | |
42 | .cm-string, | |
43 | .cm-string-2, | |
44 | .cm-operator { | |
45 | color: #b5bd68; | |
46 | } | |
47 | ||
48 | /*color: fg_purple*/ | |
49 | .cm-attribute, | |
50 | .cm-propery { | |
51 | color: #b294bb; | |
52 | } | |
53 | ||
54 | /*color: fg_blue*/ | |
55 | .cm-tag, | |
56 | .cm-qualifier { | |
57 | color: #81a2be; | |
58 | } | |
59 | ||
60 | /*color: fg_aqua*/ | |
61 | .cm-link, | |
62 | .cm-header, | |
63 | .cm-hr { | |
64 | color: #8abeb7; | |
65 | } | |
66 | ||
67 | /*color: fg_orange*/ | |
68 | .cm-builtin, { | |
69 | color: #de935f; | |
70 | } | |
71 | } |