From 8a9fee46da4d40cdf14bcfc5900f3e84c0e67873 Mon Sep 17 00:00:00 2001 From: naielv <109038805+naielv@users.noreply.github.com> Date: Thu, 5 Feb 2026 23:07:35 +0100 Subject: [PATCH] Refactor code to use single quotes for strings, update HTML structure for better readability, and improve error handling in various modules. Added Prettier configuration for consistent code formatting. --- .github/copilot-instructions.md | 2 - .prettierrc.json | 7 + assets/static/axe.js | 109 -- assets/static/gun.js | 2313 ------------------------------- assets/static/load.js | 7 - assets/static/open.js | 60 - assets/static/path.js | 31 - assets/static/radisk.js | 606 -------- assets/static/radix.js | 124 -- assets/static/rindexed.js | 79 -- assets/static/sea.js | 1537 -------------------- assets/static/simplemde.min.css | 7 - assets/static/simplemde.min.js | 15 - assets/static/store.js | 150 -- assets/static/synchronous.js | 58 - assets/static/webrtc.js | 134 -- assets/static/yson.js | 244 ---- build.py | 4 +- src/app_logic.js | 150 +- src/app_modules.js | 1402 ++++++++++--------- src/config.js | 98 +- src/db.js | 166 ++- src/gun_init.js | 2 +- src/index.html | 3 - src/page/aulas.js | 471 ++++--- src/page/avisos.js | 465 ++++--- src/page/buscar.js | 103 +- src/page/comedor.js | 145 +- src/page/dataman.js | 190 +-- src/page/index.js | 48 +- src/page/login.js | 898 ++++++------ src/page/materiales.js | 225 +-- src/page/notas.js | 490 ++++--- src/page/pagos.js | 1529 +++++++++++--------- src/page/personas.js | 182 +-- src/page/resumen_diario.js | 121 +- src/page/supercafe.js | 304 ++-- src/pwa.js | 20 +- src/sw.js | 15 +- 39 files changed, 3765 insertions(+), 8749 deletions(-) create mode 100644 .prettierrc.json delete mode 100644 assets/static/axe.js delete mode 100644 assets/static/gun.js delete mode 100644 assets/static/load.js delete mode 100644 assets/static/open.js delete mode 100644 assets/static/path.js delete mode 100644 assets/static/radisk.js delete mode 100644 assets/static/radix.js delete mode 100644 assets/static/rindexed.js delete mode 100644 assets/static/sea.js delete mode 100644 assets/static/simplemde.min.css delete mode 100644 assets/static/simplemde.min.js delete mode 100644 assets/static/store.js delete mode 100644 assets/static/synchronous.js delete mode 100644 assets/static/webrtc.js delete mode 100644 assets/static/yson.js diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 388f8b9..f446e27 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -94,8 +94,6 @@ TeleSec is a Spanish Progressive Web Application (PWA) built with vanilla JavaSc ├── manifest.json # PWA manifest ├── *.png, *.jpg # Icons and images ├── static/ # JavaScript libraries and CSS -│ │ ├── pouchdb (via CDN) # PouchDB is used for local storage and replication - │ ├── webrtc.js # WebRTC functionality │ ├── euskaditech-css/ # CSS framework │ └── ico/ # Application icons └── page/ # Page-specific assets (empty placeholder) diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..17d9208 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "printWidth": 100, + "singleQuote": true, + "semi": true, + "trailingComma": "es5", + "embeddedLanguageFormatting": "auto" +} diff --git a/assets/static/axe.js b/assets/static/axe.js deleted file mode 100644 index 98b4557..0000000 --- a/assets/static/axe.js +++ /dev/null @@ -1,109 +0,0 @@ -;(function(){ - - var sT = setTimeout || {}, u; - if(typeof window !== ''+u){ sT.window = window } - var AXE = (sT.window||'').AXE || function(){}; - if(AXE.window = sT.window){ AXE.window.AXE = AXE } - - var Gun = (AXE.window||'').GUN || require('./gun'); - (Gun.AXE = AXE).GUN = AXE.Gun = Gun; - - //if(!Gun.window){ try{ require('./lib/axe') }catch(e){} } - if(!Gun.window){ require('./lib/axe') } - - Gun.on('opt', function(at){ start(at) ; this.to.next(at) }); // make sure to call the "next" middleware adapter. - - function start(root){ - if(root.axe){ return } - var opt = root.opt, peers = opt.peers; - if(false === opt.axe){ return } - if(!Gun.window){ return } // handled by ^ lib/axe.js - var w = Gun.window, lS = w.localStorage || opt.localStorage || {}, loc = w.location || opt.location || {}, nav = w.navigator || opt.navigator || {}; - var axe = root.axe = {}, tmp, id; - var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); // DAM! - - tmp = peers[id = loc.origin + '/gun'] = peers[id] || {}; - tmp.id = tmp.url = id; tmp.retry = tmp.retry || 0; - tmp = peers[id = 'http://localhost:8765/gun'] = peers[id] || {}; - tmp.id = tmp.url = id; tmp.retry = tmp.retry || 0; - Gun.log.once("AXE", "AXE enabled: Trying to find network via (1) local peer (2) last used peers (3) a URL parameter, and last (4) hard coded peers."); - Gun.log.once("AXEWarn", "Warning: AXE is in alpha, use only for testing!"); - var last = lS.peers || ''; if(last){ last += ' ' } - last += ((loc.search||'').split('peers=')[1]||'').split('&')[0]; - - root.on('bye', function(peer){ - this.to.next(peer); - if(!peer.url){ return } // ignore WebRTC disconnects for now. - if(!nav.onLine){ peer.retry = 1 } - if(peer.retry){ return } - if(axe.fall){ delete axe.fall[peer.url || peer.id] } - (function next(){ - if(!axe.fall){ setTimeout(next, 9); return } // not found yet - var fall = Object.keys(axe.fall||''), one = fall[(Math.random()*fall.length) >> 0]; - if(!fall.length){ lS.peers = ''; one = 'https://gunjs.herokuapp.com/gun' } // out of peers - if(peers[one]){ next(); return } // already choose - mesh.hi(one); - }()); - }); - - root.on('hi', function(peer){ // TEMPORARY! Try to connect all peers. - this.to.next(peer); - if(!peer.url){ return } // ignore WebRTC disconnects for now. - return; // DO NOT COMMIT THIS FEATURE YET! KEEP TESTING NETWORK PERFORMANCE FIRST! - (function next(){ - if(!peer.wire){ return } - if(!axe.fall){ setTimeout(next, 9); return } // not found yet - var one = (next.fall = next.fall || Object.keys(axe.fall||'')).pop(); - if(!one){ return } - setTimeout(next, 99); - mesh.say({dam: 'opt', opt: {peers: one}}, peer); - }()); - }); - - function found(text){ - - axe.fall = {}; - ((text||'').match(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/ig)||[]).forEach(function(url){ - axe.fall[url] = {url: url, id: url, retry: 0}; // RETRY - }); - - return; - - // TODO: Finish porting below? Maybe not. - - Object.keys(last.peers||'').forEach(function(key){ - tmp = peers[id = key] = peers[id] || {}; - tmp.id = tmp.url = id; - }); - tmp = peers[id = 'https://guntest.herokuapp.com/gun'] = peers[id] || {}; - tmp.id = tmp.url = id; - - var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); // DAM! - mesh.way = function(msg){ - if(root.$ === msg.$ || (msg._||'').via){ - mesh.say(msg, opt.peers); - return; - } - var at = (msg.$||'')._; - if(!at){ mesh.say(msg, opt.peers); return } - if(msg.get){ - if(at.axe){ return } // don't ask for it again! - at.axe = {}; - } - mesh.say(msg, opt.peers); - } - } - - if(last){ found(last); return } - try{ fetch(((loc.search||'').split('axe=')[1]||'').split('&')[0] || loc.axe || 'https://raw.githubusercontent.com/wiki/amark/gun/volunteer.dht.md').then(function(res){ - return res.text() - }).then(function(text){ - found(lS.peers = text); - }).catch(function(){ - found(); // nothing - })}catch(e){found()} - } - - var empty = {}, yes = true; - try{ if(typeof module != ''+u){ module.exports = AXE } }catch(e){} -}()); \ No newline at end of file diff --git a/assets/static/gun.js b/assets/static/gun.js deleted file mode 100644 index 9cc3119..0000000 --- a/assets/static/gun.js +++ /dev/null @@ -1,2313 +0,0 @@ -// assets/static/gun.js - Deprecated. Replaced by PouchDB (DB module). -console.warn('assets/static/gun.js is deprecated and unused.'); - - function USE(arg, req){ - return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){ - arg(mod = {exports: {}}); - USE[R(path)] = mod.exports; - } - function R(p){ - return p.split('/').slice(-1).toString().replace('.js',''); - } - } - if(typeof module !== "undefined"){ var MODULE = module } - /* UNBUILD */ - - ;USE(function(module){ - // Shim for generic javascript utilities. - String.random = function(l, c){ - var s = ''; - l = l || 24; // you are not going to make a 0 length random number, so no need to check type - c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz'; - while(l-- > 0){ s += c.charAt(Math.floor(Math.random() * c.length)) } - return s; - } - String.match = function(t, o){ var tmp, u; - if('string' !== typeof t){ return false } - if('string' == typeof o){ o = {'=': o} } - o = o || {}; - tmp = (o['='] || o['*'] || o['>'] || o['<']); - if(t === tmp){ return true } - if(u !== o['=']){ return false } - tmp = (o['*'] || o['>']); - if(t.slice(0, (tmp||'').length) === tmp){ return true } - if(u !== o['*']){ return false } - if(u !== o['>'] && u !== o['<']){ - return (t >= o['>'] && t <= o['<'])? true : false; - } - if(u !== o['>'] && t >= o['>']){ return true } - if(u !== o['<'] && t <= o['<']){ return true } - return false; - } - String.hash = function(s, c){ // via SO - if(typeof s !== 'string'){ return } - c = c || 0; // CPU schedule hashing by - if(!s.length){ return c } - for(var i=0,l=s.length,n; i= (check.now() - l)) && c++ < 3333){ f(); return } - sI(function(){ l = check.now(); f() },c=0) - } - }()); - ;(function(){ // Too many polls block, this "threads" them in turns over a single thread in time. - var sT = setTimeout, t = sT.turn = sT.turn || function(f){ 1 == s.push(f) && p(T) } - , s = t.s = [], p = sT.poll, i = 0, f, T = function(){ - if(f = s[i++]){ f() } - if(i == s.length || 99 == i){ - s = t.s = s.slice(i); - i = 0; - } - if(s.length){ p(T) } - } - }()); - ;(function(){ - var u, sT = setTimeout, T = sT.turn; - (sT.each = sT.each || function(l,f,e,S){ S = S || 9; (function t(s,L,r){ - if(L = (s = (l||[]).splice(0,S)).length){ - for(var i = 0; i < L; i++){ - if(u !== (r = f(s[i]))){ break } - } - if(u === r){ T(t); return } - } e && e(r); - }())})(); - }()); - })(USE, './shim'); - - ;USE(function(module){ - // On event emitter generic javascript utility. - module.exports = function onto(tag, arg, as){ - if(!tag){ return {to: onto} } - var u, f = 'function' == typeof arg, tag = (this.tag || (this.tag = {}))[tag] || f && ( - this.tag[tag] = {tag: tag, to: onto._ = { next: function(arg){ var tmp; - if(tmp = this.to){ tmp.next(arg) } - }}}); - if(f){ - var be = { - off: onto.off || - (onto.off = function(){ - if(this.next === onto._.next){ return !0 } - if(this === this.the.last){ - this.the.last = this.back; - } - this.to.back = this.back; - this.next = onto._.next; - this.back.to = this.to; - if(this.the.last === this.the){ - delete this.on.tag[this.the.tag]; - } - }), - to: onto._, - next: arg, - the: tag, - on: this, - as: as, - }; - (be.back = tag.last || tag).to = be; - return tag.last = be; - } - if((tag = tag.to) && u !== arg){ tag.next(arg) } - return tag; - }; - })(USE, './onto'); - - ;USE(function(module){ - // Valid values are a subset of JSON: null, binary, number (!Infinity), text, - // or a soul relation. Arrays need special algorithms to handle concurrency, - // so they are not supported directly. Use an extension that supports them if - // needed but research their problems first. - module.exports = function (v) { - // "deletes", nulling out keys. - return v === null || - "string" === typeof v || - "boolean" === typeof v || - // we want +/- Infinity to be, but JSON does not support it, sad face. - // can you guess what v === v checks for? ;) - ("number" === typeof v && v != Infinity && v != -Infinity && v === v) || - (!!v && "string" == typeof v["#"] && Object.keys(v).length === 1 && v["#"]); - } - })(USE, './valid'); - - ;USE(function(module){ - USE('./shim'); - function State(){ - var t = +new Date; - if(last < t){ - return N = 0, last = t + State.drift; - } - return last = t + ((N += 1) / D) + State.drift; - } - State.drift = 0; - var NI = -Infinity, N = 0, D = 999, last = NI, u; // WARNING! In the future, on machines that are D times faster than 2016AD machines, you will want to increase D by another several orders of magnitude so the processing speed never out paces the decimal resolution (increasing an integer effects the state accuracy). - State.is = function(n, k, o){ // convenience function to get the state on a key on a node and return it. - var tmp = (k && n && n._ && n._['>']) || o; - if(!tmp){ return } - return ('number' == typeof (tmp = tmp[k]))? tmp : NI; - } - State.ify = function(n, k, s, v, soul){ // put a key's state on a node. - (n = n || {})._ = n._ || {}; // safety check or init. - if(soul){ n._['#'] = soul } // set a soul if specified. - var tmp = n._['>'] || (n._['>'] = {}); // grab the states data. - if(u !== k && k !== '_'){ - if('number' == typeof s){ tmp[k] = s } // add the valid state. - if(u !== v){ n[k] = v } // Note: Not its job to check for valid values! - } - return n; - } - module.exports = State; - })(USE, './state'); - - ;USE(function(module){ - USE('./shim'); - function Dup(opt){ - var dup = {s:{}}, s = dup.s; - opt = opt || {max: 999, age: 1000 * 9};//*/ 1000 * 9 * 3}; - dup.check = function(id){ - if(!s[id]){ return false } - return dt(id); - } - var dt = dup.track = function(id){ - var it = s[id] || (s[id] = {}); - it.was = dup.now = +new Date; - if(!dup.to){ dup.to = setTimeout(dup.drop, opt.age + 9) } - if(dt.ed){ dt.ed(id) } - return it; - } - dup.drop = function(age){ - dup.to = null; - dup.now = +new Date; - var l = Object.keys(s); - console.STAT && console.STAT(dup.now, +new Date - dup.now, 'dup drop keys'); // prev ~20% CPU 7% RAM 300MB // now ~25% CPU 7% RAM 500MB - setTimeout.each(l, function(id){ var it = s[id]; // TODO: .keys( is slow? - if(it && (age || opt.age) > (dup.now - it.was)){ return } - delete s[id]; - },0,99); - } - return dup; - } - module.exports = Dup; - })(USE, './dup'); - - ;USE(function(module){ - // request / response module, for asking and acking messages. - USE('./onto'); // depends upon onto! - module.exports = function ask(cb, as){ - if(!this.on){ return } - var lack = (this.opt||{}).lack || 9000; - if(!('function' == typeof cb)){ - if(!cb){ return } - var id = cb['#'] || cb, tmp = (this.tag||'')[id]; - if(!tmp){ return } - if(as){ - tmp = this.on(id, as); - clearTimeout(tmp.err); - tmp.err = setTimeout(function(){ tmp.off() }, lack); - } - return true; - } - var id = (as && as['#']) || random(9); - if(!cb){ return id } - var to = this.on(id, cb, as); - to.err = to.err || setTimeout(function(){ to.off(); - to.next({err: "Error: No ACK yet.", lack: true}); - }, lack); - return id; - } - var random = String.random || function(){ return Math.random().toString(36).slice(2) } - })(USE, './ask'); - - ;USE(function(module){ - - function Gun(o){ - if(o instanceof Gun){ return (this._ = {$: this}).$ } - if(!(this instanceof Gun)){ return new Gun(o) } - return Gun.create(this._ = {$: this, opt: o}); - } - - Gun.is = function($){ return ($ instanceof Gun) || ($ && $._ && ($ === $._.$)) || false } - - Gun.version = 0.2020; - - Gun.chain = Gun.prototype; - Gun.chain.toJSON = function(){}; - - USE('./shim'); - Gun.valid = USE('./valid'); - Gun.state = USE('./state'); - Gun.on = USE('./onto'); - Gun.dup = USE('./dup'); - Gun.ask = USE('./ask'); - - ;(function(){ - Gun.create = function(at){ - at.root = at.root || at; - at.graph = at.graph || {}; - at.on = at.on || Gun.on; - at.ask = at.ask || Gun.ask; - at.dup = at.dup || Gun.dup(); - var gun = at.$.opt(at.opt); - if(!at.once){ - at.on('in', universe, at); - at.on('out', universe, at); - at.on('put', map, at); - Gun.on('create', at); - at.on('create', at); - } - at.once = 1; - return gun; - } - function universe(msg){ - // TODO: BUG! msg.out = null being set! - //if(!F){ var eve = this; setTimeout(function(){ universe.call(eve, msg,1) },Math.random() * 100);return; } // ADD F TO PARAMS! - if(!msg){ return } - if(msg.out === universe){ this.to.next(msg); return } - var eve = this, as = eve.as, at = as.at || as, gun = at.$, dup = at.dup, tmp, DBG = msg.DBG; - (tmp = msg['#']) || (tmp = msg['#'] = text_rand(9)); - if(dup.check(tmp)){ return } dup.track(tmp); - tmp = msg._; msg._ = ('function' == typeof tmp)? tmp : function(){}; - (msg.$ && (msg.$ === (msg.$._||'').$)) || (msg.$ = gun); - if(msg['@'] && !msg.put){ ack(msg) } - if(!at.ask(msg['@'], msg)){ // is this machine listening for an ack? - DBG && (DBG.u = +new Date); - if(msg.put){ put(msg); return } else - if(msg.get){ Gun.on.get(msg, gun) } - } - DBG && (DBG.uc = +new Date); - eve.to.next(msg); - DBG && (DBG.ua = +new Date); - if(msg.nts || msg.NTS){ return } // TODO: This shouldn't be in core, but fast way to prevent NTS spread. Delete this line after all peers have upgraded to newer versions. - msg.out = universe; at.on('out', msg); - DBG && (DBG.ue = +new Date); - } - function put(msg){ - if(!msg){ return } - var ctx = msg._||'', root = ctx.root = ((ctx.$ = msg.$||'')._||'').root; - if(msg['@'] && ctx.faith && !ctx.miss){ // TODO: AXE may split/route based on 'put' what should we do here? Detect @ in AXE? I think we don't have to worry, as DAM will route it on @. - msg.out = universe; - root.on('out', msg); - return; - } - ctx.latch = root.hatch; ctx.match = root.hatch = []; - var put = msg.put; - var DBG = ctx.DBG = msg.DBG, S = +new Date; CT = CT || S; - if(put['#'] && put['.']){ /*root && root.on('put', msg);*/ return } // TODO: BUG! This needs to call HAM instead. - DBG && (DBG.p = S); - ctx['#'] = msg['#']; - ctx.msg = msg; - ctx.all = 0; - ctx.stun = 1; - var nl = Object.keys(put);//.sort(); // TODO: This is unbounded operation, large graphs will be slower. Write our own CPU scheduled sort? Or somehow do it in below? Keys itself is not O(1) either, create ES5 shim over ?weak map? or custom which is constant. - console.STAT && console.STAT(S, ((DBG||ctx).pk = +new Date) - S, 'put sort'); - var ni = 0, nj, kl, soul, node, states, err, tmp; - (function pop(o){ - if(nj != ni){ nj = ni; - if(!(soul = nl[ni])){ - console.STAT && console.STAT(S, ((DBG||ctx).pd = +new Date) - S, 'put'); - fire(ctx); - return; - } - if(!(node = put[soul])){ err = ERR+cut(soul)+"no node." } else - if(!(tmp = node._)){ err = ERR+cut(soul)+"no meta." } else - if(soul !== tmp['#']){ err = ERR+cut(soul)+"soul not same." } else - if(!(states = tmp['>'])){ err = ERR+cut(soul)+"no state." } - kl = Object.keys(node||{}); // TODO: .keys( is slow - } - if(err){ - msg.err = ctx.err = err; // invalid data should error and stun the message. - fire(ctx); - //console.log("handle error!", err) // handle! - return; - } - var i = 0, key; o = o || 0; - while(o++ < 9 && (key = kl[i++])){ - if('_' === key){ continue } - var val = node[key], state = states[key]; - if(u === state){ err = ERR+cut(key)+"on"+cut(soul)+"no state."; break } - if(!valid(val)){ err = ERR+cut(key)+"on"+cut(soul)+"bad "+(typeof val)+cut(val); break } - //ctx.all++; //ctx.ack[soul+key] = ''; - ham(val, key, soul, state, msg); - ++C; // courtesy count; - } - if((kl = kl.slice(i)).length){ turn(pop); return } - ++ni; kl = null; pop(o); - }()); - } Gun.on.put = put; - // TODO: MARK!!! clock below, reconnect sync, SEA certify wire merge, User.auth taking multiple times, // msg put, put, say ack, hear loop... - // WASIS BUG! local peer not ack. .off other people: .open - function ham(val, key, soul, state, msg){ - var ctx = msg._||'', root = ctx.root, graph = root.graph, lot, tmp; - var vertex = graph[soul] || empty, was = state_is(vertex, key, 1), known = vertex[key]; - - var DBG = ctx.DBG; if(tmp = console.STAT){ if(!graph[soul] || !known){ tmp.has = (tmp.has || 0) + 1 } } - - var now = State(), u; - if(state > now){ - setTimeout(function(){ ham(val, key, soul, state, msg) }, (tmp = state - now) > MD? MD : tmp); // Max Defer 32bit. :( - console.STAT && console.STAT(((DBG||ctx).Hf = +new Date), tmp, 'future'); - return; - } - if(state < was){ /*old;*/ if(true || !ctx.miss){ return } } // but some chains have a cache miss that need to re-fire. // TODO: Improve in future. // for AXE this would reduce rebroadcast, but GUN does it on message forwarding. // TURNS OUT CACHE MISS WAS NOT NEEDED FOR NEW CHAINS ANYMORE!!! DANGER DANGER DANGER, ALWAYS RETURN! (or am I missing something?) - if(!ctx.faith){ // TODO: BUG? Can this be used for cache miss as well? // Yes this was a bug, need to check cache miss for RAD tests, but should we care about the faith check now? Probably not. - if(state === was && (val === known || L(val) <= L(known))){ /*console.log("same");*/ /*same;*/ if(!ctx.miss){ return } } // same - } - ctx.stun++; // TODO: 'forget' feature in SEA tied to this, bad approach, but hacked in for now. Any changes here must update there. - var aid = msg['#']+ctx.all++, id = {toString: function(){ return aid }, _: ctx}; id.toJSON = id.toString; // this *trick* makes it compatible between old & new versions. - root.dup.track(id)['#'] = msg['#']; // fixes new OK acks for RPC like RTC. - DBG && (DBG.ph = DBG.ph || +new Date); - root.on('put', {'#': id, '@': msg['@'], put: {'#': soul, '.': key, ':': val, '>': state}, ok: msg.ok, _: ctx}); - } - function map(msg){ - var DBG; if(DBG = (msg._||'').DBG){ DBG.pa = +new Date; DBG.pm = DBG.pm || +new Date} - var eve = this, root = eve.as, graph = root.graph, ctx = msg._, put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp; - if((tmp = ctx.msg) && (tmp = tmp.put) && (tmp = tmp[soul])){ state_ify(tmp, key, state, val, soul) } // necessary! or else out messages do not get SEA transforms. - //var bytes = ((graph[soul]||'')[key]||'').length||1; - graph[soul] = state_ify(graph[soul], key, state, val, soul); - if(tmp = (root.next||'')[soul]){ - //tmp.bytes = (tmp.bytes||0) + ((val||'').length||1) - bytes; - //if(tmp.bytes > 2**13){ Gun.log.once('byte-limit', "Note: In the future, GUN peers will enforce a ~4KB query limit. Please see https://gun.eco/docs/Page") } - tmp.on('in', msg) - } - fire(ctx); - eve.to.next(msg); - } - function fire(ctx, msg){ var root; - if(ctx.stop){ return } - if(!ctx.err && 0 < --ctx.stun){ return } // TODO: 'forget' feature in SEA tied to this, bad approach, but hacked in for now. Any changes here must update there. - ctx.stop = 1; - if(!(root = ctx.root)){ return } - var tmp = ctx.match; tmp.end = 1; - if(tmp === root.hatch){ if(!(tmp = ctx.latch) || tmp.end){ delete root.hatch } else { root.hatch = tmp } } - ctx.hatch && ctx.hatch(); // TODO: rename/rework how put & this interact. - setTimeout.each(ctx.match, function(cb){cb && cb()}); - if(!(msg = ctx.msg) || ctx.err || msg.err){ return } - msg.out = universe; - ctx.root.on('out', msg); - - CF(); // courtesy check; - } - function ack(msg){ // aggregate ACKs. - var id = msg['@'] || '', ctx, ok, tmp; - if(!(ctx = id._)){ - var dup = (dup = msg.$) && (dup = dup._) && (dup = dup.root) && (dup = dup.dup); - if(!(dup = dup.check(id))){ return } - msg['@'] = dup['#'] || msg['@']; // This doesn't do anything anymore, backtrack it to something else? - return; - } - ctx.acks = (ctx.acks||0) + 1; - if(ctx.err = msg.err){ - msg['@'] = ctx['#']; - fire(ctx); // TODO: BUG? How it skips/stops propagation of msg if any 1 item is error, this would assume a whole batch/resync has same malicious intent. - } - ctx.ok = msg.ok || ctx.ok; - if(!ctx.stop && !ctx.crack){ ctx.crack = ctx.match && ctx.match.push(function(){back(ctx)}) } // handle synchronous acks. NOTE: If a storage peer ACKs synchronously then the PUT loop has not even counted up how many items need to be processed, so ctx.STOP flags this and adds only 1 callback to the end of the PUT loop. - back(ctx); - } - function back(ctx){ - if(!ctx || !ctx.root){ return } - if(ctx.stun || ctx.acks !== ctx.all){ return } - ctx.root.on('in', {'@': ctx['#'], err: ctx.err, ok: ctx.err? u : ctx.ok || {'':1}}); - } - - var ERR = "Error: Invalid graph!"; - var cut = function(s){ return " '"+(''+s).slice(0,9)+"...' " } - var L = JSON.stringify, MD = 2147483647, State = Gun.state; - var C = 0, CT, CF = function(){if(C>999 && (C/-(CT - (CT = +new Date))>1)){Gun.window && console.log("Warning: You're syncing 1K+ records a second, faster than DOM can update - consider limiting query.");CF=function(){C=0}}}; - - }()); - - ;(function(){ - Gun.on.get = function(msg, gun){ - var root = gun._, get = msg.get, soul = get['#'], node = root.graph[soul], has = get['.']; - var next = root.next || (root.next = {}), at = next[soul]; - - // TODO: Azarattum bug, what is in graph is not same as what is in next. Fix! - - // queue concurrent GETs? - // TODO: consider tagging original message into dup for DAM. - // TODO: ^ above? In chat app, 12 messages resulted in same peer asking for `#user.pub` 12 times. (same with #user GET too, yipes!) // DAM note: This also resulted in 12 replies from 1 peer which all had same ##hash but none of them deduped because each get was different. - // TODO: Moving quick hacks fixing these things to axe for now. - // TODO: a lot of GET #foo then GET #foo."" happening, why? - // TODO: DAM's ## hash check, on same get ACK, producing multiple replies still, maybe JSON vs YSON? - // TMP note for now: viMZq1slG was chat LEX query #. - /*if(gun !== (tmp = msg.$) && (tmp = (tmp||'')._)){ - if(tmp.Q){ tmp.Q[msg['#']] = ''; return } // chain does not need to ask for it again. - tmp.Q = {}; - }*/ - /*if(u === has){ - if(at.Q){ - //at.Q[msg['#']] = ''; - //return; - } - at.Q = {}; - }*/ - var ctx = msg._||{}, DBG = ctx.DBG = msg.DBG; - DBG && (DBG.g = +new Date); - //console.log("GET:", get, node, has, at); - //if(!node && !at){ return root.on('get', msg) } - //if(has && node){ // replace 2 below lines to continue dev? - if(!node){ return root.on('get', msg) } - if(has){ - if('string' != typeof has || u === node[has]){ - if(!((at||'').next||'')[has]){ root.on('get', msg); return } - } - node = state_ify({}, has, state_is(node, has), node[has], soul); - // If we have a key in-memory, do we really need to fetch? - // Maybe... in case the in-memory key we have is a local write - // we still need to trigger a pull/merge from peers. - } - //Gun.window? Gun.obj.copy(node) : node; // HNPERF: If !browser bump Performance? Is this too dangerous to reference root graph? Copy / shallow copy too expensive for big nodes. Gun.obj.to(node); // 1 layer deep copy // Gun.obj.copy(node); // too slow on big nodes - node && ack(msg, node); - root.on('get', msg); // send GET to storage adapters. - } - function ack(msg, node){ - var S = +new Date, ctx = msg._||{}, DBG = ctx.DBG = msg.DBG; - var to = msg['#'], id = text_rand(9), keys = Object.keys(node||'').sort(), soul = ((node||'')._||'')['#'], kl = keys.length, j = 0, root = msg.$._.root, F = (node === root.graph[soul]); - console.STAT && console.STAT(S, ((DBG||ctx).gk = +new Date) - S, 'got keys'); - // PERF: Consider commenting this out to force disk-only reads for perf testing? // TODO: .keys( is slow - node && (function go(){ - S = +new Date; - var i = 0, k, put = {}, tmp; - while(i < 9 && (k = keys[i++])){ - state_ify(put, k, state_is(node, k), node[k], soul); - } - keys = keys.slice(i); - (tmp = {})[soul] = put; put = tmp; - var faith; if(F){ faith = function(){}; faith.ram = faith.faith = true; } // HNPERF: We're testing performance improvement by skipping going through security again, but this should be audited. - tmp = keys.length; - console.STAT && console.STAT(S, -(S - (S = +new Date)), 'got copied some'); - DBG && (DBG.ga = +new Date); - root.on('in', {'@': to, '#': id, put: put, '%': (tmp? (id = text_rand(9)) : u), $: root.$, _: faith, DBG: DBG, FOO: 1}); - console.STAT && console.STAT(S, +new Date - S, 'got in'); - if(!tmp){ return } - setTimeout.turn(go); - }()); - if(!node){ root.on('in', {'@': msg['#']}) } // TODO: I don't think I like this, the default lS adapter uses this but "not found" is a sensitive issue, so should probably be handled more carefully/individually. - } Gun.on.get.ack = ack; - }()); - - ;(function(){ - Gun.chain.opt = function(opt){ - opt = opt || {}; - var gun = this, at = gun._, tmp = opt.peers || opt; - if(!Object.plain(opt)){ opt = {} } - if(!Object.plain(at.opt)){ at.opt = opt } - if('string' == typeof tmp){ tmp = [tmp] } - if(!Object.plain(at.opt.peers)){ at.opt.peers = {}} - if(tmp instanceof Array){ - opt.peers = {}; - tmp.forEach(function(url){ - var p = {}; p.id = p.url = url; - opt.peers[url] = at.opt.peers[url] = at.opt.peers[url] || p; - }) - } - obj_each(opt, function each(k){ var v = this[k]; - if((this && this.hasOwnProperty(k)) || 'string' == typeof v || Object.empty(v)){ this[k] = v; return } - if(v && v.constructor !== Object && !(v instanceof Array)){ return } - obj_each(v, each); - }); - at.opt.from = opt; - Gun.on('opt', at); - at.opt.uuid = at.opt.uuid || function uuid(l){ return Gun.state().toString(36).replace('.','') + String.random(l||12) } - return gun; - } - }()); - - var obj_each = function(o,f){ Object.keys(o).forEach(f,o) }, text_rand = String.random, turn = setTimeout.turn, valid = Gun.valid, state_is = Gun.state.is, state_ify = Gun.state.ify, u, empty = {}, C; - - Gun.log = function(){ return (!Gun.log.off && C.log.apply(C, arguments)), [].slice.call(arguments).join(' ') }; - Gun.log.once = function(w,s,o){ return (o = Gun.log.once)[w] = o[w] || 0, o[w]++ || Gun.log(s) }; - - if(typeof window !== "undefined"){ (window.GUN = window.Gun = Gun).window = window } - try{ if(typeof MODULE !== "undefined"){ MODULE.exports = Gun } }catch(e){} - module.exports = Gun; - - (Gun.window||{}).console = (Gun.window||{}).console || {log: function(){}}; - (C = console).only = function(i, s){ return (C.only.i && i === C.only.i && C.only.i++) && (C.log.apply(C, arguments) || s) }; - - ;"Please do not remove welcome log unless you are paying for a monthly sponsorship, thanks!"; - Gun.log.once("welcome", "Hello wonderful person! :) Thanks for using GUN, please ask for help on http://chat.gun.eco if anything takes you longer than 5min to figure out!"); - })(USE, './root'); - - ;USE(function(module){ - var Gun = USE('./root'); - Gun.chain.back = function(n, opt){ var tmp; - n = n || 1; - if(-1 === n || Infinity === n){ - return this._.root.$; - } else - if(1 === n){ - return (this._.back || this._).$; - } - var gun = this, at = gun._; - if(typeof n === 'string'){ - n = n.split('.'); - } - if(n instanceof Array){ - var i = 0, l = n.length, tmp = at; - for(i; i < l; i++){ - tmp = (tmp||empty)[n[i]]; - } - if(u !== tmp){ - return opt? gun : tmp; - } else - if((tmp = at.back)){ - return tmp.$.back(n, opt); - } - return; - } - if('function' == typeof n){ - var yes, tmp = {back: at}; - while((tmp = tmp.back) - && u === (yes = n(tmp, opt))){} - return yes; - } - if('number' == typeof n){ - return (at.back || at).$.back(n - 1); - } - return this; - } - var empty = {}, u; - })(USE, './back'); - - ;USE(function(module){ - // WARNING: GUN is very simple, but the JavaScript chaining API around GUN - // is complicated and was extremely hard to build. If you port GUN to another - // language, consider implementing an easier API to build. - var Gun = USE('./root'); - Gun.chain.chain = function(sub){ - var gun = this, at = gun._, chain = new (sub || gun).constructor(gun), cat = chain._, root; - cat.root = root = at.root; - cat.id = ++root.once; - cat.back = gun._; - cat.on = Gun.on; - cat.on('in', Gun.on.in, cat); // For 'in' if I add my own listeners to each then I MUST do it before in gets called. If I listen globally for all incoming data instead though, regardless of individual listeners, I can transform the data there and then as well. - cat.on('out', Gun.on.out, cat); // However for output, there isn't really the global option. I must listen by adding my own listener individually BEFORE this one is ever called. - return chain; - } - - function output(msg){ - var put, get, at = this.as, back = at.back, root = at.root, tmp; - if(!msg.$){ msg.$ = at.$ } - this.to.next(msg); - if(at.err){ at.on('in', {put: at.put = u, $: at.$}); return } - if(get = msg.get){ - /*if(u !== at.put){ - at.on('in', at); - return; - }*/ - if(root.pass){ root.pass[at.id] = at; } // will this make for buggy behavior elsewhere? - if(at.lex){ Object.keys(at.lex).forEach(function(k){ tmp[k] = at.lex[k] }, tmp = msg.get = msg.get || {}) } - if(get['#'] || at.soul){ - get['#'] = get['#'] || at.soul; - //root.graph[get['#']] = root.graph[get['#']] || {_:{'#':get['#'],'>':{}}}; - msg['#'] || (msg['#'] = text_rand(9)); // A3120 ? - back = (root.$.get(get['#'])._); - if(!(get = get['.'])){ // soul - tmp = back.ask && back.ask['']; // check if we have already asked for the full node - (back.ask || (back.ask = {}))[''] = back; // add a flag that we are now. - if(u !== back.put){ // if we already have data, - back.on('in', back); // send what is cached down the chain - if(tmp){ return } // and don't ask for it again. - } - msg.$ = back.$; - } else - if(obj_has(back.put, get)){ // TODO: support #LEX ! - tmp = back.ask && back.ask[get]; - (back.ask || (back.ask = {}))[get] = back.$.get(get)._; - back.on('in', {get: get, put: {'#': back.soul, '.': get, ':': back.put[get], '>': state_is(root.graph[back.soul], get)}}); - if(tmp){ return } - } - /*put = (back.$.get(get)._); - if(!(tmp = put.ack)){ put.ack = -1 } - back.on('in', { - $: back.$, - put: Gun.state.ify({}, get, Gun.state(back.put, get), back.put[get]), - get: back.get - }); - if(tmp){ return } - } else - if('string' != typeof get){ - var put = {}, meta = (back.put||{})._; - Gun.obj.map(back.put, function(v,k){ - if(!Gun.text.match(k, get)){ return } - put[k] = v; - }) - if(!Gun.obj.empty(put)){ - put._ = meta; - back.on('in', {$: back.$, put: put, get: back.get}) - } - if(tmp = at.lex){ - tmp = (tmp._) || (tmp._ = function(){}); - if(back.ack < tmp.ask){ tmp.ask = back.ack } - if(tmp.ask){ return } - tmp.ask = 1; - } - } - */ - root.ask(ack, msg); // A3120 ? - return root.on('in', msg); - } - //if(root.now){ root.now[at.id] = root.now[at.id] || true; at.pass = {} } - if(get['.']){ - if(at.get){ - msg = {get: {'.': at.get}, $: at.$}; - (back.ask || (back.ask = {}))[at.get] = msg.$._; // TODO: PERFORMANCE? More elegant way? - return back.on('out', msg); - } - msg = {get: at.lex? msg.get : {}, $: at.$}; - return back.on('out', msg); - } - (at.ask || (at.ask = {}))[''] = at; //at.ack = at.ack || -1; - if(at.get){ - get['.'] = at.get; - (back.ask || (back.ask = {}))[at.get] = msg.$._; // TODO: PERFORMANCE? More elegant way? - return back.on('out', msg); - } - } - return back.on('out', msg); - }; Gun.on.out = output; - - function input(msg, cat){ cat = cat || this.as; // TODO: V8 may not be able to optimize functions with different parameter calls, so try to do benchmark to see if there is any actual difference. - var root = cat.root, gun = msg.$ || (msg.$ = cat.$), at = (gun||'')._ || empty, tmp = msg.put||'', soul = tmp['#'], key = tmp['.'], change = (u !== tmp['='])? tmp['='] : tmp[':'], state = tmp['>'] || -Infinity, sat; // eve = event, at = data at, cat = chain at, sat = sub at (children chains). - if(u !== msg.put && (u === tmp['#'] || u === tmp['.'] || (u === tmp[':'] && u === tmp['=']) || u === tmp['>'])){ // convert from old format - if(!valid(tmp)){ - if(!(soul = ((tmp||'')._||'')['#'])){ console.log("chain not yet supported for", tmp, '...', msg, cat); return; } - gun = cat.root.$.get(soul); - return setTimeout.each(Object.keys(tmp).sort(), function(k){ // TODO: .keys( is slow // BUG? ?Some re-in logic may depend on this being sync? - if('_' == k || u === (state = state_is(tmp, k))){ return } - cat.on('in', {$: gun, put: {'#': soul, '.': k, '=': tmp[k], '>': state}, VIA: msg}); - }); - } - cat.on('in', {$: at.back.$, put: {'#': soul = at.back.soul, '.': key = at.has || at.get, '=': tmp, '>': state_is(at.back.put, key)}, via: msg}); // TODO: This could be buggy! It assumes/approxes data, other stuff could have corrupted it. - return; - } - if((msg.seen||'')[cat.id]){ return } (msg.seen || (msg.seen = function(){}))[cat.id] = cat; // help stop some infinite loops - - if(cat !== at){ // don't worry about this when first understanding the code, it handles changing contexts on a message. A soul chain will never have a different context. - Object.keys(msg).forEach(function(k){ tmp[k] = msg[k] }, tmp = {}); // make copy of message - tmp.get = cat.get || tmp.get; - if(!cat.soul && !cat.has){ // if we do not recognize the chain type - tmp.$$$ = tmp.$$$ || cat.$; // make a reference to wherever it came from. - } else - if(at.soul){ // a has (property) chain will have a different context sometimes if it is linked (to a soul chain). Anything that is not a soul or has chain, will always have different contexts. - tmp.$ = cat.$; - tmp.$$ = tmp.$$ || at.$; - } - msg = tmp; // use the message with the new context instead; - } - unlink(msg, cat); - - if(((cat.soul/* && (cat.ask||'')['']*/) || msg.$$) && state >= state_is(root.graph[soul], key)){ // The root has an in-memory cache of the graph, but if our peer has asked for the data then we want a per deduplicated chain copy of the data that might have local edits on it. - (tmp = root.$.get(soul)._).put = state_ify(tmp.put, key, state, change, soul); - } - if(!at.soul /*&& (at.ask||'')['']*/ && state >= state_is(root.graph[soul], key) && (sat = (root.$.get(soul)._.next||'')[key])){ // Same as above here, but for other types of chains. // TODO: Improve perf by preventing echoes recaching. - sat.put = change; // update cache - if('string' == typeof (tmp = valid(change))){ - sat.put = root.$.get(tmp)._.put || change; // share same cache as what we're linked to. - } - } - - this.to && this.to.next(msg); // 1st API job is to call all chain listeners. - // TODO: Make input more reusable by only doing these (some?) calls if we are a chain we recognize? This means each input listener would be responsible for when listeners need to be called, which makes sense, as they might want to filter. - cat.any && setTimeout.each(Object.keys(cat.any), function(any){ (any = cat.any[any]) && any(msg) },0,99); // 1st API job is to call all chain listeners. // TODO: .keys( is slow // BUG: Some re-in logic may depend on this being sync. - cat.echo && setTimeout.each(Object.keys(cat.echo), function(lat){ (lat = cat.echo[lat]) && lat.on('in', msg) },0,99); // & linked at chains // TODO: .keys( is slow // BUG: Some re-in logic may depend on this being sync. - - if(((msg.$$||'')._||at).soul){ // comments are linear, but this line of code is non-linear, so if I were to comment what it does, you'd have to read 42 other comments first... but you can't read any of those comments until you first read this comment. What!? // shouldn't this match link's check? - // is there cases where it is a $$ that we do NOT want to do the following? - if((sat = cat.next) && (sat = sat[key])){ // TODO: possible trick? Maybe have `ionmap` code set a sat? // TODO: Maybe we should do `cat.ask` instead? I guess does not matter. - tmp = {}; Object.keys(msg).forEach(function(k){ tmp[k] = msg[k] }); - tmp.$ = (msg.$$||msg.$).get(tmp.get = key); delete tmp.$$; delete tmp.$$$; - sat.on('in', tmp); - } - } - - link(msg, cat); - }; Gun.on.in = input; - - function link(msg, cat){ cat = cat || this.as || msg.$._; - if(msg.$$ && this !== Gun.on){ return } // $$ means we came from a link, so we are at the wrong level, thus ignore it unless overruled manually by being called directly. - if(!msg.put || cat.soul){ return } // But you cannot overrule being linked to nothing, or trying to link a soul chain - that must never happen. - var put = msg.put||'', link = put['=']||put[':'], tmp; - var root = cat.root, tat = root.$.get(put['#']).get(put['.'])._; - if('string' != typeof (link = valid(link))){ - if(this === Gun.on){ (tat.echo || (tat.echo = {}))[cat.id] = cat } // allow some chain to explicitly force linking to simple data. - return; // by default do not link to data that is not a link. - } - if((tat.echo || (tat.echo = {}))[cat.id] // we've already linked ourselves so we do not need to do it again. Except... (annoying implementation details) - && !(root.pass||'')[cat.id]){ return } // if a new event listener was added, we need to make a pass through for it. The pass will be on the chain, not always the chain passed down. - if(tmp = root.pass){ if(tmp[link+cat.id]){ return } tmp[link+cat.id] = 1 } // But the above edge case may "pass through" on a circular graph causing infinite passes, so we hackily add a temporary check for that. - - (tat.echo||(tat.echo={}))[cat.id] = cat; // set ourself up for the echo! // TODO: BUG? Echo to self no longer causes problems? Confirm. - - if(cat.has){ cat.link = link } - var sat = root.$.get(tat.link = link)._; // grab what we're linking to. - (sat.echo || (sat.echo = {}))[tat.id] = tat; // link it. - var tmp = cat.ask||''; // ask the chain for what needs to be loaded next! - if(tmp[''] || cat.lex){ // we might need to load the whole thing // TODO: cat.lex probably has edge case bugs to it, need more test coverage. - sat.on('out', {get: {'#': link}}); - } - setTimeout.each(Object.keys(tmp), function(get, sat){ // if sub chains are asking for data. // TODO: .keys( is slow // BUG? ?Some re-in logic may depend on this being sync? - if(!get || !(sat = tmp[get])){ return } - sat.on('out', {get: {'#': link, '.': get}}); // go get it. - },0,99); - }; Gun.on.link = link; - - function unlink(msg, cat){ // ugh, so much code for seemingly edge case behavior. - var put = msg.put||'', change = (u !== put['='])? put['='] : put[':'], root = cat.root, link, tmp; - if(u === change){ // 1st edge case: If we have a brand new database, no data will be found. - // TODO: BUG! because emptying cache could be async from below, make sure we are not emptying a newer cache. So maybe pass an Async ID to check against? - // TODO: BUG! What if this is a map? // Warning! Clearing things out needs to be robust against sync/async ops, or else you'll see `map val get put` test catastrophically fail because map attempts to link when parent graph is streamed before child value gets set. Need to differentiate between lack acks and force clearing. - if(cat.soul && u !== cat.put){ return } // data may not be found on a soul, but if a soul already has data, then nothing can clear the soul as a whole. - //if(!cat.has){ return } - tmp = (msg.$$||msg.$||'')._||''; - if(msg['@'] && (u !== tmp.put || u !== cat.put)){ return } // a "not found" from other peers should not clear out data if we have already found it. - //if(cat.has && u === cat.put && !(root.pass||'')[cat.id]){ return } // if we are already unlinked, do not call again, unless edge case. // TODO: BUG! This line should be deleted for "unlink deeply nested". - if(link = cat.link || msg.linked){ - delete (root.$.get(link)._.echo||'')[cat.id]; - } - if(cat.has){ // TODO: Empty out links, maps, echos, acks/asks, etc.? - cat.link = null; - } - cat.put = u; // empty out the cache if, for example, alice's car's color no longer exists (relative to alice) if alice no longer has a car. - // TODO: BUG! For maps, proxy this so the individual sub is triggered, not all subs. - setTimeout.each(Object.keys(cat.next||''), function(get, sat){ // empty out all sub chains. // TODO: .keys( is slow // BUG? ?Some re-in logic may depend on this being sync? // TODO: BUG? This will trigger deeper put first, does put logic depend on nested order? // TODO: BUG! For map, this needs to be the isolated child, not all of them. - if(!(sat = cat.next[get])){ return } - //if(cat.has && u === sat.put && !(root.pass||'')[sat.id]){ return } // if we are already unlinked, do not call again, unless edge case. // TODO: BUG! This line should be deleted for "unlink deeply nested". - if(link){ delete (root.$.get(link).get(get)._.echo||'')[sat.id] } - sat.on('in', {get: get, put: u, $: sat.$}); // TODO: BUG? Add recursive seen check? - },0,99); - return; - } - if(cat.soul){ return } // a soul cannot unlink itself. - if(msg.$$){ return } // a linked chain does not do the unlinking, the sub chain does. // TODO: BUG? Will this cancel maps? - link = valid(change); // need to unlink anytime we are not the same link, though only do this once per unlink (and not on init). - tmp = msg.$._||''; - if(link === tmp.link || (cat.has && !tmp.link)){ - if((root.pass||'')[cat.id] && 'string' !== typeof link){ - - } else { - return; - } - } - delete (tmp.echo||'')[cat.id]; - unlink({get: cat.get, put: u, $: msg.$, linked: msg.linked = msg.linked || tmp.link}, cat); // unlink our sub chains. - }; Gun.on.unlink = unlink; - - function ack(msg, ev){ - //if(!msg['%'] && (this||'').off){ this.off() } // do NOT memory leak, turn off listeners! Now handled by .ask itself - // manhattan: - var as = this.as, at = as.$._, root = at.root, get = as.get||'', tmp = (msg.put||'')[get['#']]||''; - if(!msg.put || ('string' == typeof get['.'] && u === tmp[get['.']])){ - if(u !== at.put){ return } - if(!at.soul && !at.has){ return } // TODO: BUG? For now, only core-chains will handle not-founds, because bugs creep in if non-core chains are used as $ but we can revisit this later for more powerful extensions. - at.ack = (at.ack || 0) + 1; - at.on('in', { - get: at.get, - put: at.put = u, - $: at.$, - '@': msg['@'] - }); - /*(tmp = at.Q) && setTimeout.each(Object.keys(tmp), function(id){ // TODO: Temporary testing, not integrated or being used, probably delete. - Object.keys(msg).forEach(function(k){ tmp[k] = msg[k] }, tmp = {}); tmp['@'] = id; // copy message - root.on('in', tmp); - }); delete at.Q;*/ - return; - } - (msg._||{}).miss = 1; - Gun.on.put(msg); - return; // eom - } - - var empty = {}, u, text_rand = String.random, valid = Gun.valid, obj_has = function(o, k){ return o && Object.prototype.hasOwnProperty.call(o, k) }, state = Gun.state, state_is = state.is, state_ify = state.ify; - })(USE, './chain'); - - ;USE(function(module){ - var Gun = USE('./root'); - Gun.chain.get = function(key, cb, as){ - var gun, tmp; - if(typeof key === 'string'){ - if(key.length == 0) { - (gun = this.chain())._.err = {err: Gun.log('0 length key!', key)}; - if(cb){ cb.call(gun, gun._.err) } - return gun; - } - var back = this, cat = back._; - var next = cat.next || empty; - if(!(gun = next[key])){ - gun = key && cache(key, back); - } - gun = gun && gun.$; - } else - if('function' == typeof key){ - if(true === cb){ return soul(this, key, cb, as), this } - gun = this; - var cat = gun._, opt = cb || {}, root = cat.root, id; - opt.at = cat; - opt.ok = key; - var wait = {}; // can we assign this to the at instead, like in once? - //var path = []; cat.$.back(at => { at.get && path.push(at.get.slice(0,9))}); path = path.reverse().join('.'); - function any(msg, eve, f){ - if(any.stun){ return } - if((tmp = root.pass) && !tmp[id]){ return } - var at = msg.$._, sat = (msg.$$||'')._, data = (sat||at).put, odd = (!at.has && !at.soul), test = {}, link, tmp; - if(odd || u === data){ // handles non-core - data = (u === ((tmp = msg.put)||'')['='])? (u === (tmp||'')[':'])? tmp : tmp[':'] : tmp['=']; - } - if(link = ('string' == typeof (tmp = Gun.valid(data)))){ - data = (u === (tmp = root.$.get(tmp)._.put))? opt.not? u : data : tmp; - } - if(opt.not && u === data){ return } - if(u === opt.stun){ - if((tmp = root.stun) && tmp.on){ - cat.$.back(function(a){ // our chain stunned? - tmp.on(''+a.id, test = {}); - if((test.run || 0) < any.id){ return test } // if there is an earlier stun on gapless parents/self. - }); - !test.run && tmp.on(''+at.id, test = {}); // this node stunned? - !test.run && sat && tmp.on(''+sat.id, test = {}); // linked node stunned? - if(any.id > test.run){ - if(!test.stun || test.stun.end){ - test.stun = tmp.on('stun'); - test.stun = test.stun && test.stun.last; - } - if(test.stun && !test.stun.end){ - //if(odd && u === data){ return } - //if(u === msg.put){ return } // "not found" acks will be found if there is stun, so ignore these. - (test.stun.add || (test.stun.add = {}))[id] = function(){ any(msg,eve,1) } // add ourself to the stun callback list that is called at end of the write. - return; - } - } - } - if(/*odd &&*/ u === data){ f = 0 } // if data not found, keep waiting/trying. - /*if(f && u === data){ - cat.on('out', opt.out); - return; - }*/ - if((tmp = root.hatch) && !tmp.end && u === opt.hatch && !f){ // quick hack! // What's going on here? Because data is streamed, we get things one by one, but a lot of developers would rather get a callback after each batch instead, so this does that by creating a wait list per chain id that is then called at the end of the batch by the hatch code in the root put listener. - if(wait[at.$._.id]){ return } wait[at.$._.id] = 1; - tmp.push(function(){any(msg,eve,1)}); - return; - }; wait = {}; // end quick hack. - } - // call: - if(root.pass){ if(root.pass[id+at.id]){ return } root.pass[id+at.id] = 1 } - if(opt.on){ opt.ok.call(at.$, data, at.get, msg, eve || any); return } // TODO: Also consider breaking `this` since a lot of people do `=>` these days and `.call(` has slower performance. - if(opt.v2020){ opt.ok(msg, eve || any); return } - Object.keys(msg).forEach(function(k){ tmp[k] = msg[k] }, tmp = {}); msg = tmp; msg.put = data; // 2019 COMPATIBILITY! TODO: GET RID OF THIS! - opt.ok.call(opt.as, msg, eve || any); // is this the right - }; - any.at = cat; - //(cat.any||(cat.any=function(msg){ setTimeout.each(Object.keys(cat.any||''), function(act){ (act = cat.any[act]) && act(msg) },0,99) }))[id = String.random(7)] = any; // maybe switch to this in future? - (cat.any||(cat.any={}))[id = String.random(7)] = any; - any.off = function(){ any.stun = 1; if(!cat.any){ return } delete cat.any[id] } - any.rid = rid; // logic from old version, can we clean it up now? - any.id = opt.run || ++root.once; // used in callback to check if we are earlier than a write. // will this ever cause an integer overflow? - tmp = root.pass; (root.pass = {})[id] = 1; // Explanation: test trade-offs want to prevent recursion so we add/remove pass flag as it gets fulfilled to not repeat, however map map needs many pass flags - how do we reconcile? - opt.out = opt.out || {get: {}}; - cat.on('out', opt.out); - root.pass = tmp; - return gun; - } else - if('number' == typeof key){ - return this.get(''+key, cb, as); - } else - if('string' == typeof (tmp = valid(key))){ - return this.get(tmp, cb, as); - } else - if(tmp = this.get.next){ - gun = tmp(this, key); - } - if(!gun){ - (gun = this.chain())._.err = {err: Gun.log('Invalid get request!', key)}; // CLEAN UP - if(cb){ cb.call(gun, gun._.err) } - return gun; - } - if(cb && 'function' == typeof cb){ - gun.get(cb, as); - } - return gun; - } - function cache(key, back){ - var cat = back._, next = cat.next, gun = back.chain(), at = gun._; - if(!next){ next = cat.next = {} } - next[at.get = key] = at; - if(back === cat.root.$){ - at.soul = key; - //at.put = {}; - } else - if(cat.soul || cat.has){ - at.has = key; - //if(obj_has(cat.put, key)){ - //at.put = cat.put[key]; - //} - } - return at; - } - function soul(gun, cb, opt, as){ - var cat = gun._, acks = 0, tmp; - if(tmp = cat.soul || cat.link){ return cb(tmp, as, cat) } - if(cat.jam){ return cat.jam.push([cb, as]) } - cat.jam = [[cb,as]]; - gun.get(function go(msg, eve){ - if(u === msg.put && !cat.root.opt.super && (tmp = Object.keys(cat.root.opt.peers).length) && ++acks <= tmp){ // TODO: super should not be in core code, bring AXE up into core instead to fix? // TODO: .keys( is slow - return; - } - eve.rid(msg); - var at = ((at = msg.$) && at._) || {}, i = 0, as; - tmp = cat.jam; delete cat.jam; // tmp = cat.jam.splice(0, 100); - //if(tmp.length){ process.nextTick(function(){ go(msg, eve) }) } - while(as = tmp[i++]){ //Gun.obj.map(tmp, function(as, cb){ - var cb = as[0], id; as = as[1]; - cb && cb(id = at.link || at.soul || Gun.valid(msg.put) || ((msg.put||{})._||{})['#'], as, msg, eve); - } //); - }, {out: {get: {'.':true}}}); - return gun; - } - function rid(at){ - var cat = this.at || this.on; - if(!at || cat.soul || cat.has){ return this.off() } - if(!(at = (at = (at = at.$ || at)._ || at).id)){ return } - var map = cat.map, tmp, seen; - //if(!map || !(tmp = map[at]) || !(tmp = tmp.at)){ return } - if(tmp = (seen = this.seen || (this.seen = {}))[at]){ return true } - seen[at] = true; - return; - //tmp.echo[cat.id] = {}; // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. - //obj.del(map, at); // TODO: Warning: This unsubscribes ALL of this chain's listeners from this link, not just the one callback event. - return; - } - var empty = {}, valid = Gun.valid, u; - })(USE, './get'); - - ;USE(function(module){ - var Gun = USE('./root'); - Gun.chain.put = function(data, cb, as){ // I rewrote it :) - var gun = this, at = gun._, root = at.root; - as = as || {}; - as.root = at.root; - as.run || (as.run = root.once); - stun(as, at.id); // set a flag for reads to check if this chain is writing. - as.ack = as.ack || cb; - as.via = as.via || gun; - as.data = as.data || data; - as.soul || (as.soul = at.soul || ('string' == typeof cb && cb)); - var s = as.state = as.state || Gun.state(); - if('function' == typeof data){ data(function(d){ as.data = d; gun.put(u,u,as) }); return gun } - if(!as.soul){ return get(as), gun } - as.$ = root.$.get(as.soul); // TODO: This may not allow user chaining and similar? - as.todo = [{it: as.data, ref: as.$}]; - as.turn = as.turn || turn; - as.ran = as.ran || ran; - //var path = []; as.via.back(at => { at.get && path.push(at.get.slice(0,9)) }); path = path.reverse().join('.'); - // TODO: Perf! We only need to stun chains that are being modified, not necessarily written to. - (function walk(){ - var to = as.todo, at = to.pop(), d = at.it, cid = at.ref && at.ref._.id, v, k, cat, tmp, g; - stun(as, at.ref); - if(tmp = at.todo){ - k = tmp.pop(); d = d[k]; - if(tmp.length){ to.push(at) } - } - k && (to.path || (to.path = [])).push(k); - if(!(v = valid(d)) && !(g = Gun.is(d))){ - if(!Object.plain(d)){ ran.err(as, "Invalid data: "+ check(d) +" at " + (as.via.back(function(at){at.get && tmp.push(at.get)}, tmp = []) || tmp.join('.'))+'.'+(to.path||[]).join('.')); return } - var seen = as.seen || (as.seen = []), i = seen.length; - while(i--){ if(d === (tmp = seen[i]).it){ v = d = tmp.link; break } } - } - if(k && v){ at.node = state_ify(at.node, k, s, d) } // handle soul later. - else { - if(!as.seen){ ran.err(as, "Data at root of graph must be a node (an object)."); return } - as.seen.push(cat = {it: d, link: {}, todo: g? [] : Object.keys(d).sort().reverse(), path: (to.path||[]).slice(), up: at}); // Any perf reasons to CPU schedule this .keys( ? - at.node = state_ify(at.node, k, s, cat.link); - !g && cat.todo.length && to.push(cat); - // --------------- - var id = as.seen.length; - (as.wait || (as.wait = {}))[id] = ''; - tmp = (cat.ref = (g? d : k? at.ref.get(k) : at.ref))._; - (tmp = (d && (d._||'')['#']) || tmp.soul || tmp.link)? resolve({soul: tmp}) : cat.ref.get(resolve, {run: as.run, /*hatch: 0,*/ v2020:1, out:{get:{'.':' '}}}); // TODO: BUG! This should be resolve ONLY soul to prevent full data from being loaded. // Fixed now? - //setTimeout(function(){ if(F){ return } console.log("I HAVE NOT BEEN CALLED!", path, id, cat.ref._.id, k) }, 9000); var F; // MAKE SURE TO ADD F = 1 below! - function resolve(msg, eve){ - var end = cat.link['#']; - if(eve){ eve.off(); eve.rid(msg) } // TODO: Too early! Check all peers ack not found. - // TODO: BUG maybe? Make sure this does not pick up a link change wipe, that it uses the changign link instead. - var soul = end || msg.soul || (tmp = (msg.$$||msg.$)._||'').soul || tmp.link || ((tmp = tmp.put||'')._||'')['#'] || tmp['#'] || (((tmp = msg.put||'') && msg.$$)? tmp['#'] : (tmp['=']||tmp[':']||'')['#']); - !end && stun(as, msg.$); - if(!soul && !at.link['#']){ // check soul link above us - (at.wait || (at.wait = [])).push(function(){ resolve(msg, eve) }) // wait - return; - } - if(!soul){ - soul = []; - (msg.$$||msg.$).back(function(at){ - if(tmp = at.soul || at.link){ return soul.push(tmp) } - soul.push(at.get); - }); - soul = soul.reverse().join('/'); - } - cat.link['#'] = soul; - !g && (((as.graph || (as.graph = {}))[soul] = (cat.node || (cat.node = {_:{}})))._['#'] = soul); - delete as.wait[id]; - cat.wait && setTimeout.each(cat.wait, function(cb){ cb && cb() }); - as.ran(as); - }; - // --------------- - } - if(!to.length){ return as.ran(as) } - as.turn(walk); - }()); - return gun; - } - - function stun(as, id){ - if(!id){ return } id = (id._||'').id||id; - var run = as.root.stun || (as.root.stun = {on: Gun.on}), test = {}, tmp; - as.stun || (as.stun = run.on('stun', function(){ })); - if(tmp = run.on(''+id)){ tmp.the.last.next(test) } - if(test.run >= as.run){ return } - run.on(''+id, function(test){ - if(as.stun.end){ - this.off(); - this.to.next(test); - return; - } - test.run = test.run || as.run; - test.stun = test.stun || as.stun; return; - if(this.to.to){ - this.the.last.next(test); - return; - } - test.stun = as.stun; - }); - } - - function ran(as){ - if(as.err){ ran.end(as.stun, as.root); return } // move log handle here. - if(as.todo.length || as.end || !Object.empty(as.wait)){ return } as.end = 1; - //(as.retry = function(){ as.acks = 0; - var cat = (as.$.back(-1)._), root = cat.root, ask = cat.ask(function(ack){ - root.on('ack', ack); - if(ack.err && !ack.lack){ Gun.log(ack) } - if(++acks > (as.acks || 0)){ this.off() } // Adjustable ACKs! Only 1 by default. - if(!as.ack){ return } - as.ack(ack, this); - }, as.opt), acks = 0, stun = as.stun, tmp; - (tmp = function(){ // this is not official yet, but quick solution to hack in for now. - if(!stun){ return } - ran.end(stun, root); - setTimeout.each(Object.keys(stun = stun.add||''), function(cb){ if(cb = stun[cb]){cb()} }); // resume the stunned reads // Any perf reasons to CPU schedule this .keys( ? - }).hatch = tmp; // this is not official yet ^ - //console.log(1, "PUT", as.run, as.graph); - if(as.ack && !as.ok){ as.ok = as.acks || 9 } // TODO: In future! Remove this! This is just old API support. - (as.via._).on('out', {put: as.out = as.graph, ok: as.ok && {'@': as.ok+1}, opt: as.opt, '#': ask, _: tmp}); - //})(); - }; ran.end = function(stun,root){ - stun.end = noop; // like with the earlier id, cheaper to make this flag a function so below callbacks do not have to do an extra type check. - if(stun.the.to === stun && stun === stun.the.last){ delete root.stun } - stun.off(); - }; ran.err = function(as, err){ - (as.ack||noop).call(as, as.out = { err: as.err = Gun.log(err) }); - as.ran(as); - } - - function get(as){ - var at = as.via._, tmp; - as.via = as.via.back(function(at){ - if(at.soul || !at.get){ return at.$ } - tmp = as.data; (as.data = {})[at.get] = tmp; - }); - if(!as.via || !as.via._.soul){ - as.via = at.root.$.get(((as.data||'')._||'')['#'] || at.$.back('opt.uuid')()) - } - as.via.put(as.data, as.ack, as); - - - return; - if(at.get && at.back.soul){ - tmp = as.data; - as.via = at.back.$; - (as.data = {})[at.get] = tmp; - as.via.put(as.data, as.ack, as); - return; - } - } - function check(d, tmp){ return ((d && (tmp = d.constructor) && tmp.name) || typeof d) } - - var u, empty = {}, noop = function(){}, turn = setTimeout.turn, valid = Gun.valid, state_ify = Gun.state.ify; - var iife = function(fn,as){fn.call(as||empty)} - })(USE, './put'); - - ;USE(function(module){ - var Gun = USE('./root'); - USE('./chain'); - USE('./back'); - USE('./put'); - USE('./get'); - module.exports = Gun; - })(USE, './index'); - - ;USE(function(module){ - var Gun = USE('./index'); - Gun.chain.on = function(tag, arg, eas, as){ // don't rewrite! - var gun = this, cat = gun._, root = cat.root, act, off, id, tmp; - if(typeof tag === 'string'){ - if(!arg){ return cat.on(tag) } - act = cat.on(tag, arg, eas || cat, as); - if(eas && eas.$){ - (eas.subs || (eas.subs = [])).push(act); - } - return gun; - } - var opt = arg; - (opt = (true === opt)? {change: true} : opt || {}).not = 1; opt.on = 1; - //opt.at = cat; - //opt.ok = tag; - //opt.last = {}; - var wait = {}; // can we assign this to the at instead, like in once? - gun.get(tag, opt); - /*gun.get(function on(data,key,msg,eve){ var $ = this; - if(tmp = root.hatch){ // quick hack! - if(wait[$._.id]){ return } wait[$._.id] = 1; - tmp.push(function(){on.call($, data,key,msg,eve)}); - return; - }; wait = {}; // end quick hack. - tag.call($, data,key,msg,eve); - }, opt); // TODO: PERF! Event listener leak!!!?*/ - /* - function one(msg, eve){ - if(one.stun){ return } - var at = msg.$._, data = at.put, tmp; - if(tmp = at.link){ data = root.$.get(tmp)._.put } - if(opt.not===u && u === data){ return } - if(opt.stun===u && (tmp = root.stun) && (tmp = tmp[at.id] || tmp[at.back.id]) && !tmp.end){ // Remember! If you port this into `.get(cb` make sure you allow stun:0 skip option for `.put(`. - tmp[id] = function(){one(msg,eve)}; - return; - } - //tmp = one.wait || (one.wait = {}); console.log(tmp[at.id] === ''); if(tmp[at.id] !== ''){ tmp[at.id] = tmp[at.id] || setTimeout(function(){tmp[at.id]='';one(msg,eve)},1); return } delete tmp[at.id]; - // call: - if(opt.as){ - opt.ok.call(opt.as, msg, eve || one); - } else { - opt.ok.call(at.$, data, msg.get || at.get, msg, eve || one); - } - }; - one.at = cat; - (cat.act||(cat.act={}))[id = String.random(7)] = one; - one.off = function(){ one.stun = 1; if(!cat.act){ return } delete cat.act[id] } - cat.on('out', {get: {}});*/ - return gun; - } - // Rules: - // 1. If cached, should be fast, but not read while write. - // 2. Should not retrigger other listeners, should get triggered even if nothing found. - // 3. If the same callback passed to many different once chains, each should resolve - an unsubscribe from the same callback should not effect the state of the other resolving chains, if you do want to cancel them all early you should mutate the callback itself with a flag & check for it at top of callback - Gun.chain.once = function(cb, opt){ opt = opt || {}; // avoid rewriting - if(!cb){ return none(this,opt) } - var gun = this, cat = gun._, root = cat.root, data = cat.put, id = String.random(7), one, tmp; - gun.get(function(data,key,msg,eve){ - var $ = this, at = $._, one = (at.one||(at.one={})); - if(eve.stun){ return } if('' === one[id]){ return } - if(true === (tmp = Gun.valid(data))){ once(); return } - if('string' == typeof tmp){ return } // TODO: BUG? Will this always load? - clearTimeout((cat.one||'')[id]); // clear "not found" since they only get set on cat. - clearTimeout(one[id]); one[id] = setTimeout(once, opt.wait||99); // TODO: Bug? This doesn't handle plural chains. - function once(f){ - if(!at.has && !at.soul){ at = {put: data, get: key} } // handles non-core messages. - if(u === (tmp = at.put)){ tmp = ((msg.$$||'')._||'').put } - if('string' == typeof Gun.valid(tmp)){ - tmp = root.$.get(tmp)._.put; - if(tmp === u && !f){ - one[id] = setTimeout(function(){ once(1) }, opt.wait||99); // TODO: Quick fix. Maybe use ack count for more predictable control? - return - } - } - //console.log("AND VANISHED", data); - if(eve.stun){ return } if('' === one[id]){ return } one[id] = ''; - if(cat.soul || cat.has){ eve.off() } // TODO: Plural chains? // else { ?.off() } // better than one check? - cb.call($, tmp, at.get); - clearTimeout(one[id]); // clear "not found" since they only get set on cat. // TODO: This was hackily added, is it necessary or important? Probably not, in future try removing this. Was added just as a safety for the `&& !f` check. - }; - }, {on: 1}); - return gun; - } - function none(gun,opt,chain){ - Gun.log.once("valonce", "Chainable val is experimental, its behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); - (chain = gun.chain())._.nix = gun.once(function(data, key){ chain._.on('in', this._) }); - chain._.lex = gun._.lex; // TODO: Better approach in future? This is quick for now. - return chain; - } - - Gun.chain.off = function(){ - // make off more aggressive. Warning, it might backfire! - var gun = this, at = gun._, tmp; - var cat = at.back; - if(!cat){ return } - at.ack = 0; // so can resubscribe. - if(tmp = cat.next){ - if(tmp[at.get]){ - delete tmp[at.get]; - } else { - - } - } - // TODO: delete cat.one[map.id]? - if (tmp = cat.any) { - delete cat.any; - cat.any = {}; - } - if(tmp = cat.ask){ - delete tmp[at.get]; - } - if(tmp = cat.put){ - delete tmp[at.get]; - } - if(tmp = at.soul){ - delete cat.root.graph[tmp]; - } - if(tmp = at.map){ - Object.keys(tmp).forEach(function(i,at){ at = tmp[i]; //obj_map(tmp, function(at){ - if(at.link){ - cat.root.$.get(at.link).off(); - } - }); - } - if(tmp = at.next){ - Object.keys(tmp).forEach(function(i,neat){ neat = tmp[i]; //obj_map(tmp, function(neat){ - neat.$.off(); - }); - } - at.on('off', {}); - return gun; - } - var empty = {}, noop = function(){}, u; - })(USE, './on'); - - ;USE(function(module){ - var Gun = USE('./index'), next = Gun.chain.get.next; - Gun.chain.get.next = function(gun, lex){ var tmp; - if(!Object.plain(lex)){ return (next||noop)(gun, lex) } - if(tmp = ((tmp = lex['#'])||'')['='] || tmp){ return gun.get(tmp) } - (tmp = gun.chain()._).lex = lex; // LEX! - gun.on('in', function(eve){ - if(String.match(eve.get|| (eve.put||'')['.'], lex['.'] || lex['#'] || lex)){ - tmp.on('in', eve); - } - this.to.next(eve); - }); - return tmp.$; - } - Gun.chain.map = function(cb, opt, t){ - var gun = this, cat = gun._, lex, chain; - if(Object.plain(cb)){ lex = cb['.']? cb : {'.': cb}; cb = u } - if(!cb){ - if(chain = cat.each){ return chain } - (cat.each = chain = gun.chain())._.lex = lex || chain._.lex || cat.lex; - chain._.nix = gun.back('nix'); - gun.on('in', map, chain._); - return chain; - } - Gun.log.once("mapfn", "Map functions are experimental, their behavior and API may change moving forward. Please play with it and report bugs and ideas on how to improve it."); - chain = gun.chain(); - gun.map().on(function(data, key, msg, eve){ - var next = (cb||noop).call(this, data, key, msg, eve); - if(u === next){ return } - if(data === next){ return chain._.on('in', msg) } - if(Gun.is(next)){ return chain._.on('in', next._) } - var tmp = {}; Object.keys(msg.put).forEach(function(k){ tmp[k] = msg.put[k] }, tmp); tmp['='] = next; - chain._.on('in', {get: key, put: tmp}); - }); - return chain; - } - function map(msg){ this.to.next(msg); - var cat = this.as, gun = msg.$, at = gun._, put = msg.put, tmp; - if(!at.soul && !msg.$$){ return } // this line took hundreds of tries to figure out. It only works if core checks to filter out above chains during link tho. This says "only bother to map on a node" for this layer of the chain. If something is not a node, map should not work. - if((tmp = cat.lex) && !String.match(msg.get|| (put||'')['.'], tmp['.'] || tmp['#'] || tmp)){ return } - Gun.on.link(msg, cat); - } - var noop = function(){}, event = {stun: noop, off: noop}, u; - })(USE, './map'); - - ;USE(function(module){ - var Gun = USE('./index'); - Gun.chain.set = function(item, cb, opt){ - var gun = this, root = gun.back(-1), soul, tmp; - cb = cb || function(){}; - opt = opt || {}; opt.item = opt.item || item; - if(soul = ((item||'')._||'')['#']){ (item = {})['#'] = soul } // check if node, make link. - if('string' == typeof (tmp = Gun.valid(item))){ return gun.get(soul = tmp).put(item, cb, opt) } // check if link - if(!Gun.is(item)){ - if(Object.plain(item)){ - item = root.get(soul = gun.back('opt.uuid')()).put(item); - } - return gun.get(soul || root.back('opt.uuid')(7)).put(item, cb, opt); - } - gun.put(function(go){ - item.get(function(soul, o, msg){ // TODO: BUG! We no longer have this option? & go error not handled? - if(!soul){ return cb.call(gun, {err: Gun.log('Only a node can be linked! Not "' + msg.put + '"!')}) } - (tmp = {})[soul] = {'#': soul}; go(tmp); - },true); - }) - return item; - } - })(USE, './set'); - - ;USE(function(module){ - USE('./shim'); - - var noop = function(){} - var parse = JSON.parseAsync || function(t,cb,r){ var u, d = +new Date; try{ cb(u, JSON.parse(t,r), json.sucks(+new Date - d)) }catch(e){ cb(e) } } - var json = JSON.stringifyAsync || function(v,cb,r,s){ var u, d = +new Date; try{ cb(u, JSON.stringify(v,r,s), json.sucks(+new Date - d)) }catch(e){ cb(e) } } - json.sucks = function(d){ if(d > 99){ console.log("Warning: JSON blocking CPU detected. Add `gun/lib/yson.js` to fix."); json.sucks = noop } } - - function Mesh(root){ - var mesh = function(){}; - var opt = root.opt || {}; - opt.log = opt.log || console.log; - opt.gap = opt.gap || opt.wait || 0; - opt.max = opt.max || (opt.memory? (opt.memory * 999 * 999) : 300000000) * 0.3; - opt.pack = opt.pack || (opt.max * 0.01 * 0.01); - opt.puff = opt.puff || 9; // IDEA: do a start/end benchmark, divide ops/result. - var puff = setTimeout.turn || setTimeout; - - var dup = root.dup, dup_check = dup.check, dup_track = dup.track; - - var ST = +new Date, LT = ST; - - var hear = mesh.hear = function(raw, peer){ - if(!raw){ return } - if(opt.max <= raw.length){ return mesh.say({dam: '!', err: "Message too big!"}, peer) } - if(mesh === this){ - /*if('string' == typeof raw){ try{ - var stat = console.STAT || {}; - //console.log('HEAR:', peer.id, (raw||'').slice(0,250), ((raw||'').length / 1024 / 1024).toFixed(4)); - - //console.log(setTimeout.turn.s.length, 'stacks', parseFloat((-(LT - (LT = +new Date))/1000).toFixed(3)), 'sec', parseFloat(((LT-ST)/1000 / 60).toFixed(1)), 'up', stat.peers||0, 'peers', stat.has||0, 'has', stat.memhused||0, stat.memused||0, stat.memax||0, 'heap mem max'); - }catch(e){ console.log('DBG err', e) }}*/ - hear.d += raw.length||0 ; ++hear.c } // STATS! - var S = peer.SH = +new Date; - var tmp = raw[0], msg; - //raw && raw.slice && console.log("hear:", ((peer.wire||'').headers||'').origin, raw.length, raw.slice && raw.slice(0,50)); //tc-iamunique-tc-package-ds1 - if('[' === tmp){ - parse(raw, function(err, msg){ - if(err || !msg){ return mesh.say({dam: '!', err: "DAM JSON parse error."}, peer) } - console.STAT && console.STAT(+new Date, msg.length, '# on hear batch'); - var P = opt.puff; - (function go(){ - var S = +new Date; - var i = 0, m; while(i < P && (m = msg[i++])){ mesh.hear(m, peer) } - msg = msg.slice(i); // slicing after is faster than shifting during. - console.STAT && console.STAT(S, +new Date - S, 'hear loop'); - flush(peer); // force send all synchronously batched acks. - if(!msg.length){ return } - puff(go, 0); - }()); - }); - raw = ''; // - return; - } - if('{' === tmp || ((raw['#'] || Object.plain(raw)) && (msg = raw))){ - if(msg){ return hear.one(msg, peer, S) } - parse(raw, function(err, msg){ - if(err || !msg){ return mesh.say({dam: '!', err: "DAM JSON parse error."}, peer) } - hear.one(msg, peer, S); - }); - return; - } - } - hear.one = function(msg, peer, S){ // S here is temporary! Undo. - var id, hash, tmp, ash, DBG; - if(msg.DBG){ msg.DBG = DBG = {DBG: msg.DBG} } - DBG && (DBG.h = S); - DBG && (DBG.hp = +new Date); - if(!(id = msg['#'])){ id = msg['#'] = String.random(9) } - if(tmp = dup_check(id)){ return } - // DAM logic: - if(!(hash = msg['##']) && false && u !== msg.put){ /*hash = msg['##'] = Type.obj.hash(msg.put)*/ } // disable hashing for now // TODO: impose warning/penalty instead (?) - if(hash && (tmp = msg['@'] || (msg.get && id)) && dup.check(ash = tmp+hash)){ return } // Imagine A <-> B <=> (C & D), C & D reply with same ACK but have different IDs, B can use hash to dedup. Or if a GET has a hash already, we shouldn't ACK if same. - (msg._ = function(){}).via = mesh.leap = peer; - if((tmp = msg['><']) && 'string' == typeof tmp){ tmp.slice(0,99).split(',').forEach(function(k){ this[k] = 1 }, (msg._).yo = {}) } // Peers already sent to, do not resend. - // DAM ^ - if(tmp = msg.dam){ - if(tmp = mesh.hear[tmp]){ - tmp(msg, peer, root); - } - dup_track(id); - return; - } - if(tmp = msg.ok){ msg._.near = tmp['/'] } - var S = +new Date; - DBG && (DBG.is = S); peer.SI = id; - dup_track.ed = function(d){ - if(id !== d){ return } - dup_track.ed = 0; - if(!(d = dup.s[id])){ return } - d.via = peer; - if(msg.get){ d.it = msg } - } - root.on('in', mesh.last = msg); - DBG && (DBG.hd = +new Date); - console.STAT && console.STAT(S, +new Date - S, msg.get? 'msg get' : msg.put? 'msg put' : 'msg'); - dup_track(id); // in case 'in' does not call track. - if(ash){ dup_track(ash) } //dup.track(tmp+hash, true).it = it(msg); - mesh.leap = mesh.last = null; // warning! mesh.leap could be buggy. - } - var tomap = function(k,i,m){m(k,true)}; - hear.c = hear.d = 0; - - ;(function(){ - var SMIA = 0; - var loop; - mesh.hash = function(msg, peer){ var h, s, t; - var S = +new Date; - json(msg.put, function hash(err, text){ - var ss = (s || (s = t = text||'')).slice(0, 32768); // 1024 * 32 - h = String.hash(ss, h); s = s.slice(32768); - if(s){ puff(hash, 0); return } - console.STAT && console.STAT(S, +new Date - S, 'say json+hash'); - msg._.$put = t; - msg['##'] = h; - mesh.say(msg, peer); - delete msg._.$put; - }, sort); - } - function sort(k, v){ var tmp; - if(!(v instanceof Object)){ return v } - Object.keys(v).sort().forEach(sorta, {to: tmp = {}, on: v}); - return tmp; - } function sorta(k){ this.to[k] = this.on[k] } - - var say = mesh.say = function(msg, peer){ var tmp; - if((tmp = this) && (tmp = tmp.to) && tmp.next){ tmp.next(msg) } // compatible with middleware adapters. - if(!msg){ return false } - var id, hash, raw, ack = msg['@']; -//if(opt.super && (!ack || !msg.put)){ return } // TODO: MANHATTAN STUB //OBVIOUSLY BUG! But squelch relay. // :( get only is 100%+ CPU usage :( - var meta = msg._||(msg._=function(){}); - var DBG = msg.DBG, S = +new Date; meta.y = meta.y || S; if(!peer){ DBG && (DBG.y = S) } - if(!(id = msg['#'])){ id = msg['#'] = String.random(9) } - !loop && dup_track(id);//.it = it(msg); // track for 9 seconds, default. Earth<->Mars would need more! // always track, maybe move this to the 'after' logic if we split function. - //if(msg.put && (msg.err || (dup.s[id]||'').err)){ return false } // TODO: in theory we should not be able to stun a message, but for now going to check if it can help network performance preventing invalid data to relay. - if(!(hash = msg['##']) && u !== msg.put && !meta.via && ack){ mesh.hash(msg, peer); return } // TODO: Should broadcasts be hashed? - if(!peer && ack){ peer = ((tmp = dup.s[ack]) && (tmp.via || ((tmp = tmp.it) && (tmp = tmp._) && tmp.via))) || ((tmp = mesh.last) && ack === tmp['#'] && mesh.leap) } // warning! mesh.leap could be buggy! mesh last check reduces this. // TODO: CLEAN UP THIS LINE NOW? `.it` should be reliable. - if(!peer && ack){ // still no peer, then ack daisy chain 'tunnel' got lost. - if(dup.s[ack]){ return } // in dups but no peer hints that this was ack to ourself, ignore. - console.STAT && console.STAT(+new Date, ++SMIA, 'total no peer to ack to'); // TODO: Delete this now. Dropping lost ACKs is protocol fine now. - return false; - } // TODO: Temporary? If ack via trace has been lost, acks will go to all peers, which trashes browser bandwidth. Not relaying the ack will force sender to ask for ack again. Note, this is technically wrong for mesh behavior. - if(ack && !msg.put && !hash && ((dup.s[ack]||'').it||'')['##']){ return false } // If we're saying 'not found' but a relay had data, do not bother sending our not found. // Is this correct, return false? // NOTE: ADD PANIC TEST FOR THIS! - if(!peer && mesh.way){ return mesh.way(msg) } - DBG && (DBG.yh = +new Date); - if(!(raw = meta.raw)){ mesh.raw(msg, peer); return } - DBG && (DBG.yr = +new Date); - if(!peer || !peer.id){ - if(!Object.plain(peer || opt.peers)){ return false } - var S = +new Date; - var P = opt.puff, ps = opt.peers, pl = Object.keys(peer || opt.peers || {}); // TODO: .keys( is slow - console.STAT && console.STAT(S, +new Date - S, 'peer keys'); - ;(function go(){ - var S = +new Date; - //Type.obj.map(peer || opt.peers, each); // in case peer is a peer list. - loop = 1; var wr = meta.raw; meta.raw = raw; // quick perf hack - var i = 0, p; while(i < 9 && (p = (pl||'')[i++])){ - if(!(p = ps[p] || (peer||'')[p])){ continue } - mesh.say(msg, p); - } - meta.raw = wr; loop = 0; - pl = pl.slice(i); // slicing after is faster than shifting during. - console.STAT && console.STAT(S, +new Date - S, 'say loop'); - if(!pl.length){ return } - puff(go, 0); - ack && dup_track(ack); // keep for later - }()); - return; - } - // TODO: PERF: consider splitting function here, so say loops do less work. - if(!peer.wire && mesh.wire){ mesh.wire(peer) } - if(id === peer.last){ return } peer.last = id; // was it just sent? - if(peer === meta.via){ return false } // don't send back to self. - if((tmp = meta.yo) && (tmp[peer.url] || tmp[peer.pid] || tmp[peer.id]) /*&& !o*/){ return false } - console.STAT && console.STAT(S, ((DBG||meta).yp = +new Date) - (meta.y || S), 'say prep'); - !loop && ack && dup_track(ack); // streaming long responses needs to keep alive the ack. - if(peer.batch){ - peer.tail = (tmp = peer.tail || 0) + raw.length; - if(peer.tail <= opt.pack){ - peer.batch += (tmp?',':'')+raw; - return; - } - flush(peer); - } - peer.batch = '['; // Prevents double JSON! - var ST = +new Date; - setTimeout(function(){ - console.STAT && console.STAT(ST, +new Date - ST, '0ms TO'); - flush(peer); - }, opt.gap); // TODO: queuing/batching might be bad for low-latency video game performance! Allow opt out? - send(raw, peer); - console.STAT && (ack === peer.SI) && console.STAT(S, +new Date - peer.SH, 'say ack'); - } - mesh.say.c = mesh.say.d = 0; - // TODO: this caused a out-of-memory crash! - mesh.raw = function(msg, peer){ // TODO: Clean this up / delete it / move logic out! - if(!msg){ return '' } - var meta = (msg._) || {}, put, tmp; - if(tmp = meta.raw){ return tmp } - if('string' == typeof msg){ return msg } - var hash = msg['##'], ack = msg['@']; - if(hash && ack){ - if(!meta.via && dup_check(ack+hash)){ return false } // for our own out messages, memory & storage may ack the same thing, so dedup that. Tho if via another peer, we already tracked it upon hearing, so this will always trigger false positives, so don't do that! - if(tmp = (dup.s[ack]||'').it){ - if(hash === tmp['##']){ return false } // if ask has a matching hash, acking is optional. - if(!tmp['##']){ tmp['##'] = hash } // if none, add our hash to ask so anyone we relay to can dedup. // NOTE: May only check against 1st ack chunk, 2nd+ won't know and still stream back to relaying peers which may then dedup. Any way to fix this wasted bandwidth? I guess force rate limiting breaking change, that asking peer has to ask for next lexical chunk. - } - } - if(!msg.dam && !msg['@']){ - var i = 0, to = []; tmp = opt.peers; - for(var k in tmp){ var p = tmp[k]; // TODO: Make it up peers instead! - to.push(p.url || p.pid || p.id); - if(++i > 6){ break } - } - if(i > 1){ msg['><'] = to.join() } // TODO: BUG! This gets set regardless of peers sent to! Detect? - } - if(msg.put && (tmp = msg.ok)){ msg.ok = {'@':(tmp['@']||1)-1, '/': (tmp['/']==msg._.near)? mesh.near : tmp['/']}; } - if(put = meta.$put){ - tmp = {}; Object.keys(msg).forEach(function(k){ tmp[k] = msg[k] }); - tmp.put = ':])([:'; - json(tmp, function(err, raw){ - if(err){ return } // TODO: Handle!! - var S = +new Date; - tmp = raw.indexOf('"put":":])([:"'); - res(u, raw = raw.slice(0, tmp+6) + put + raw.slice(tmp + 14)); - console.STAT && console.STAT(S, +new Date - S, 'say slice'); - }); - return; - } - json(msg, res); - function res(err, raw){ - if(err){ return } // TODO: Handle!! - meta.raw = raw; //if(meta && (raw||'').length < (999 * 99)){ meta.raw = raw } // HNPERF: If string too big, don't keep in memory. - mesh.say(msg, peer); - } - } - }()); - - function flush(peer){ - var tmp = peer.batch, t = 'string' == typeof tmp, l; - if(t){ tmp += ']' }// TODO: Prevent double JSON! - peer.batch = peer.tail = null; - if(!tmp){ return } - if(t? 3 > tmp.length : !tmp.length){ return } // TODO: ^ - if(!t){try{tmp = (1 === tmp.length? tmp[0] : JSON.stringify(tmp)); - }catch(e){return opt.log('DAM JSON stringify error', e)}} - if(!tmp){ return } - send(tmp, peer); - } - // for now - find better place later. - function send(raw, peer){ try{ - var wire = peer.wire; - if(peer.say){ - peer.say(raw); - } else - if(wire.send){ - wire.send(raw); - } - mesh.say.d += raw.length||0; ++mesh.say.c; // STATS! - }catch(e){ - (peer.queue = peer.queue || []).push(raw); - }} - - mesh.near = 0; - mesh.hi = function(peer){ - var wire = peer.wire, tmp; - if(!wire){ mesh.wire((peer.length && {url: peer, id: peer}) || peer); return } - if(peer.id){ - opt.peers[peer.url || peer.id] = peer; - } else { - tmp = peer.id = peer.id || peer.url || String.random(9); - mesh.say({dam: '?', pid: root.opt.pid}, opt.peers[tmp] = peer); - delete dup.s[peer.last]; // IMPORTANT: see https://gun.eco/docs/DAM#self - } - if(!peer.met){ - mesh.near++; - peer.met = +(new Date); - root.on('hi', peer) - } - // @rogowski I need this here by default for now to fix go1dfish's bug - tmp = peer.queue; peer.queue = []; - setTimeout.each(tmp||[],function(msg){ - send(msg, peer); - },0,9); - //Type.obj.native && Type.obj.native(); // dirty place to check if other JS polluted. - } - mesh.bye = function(peer){ - peer.met && --mesh.near; - delete peer.met; - root.on('bye', peer); - var tmp = +(new Date); tmp = (tmp - (peer.met||tmp)); - mesh.bye.time = ((mesh.bye.time || tmp) + tmp) / 2; - } - mesh.hear['!'] = function(msg, peer){ opt.log('Error:', msg.err) } - mesh.hear['?'] = function(msg, peer){ - if(msg.pid){ - if(!peer.pid){ peer.pid = msg.pid } - if(msg['@']){ return } - } - mesh.say({dam: '?', pid: opt.pid, '@': msg['#']}, peer); - delete dup.s[peer.last]; // IMPORTANT: see https://gun.eco/docs/DAM#self - } - mesh.hear['mob'] = function(msg, peer){ // NOTE: AXE will overload this with better logic. - if(!msg.peers){ return } - var peers = Object.keys(msg.peers), one = peers[(Math.random()*peers.length) >> 0]; - if(!one){ return } - mesh.bye(peer); - mesh.hi(one); - } - - root.on('create', function(root){ - root.opt.pid = root.opt.pid || String.random(9); - this.to.next(root); - root.on('out', mesh.say); - }); - - root.on('bye', function(peer, tmp){ - peer = opt.peers[peer.id || peer] || peer; - this.to.next(peer); - peer.bye? peer.bye() : (tmp = peer.wire) && tmp.close && tmp.close(); - delete opt.peers[peer.id]; - peer.wire = null; - }); - - var gets = {}; - root.on('bye', function(peer, tmp){ this.to.next(peer); - if(tmp = console.STAT){ tmp.peers = mesh.near; } - if(!(tmp = peer.url)){ return } gets[tmp] = true; - setTimeout(function(){ delete gets[tmp] },opt.lack || 9000); - }); - root.on('hi', function(peer, tmp){ this.to.next(peer); - if(tmp = console.STAT){ tmp.peers = mesh.near } - if(opt.super){ return } // temporary (?) until we have better fix/solution? - var souls = Object.keys(root.next||''); // TODO: .keys( is slow - if(souls.length > 9999 && !console.SUBS){ console.log(console.SUBS = "Warning: You have more than 10K live GETs, which might use more bandwidth than your screen can show - consider `.off()`.") } - setTimeout.each(souls, function(soul){ var node = root.next[soul]; - if(opt.super || (node.ask||'')['']){ mesh.say({get: {'#': soul}}, peer); return } - setTimeout.each(Object.keys(node.ask||''), function(key){ if(!key){ return } - // is the lack of ## a !onion hint? - mesh.say({'##': String.hash((root.graph[soul]||'')[key]), get: {'#': soul, '.': key}}, peer); - // TODO: Switch this so Book could route? - }) - }); - }); - - return mesh; - } - var empty = {}, ok = true, u; - - try{ module.exports = Mesh }catch(e){} - - })(USE, './mesh'); - - ;USE(function(module){ - var Gun = USE('./index'); - Gun.Mesh = USE('./mesh'); - - // TODO: resync upon reconnect online/offline - //window.ononline = window.onoffline = function(){ console.log('online?', navigator.onLine) } - - Gun.on('opt', function(root){ - this.to.next(root); - if(root.once){ return } - var opt = root.opt; - if(false === opt.WebSocket){ return } - - var env = Gun.window || {}; - var websocket = opt.WebSocket || env.WebSocket || env.webkitWebSocket || env.mozWebSocket; - if(!websocket){ return } - opt.WebSocket = websocket; - - var mesh = opt.mesh = opt.mesh || Gun.Mesh(root); - - var wire = mesh.wire || opt.wire; - mesh.wire = opt.wire = open; - function open(peer){ try{ - if(!peer || !peer.url){ return wire && wire(peer) } - var url = peer.url.replace(/^http/, 'ws'); - var wire = peer.wire = new opt.WebSocket(url); - wire.onclose = function(){ - reconnect(peer); - opt.mesh.bye(peer); - }; - wire.onerror = function(err){ - reconnect(peer); - }; - wire.onopen = function(){ - opt.mesh.hi(peer); - } - wire.onmessage = function(msg){ - if(!msg){ return } - opt.mesh.hear(msg.data || msg, peer); - }; - return wire; - }catch(e){ opt.mesh.bye(peer) }} - - setTimeout(function(){ !opt.super && root.on('out', {dam:'hi'}) },1); // it can take a while to open a socket, so maybe no longer lazy load for perf reasons? - - var wait = 2 * 999; - function reconnect(peer){ - clearTimeout(peer.defer); - //if(!opt.peers[peer.url]){ return } - if(doc && peer.retry <= 0){ return } - peer.retry = (peer.retry || opt.retry+1 || 60) - ((-peer.tried + (peer.tried = +new Date) < wait*4)?1:0); - peer.defer = setTimeout(function to(){ - if(doc && doc.hidden){ return setTimeout(to,wait) } - open(peer); - }, wait); - } - var doc = (''+u !== typeof document) && document; - }); - var noop = function(){}, u; - })(USE, './websocket'); - - ;USE(function(module){ - if(typeof Gun === 'undefined'){ return } - - var noop = function(){}, store, u; - try{store = (Gun.window||noop).localStorage}catch(e){} - if(!store){ - Gun.log("Warning: No localStorage exists to persist data to!"); - store = {setItem: function(k,v){this[k]=v}, removeItem: function(k){delete this[k]}, getItem: function(k){return this[k]}}; - } - - var parse = JSON.parseAsync || function(t,cb,r){ var u; try{ cb(u, JSON.parse(t,r)) }catch(e){ cb(e) } } - var json = JSON.stringifyAsync || function(v,cb,r,s){ var u; try{ cb(u, JSON.stringify(v,r,s)) }catch(e){ cb(e) } } - - Gun.on('create', function lg(root){ - this.to.next(root); - var opt = root.opt, graph = root.graph, acks = [], disk, to, size, stop; - if(false === opt.localStorage){ return } - opt.prefix = opt.file || 'gun/'; - try{ disk = lg[opt.prefix] = lg[opt.prefix] || JSON.parse(size = store.getItem(opt.prefix)) || {}; // TODO: Perf! This will block, should we care, since limited to 5MB anyways? - }catch(e){ disk = lg[opt.prefix] = {}; } - size = (size||'').length; - - root.on('get', function(msg){ - this.to.next(msg); - var lex = msg.get, soul, data, tmp, u; - if(!lex || !(soul = lex['#'])){ return } - data = disk[soul] || u; - if(data && (tmp = lex['.']) && !Object.plain(tmp)){ // pluck! - data = Gun.state.ify({}, tmp, Gun.state.is(data, tmp), data[tmp], soul); - } - //if(data){ (tmp = {})[soul] = data } // back into a graph. - //setTimeout(function(){ - Gun.on.get.ack(msg, data); //root.on('in', {'@': msg['#'], put: tmp, lS:1});// || root.$}); - //}, Math.random() * 10); // FOR TESTING PURPOSES! - }); - - root.on('put', function(msg){ - this.to.next(msg); // remember to call next middleware adapter - var put = msg.put, soul = put['#'], key = put['.'], id = msg['#'], ok = msg.ok||'', tmp; // pull data off wire envelope - if (!(root.next || '')[soul]){ return } // fix https://github.com/amark/gun/issues/1377 - disk[soul] = Gun.state.ify(disk[soul], key, put['>'], put[':'], soul); // merge into disk object - if(stop && size > (4999880)){ root.on('in', {'@': id, err: "localStorage max!"}); return; } - //if(!msg['@']){ acks.push(id) } // then ack any non-ack write. // TODO: use batch id. - if(!msg['@'] && (!msg._.via || Math.random() < (ok['@'] / ok['/']))){ acks.push(id) } // then ack any non-ack write. // TODO: use batch id. - if(to){ return } - to = setTimeout(flush, 9+(size / 333)); // 0.1MB = 0.3s, 5MB = 15s - }); - function flush(){ - if(!acks.length && ((setTimeout.turn||'').s||'').length){ setTimeout(flush,99); return; } // defer if "busy" && no saves. - var err, ack = acks; clearTimeout(to); to = false; acks = []; - json(disk, function(err, tmp){ - try{!err && store.setItem(opt.prefix, tmp); - }catch(e){ err = stop = e || "localStorage failure" } - if(err){ - Gun.log(err + " Consider using GUN's IndexedDB plugin for RAD for more storage space, https://gun.eco/docs/RAD#install"); - root.on('localStorage:error', {err: err, get: opt.prefix, put: disk}); - } - size = tmp.length; - - //if(!err && !Object.empty(opt.peers)){ return } // only ack if there are no peers. // Switch this to probabilistic mode - setTimeout.each(ack, function(id){ - root.on('in', {'@': id, err: err, ok: 0}); // localStorage isn't reliable, so make its `ok` code be a low number. - },0,99); - }) - } - - }); - })(USE, './localStorage'); - -}()); - -/* BELOW IS TEMPORARY FOR OLD INTERNAL COMPATIBILITY, THEY ARE IMMEDIATELY DEPRECATED AND WILL BE REMOVED IN NEXT VERSION */ -;(function(){ - var u; - if(''+u == typeof Gun){ return } - var DEP = function(n){ console.warn("Warning! Deprecated internal utility will break in next version:", n) } - // Generic javascript utilities. - var Type = Gun; - //Type.fns = Type.fn = {is: function(fn){ return (!!fn && fn instanceof Function) }} - Type.fn = Type.fn || {is: function(fn){ DEP('fn'); return (!!fn && 'function' == typeof fn) }} - Type.bi = Type.bi || {is: function(b){ DEP('bi');return (b instanceof Boolean || typeof b == 'boolean') }} - Type.num = Type.num || {is: function(n){ DEP('num'); return !list_is(n) && ((n - parseFloat(n) + 1) >= 0 || Infinity === n || -Infinity === n) }} - Type.text = Type.text || {is: function(t){ DEP('text'); return (typeof t == 'string') }} - Type.text.ify = Type.text.ify || function(t){ DEP('text.ify'); - if(Type.text.is(t)){ return t } - if(typeof JSON !== "undefined"){ return JSON.stringify(t) } - return (t && t.toString)? t.toString() : t; - } - Type.text.random = Type.text.random || function(l, c){ DEP('text.random'); - var s = ''; - l = l || 24; // you are not going to make a 0 length random number, so no need to check type - c = c || '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz'; - while(l > 0){ s += c.charAt(Math.floor(Math.random() * c.length)); l-- } - return s; - } - Type.text.match = Type.text.match || function(t, o){ var tmp, u; DEP('text.match'); - if('string' !== typeof t){ return false } - if('string' == typeof o){ o = {'=': o} } - o = o || {}; - tmp = (o['='] || o['*'] || o['>'] || o['<']); - if(t === tmp){ return true } - if(u !== o['=']){ return false } - tmp = (o['*'] || o['>'] || o['<']); - if(t.slice(0, (tmp||'').length) === tmp){ return true } - if(u !== o['*']){ return false } - if(u !== o['>'] && u !== o['<']){ - return (t >= o['>'] && t <= o['<'])? true : false; - } - if(u !== o['>'] && t >= o['>']){ return true } - if(u !== o['<'] && t <= o['<']){ return true } - return false; - } - Type.text.hash = Type.text.hash || function(s, c){ // via SO - DEP('text.hash'); - if(typeof s !== 'string'){ return } - c = c || 0; - if(!s.length){ return c } - for(var i=0,l=s.length,n; i B){ return 1 } - else { return 0 } - } - } - Type.list.map = Type.list.map || function(l, c, _){ DEP('list.map'); return obj_map(l, c, _) } - Type.list.index = 1; // change this to 0 if you want non-logical, non-mathematical, non-matrix, non-convenient array notation - Type.obj = Type.boj || {is: function(o){ DEP('obj'); return o? (o instanceof Object && o.constructor === Object) || Object.prototype.toString.call(o).match(/^\[object (\w+)\]$/)[1] === 'Object' : false }} - Type.obj.put = Type.obj.put || function(o, k, v){ DEP('obj.put'); return (o||{})[k] = v, o } - Type.obj.has = Type.obj.has || function(o, k){ DEP('obj.has'); return o && Object.prototype.hasOwnProperty.call(o, k) } - Type.obj.del = Type.obj.del || function(o, k){ DEP('obj.del'); - if(!o){ return } - o[k] = null; - delete o[k]; - return o; - } - Type.obj.as = Type.obj.as || function(o, k, v, u){ DEP('obj.as'); return o[k] = o[k] || (u === v? {} : v) } - Type.obj.ify = Type.obj.ify || function(o){ DEP('obj.ify'); - if(obj_is(o)){ return o } - try{o = JSON.parse(o); - }catch(e){o={}}; - return o; - } - ;(function(){ var u; - function map(v,k){ - if(obj_has(this,k) && u !== this[k]){ return } - this[k] = v; - } - Type.obj.to = Type.obj.to || function(from, to){ DEP('obj.to'); - to = to || {}; - obj_map(from, map, to); - return to; - } - }()); - Type.obj.copy = Type.obj.copy || function(o){ DEP('obj.copy'); // because http://web.archive.org/web/20140328224025/http://jsperf.com/cloning-an-object/2 - return !o? o : JSON.parse(JSON.stringify(o)); // is shockingly faster than anything else, and our data has to be a subset of JSON anyways! - } - ;(function(){ - function empty(v,i){ var n = this.n, u; - if(n && (i === n || (obj_is(n) && obj_has(n, i)))){ return } - if(u !== i){ return true } - } - Type.obj.empty = Type.obj.empty || function(o, n){ DEP('obj.empty'); - if(!o){ return true } - return obj_map(o,empty,{n:n})? false : true; - } - }()); - ;(function(){ - function t(k,v){ - if(2 === arguments.length){ - t.r = t.r || {}; - t.r[k] = v; - return; - } t.r = t.r || []; - t.r.push(k); - }; - var keys = Object.keys, map, u; - Object.keys = Object.keys || function(o){ return map(o, function(v,k,t){t(k)}) } - Type.obj.map = map = Type.obj.map || function(l, c, _){ DEP('obj.map'); - var u, i = 0, x, r, ll, lle, f = 'function' == typeof c; - t.r = u; - if(keys && obj_is(l)){ - ll = keys(l); lle = true; - } - _ = _ || {}; - if(list_is(l) || ll){ - x = (ll || l).length; - for(;i < x; i++){ - var ii = (i + Type.list.index); - if(f){ - r = lle? c.call(_, l[ll[i]], ll[i], t) : c.call(_, l[i], ii, t); - if(r !== u){ return r } - } else { - //if(Type.test.is(c,l[i])){ return ii } // should implement deep equality testing! - if(c === l[lle? ll[i] : i]){ return ll? ll[i] : ii } // use this for now - } - } - } else { - for(i in l){ - if(f){ - if(obj_has(l,i)){ - r = _? c.call(_, l[i], i, t) : c(l[i], i, t); - if(r !== u){ return r } - } - } else { - //if(a.test.is(c,l[i])){ return i } // should implement deep equality testing! - if(c === l[i]){ return i } // use this for now - } - } - } - return f? t.r : Type.list.index? 0 : -1; - } - }()); - Type.time = Type.time || {}; - Type.time.is = Type.time.is || function(t){ DEP('time'); return t? t instanceof Date : (+new Date().getTime()) } - - var fn_is = Type.fn.is; - var list_is = Type.list.is; - var obj = Type.obj, obj_is = obj.is, obj_has = obj.has, obj_map = obj.map; - - var Val = {}; - Val.is = function(v){ DEP('val.is'); // Valid values are a subset of JSON: null, binary, number (!Infinity), text, or a soul relation. Arrays need special algorithms to handle concurrency, so they are not supported directly. Use an extension that supports them if needed but research their problems first. - if(v === u){ return false } - if(v === null){ return true } // "deletes", nulling out keys. - if(v === Infinity){ return false } // we want this to be, but JSON does not support it, sad face. - if(text_is(v) // by "text" we mean strings. - || bi_is(v) // by "binary" we mean boolean. - || num_is(v)){ // by "number" we mean integers or decimals. - return true; // simple values are valid. - } - return Val.link.is(v) || false; // is the value a soul relation? Then it is valid and return it. If not, everything else remaining is an invalid data type. Custom extensions can be built on top of these primitives to support other types. - } - Val.link = Val.rel = {_: '#'}; - ;(function(){ - Val.link.is = function(v){ DEP('val.link.is'); // this defines whether an object is a soul relation or not, they look like this: {'#': 'UUID'} - if(v && v[rel_] && !v._ && obj_is(v)){ // must be an object. - var o = {}; - obj_map(v, map, o); - if(o.id){ // a valid id was found. - return o.id; // yay! Return it. - } - } - return false; // the value was not a valid soul relation. - } - function map(s, k){ var o = this; // map over the object... - if(o.id){ return o.id = false } // if ID is already defined AND we're still looping through the object, it is considered invalid. - if(k == rel_ && text_is(s)){ // the key should be '#' and have a text value. - o.id = s; // we found the soul! - } else { - return o.id = false; // if there exists anything else on the object that isn't the soul, then it is considered invalid. - } - } - }()); - Val.link.ify = function(t){ DEP('val.link.ify'); return obj_put({}, rel_, t) } // convert a soul into a relation and return it. - Type.obj.has._ = '.'; - var rel_ = Val.link._, u; - var bi_is = Type.bi.is; - var num_is = Type.num.is; - var text_is = Type.text.is; - var obj = Type.obj, obj_is = obj.is, obj_put = obj.put, obj_map = obj.map; - - Type.val = Type.val || Val; - - var Node = {_: '_'}; - Node.soul = function(n, o){ DEP('node.soul'); return (n && n._ && n._[o || soul_]) } // convenience function to check to see if there is a soul on a node and return it. - Node.soul.ify = function(n, o){ DEP('node.soul.ify'); // put a soul on an object. - o = (typeof o === 'string')? {soul: o} : o || {}; - n = n || {}; // make sure it exists. - n._ = n._ || {}; // make sure meta exists. - n._[soul_] = o.soul || n._[soul_] || text_random(); // put the soul on it. - return n; - } - Node.soul._ = Val.link._; - ;(function(){ - Node.is = function(n, cb, as){ DEP('node.is'); var s; // checks to see if an object is a valid node. - if(!obj_is(n)){ return false } // must be an object. - if(s = Node.soul(n)){ // must have a soul on it. - return !obj_map(n, map, {as:as,cb:cb,s:s,n:n}); - } - return false; // nope! This was not a valid node. - } - function map(v, k){ // we invert this because the way we check for this is via a negation. - if(k === Node._){ return } // skip over the metadata. - if(!Val.is(v)){ return true } // it is true that this is an invalid node. - if(this.cb){ this.cb.call(this.as, v, k, this.n, this.s) } // optionally callback each key/value. - } - }()); - ;(function(){ - Node.ify = function(obj, o, as){ DEP('node.ify'); // returns a node from a shallow object. - if(!o){ o = {} } - else if(typeof o === 'string'){ o = {soul: o} } - else if('function' == typeof o){ o = {map: o} } - if(o.map){ o.node = o.map.call(as, obj, u, o.node || {}) } - if(o.node = Node.soul.ify(o.node || {}, o)){ - obj_map(obj, map, {o:o,as:as}); - } - return o.node; // This will only be a valid node if the object wasn't already deep! - } - function map(v, k){ var o = this.o, tmp, u; // iterate over each key/value. - if(o.map){ - tmp = o.map.call(this.as, v, ''+k, o.node); - if(u === tmp){ - obj_del(o.node, k); - } else - if(o.node){ o.node[k] = tmp } - return; - } - if(Val.is(v)){ - o.node[k] = v; - } - } - }()); - var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_map = obj.map; - var text = Type.text, text_random = text.random; - var soul_ = Node.soul._; - var u; - Type.node = Type.node || Node; - - var State = Type.state; - State.lex = function(){ DEP('state.lex'); return State().toString(36).replace('.','') } - State.to = function(from, k, to){ DEP('state.to'); - var val = (from||{})[k]; - if(obj_is(val)){ - val = obj_copy(val); - } - return State.ify(to, k, State.is(from, k), val, Node.soul(from)); - } - ;(function(){ - State.map = function(cb, s, as){ DEP('state.map'); var u; // for use with Node.ify - var o = obj_is(o = cb || s)? o : null; - cb = fn_is(cb = cb || s)? cb : null; - if(o && !cb){ - s = num_is(s)? s : State(); - o[N_] = o[N_] || {}; - obj_map(o, map, {o:o,s:s}); - return o; - } - as = as || obj_is(s)? s : u; - s = num_is(s)? s : State(); - return function(v, k, o, opt){ - if(!cb){ - map.call({o: o, s: s}, v,k); - return v; - } - cb.call(as || this || {}, v, k, o, opt); - if(obj_has(o,k) && u === o[k]){ return } - map.call({o: o, s: s}, v,k); - } - } - function map(v,k){ - if(N_ === k){ return } - State.ify(this.o, k, this.s) ; - } - }()); - var obj = Type.obj, obj_as = obj.as, obj_has = obj.has, obj_is = obj.is, obj_map = obj.map, obj_copy = obj.copy; - var num = Type.num, num_is = num.is; - var fn = Type.fn, fn_is = fn.is; - var N_ = Node._, u; - - var Graph = {}; - ;(function(){ - Graph.is = function(g, cb, fn, as){ DEP('graph.is'); // checks to see if an object is a valid graph. - if(!g || !obj_is(g) || obj_empty(g)){ return false } // must be an object. - return !obj_map(g, map, {cb:cb,fn:fn,as:as}); // makes sure it wasn't an empty object. - } - function map(n, s){ // we invert this because the way'? we check for this is via a negation. - if(!n || s !== Node.soul(n) || !Node.is(n, this.fn, this.as)){ return true } // it is true that this is an invalid graph. - if(!this.cb){ return } - nf.n = n; nf.as = this.as; // sequential race conditions aren't races. - this.cb.call(nf.as, n, s, nf); - } - function nf(fn){ // optional callback for each node. - if(fn){ Node.is(nf.n, fn, nf.as) } // where we then have an optional callback for each key/value. - } - }()); - ;(function(){ - Graph.ify = function(obj, env, as){ DEP('graph.ify'); - var at = {path: [], obj: obj}; - if(!env){ - env = {}; - } else - if(typeof env === 'string'){ - env = {soul: env}; - } else - if('function' == typeof env){ - env.map = env; - } - if(typeof as === 'string'){ - env.soul = env.soul || as; - as = u; - } - if(env.soul){ - at.link = Val.link.ify(env.soul); - } - env.shell = (as||{}).shell; - env.graph = env.graph || {}; - env.seen = env.seen || []; - env.as = env.as || as; - node(env, at); - env.root = at.node; - return env.graph; - } - function node(env, at){ var tmp; - if(tmp = seen(env, at)){ return tmp } - at.env = env; - at.soul = soul; - if(Node.ify(at.obj, map, at)){ - at.link = at.link || Val.link.ify(Node.soul(at.node)); - if(at.obj !== env.shell){ - env.graph[Val.link.is(at.link)] = at.node; - } - } - return at; - } - function map(v,k,n){ - var at = this, env = at.env, is, tmp; - if(Node._ === k && obj_has(v,Val.link._)){ - return n._; // TODO: Bug? - } - if(!(is = valid(v,k,n, at,env))){ return } - if(!k){ - at.node = at.node || n || {}; - if(obj_has(v, Node._) && Node.soul(v)){ // ? for safety ? - at.node._ = obj_copy(v._); - } - at.node = Node.soul.ify(at.node, Val.link.is(at.link)); - at.link = at.link || Val.link.ify(Node.soul(at.node)); - } - if(tmp = env.map){ - tmp.call(env.as || {}, v,k,n, at); - if(obj_has(n,k)){ - v = n[k]; - if(u === v){ - obj_del(n, k); - return; - } - if(!(is = valid(v,k,n, at,env))){ return } - } - } - if(!k){ return at.node } - if(true === is){ - return v; - } - tmp = node(env, {obj: v, path: at.path.concat(k)}); - if(!tmp.node){ return } - return tmp.link; //{'#': Node.soul(tmp.node)}; - } - function soul(id){ var at = this; - var prev = Val.link.is(at.link), graph = at.env.graph; - at.link = at.link || Val.link.ify(id); - at.link[Val.link._] = id; - if(at.node && at.node[Node._]){ - at.node[Node._][Val.link._] = id; - } - if(obj_has(graph, prev)){ - graph[id] = graph[prev]; - obj_del(graph, prev); - } - } - function valid(v,k,n, at,env){ var tmp; - if(Val.is(v)){ return true } - if(obj_is(v)){ return 1 } - if(tmp = env.invalid){ - v = tmp.call(env.as || {}, v,k,n); - return valid(v,k,n, at,env); - } - env.err = "Invalid value at '" + at.path.concat(k).join('.') + "'!"; - if(Type.list.is(v)){ env.err += " Use `.set(item)` instead of an Array." } - } - function seen(env, at){ - var arr = env.seen, i = arr.length, has; - while(i--){ has = arr[i]; - if(at.obj === has.obj){ return has } - } - arr.push(at); - } - }()); - Graph.node = function(node){ DEP('graph.node'); - var soul = Node.soul(node); - if(!soul){ return } - return obj_put({}, soul, node); - } - ;(function(){ - Graph.to = function(graph, root, opt){ DEP('graph.to'); - if(!graph){ return } - var obj = {}; - opt = opt || {seen: {}}; - obj_map(graph[root], map, {obj:obj, graph: graph, opt: opt}); - return obj; - } - function map(v,k){ var tmp, obj; - if(Node._ === k){ - if(obj_empty(v, Val.link._)){ - return; - } - this.obj[k] = obj_copy(v); - return; - } - if(!(tmp = Val.link.is(v))){ - this.obj[k] = v; - return; - } - if(obj = this.opt.seen[tmp]){ - this.obj[k] = obj; - return; - } - this.obj[k] = this.opt.seen[tmp] = Graph.to(this.graph, tmp, this.opt); - } - }()); - var fn_is = Type.fn.is; - var obj = Type.obj, obj_is = obj.is, obj_del = obj.del, obj_has = obj.has, obj_empty = obj.empty, obj_put = obj.put, obj_map = obj.map, obj_copy = obj.copy; - var u; - Type.graph = Type.graph || Graph; -}()); \ No newline at end of file diff --git a/assets/static/load.js b/assets/static/load.js deleted file mode 100644 index d888fe3..0000000 --- a/assets/static/load.js +++ /dev/null @@ -1,7 +0,0 @@ -var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); -Gun.chain.open || require('./open'); - -Gun.chain.load = function(cb, opt, at){ - (opt = opt || {}).off = !0; - return this.open(cb, opt, at); -} \ No newline at end of file diff --git a/assets/static/open.js b/assets/static/open.js deleted file mode 100644 index 83ee67f..0000000 --- a/assets/static/open.js +++ /dev/null @@ -1,60 +0,0 @@ -// assets/static/open.js - Deprecated. Part of Gun library, not used after migration to PouchDB. -console.warn('assets/static/open.js is deprecated and unused.'); -var Gun = (typeof window !== "undefined")? window.Gun || {} : {}; - -Gun.chain.open = function(cb, opt, at, depth){ // this is a recursive function, BEWARE! - depth = depth || 1; - opt = opt || {}; // init top level options. - opt.doc = opt.doc || {}; - opt.ids = opt.ids || {}; - opt.any = opt.any || cb; - opt.meta = opt.meta || false; - opt.eve = opt.eve || {off: function(){ // collect all recursive events to unsubscribe to if needed. - Object.keys(opt.eve.s).forEach(function(i,e){ // switch to CPU scheduled setTimeout.each? - if(e = opt.eve.s[i]){ e.off() } - }); - opt.eve.s = {}; - }, s:{}} - return this.on(function(data, key, ctx, eve){ // subscribe to 1 deeper of data! - clearTimeout(opt.to); // do not trigger callback if bunch of changes... - opt.to = setTimeout(function(){ // but schedule the callback to fire soon! - if(!opt.any){ return } - opt.any.call(opt.at.$, opt.doc, opt.key, opt, opt.eve); // call it. - if(opt.off){ // check for unsubscribing. - opt.eve.off(); - opt.any = null; - } - }, opt.wait || 9); - opt.at = opt.at || ctx; // opt.at will always be the first context it finds. - opt.key = opt.key || key; - opt.eve.s[this._.id] = eve; // collect all the events together. - if(true === Gun.valid(data)){ // if primitive value... - if(!at){ - opt.doc = data; - } else { - at[key] = data; - } - return; - } - var tmp = this; // else if a sub-object, CPU schedule loop over properties to do recursion. - setTimeout.each(Object.keys(data), function(key, val){ - if('_' === key && !opt.meta){ return } - val = data[key]; - var doc = at || opt.doc, id; // first pass this becomes the root of open, then at is passed below, and will be the parent for each sub-document/object. - if(!doc){ return } // if no "parent" - if('string' !== typeof (id = Gun.valid(val))){ // if primitive... - doc[key] = val; - return; - } - if(opt.ids[id]){ // if we've already seen this sub-object/document - doc[key] = opt.ids[id]; // link to itself, our already in-memory one, not a new copy. - return; - } - if(opt.depth <= depth){ // stop recursive open at max depth. - doc[key] = doc[key] || val; // show link so app can load it if need. - return; - } // now open up the recursion of sub-documents! - tmp.get(key).open(opt.any, opt, opt.ids[id] = doc[key] = {}, depth+1); // 3rd param is now where we are "at". - }); - }) -} \ No newline at end of file diff --git a/assets/static/path.js b/assets/static/path.js deleted file mode 100644 index 71fd333..0000000 --- a/assets/static/path.js +++ /dev/null @@ -1,31 +0,0 @@ -var Gun = (typeof window !== "undefined")? window.Gun : require('../gun'); - -Gun.chain.path = function(field, opt){ - var back = this, gun = back, tmp; - if(typeof field === 'string'){ - tmp = field.split(opt || '.'); - if(1 === tmp.length){ - gun = back.get(field); - return gun; - } - field = tmp; - } - if(field instanceof Array){ - if(field.length > 1){ - gun = back; - var i = 0, l = field.length; - for(i; i < l; i++){ - //gun = gun.get(field[i], (i+1 === l)? cb : null, opt); - gun = gun.get(field[i]); - } - } else { - gun = back.get(field[0]); - } - return gun; - } - if(!field && 0 != field){ - return back; - } - gun = back.get(''+field); - return gun; -} \ No newline at end of file diff --git a/assets/static/radisk.js b/assets/static/radisk.js deleted file mode 100644 index cdb8e2d..0000000 --- a/assets/static/radisk.js +++ /dev/null @@ -1,606 +0,0 @@ -;(function(){ - - function Radisk(opt){ - - opt = opt || {}; - opt.log = opt.log || console.log; - opt.file = String(opt.file || 'radata'); - var has = (Radisk.has || (Radisk.has = {}))[opt.file]; - if(has){ return has } - - opt.max = opt.max || (opt.memory? (opt.memory * 999 * 999) : 300000000) * 0.3; - opt.until = opt.until || opt.wait || 250; - opt.batch = opt.batch || (10 * 1000); - opt.chunk = opt.chunk || (1024 * 1024 * 1); // 1MB - opt.code = opt.code || {}; - opt.code.from = opt.code.from || '!'; - opt.jsonify = true; - - - function ename(t){ return encodeURIComponent(t).replace(/\*/g, '%2A') } // TODO: Hash this also, but allow migration! - function atomic(v){ return u !== v && (!v || 'object' != typeof v) } - var timediate = (''+u === typeof setImmediate)? setTimeout : setImmediate; - var puff = setTimeout.turn || timediate, u; - var map = Radix.object; - var ST = 0; - - if(!opt.store){ - return opt.log("ERROR: Radisk needs `opt.store` interface with `{get: fn, put: fn (, list: fn)}`!"); - } - if(!opt.store.put){ - return opt.log("ERROR: Radisk needs `store.put` interface with `(file, data, cb)`!"); - } - if(!opt.store.get){ - return opt.log("ERROR: Radisk needs `store.get` interface with `(file, cb)`!"); - } - if(!opt.store.list){ - //opt.log("WARNING: `store.list` interface might be needed!"); - } - - if(''+u != typeof require){ require('./yson') } - var parse = JSON.parseAsync || function(t,cb,r){ var u; try{ cb(u, JSON.parse(t,r)) }catch(e){ cb(e) } } - var json = JSON.stringifyAsync || function(v,cb,r,s){ var u; try{ cb(u, JSON.stringify(v,r,s)) }catch(e){ cb(e) } } - /* - Any and all storage adapters should... - 1. Because writing to disk takes time, we should batch data to disk. This improves performance, and reduces potential disk corruption. - 2. If a batch exceeds a certain number of writes, we should immediately write to disk when physically possible. This caps total performance, but reduces potential loss. - */ - var r = function(key, data, cb, tag, DBG){ - if('function' === typeof data){ - var o = cb || {}; - cb = data; - r.read(key, cb, o, DBG || tag); - return; - } - //var tmp = (tmp = r.batch = r.batch || {})[key] = tmp[key] || {}; - //var tmp = (tmp = r.batch = r.batch || {})[key] = data; - r.save(key, data, cb, tag, DBG); - } - r.save = function(key, data, cb, tag, DBG){ - var s = {key: key}, tags, f, d, q; - s.find = function(file){ var tmp; - s.file = file || (file = opt.code.from); - DBG && (DBG = DBG[file] = DBG[file] || {}); - DBG && (DBG.sf = DBG.sf || +new Date); - //console.only.i && console.log('found', file); - if(tmp = r.disk[file]){ s.mix(u, tmp); return } - r.parse(file, s.mix, u, DBG); - } - s.mix = function(err, disk){ - DBG && (DBG.sml = +new Date); - DBG && (DBG.sm = DBG.sm || +new Date); - if(s.err = err || s.err){ cb(err); return } // TODO: HANDLE BATCH EMIT - var file = s.file = (disk||'').file || s.file, tmp; - if(!disk && file !== opt.code.from){ // corrupt file? - r.find.bad(file); // remove from dir list - r.save(key, data, cb, tag); // try again - return; - } - (disk = r.disk[file] || (r.disk[file] = disk || Radix())).file || (disk.file = file); - if(opt.compare){ - data = opt.compare(disk(key), data, key, file); - if(u === data){ cb(err, -1); return } // TODO: HANDLE BATCH EMIT - } - (s.disk = disk)(key, data); - if(tag){ - (tmp = (tmp = disk.tags || (disk.tags = {}))[tag] || (tmp[tag] = r.tags[tag] || (r.tags[tag] = {})))[file] || (tmp[file] = r.one[tag] || (r.one[tag] = cb)); - cb = null; - } - DBG && (DBG.st = DBG.st || +new Date); - //console.only.i && console.log('mix', disk.Q); - if(disk.Q){ cb && disk.Q.push(cb); return } disk.Q = (cb? [cb] : []); - disk.to = setTimeout(s.write, opt.until); - } - s.write = function(){ - DBG && (DBG.sto = DBG.sto || +new Date); - var file = f = s.file, disk = d = s.disk; - q = s.q = disk.Q; - tags = s.tags = disk.tags; - delete disk.Q; - delete r.disk[file]; - delete disk.tags; - //console.only.i && console.log('write', file, disk, 'was saving:', key, data); - r.write(file, disk, s.ack, u, DBG); - } - s.ack = function(err, ok){ - DBG && (DBG.sa = DBG.sa || +new Date); - DBG && (DBG.sal = q.length); - var ack, tmp; - // TODO!!!! CHANGE THIS INTO PUFF!!!!!!!!!!!!!!!! - for(var id in r.tags){ - if(!r.tags.hasOwnProperty(id)){ continue } var tag = r.tags[id]; - if((tmp = r.disk[f]) && (tmp = tmp.tags) && tmp[tag]){ continue } - ack = tag[f]; - delete tag[f]; - var ne; for(var k in tag){ if(tag.hasOwnProperty(k)){ ne = true; break } } // is not empty? - if(ne){ continue } //if(!obj_empty(tag)){ continue } - delete r.tags[tag]; - ack && ack(err, ok); - } - !q && (q = ''); - var l = q.length, i = 0; - // TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!! - // TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!! - // TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!! - // TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!! - // TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!! - // TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!! - // TODO: PERF: Why is acks so slow, what work do they do??? CHECK THIS!! - var S = +new Date; - for(;i < l; i++){ (ack = q[i]) && ack(err, ok) } - console.STAT && console.STAT(S, +new Date - S, 'rad acks', ename(s.file)); - console.STAT && console.STAT(S, q.length, 'rad acks #', ename(s.file)); - } - cb || (cb = function(err, ok){ // test delete! - if(!err){ return } - }); - //console.only.i && console.log('save', key); - r.find(key, s.find); - } - r.disk = {}; - r.one = {}; - r.tags = {}; - - /* - Any storage engine at some point will have to do a read in order to write. - This is true of even systems that use an append only log, if they support updates. - Therefore it is unavoidable that a read will have to happen, - the question is just how long you delay it. - */ - var RWC = 0; - r.write = function(file, rad, cb, o, DBG){ - if(!rad){ cb('No radix!'); return } - o = ('object' == typeof o)? o : {force: o}; - var f = function Fractal(){}, a, b; - f.text = ''; - f.file = file = rad.file || (rad.file = file); - if(!file){ cb('What file?'); return } - f.write = function(){ - var text = rad.raw = f.text; - r.disk[file = rad.file || f.file || file] = rad; - var S = +new Date; - DBG && (DBG.wd = S); - //console.only.i && console.log('add', file); - r.find.add(file, function add(err){ - DBG && (DBG.wa = +new Date); - if(err){ cb(err); return } - //console.only.i && console.log('disk', file, text); - opt.store.put(ename(file), text, function safe(err, ok){ - DBG && (DBG.wp = +new Date); - console.STAT && console.STAT(S, ST = +new Date - S, "wrote disk", JSON.stringify(file), ++RWC, 'total all writes.'); - //console.only.i && console.log('done', err, ok || 1, cb); - cb(err, ok || 1); - if(!rad.Q){ delete r.disk[file] } // VERY IMPORTANT! Clean up memory, but not if there is already queued writes on it! - }); - }); - } - f.split = function(){ - var S = +new Date; - DBG && (DBG.wf = S); - f.text = ''; - if(!f.count){ f.count = 0; - Radix.map(rad, function count(){ f.count++ }); // TODO: Perf? Any faster way to get total length? - } - DBG && (DBG.wfc = f.count); - f.limit = Math.ceil(f.count/2); - var SC = f.count; - f.count = 0; - DBG && (DBG.wf1 = +new Date); - f.sub = Radix(); - Radix.map(rad, f.slice, {reverse: 1}); // IMPORTANT: DO THIS IN REVERSE, SO LAST HALF OF DATA MOVED TO NEW FILE BEFORE DROPPING FROM CURRENT FILE. - DBG && (DBG.wf2 = +new Date); - r.write(f.end, f.sub, f.both, o); - DBG && (DBG.wf3 = +new Date); - f.hub = Radix(); - Radix.map(rad, f.stop); - DBG && (DBG.wf4 = +new Date); - r.write(rad.file, f.hub, f.both, o); - DBG && (DBG.wf5 = +new Date); - console.STAT && console.STAT(S, +new Date - S, "rad split", ename(rad.file), SC); - return true; - } - f.slice = function(val, key){ - f.sub(f.end = key, val); - if(f.limit <= (++f.count)){ return true } - } - f.stop = function(val, key){ - if(key >= f.end){ return true } - f.hub(key, val); - } - f.both = function(err, ok){ - DBG && (DBG.wfd = +new Date); - if(b){ cb(err || b); return } - if(a){ cb(err, ok); return } - a = true; - b = err; - } - f.each = function(val, key, k, pre){ - if(u !== val){ f.count++ } - if(opt.max <= (val||'').length){ return cb("Data too big!"), true } - var enc = Radisk.encode(pre.length) +'#'+ Radisk.encode(k) + (u === val? '' : ':'+ Radisk.encode(val)) +'\n'; - if((opt.chunk < f.text.length + enc.length) && (1 < f.count) && !o.force){ - return f.split(); - } - f.text += enc; - } - //console.only.i && console.log('writing'); - if(opt.jsonify){ r.write.jsonify(f, rad, cb, o, DBG); return } // temporary testing idea - if(!Radix.map(rad, f.each, true)){ f.write() } - } - - r.write.jsonify = function(f, rad, cb, o, DBG){ - var raw; - var S = +new Date; - DBG && (DBG.w = S); - try{raw = JSON.stringify(rad.$); - }catch(e){ cb("Cannot radisk!"); return } - DBG && (DBG.ws = +new Date); - console.STAT && console.STAT(S, +new Date - S, "rad stringified JSON"); - if(opt.chunk < raw.length && !o.force){ - var c = 0; - Radix.map(rad, function(){ - if(c++){ return true } // more than 1 item - }); - if(c > 1){ - return f.split(); - } - } - f.text = raw; - f.write(); - } - - r.range = function(tree, o){ - if(!tree || !o){ return } - if(u === o.start && u === o.end){ return tree } - if(atomic(tree)){ return tree } - var sub = Radix(); - Radix.map(tree, function(v,k){ sub(k,v) }, o); // ONLY PLACE THAT TAKES TREE, maybe reduce API for better perf? - return sub(''); - } - - ;(function(){ - r.read = function(key, cb, o, DBG){ - o = o || {}; - var g = {key: key}; - g.find = function(file){ var tmp; - g.file = file || (file = opt.code.from); - DBG && (DBG = DBG[file] = DBG[file] || {}); - DBG && (DBG.rf = DBG.rf || +new Date); - if(tmp = r.disk[g.file = file]){ g.check(u, tmp); return } - r.parse(file, g.check, u, DBG); - } - g.get = function(err, disk, info){ - DBG && (DBG.rgl = +new Date); - DBG && (DBG.rg = DBG.rg || +new Date); - if(g.err = err || g.err){ cb(err); return } - var file = g.file = (disk||'').file || g.file; - if(!disk && file !== opt.code.from){ // corrupt file? - r.find.bad(file); // remove from dir list - r.read(key, cb, o); // try again - return; - } - disk = r.disk[file] || (r.disk[file] = disk); - if(!disk){ cb(file === opt.code.from? u : "No file!"); return } - disk.file || (disk.file = file); - var data = r.range(disk(key), o); - DBG && (DBG.rr = +new Date); - o.unit = disk.unit; - o.chunks = (o.chunks || 0) + 1; - o.parsed = (o.parsed || 0) + ((info||'').parsed||(o.chunks*opt.chunk)); - o.more = 1; - o.next = u; - Radix.map(r.list, function next(v,f){ - if(!v || file === f){ return } - o.next = f; - return 1; - }, o.reverse? {reverse: 1, end: file} : {start: file}); - DBG && (DBG.rl = +new Date); - if(!o.next){ o.more = 0 } - if(o.next){ - if(!o.reverse && ((key < o.next && 0 != o.next.indexOf(key)) || (u !== o.end && (o.end || '\uffff') < o.next))){ o.more = 0 } - if(o.reverse && ((key > o.next && 0 != key.indexOf(o.next)) || ((u !== o.start && (o.start || '') > o.next && file <= o.start)))){ o.more = 0 } - } - //console.log(5, process.memoryUsage().heapUsed); - if(!o.more){ cb(g.err, data, o); return } - if(data){ cb(g.err, data, o) } - if(o.parsed >= o.limit){ return } - var S = +new Date; - DBG && (DBG.rm = S); - var next = o.next; - timediate(function(){ - console.STAT && console.STAT(S, +new Date - S, 'rad more'); - r.parse(next, g.check); - },0); - } - g.check = function(err, disk, info){ - //console.log(4, process.memoryUsage().heapUsed); - g.get(err, disk, info); - if(!disk || disk.check){ return } disk.check = 1; - var S = +new Date; - (info || (info = {})).file || (info.file = g.file); - Radix.map(disk, function(val, key){ - // assume in memory for now, since both write/read already call r.find which will init it. - r.find(key, function(file){ - if((file || (file = opt.code.from)) === info.file){ return } - var id = (''+Math.random()).slice(-3); - puff(function(){ - r.save(key, val, function ack(err, ok){ - if(err){ r.save(key, val, ack); return } // ad infinitum??? - // TODO: NOTE!!! Mislocated data could be because of a synchronous `put` from the `g.get(` other than perf shouldn't we do the check first before acking? - console.STAT && console.STAT("MISLOCATED DATA CORRECTED", id, ename(key), ename(info.file), ename(file)); - }); - },0); - }) - }); - console.STAT && console.STAT(S, +new Date - S, "rad check"); - } - r.find(key || (o.reverse? (o.end||'') : (o.start||'')), g.find); - } - function rev(a,b){ return b } - var revo = {reverse: true}; - }()); - - ;(function(){ - /* - Let us start by assuming we are the only process that is - changing the directory or bucket. Not because we do not want - to be multi-process/machine, but because we want to experiment - with how much performance and scale we can get out of only one. - Then we can work on the harder problem of being multi-process. - */ - var RPC = 0; - var Q = {}, s = String.fromCharCode(31); - r.parse = function(file, cb, raw, DBG){ var q; - if(!file){ return cb(); } - if(q = Q[file]){ q.push(cb); return } q = Q[file] = [cb]; - var p = function Parse(){}, info = {file: file}; - (p.disk = Radix()).file = file; - p.read = function(err, data){ var tmp; - DBG && (DBG.rpg = +new Date); - console.STAT && console.STAT(S, +new Date - S, 'read disk', JSON.stringify(file), ++RPC, 'total all parses.'); - //console.log(2, process.memoryUsage().heapUsed); - if((p.err = err) || (p.not = !data)){ - delete Q[file]; - p.map(q, p.ack); - return; - } - if('string' !== typeof data){ - try{ - if(opt.max <= data.length){ - p.err = "Chunk too big!"; - } else { - data = data.toString(); // If it crashes, it crashes here. How!?? We check size first! - } - }catch(e){ p.err = e } - if(p.err){ - delete Q[file]; - p.map(q, p.ack); - return; - } - } - info.parsed = data.length; - DBG && (DBG.rpl = info.parsed); - DBG && (DBG.rpa = q.length); - S = +new Date; - if(!(opt.jsonify || '{' === data[0])){ - p.radec(err, data); - return; - } - parse(data, function(err, tree){ - //console.log(3, process.memoryUsage().heapUsed); - if(!err){ - delete Q[file]; - p.disk.$ = tree; - console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'rad parsed JSON'); - DBG && (DBG.rpd = +new Date); - p.map(q, p.ack); // hmmm, v8 profiler can't see into this cause of try/catch? - return; - } - if('{' === data[0]){ - delete Q[file]; - p.err = tmp || "JSON error!"; - p.map(q, p.ack); - return; - } - p.radec(err, data); - }); - } - p.map = function(){ // switch to setTimeout.each now? - if(!q || !q.length){ return } - //var i = 0, l = q.length, ack; - var S = +new Date; - var err = p.err, data = p.not? u : p.disk; - var i = 0, ack; while(i < 9 && (ack = q[i++])){ ack(err, data, info) } // too much? - console.STAT && console.STAT(S, +new Date - S, 'rad packs', ename(file)); - console.STAT && console.STAT(S, i, 'rad packs #', ename(file)); - if(!(q = q.slice(i)).length){ return } - puff(p.map, 0); - } - p.ack = function(cb){ - if(!cb){ return } - if(p.err || p.not){ - cb(p.err, u, info); - return; - } - cb(u, p.disk, info); - } - p.radec = function(err, data){ - delete Q[file]; - S = +new Date; - var tmp = p.split(data), pre = [], i, k, v; - if(!tmp || 0 !== tmp[1]){ - p.err = "File '"+file+"' does not have root radix! "; - p.map(q, p.ack); - return; - } - while(tmp){ - k = v = u; - i = tmp[1]; - tmp = p.split(tmp[2])||''; - if('#' == tmp[0]){ - k = tmp[1]; - pre = pre.slice(0,i); - if(i <= pre.length){ - pre.push(k); - } - } - tmp = p.split(tmp[2])||''; - if('\n' == tmp[0]){ continue } - if('=' == tmp[0] || ':' == tmp[0]){ v = tmp[1] } - if(u !== k && u !== v){ p.disk(pre.join(''), v) } - tmp = p.split(tmp[2]); - } - console.STAT && console.STAT(S, +new Date - S, 'parsed RAD'); - p.map(q, p.ack); - }; - p.split = function(t){ - if(!t){ return } - var l = [], o = {}, i = -1, a = '', b, c; - i = t.indexOf(s); - if(!t[i]){ return } - a = t.slice(0, i); - l[0] = a; - l[1] = b = Radisk.decode(t.slice(i), o); - l[2] = t.slice(i + o.i); - return l; - } - if(r.disk){ raw || (raw = (r.disk[file]||'').raw) } - var S = +new Date, SM, SL; - DBG && (DBG.rp = S); - if(raw){ return puff(function(){ p.read(u, raw) }, 0) } - opt.store.get(ename(file), p.read); - // TODO: What if memory disk gets filled with updates, and we get an old one back? - } - }()); - - ;(function(){ - var dir, f = String.fromCharCode(28), Q; - r.find = function(key, cb){ - if(!dir){ - if(Q){ Q.push([key, cb]); return } Q = [[key, cb]]; - r.parse(f, init); - return; - } - Radix.map(r.list = dir, function(val, key){ - if(!val){ return } - return cb(key) || true; - }, {reverse: 1, end: key}) || cb(opt.code.from); - } - r.find.add = function(file, cb){ - var has = dir(file); - if(has || file === f){ cb(u, 1); return } - dir(file, 1); - cb.found = (cb.found || 0) + 1; - r.write(f, dir, function(err, ok){ - if(err){ cb(err); return } - cb.found = (cb.found || 0) - 1; - if(0 !== cb.found){ return } - cb(u, 1); - }, true); - } - r.find.bad = function(file, cb){ - dir(file, 0); - r.write(f, dir, cb||noop); - } - function init(err, disk){ - if(err){ - opt.log('list', err); - setTimeout(function(){ r.parse(f, init) }, 1000); - return; - } - if(disk){ drain(disk); return } - dir = dir || disk || Radix(); - if(!opt.store.list){ drain(dir); return } - // import directory. - opt.store.list(function(file){ - if(!file){ drain(dir); return } - r.find.add(file, noop); - }); - } - function drain(rad, tmp){ - dir = dir || rad; - dir.file = f; - tmp = Q; Q = null; - map(tmp, function(arg){ - r.find(arg[0], arg[1]); - }); - } - }()); - - try{ !Gun.window && require('./radmigtmp')(r) }catch(e){} - - var noop = function(){}, RAD, u; - Radisk.has[opt.file] = r; - return r; - } - - ;(function(){ - var _ = String.fromCharCode(31), u; - Radisk.encode = function(d, o, s){ s = s || _; - var t = s, tmp; - if(typeof d == 'string'){ - var i = d.indexOf(s); - while(i != -1){ t += s; i = d.indexOf(s, i+1) } - return t + '"' + d + s; - } else - if(d && d['#'] && 1 == Object.keys(d).length){ - return t + '#' + tmp + t; - } else - if('number' == typeof d){ - return t + '+' + (d||0) + t; - } else - if(null === d){ - return t + ' ' + t; - } else - if(true === d){ - return t + '+' + t; - } else - if(false === d){ - return t + '-' + t; - }// else - //if(binary){} - } - Radisk.decode = function(t, o, s){ s = s || _; - var d = '', i = -1, n = 0, c, p; - if(s !== t[0]){ return } - while(s === t[++i]){ ++n } - p = t[c = n] || true; - while(--n >= 0){ i = t.indexOf(s, i+1) } - if(i == -1){ i = t.length } - d = t.slice(c+1, i); - if(o){ o.i = i+1 } - if('"' === p){ - return d; - } else - if('#' === p){ - return {'#':d}; - } else - if('+' === p){ - if(0 === d.length){ - return true; - } - return parseFloat(d); - } else - if(' ' === p){ - return null; - } else - if('-' === p){ - return false; - } - } - }()); - - if(typeof window !== "undefined"){ - var Gun = window.Gun; - var Radix = window.Radix; - window.Radisk = Radisk; - } else { - var Gun = require('../gun'); - var Radix = require('./radix'); - //var Radix = require('./radix2'); Radisk = require('./radisk2'); - try{ module.exports = Radisk }catch(e){} - } - - Radisk.Radix = Radix; - -}()); \ No newline at end of file diff --git a/assets/static/radix.js b/assets/static/radix.js deleted file mode 100644 index e60789e..0000000 --- a/assets/static/radix.js +++ /dev/null @@ -1,124 +0,0 @@ -;(function(){ - - function Radix(){ - var radix = function(key, val, t){ - radix.unit = 0; - if(!t && u !== val){ - radix.last = (''+key < radix.last)? radix.last : ''+key; - delete (radix.$||{})[_]; - } - t = t || radix.$ || (radix.$ = {}); - if(!key && Object.keys(t).length){ return t } - key = ''+key; - var i = 0, l = key.length-1, k = key[i], at, tmp; - while(!(at = t[k]) && i < l){ - k += key[++i]; - } - if(!at){ - if(!each(t, function(r, s){ - var ii = 0, kk = ''; - if((s||'').length){ while(s[ii] == key[ii]){ - kk += s[ii++]; - } } - if(kk){ - if(u === val){ - if(ii <= l){ return } - (tmp || (tmp = {}))[s.slice(ii)] = r; - //(tmp[_] = function $(){ $.sort = Object.keys(tmp).sort(); return $ }()); // get rid of this one, cause it is on read? - return r; - } - var __ = {}; - __[s.slice(ii)] = r; - ii = key.slice(ii); - ('' === ii)? (__[''] = val) : ((__[ii] = {})[''] = val); - //(__[_] = function $(){ $.sort = Object.keys(__).sort(); return $ }()); - t[kk] = __; - if(Radix.debug && 'undefined' === ''+kk){ console.log(0, kk); debugger } - delete t[s]; - //(t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()); - return true; - } - })){ - if(u === val){ return; } - (t[k] || (t[k] = {}))[''] = val; - if(Radix.debug && 'undefined' === ''+k){ console.log(1, k); debugger } - //(t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()); - } - if(u === val){ - return tmp; - } - } else - if(i == l){ - //if(u === val){ return (u === (tmp = at['']))? at : tmp } // THIS CODE IS CORRECT, below is - if(u === val){ return (u === (tmp = at['']))? at : ((radix.unit = 1) && tmp) } // temporary help?? - at[''] = val; - //(at[_] = function $(){ $.sort = Object.keys(at).sort(); return $ }()); - } else { - if(u !== val){ delete at[_] } - //at && (at[_] = function $(){ $.sort = Object.keys(at).sort(); return $ }()); - return radix(key.slice(++i), val, at || (at = {})); - } - } - return radix; - }; - - Radix.map = function rap(radix, cb, opt, pre){ - try { - pre = pre || []; // TODO: BUG: most out-of-memory crashes come from here. - var t = ('function' == typeof radix)? radix.$ || {} : radix; - //!opt && console.log("WHAT IS T?", JSON.stringify(t).length); - if(!t){ return } - if('string' == typeof t){ if(Radix.debug){ throw ['BUG:', radix, cb, opt, pre] } return; } - var keys = (t[_]||no).sort || (t[_] = function $(){ $.sort = Object.keys(t).sort(); return $ }()).sort, rev; // ONLY 17% of ops are pre-sorted! - //var keys = Object.keys(t).sort(); - opt = (true === opt)? {branch: true} : (opt || {}); - if(rev = opt.reverse){ keys = keys.slice(0).reverse() } - var start = opt.start, end = opt.end, END = '\uffff'; - var i = 0, l = keys.length; - for(;i < l; i++){ var key = keys[i], tree = t[key], tmp, p, pt; - if(!tree || '' === key || _ === key || 'undefined' === key){ continue } - p = pre.slice(0); p.push(key); - pt = p.join(''); - if(u !== start && pt < (start||'').slice(0,pt.length)){ continue } - if(u !== end && (end || END) < pt){ continue } - if(rev){ // children must be checked first when going in reverse. - tmp = rap(tree, cb, opt, p); - if(u !== tmp){ return tmp } - } - if(u !== (tmp = tree[''])){ - var yes = 1; - if(u !== start && pt < (start||'')){ yes = 0 } - if(u !== end && pt > (end || END)){ yes = 0 } - if(yes){ - tmp = cb(tmp, pt, key, pre); - if(u !== tmp){ return tmp } - } - } else - if(opt.branch){ - tmp = cb(u, pt, key, pre); - if(u !== tmp){ return tmp } - } - pre = p; - if(!rev){ - tmp = rap(tree, cb, opt, pre); - if(u !== tmp){ return tmp } - } - pre.pop(); - } - } catch (e) { console.error(e); } - }; - - if(typeof window !== "undefined"){ - window.Radix = Radix; - } else { - try{ module.exports = Radix }catch(e){} - } - var each = Radix.object = function(o, f, r){ - for(var k in o){ - if(!o.hasOwnProperty(k)){ continue } - if((r = f(o[k], k)) !== u){ return r } - } - }, no = {}, u; - var _ = String.fromCharCode(24); - -}()); diff --git a/assets/static/rindexed.js b/assets/static/rindexed.js deleted file mode 100644 index b49673c..0000000 --- a/assets/static/rindexed.js +++ /dev/null @@ -1,79 +0,0 @@ -;(function(){ -/* // from @jabis -if (navigator.storage && navigator.storage.estimate) { - const quota = await navigator.storage.estimate(); - // quota.usage -> Number of bytes used. - // quota.quota -> Maximum number of bytes available. - const percentageUsed = (quota.usage / quota.quota) * 100; - console.log(`You've used ${percentageUsed}% of the available storage.`); - const remaining = quota.quota - quota.usage; - console.log(`You can write up to ${remaining} more bytes.`); -} -*/ - function Store(opt){ - opt = opt || {}; - opt.file = String(opt.file || 'radata'); - var store = Store[opt.file], db = null, u; - - if(store){ - console.log("Warning: reusing same IndexedDB store and options as 1st."); - return Store[opt.file]; - } - store = Store[opt.file] = function(){}; - - try{opt.indexedDB = opt.indexedDB || Store.indexedDB || indexedDB}catch(e){} - try{if(!opt.indexedDB || 'file:' == location.protocol){ - var s = store.d || (store.d = {}); - store.put = function(f, d, cb){ s[f] = d; setTimeout(function(){ cb(null, 1) },250) }; - store.get = function(f, cb){ setTimeout(function(){ cb(null, s[f] || u) },5) }; - console.log('Warning: No indexedDB exists to persist data to!'); - return store; - }}catch(e){} - - - store.start = function(){ - var o = indexedDB.open(opt.file, 1); - o.onupgradeneeded = function(eve){ (eve.target.result).createObjectStore(opt.file) } - o.onsuccess = function(){ db = o.result } - o.onerror = function(eve){ console.log(eve||1); } - }; store.start(); - - store.put = function(key, data, cb){ - if(!db){ setTimeout(function(){ store.put(key, data, cb) },1); return } - var tx = db.transaction([opt.file], 'readwrite'); - var obj = tx.objectStore(opt.file); - var req = obj.put(data, ''+key); - req.onsuccess = obj.onsuccess = tx.onsuccess = function(){ cb(null, 1) } - req.onabort = obj.onabort = tx.onabort = function(eve){ cb(eve||'put.tx.abort') } - req.onerror = obj.onerror = tx.onerror = function(eve){ cb(eve||'put.tx.error') } - } - - store.get = function(key, cb){ - if(!db){ setTimeout(function(){ store.get(key, cb) },9); return } - var tx = db.transaction([opt.file], 'readonly'); - var obj = tx.objectStore(opt.file); - var req = obj.get(''+key); - req.onsuccess = function(){ cb(null, req.result) } - req.onabort = function(eve){ cb(eve||4) } - req.onerror = function(eve){ cb(eve||5) } - } - setInterval(function(){ db && db.close(); db = null; store.start() }, 1000 * 15); // reset webkit bug? - return store; - } - - if(typeof window !== "undefined"){ - (Store.window = window).RindexedDB = Store; - Store.indexedDB = window.indexedDB; // safari bug - } else { - try{ module.exports = Store }catch(e){} - } - - try{ - var Gun = Store.window.Gun || require('../gun'); - Gun.on('create', function(root){ - this.to.next(root); - root.opt.store = root.opt.store || Store(root.opt); - }); - }catch(e){} - -}()); \ No newline at end of file diff --git a/assets/static/sea.js b/assets/static/sea.js deleted file mode 100644 index 1010a2b..0000000 --- a/assets/static/sea.js +++ /dev/null @@ -1,1537 +0,0 @@ -// assets/static/sea.js - Deprecated. Replaced by CryptoJS AES usage in app_modules.js. -console.warn('assets/static/sea.js is deprecated and unused.'); - - function USE(arg, req){ - return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){ - arg(mod = {exports: {}}); - USE[R(path)] = mod.exports; - } - function R(p){ - return p.split('/').slice(-1).toString().replace('.js',''); - } - } - if(typeof module !== "undefined"){ var MODULE = module } - /* UNBUILD */ - - ;USE(function(module){ - // Security, Encryption, and Authorization: SEA.js - // MANDATORY READING: https://gun.eco/explainers/data/security.html - // IT IS IMPLEMENTED IN A POLYFILL/SHIM APPROACH. - // THIS IS AN EARLY ALPHA! - - if(typeof self !== "undefined"){ module.window = self } // should be safe for at least browser/worker/nodejs, need to check other envs like RN etc. - if(typeof window !== "undefined"){ module.window = window } - - var tmp = module.window || module, u; - var SEA = tmp.SEA || {}; - - if(SEA.window = module.window){ SEA.window.SEA = SEA } - - try{ if(u+'' !== typeof MODULE){ MODULE.exports = SEA } }catch(e){} - module.exports = SEA; - })(USE, './root'); - - ;USE(function(module){ - var SEA = USE('./root'); - try{ if(SEA.window){ - if(location.protocol.indexOf('s') < 0 - && location.host.indexOf('localhost') < 0 - && ! /^127\.\d+\.\d+\.\d+$/.test(location.hostname) - && location.protocol.indexOf('file:') < 0){ - console.warn('HTTPS needed for WebCrypto in SEA, redirecting...'); - location.protocol = 'https:'; // WebCrypto does NOT work without HTTPS! - } - } }catch(e){} - })(USE, './https'); - - ;USE(function(module){ - var u; - if(u+''== typeof btoa){ - if(u+'' == typeof Buffer){ - try{ global.Buffer = USE("buffer", 1).Buffer }catch(e){ console.log("Please `npm install buffer` or add it to your package.json !") } - } - global.btoa = function(data){ return Buffer.from(data, "binary").toString("base64") }; - global.atob = function(data){ return Buffer.from(data, "base64").toString("binary") }; - } - })(USE, './base64'); - - ;USE(function(module){ - USE('./base64'); - // This is Array extended to have .toString(['utf8'|'hex'|'base64']) - function SeaArray() {} - Object.assign(SeaArray, { from: Array.from }) - SeaArray.prototype = Object.create(Array.prototype) - SeaArray.prototype.toString = function(enc, start, end) { enc = enc || 'utf8'; start = start || 0; - const length = this.length - if (enc === 'hex') { - const buf = new Uint8Array(this) - return [ ...Array(((end && (end + 1)) || length) - start).keys()] - .map((i) => buf[ i + start ].toString(16).padStart(2, '0')).join('') - } - if (enc === 'utf8') { - return Array.from( - { length: (end || length) - start }, - (_, i) => String.fromCharCode(this[ i + start]) - ).join('') - } - if (enc === 'base64') { - return btoa(this) - } - } - module.exports = SeaArray; - })(USE, './array'); - - ;USE(function(module){ - USE('./base64'); - // This is Buffer implementation used in SEA. Functionality is mostly - // compatible with NodeJS 'safe-buffer' and is used for encoding conversions - // between binary and 'hex' | 'utf8' | 'base64' - // See documentation and validation for safe implementation in: - // https://github.com/feross/safe-buffer#update - var SeaArray = USE('./array'); - function SafeBuffer(...props) { - console.warn('new SafeBuffer() is depreciated, please use SafeBuffer.from()') - return SafeBuffer.from(...props) - } - SafeBuffer.prototype = Object.create(Array.prototype) - Object.assign(SafeBuffer, { - // (data, enc) where typeof data === 'string' then enc === 'utf8'|'hex'|'base64' - from() { - if (!Object.keys(arguments).length || arguments[0]==null) { - throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') - } - const input = arguments[0] - let buf - if (typeof input === 'string') { - const enc = arguments[1] || 'utf8' - if (enc === 'hex') { - const bytes = input.match(/([\da-fA-F]{2})/g) - .map((byte) => parseInt(byte, 16)) - if (!bytes || !bytes.length) { - throw new TypeError('Invalid first argument for type \'hex\'.') - } - buf = SeaArray.from(bytes) - } else if (enc === 'utf8' || 'binary' === enc) { // EDIT BY MARK: I think this is safe, tested it against a couple "binary" strings. This lets SafeBuffer match NodeJS Buffer behavior more where it safely btoas regular strings. - const length = input.length - const words = new Uint16Array(length) - Array.from({ length: length }, (_, i) => words[i] = input.charCodeAt(i)) - buf = SeaArray.from(words) - } else if (enc === 'base64') { - const dec = atob(input) - const length = dec.length - const bytes = new Uint8Array(length) - Array.from({ length: length }, (_, i) => bytes[i] = dec.charCodeAt(i)) - buf = SeaArray.from(bytes) - } else if (enc === 'binary') { // deprecated by above comment - buf = SeaArray.from(input) // some btoas were mishandled. - } else { - console.info('SafeBuffer.from unknown encoding: '+enc) - } - return buf - } - const byteLength = input.byteLength // what is going on here? FOR MARTTI - const length = input.byteLength ? input.byteLength : input.length - if (length) { - let buf - if (input instanceof ArrayBuffer) { - buf = new Uint8Array(input) - } - return SeaArray.from(buf || input) - } - }, - // This is 'safe-buffer.alloc' sans encoding support - alloc(length, fill = 0 /*, enc*/ ) { - return SeaArray.from(new Uint8Array(Array.from({ length: length }, () => fill))) - }, - // This is normal UNSAFE 'buffer.alloc' or 'new Buffer(length)' - don't use! - allocUnsafe(length) { - return SeaArray.from(new Uint8Array(Array.from({ length : length }))) - }, - // This puts together array of array like members - concat(arr) { // octet array - if (!Array.isArray(arr)) { - throw new TypeError('First argument must be Array containing ArrayBuffer or Uint8Array instances.') - } - return SeaArray.from(arr.reduce((ret, item) => ret.concat(Array.from(item)), [])) - } - }) - SafeBuffer.prototype.from = SafeBuffer.from - SafeBuffer.prototype.toString = SeaArray.prototype.toString - - module.exports = SafeBuffer; - })(USE, './buffer'); - - ;USE(function(module){ - const SEA = USE('./root') - const api = {Buffer: USE('./buffer')} - var o = {}, u; - - // ideally we can move away from JSON entirely? unlikely due to compatibility issues... oh well. - JSON.parseAsync = JSON.parseAsync || function(t,cb,r){ var u; try{ cb(u, JSON.parse(t,r)) }catch(e){ cb(e) } } - JSON.stringifyAsync = JSON.stringifyAsync || function(v,cb,r,s){ var u; try{ cb(u, JSON.stringify(v,r,s)) }catch(e){ cb(e) } } - - api.parse = function(t,r){ return new Promise(function(res, rej){ - JSON.parseAsync(t,function(err, raw){ err? rej(err) : res(raw) },r); - })} - api.stringify = function(v,r,s){ return new Promise(function(res, rej){ - JSON.stringifyAsync(v,function(err, raw){ err? rej(err) : res(raw) },r,s); - })} - - if(SEA.window){ - api.crypto = SEA.window.crypto || SEA.window.msCrypto - api.subtle = (api.crypto||o).subtle || (api.crypto||o).webkitSubtle; - api.TextEncoder = SEA.window.TextEncoder; - api.TextDecoder = SEA.window.TextDecoder; - api.random = (len) => api.Buffer.from(api.crypto.getRandomValues(new Uint8Array(api.Buffer.alloc(len)))); - } - if(!api.TextDecoder) - { - const { TextEncoder, TextDecoder } = USE((u+'' == typeof MODULE?'.':'')+'./lib/text-encoding', 1); - api.TextDecoder = TextDecoder; - api.TextEncoder = TextEncoder; - } - if(!api.crypto) - { - try - { - var crypto = USE('crypto', 1); - Object.assign(api, { - crypto, - random: (len) => api.Buffer.from(crypto.randomBytes(len)) - }); - const { Crypto: WebCrypto } = USE('@peculiar/webcrypto', 1); - api.ossl = api.subtle = new WebCrypto({directory: 'ossl'}).subtle // ECDH - } - catch(e){ - console.log("Please `npm install @peculiar/webcrypto` or add it to your package.json !"); - }} - - module.exports = api - })(USE, './shim'); - - ;USE(function(module){ - var SEA = USE('./root'); - var shim = USE('./shim'); - var s = {}; - s.pbkdf2 = {hash: {name : 'SHA-256'}, iter: 100000, ks: 64}; - s.ecdsa = { - pair: {name: 'ECDSA', namedCurve: 'P-256'}, - sign: {name: 'ECDSA', hash: {name: 'SHA-256'}} - }; - s.ecdh = {name: 'ECDH', namedCurve: 'P-256'}; - - // This creates Web Cryptography API compliant JWK for sign/verify purposes - s.jwk = function(pub, d){ // d === priv - pub = pub.split('.'); - var x = pub[0], y = pub[1]; - var jwk = {kty: "EC", crv: "P-256", x: x, y: y, ext: true}; - jwk.key_ops = d ? ['sign'] : ['verify']; - if(d){ jwk.d = d } - return jwk; - }; - - s.keyToJwk = function(keyBytes) { - const keyB64 = keyBytes.toString('base64'); - const k = keyB64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=/g, ''); - return { kty: 'oct', k: k, ext: false, alg: 'A256GCM' }; - } - - s.recall = { - validity: 12 * 60 * 60, // internally in seconds : 12 hours - hook: function(props){ return props } // { iat, exp, alias, remember } // or return new Promise((resolve, reject) => resolve(props) - }; - - s.check = function(t){ return (typeof t == 'string') && ('SEA{' === t.slice(0,4)) } - s.parse = async function p(t){ try { - var yes = (typeof t == 'string'); - if(yes && 'SEA{' === t.slice(0,4)){ t = t.slice(3) } - return yes ? await shim.parse(t) : t; - } catch (e) {} - return t; - } - - SEA.opt = s; - module.exports = s - })(USE, './settings'); - - ;USE(function(module){ - var shim = USE('./shim'); - module.exports = async function(d, o){ - var t = (typeof d == 'string')? d : await shim.stringify(d); - var hash = await shim.subtle.digest({name: o||'SHA-256'}, new shim.TextEncoder().encode(t)); - return shim.Buffer.from(hash); - } - })(USE, './sha256'); - - ;USE(function(module){ - // This internal func returns SHA-1 hashed data for KeyID generation - const __shim = USE('./shim') - const subtle = __shim.subtle - const ossl = __shim.ossl ? __shim.ossl : subtle - const sha1hash = (b) => ossl.digest({name: 'SHA-1'}, new ArrayBuffer(b)) - module.exports = sha1hash - })(USE, './sha1'); - - ;USE(function(module){ - var SEA = USE('./root'); - var shim = USE('./shim'); - var S = USE('./settings'); - var sha = USE('./sha256'); - var u; - - SEA.work = SEA.work || (async (data, pair, cb, opt) => { try { // used to be named `proof` - var salt = (pair||{}).epub || pair; // epub not recommended, salt should be random! - opt = opt || {}; - if(salt instanceof Function){ - cb = salt; - salt = u; - } - data = (typeof data == 'string')? data : await shim.stringify(data); - if('sha' === (opt.name||'').toLowerCase().slice(0,3)){ - var rsha = shim.Buffer.from(await sha(data, opt.name), 'binary').toString(opt.encode || 'base64') - if(cb){ try{ cb(rsha) }catch(e){console.log(e)} } - return rsha; - } - salt = salt || shim.random(9); - var key = await (shim.ossl || shim.subtle).importKey('raw', new shim.TextEncoder().encode(data), {name: opt.name || 'PBKDF2'}, false, ['deriveBits']); - var work = await (shim.ossl || shim.subtle).deriveBits({ - name: opt.name || 'PBKDF2', - iterations: opt.iterations || S.pbkdf2.iter, - salt: new shim.TextEncoder().encode(opt.salt || salt), - hash: opt.hash || S.pbkdf2.hash, - }, key, opt.length || (S.pbkdf2.ks * 8)) - data = shim.random(data.length) // Erase data in case of passphrase - var r = shim.Buffer.from(work, 'binary').toString(opt.encode || 'base64') - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } catch(e) { - console.log(e); - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); - - module.exports = SEA.work; - })(USE, './work'); - - ;USE(function(module){ - var SEA = USE('./root'); - var shim = USE('./shim'); - var S = USE('./settings'); - - SEA.name = SEA.name || (async (cb, opt) => { try { - if(cb){ try{ cb() }catch(e){console.log(e)} } - return; - } catch(e) { - console.log(e); - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); - - //SEA.pair = async (data, proof, cb) => { try { - SEA.pair = SEA.pair || (async (cb, opt) => { try { - - var ecdhSubtle = shim.ossl || shim.subtle; - // First: ECDSA keys for signing/verifying... - var sa = await shim.subtle.generateKey({name: 'ECDSA', namedCurve: 'P-256'}, true, [ 'sign', 'verify' ]) - .then(async (keys) => { - // privateKey scope doesn't leak out from here! - //const { d: priv } = await shim.subtle.exportKey('jwk', keys.privateKey) - var key = {}; - key.priv = (await shim.subtle.exportKey('jwk', keys.privateKey)).d; - var pub = await shim.subtle.exportKey('jwk', keys.publicKey); - //const pub = Buff.from([ x, y ].join(':')).toString('base64') // old - key.pub = pub.x+'.'+pub.y; // new - // x and y are already base64 - // pub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) - // but split on a non-base64 letter. - return key; - }) - - // To include PGPv4 kind of keyId: - // const pubId = await SEA.keyid(keys.pub) - // Next: ECDH keys for encryption/decryption... - - try{ - var dh = await ecdhSubtle.generateKey({name: 'ECDH', namedCurve: 'P-256'}, true, ['deriveKey']) - .then(async (keys) => { - // privateKey scope doesn't leak out from here! - var key = {}; - key.epriv = (await ecdhSubtle.exportKey('jwk', keys.privateKey)).d; - var pub = await ecdhSubtle.exportKey('jwk', keys.publicKey); - //const epub = Buff.from([ ex, ey ].join(':')).toString('base64') // old - key.epub = pub.x+'.'+pub.y; // new - // ex and ey are already base64 - // epub is UTF8 but filename/URL safe (https://www.ietf.org/rfc/rfc3986.txt) - // but split on a non-base64 letter. - return key; - }) - }catch(e){ - if(SEA.window){ throw e } - if(e == 'Error: ECDH is not a supported algorithm'){ console.log('Ignoring ECDH...') } - else { throw e } - } dh = dh || {}; - - var r = { pub: sa.pub, priv: sa.priv, /* pubId, */ epub: dh.epub, epriv: dh.epriv } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } catch(e) { - console.log(e); - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); - - module.exports = SEA.pair; - })(USE, './pair'); - - ;USE(function(module){ - var SEA = USE('./root'); - var shim = USE('./shim'); - var S = USE('./settings'); - var sha = USE('./sha256'); - var u; - - SEA.sign = SEA.sign || (async (data, pair, cb, opt) => { try { - opt = opt || {}; - if(!(pair||opt).priv){ - if(!SEA.I){ throw 'No signing key.' } - pair = await SEA.I(null, {what: data, how: 'sign', why: opt.why}); - } - if(u === data){ throw '`undefined` not allowed.' } - var json = await S.parse(data); - var check = opt.check = opt.check || json; - if(SEA.verify && (SEA.opt.check(check) || (check && check.s && check.m)) - && u !== await SEA.verify(check, pair)){ // don't sign if we already signed it. - var r = await S.parse(check); - if(!opt.raw){ r = 'SEA' + await shim.stringify(r) } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } - var pub = pair.pub; - var priv = pair.priv; - var jwk = S.jwk(pub, priv); - var hash = await sha(json); - var sig = await (shim.ossl || shim.subtle).importKey('jwk', jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ['sign']) - .then((key) => (shim.ossl || shim.subtle).sign({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, new Uint8Array(hash))) // privateKey scope doesn't leak out from here! - var r = {m: json, s: shim.Buffer.from(sig, 'binary').toString(opt.encode || 'base64')} - if(!opt.raw){ r = 'SEA' + await shim.stringify(r) } - - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } catch(e) { - console.log(e); - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); - - module.exports = SEA.sign; - })(USE, './sign'); - - ;USE(function(module){ - var SEA = USE('./root'); - var shim = USE('./shim'); - var S = USE('./settings'); - var sha = USE('./sha256'); - var u; - - SEA.verify = SEA.verify || (async (data, pair, cb, opt) => { try { - var json = await S.parse(data); - if(false === pair){ // don't verify! - var raw = await S.parse(json.m); - if(cb){ try{ cb(raw) }catch(e){console.log(e)} } - return raw; - } - opt = opt || {}; - // SEA.I // verify is free! Requires no user permission. - var pub = pair.pub || pair; - var key = SEA.opt.slow_leak? await SEA.opt.slow_leak(pub) : await (shim.ossl || shim.subtle).importKey('jwk', S.jwk(pub), {name: 'ECDSA', namedCurve: 'P-256'}, false, ['verify']); - var hash = await sha(json.m); - var buf, sig, check, tmp; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64'); // NEW DEFAULT! - sig = new Uint8Array(buf); - check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)); - if(!check){ throw "Signature did not match." } - }catch(e){ - if(SEA.opt.fallback){ - return await SEA.opt.fall_verify(data, pair, cb, opt); - } - } - var r = check? await S.parse(json.m) : u; - - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } catch(e) { - console.log(e); // mismatched owner FOR MARTTI - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); - - module.exports = SEA.verify; - // legacy & ossl memory leak mitigation: - - var knownKeys = {}; - var keyForPair = SEA.opt.slow_leak = pair => { - if (knownKeys[pair]) return knownKeys[pair]; - var jwk = S.jwk(pair); - knownKeys[pair] = (shim.ossl || shim.subtle).importKey("jwk", jwk, {name: 'ECDSA', namedCurve: 'P-256'}, false, ["verify"]); - return knownKeys[pair]; - }; - - var O = SEA.opt; - SEA.opt.fall_verify = async function(data, pair, cb, opt, f){ - if(f === SEA.opt.fallback){ throw "Signature did not match" } f = f || 1; - var tmp = data||''; - data = SEA.opt.unpack(data) || data; - var json = await S.parse(data), pub = pair.pub || pair, key = await SEA.opt.slow_leak(pub); - var hash = (f <= SEA.opt.fallback)? shim.Buffer.from(await shim.subtle.digest({name: 'SHA-256'}, new shim.TextEncoder().encode(await S.parse(json.m)))) : await sha(json.m); // this line is old bad buggy code but necessary for old compatibility. - var buf; var sig; var check; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64') // NEW DEFAULT! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) - if(!check){ throw "Signature did not match." } - }catch(e){ try{ - buf = shim.Buffer.from(json.s, 'utf8') // AUTO BACKWARD OLD UTF8 DATA! - sig = new Uint8Array(buf) - check = await (shim.ossl || shim.subtle).verify({name: 'ECDSA', hash: {name: 'SHA-256'}}, key, sig, new Uint8Array(hash)) - }catch(e){ - if(!check){ throw "Signature did not match." } - } - } - var r = check? await S.parse(json.m) : u; - O.fall_soul = tmp['#']; O.fall_key = tmp['.']; O.fall_val = data; O.fall_state = tmp['>']; - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } - SEA.opt.fallback = 2; - - })(USE, './verify'); - - ;USE(function(module){ - var shim = USE('./shim'); - var S = USE('./settings'); - var sha256hash = USE('./sha256'); - - const importGen = async (key, salt, opt) => { - //const combo = shim.Buffer.concat([shim.Buffer.from(key, 'utf8'), salt || shim.random(8)]).toString('utf8') // old - opt = opt || {}; - const combo = key + (salt || shim.random(8)).toString('utf8'); // new - const hash = shim.Buffer.from(await sha256hash(combo), 'binary') - - const jwkKey = S.keyToJwk(hash) - return await shim.subtle.importKey('jwk', jwkKey, {name:'AES-GCM'}, false, ['encrypt', 'decrypt']) - } - module.exports = importGen; - })(USE, './aeskey'); - - ;USE(function(module){ - var SEA = USE('./root'); - var shim = USE('./shim'); - var S = USE('./settings'); - var aeskey = USE('./aeskey'); - var u; - - SEA.encrypt = SEA.encrypt || (async (data, pair, cb, opt) => { try { - opt = opt || {}; - var key = (pair||opt).epriv || pair; - if(u === data){ throw '`undefined` not allowed.' } - if(!key){ - if(!SEA.I){ throw 'No encryption key.' } - pair = await SEA.I(null, {what: data, how: 'encrypt', why: opt.why}); - key = pair.epriv || pair; - } - var msg = (typeof data == 'string')? data : await shim.stringify(data); - var rand = {s: shim.random(9), iv: shim.random(15)}; // consider making this 9 and 15 or 18 or 12 to reduce == padding. - var ct = await aeskey(key, rand.s, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).encrypt({ // Keeping the AES key scope as private as possible... - name: opt.name || 'AES-GCM', iv: new Uint8Array(rand.iv) - }, aes, new shim.TextEncoder().encode(msg))); - var r = { - ct: shim.Buffer.from(ct, 'binary').toString(opt.encode || 'base64'), - iv: rand.iv.toString(opt.encode || 'base64'), - s: rand.s.toString(opt.encode || 'base64') - } - if(!opt.raw){ r = 'SEA' + await shim.stringify(r) } - - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } catch(e) { - console.log(e); - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); - - module.exports = SEA.encrypt; - })(USE, './encrypt'); - - ;USE(function(module){ - var SEA = USE('./root'); - var shim = USE('./shim'); - var S = USE('./settings'); - var aeskey = USE('./aeskey'); - - SEA.decrypt = SEA.decrypt || (async (data, pair, cb, opt) => { try { - opt = opt || {}; - var key = (pair||opt).epriv || pair; - if(!key){ - if(!SEA.I){ throw 'No decryption key.' } - pair = await SEA.I(null, {what: data, how: 'decrypt', why: opt.why}); - key = pair.epriv || pair; - } - var json = await S.parse(data); - var buf, bufiv, bufct; try{ - buf = shim.Buffer.from(json.s, opt.encode || 'base64'); - bufiv = shim.Buffer.from(json.iv, opt.encode || 'base64'); - bufct = shim.Buffer.from(json.ct, opt.encode || 'base64'); - var ct = await aeskey(key, buf, opt).then((aes) => (/*shim.ossl ||*/ shim.subtle).decrypt({ // Keeping aesKey scope as private as possible... - name: opt.name || 'AES-GCM', iv: new Uint8Array(bufiv), tagLength: 128 - }, aes, new Uint8Array(bufct))); - }catch(e){ - if('utf8' === opt.encode){ throw "Could not decrypt" } - if(SEA.opt.fallback){ - opt.encode = 'utf8'; - return await SEA.decrypt(data, pair, cb, opt); - } - } - var r = await S.parse(new shim.TextDecoder('utf8').decode(ct)); - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } catch(e) { - console.log(e); - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); - - module.exports = SEA.decrypt; - })(USE, './decrypt'); - - ;USE(function(module){ - var SEA = USE('./root'); - var shim = USE('./shim'); - var S = USE('./settings'); - // Derive shared secret from other's pub and my epub/epriv - SEA.secret = SEA.secret || (async (key, pair, cb, opt) => { try { - opt = opt || {}; - if(!pair || !pair.epriv || !pair.epub){ - if(!SEA.I){ throw 'No secret mix.' } - pair = await SEA.I(null, {what: key, how: 'secret', why: opt.why}); - } - var pub = key.epub || key; - var epub = pair.epub; - var epriv = pair.epriv; - var ecdhSubtle = shim.ossl || shim.subtle; - var pubKeyData = keysToEcdhJwk(pub); - var props = Object.assign({ public: await ecdhSubtle.importKey(...pubKeyData, true, []) },{name: 'ECDH', namedCurve: 'P-256'}); // Thanks to @sirpy ! - var privKeyData = keysToEcdhJwk(epub, epriv); - var derived = await ecdhSubtle.importKey(...privKeyData, false, ['deriveBits']).then(async (privKey) => { - // privateKey scope doesn't leak out from here! - var derivedBits = await ecdhSubtle.deriveBits(props, privKey, 256); - var rawBits = new Uint8Array(derivedBits); - var derivedKey = await ecdhSubtle.importKey('raw', rawBits,{ name: 'AES-GCM', length: 256 }, true, [ 'encrypt', 'decrypt' ]); - return ecdhSubtle.exportKey('jwk', derivedKey).then(({ k }) => k); - }) - var r = derived; - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } catch(e) { - console.log(e); - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); - - // can this be replaced with settings.jwk? - var keysToEcdhJwk = (pub, d) => { // d === priv - //var [ x, y ] = shim.Buffer.from(pub, 'base64').toString('utf8').split(':') // old - var [ x, y ] = pub.split('.') // new - var jwk = d ? { d: d } : {} - return [ // Use with spread returned value... - 'jwk', - Object.assign( - jwk, - { x: x, y: y, kty: 'EC', crv: 'P-256', ext: true } - ), // ??? refactor - {name: 'ECDH', namedCurve: 'P-256'} - ] - } - - module.exports = SEA.secret; - })(USE, './secret'); - - ;USE(function(module){ - var SEA = USE('./root'); - // This is to certify that a group of "certificants" can "put" anything at a group of matched "paths" to the certificate authority's graph - SEA.certify = SEA.certify || (async (certificants, policy = {}, authority, cb, opt = {}) => { try { - /* - The Certify Protocol was made out of love by a Vietnamese code enthusiast. Vietnamese people around the world deserve respect! - IMPORTANT: A Certificate is like a Signature. No one knows who (authority) created/signed a cert until you put it into their graph. - "certificants": '*' or a String (Bob.pub) || an Object that contains "pub" as a key || an array of [object || string]. These people will have the rights. - "policy": A string ('inbox'), or a RAD/LEX object {'*': 'inbox'}, or an Array of RAD/LEX objects or strings. RAD/LEX object can contain key "?" with indexOf("*") > -1 to force key equals certificant pub. This rule is used to check against soul+'/'+key using Gun.text.match or String.match. - "authority": Key pair or priv of the certificate authority. - "cb": A callback function after all things are done. - "opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry. If opt.block is set, SEA will look for block before syncing. - */ - console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.') - - certificants = (() => { - var data = [] - if (certificants) { - if ((typeof certificants === 'string' || Array.isArray(certificants)) && certificants.indexOf('*') > -1) return '*' - if (typeof certificants === 'string') return certificants - if (Array.isArray(certificants)) { - if (certificants.length === 1 && certificants[0]) return typeof certificants[0] === 'object' && certificants[0].pub ? certificants[0].pub : typeof certificants[0] === 'string' ? certificants[0] : null - certificants.map(certificant => { - if (typeof certificant ==='string') data.push(certificant) - else if (typeof certificant === 'object' && certificant.pub) data.push(certificant.pub) - }) - } - - if (typeof certificants === 'object' && certificants.pub) return certificants.pub - return data.length > 0 ? data : null - } - return - })() - - if (!certificants) return console.log("No certificant found.") - - const expiry = opt.expiry && (typeof opt.expiry === 'number' || typeof opt.expiry === 'string') ? parseFloat(opt.expiry) : null - const readPolicy = (policy || {}).read ? policy.read : null - const writePolicy = (policy || {}).write ? policy.write : typeof policy === 'string' || Array.isArray(policy) || policy["+"] || policy["#"] || policy["."] || policy["="] || policy["*"] || policy[">"] || policy["<"] ? policy : null - // The "blacklist" feature is now renamed to "block". Why ? BECAUSE BLACK LIVES MATTER! - // We can now use 3 keys: block, blacklist, ban - const block = (opt || {}).block || (opt || {}).blacklist || (opt || {}).ban || {} - const readBlock = block.read && (typeof block.read === 'string' || (block.read || {})['#']) ? block.read : null - const writeBlock = typeof block === 'string' ? block : block.write && (typeof block.write === 'string' || block.write['#']) ? block.write : null - - if (!readPolicy && !writePolicy) return console.log("No policy found.") - - // reserved keys: c, e, r, w, rb, wb - const data = JSON.stringify({ - c: certificants, - ...(expiry ? {e: expiry} : {}), // inject expiry if possible - ...(readPolicy ? {r: readPolicy } : {}), // "r" stands for read, which means read permission. - ...(writePolicy ? {w: writePolicy} : {}), // "w" stands for write, which means write permission. - ...(readBlock ? {rb: readBlock} : {}), // inject READ block if possible - ...(writeBlock ? {wb: writeBlock} : {}), // inject WRITE block if possible - }) - - const certificate = await SEA.sign(data, authority, null, {raw:1}) - - var r = certificate - if(!opt.raw){ r = 'SEA'+JSON.stringify(r) } - if(cb){ try{ cb(r) }catch(e){console.log(e)} } - return r; - } catch(e) { - SEA.err = e; - if(SEA.throw){ throw e } - if(cb){ cb() } - return; - }}); - - module.exports = SEA.certify; - })(USE, './certify'); - - ;USE(function(module){ - var shim = USE('./shim'); - // Practical examples about usage found in tests. - var SEA = USE('./root'); - SEA.work = USE('./work'); - SEA.sign = USE('./sign'); - SEA.verify = USE('./verify'); - SEA.encrypt = USE('./encrypt'); - SEA.decrypt = USE('./decrypt'); - SEA.certify = USE('./certify'); - //SEA.opt.aeskey = USE('./aeskey'); // not official! // this causes problems in latest WebCrypto. - - SEA.random = SEA.random || shim.random; - - // This is Buffer used in SEA and usable from Gun/SEA application also. - // For documentation see https://nodejs.org/api/buffer.html - SEA.Buffer = SEA.Buffer || USE('./buffer'); - - // These SEA functions support now ony Promises or - // async/await (compatible) code, use those like Promises. - // - // Creates a wrapper library around Web Crypto API - // for various AES, ECDSA, PBKDF2 functions we called above. - // Calculate public key KeyID aka PGPv4 (result: 8 bytes as hex string) - SEA.keyid = SEA.keyid || (async (pub) => { - try { - // base64('base64(x):base64(y)') => shim.Buffer(xy) - const pb = shim.Buffer.concat( - pub.replace(/-/g, '+').replace(/_/g, '/').split('.') - .map((t) => shim.Buffer.from(t, 'base64')) - ) - // id is PGPv4 compliant raw key - const id = shim.Buffer.concat([ - shim.Buffer.from([0x99, pb.length / 0x100, pb.length % 0x100]), pb - ]) - const sha1 = await sha1hash(id) - const hash = shim.Buffer.from(sha1, 'binary') - return hash.toString('hex', hash.length - 8) // 16-bit ID as hex - } catch (e) { - console.log(e) - throw e - } - }); - // all done! - // Obviously it is missing MANY necessary features. This is only an alpha release. - // Please experiment with it, audit what I've done so far, and complain about what needs to be added. - // SEA should be a full suite that is easy and seamless to use. - // Again, scroll naer the top, where I provide an EXAMPLE of how to create a user and sign in. - // Once logged in, the rest of the code you just read handled automatically signing/validating data. - // But all other behavior needs to be equally easy, like opinionated ways of - // Adding friends (trusted public keys), sending private messages, etc. - // Cheers! Tell me what you think. - ((SEA.window||{}).GUN||{}).SEA = SEA; - - module.exports = SEA - // -------------- END SEA MODULES -------------------- - // -- BEGIN SEA+GUN MODULES: BUNDLED BY DEFAULT UNTIL OTHERS USE SEA ON OWN ------- - })(USE, './sea'); - - ;USE(function(module){ - var SEA = USE('./sea'), Gun, u; - if(SEA.window){ - Gun = SEA.window.GUN || {chain:{}}; - } else { - Gun = USE((u+'' == typeof MODULE?'.':'')+'./gun', 1); - } - SEA.GUN = Gun; - - function User(root){ - this._ = {$: this}; - } - User.prototype = (function(){ function F(){}; F.prototype = Gun.chain; return new F() }()) // Object.create polyfill - User.prototype.constructor = User; - - // let's extend the gun chain with a `user` function. - // only one user can be logged in at a time, per gun instance. - Gun.chain.user = function(pub){ - var gun = this, root = gun.back(-1), user; - if(pub){ - pub = SEA.opt.pub((pub._||'')['#']) || pub; - return root.get('~'+pub); - } - if(user = root.back('user')){ return user } - var root = (root._), at = root, uuid = at.opt.uuid || lex; - (at = (user = at.user = gun.chain(new User))._).opt = {}; - at.opt.uuid = function(cb){ - var id = uuid(), pub = root.user; - if(!pub || !(pub = pub.is) || !(pub = pub.pub)){ return id } - id = '~' + pub + '/' + id; - if(cb && cb.call){ cb(null, id) } - return id; - } - return user; - } - function lex(){ return Gun.state().toString(36).replace('.','') } - Gun.User = User; - User.GUN = Gun; - User.SEA = Gun.SEA = SEA; - module.exports = User; - })(USE, './user'); - - ;USE(function(module){ - var u, Gun = (''+u != typeof GUN)? (GUN||{chain:{}}) : USE((''+u === typeof MODULE?'.':'')+'./gun', 1); - Gun.chain.then = function(cb, opt){ - var gun = this, p = (new Promise(function(res, rej){ - gun.once(res, opt); - })); - return cb? p.then(cb) : p; - } - })(USE, './then'); - - ;USE(function(module){ - var User = USE('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){}; - - // Well first we have to actually create a user. That is what this function does. - User.prototype.create = function(...args){ - var pair = typeof args[0] === 'object' && (args[0].pub || args[0].epub) ? args[0] : typeof args[1] === 'object' && (args[1].pub || args[1].epub) ? args[1] : null; - var alias = pair && (pair.pub || pair.epub) ? pair.pub : typeof args[0] === 'string' ? args[0] : null; - var pass = pair && (pair.pub || pair.epub) ? pair : alias && typeof args[1] === 'string' ? args[1] : null; - var cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair - var opt = args && args.length > 1 && typeof args[args.length-1] === 'object' ? args[args.length-1] : {}; // opt is always the last parameter which typeof === 'object' and stands after cb - - var gun = this, cat = (gun._), root = gun.back(-1); - cb = cb || noop; - opt = opt || {}; - if(false !== opt.check){ - var err; - if(!alias){ err = "No user." } - if((pass||'').length < 8){ err = "Password too short!" } - if(err){ - cb({err: Gun.log(err)}); - return gun; - } - } - if(cat.ing){ - (cb || noop)({err: Gun.log("User is already being created or authenticated!"), wait: true}); - return gun; - } - cat.ing = true; - var act = {}, u; - act.a = function(pubs){ - act.pubs = pubs; - if(pubs && !opt.already){ - // If we can enforce that a user name is already taken, it might be nice to try, but this is not guaranteed. - var ack = {err: Gun.log('User already created!')}; - cat.ing = false; - (cb || noop)(ack); - gun.leave(); - return; - } - act.salt = String.random(64); // pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it. - SEA.work(pass, act.salt, act.b); // this will take some short amount of time to produce a proof, which slows brute force attacks. - } - act.b = function(proof){ - act.proof = proof; - pair ? act.c(pair) : SEA.pair(act.c) // generate a brand new key pair or use the existing. - } - act.c = function(pair){ - var tmp - act.pair = pair || {}; - if(tmp = cat.root.user){ - tmp._.sea = pair; - tmp.is = {pub: pair.pub, epub: pair.epub, alias: alias}; - } - // the user's public key doesn't need to be signed. But everything else needs to be signed with it! // we have now automated it! clean up these extra steps now! - act.data = {pub: pair.pub}; - act.d(); - } - act.d = function(){ - act.data.alias = alias; - act.e(); - } - act.e = function(){ - act.data.epub = act.pair.epub; - SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, act.proof, act.f, {raw:1}); // to keep the private key safe, we AES encrypt it with the proof of work! - } - act.f = function(auth){ - act.data.auth = JSON.stringify({ek: auth, s: act.salt}); - act.g(act.data.auth); - } - act.g = function(auth){ var tmp; - act.data.auth = act.data.auth || auth; - root.get(tmp = '~'+act.pair.pub).put(act.data).on(act.h); // awesome, now we can actually save the user with their public key as their ID. - var link = {}; link[tmp] = {'#': tmp}; root.get('~@'+alias).put(link).get(tmp).on(act.i); // next up, we want to associate the alias with the public key. So we add it to the alias list. - } - act.h = function(data, key, msg, eve){ - eve.off(); act.h.ok = 1; act.i(); - } - act.i = function(data, key, msg, eve){ - if(eve){ act.i.ok = 1; eve.off() } - if(!act.h.ok || !act.i.ok){ return } - cat.ing = false; - cb({ok: 0, pub: act.pair.pub}); // callback that the user has been created. (Note: ok = 0 because we didn't wait for disk to ack) - if(noop === cb){ pair ? gun.auth(pair) : gun.auth(alias, pass) } // if no callback is passed, auto-login after signing up. - } - root.get('~@'+alias).once(act.a); - return gun; - } - User.prototype.leave = function(opt, cb){ - var gun = this, user = (gun.back(-1)._).user; - if(user){ - delete user.is; - delete user._.is; - delete user._.sea; - } - if(SEA.window){ - try{var sS = {}; - sS = SEA.window.sessionStorage; - delete sS.recall; - delete sS.pair; - }catch(e){}; - } - return gun; - } - })(USE, './create'); - - ;USE(function(module){ - var User = USE('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){}; - // now that we have created a user, we want to authenticate them! - User.prototype.auth = function(...args){ // TODO: this PR with arguments need to be cleaned up / refactored. - var pair = typeof args[0] === 'object' && (args[0].pub || args[0].epub) ? args[0] : typeof args[1] === 'object' && (args[1].pub || args[1].epub) ? args[1] : null; - var alias = !pair && typeof args[0] === 'string' ? args[0] : null; - var pass = (alias || (pair && !(pair.priv && pair.epriv))) && typeof args[1] === 'string' ? args[1] : null; - var cb = args.filter(arg => typeof arg === 'function')[0] || null; // cb now can stand anywhere, after alias/pass or pair - var opt = args && args.length > 1 && typeof args[args.length-1] === 'object' ? args[args.length-1] : {}; // opt is always the last parameter which typeof === 'object' and stands after cb - - var gun = this, cat = (gun._), root = gun.back(-1); - - if(cat.ing){ - (cb || noop)({err: Gun.log("User is already being created or authenticated!"), wait: true}); - return gun; - } - cat.ing = true; - - var act = {}, u, tries = 9; - act.a = function(data){ - if(!data){ return act.b() } - if(!data.pub){ - var tmp = []; Object.keys(data).forEach(function(k){ if('_'==k){ return } tmp.push(data[k]) }) - return act.b(tmp); - } - if(act.name){ return act.f(data) } - act.c((act.data = data).auth); - } - act.b = function(list){ - var get = (act.list = (act.list||[]).concat(list||[])).shift(); - if(u === get){ - if(act.name){ return act.err('Your user account is not published for dApps to access, please consider syncing it online, or allowing local access by adding your device as a peer.') } - if(alias && tries--){ - root.get('~@'+alias).once(act.a); - return; - } - return act.err('Wrong user or password.') - } - root.get(get).once(act.a); - } - act.c = function(auth){ - if(u === auth){ return act.b() } - if('string' == typeof auth){ return act.c(obj_ify(auth)) } // in case of legacy - SEA.work(pass, (act.auth = auth).s, act.d, act.enc); // the proof of work is evidence that we've spent some time/effort trying to log in, this slows brute force. - } - act.d = function(proof){ - SEA.decrypt(act.auth.ek, proof, act.e, act.enc); - } - act.e = function(half){ - if(u === half){ - if(!act.enc){ // try old format - act.enc = {encode: 'utf8'}; - return act.c(act.auth); - } act.enc = null; // end backwards - return act.b(); - } - act.half = half; - act.f(act.data); - } - act.f = function(pair){ - var half = act.half || {}, data = act.data || {}; - act.g(act.lol = {pub: pair.pub || data.pub, epub: pair.epub || data.epub, priv: pair.priv || half.priv, epriv: pair.epriv || half.epriv}); - } - act.g = function(pair){ - if(!pair || !pair.pub || !pair.epub){ return act.b() } - act.pair = pair; - var user = (root._).user, at = (user._); - var tmp = at.tag; - var upt = at.opt; - at = user._ = root.get('~'+pair.pub)._; - at.opt = upt; - // add our credentials in-memory only to our root user instance - user.is = {pub: pair.pub, epub: pair.epub, alias: alias || pair.pub}; - at.sea = act.pair; - cat.ing = false; - try{if(pass && u == (obj_ify(cat.root.graph['~'+pair.pub].auth)||'')[':']){ opt.shuffle = opt.change = pass; } }catch(e){} // migrate UTF8 & Shuffle! - opt.change? act.z() : (cb || noop)(at); - if(SEA.window && ((gun.back('user')._).opt||opt).remember){ - // TODO: this needs to be modular. - try{var sS = {}; - sS = SEA.window.sessionStorage; // TODO: FIX BUG putting on `.is`! - sS.recall = true; - sS.pair = JSON.stringify(pair); // auth using pair is more reliable than alias/pass - }catch(e){} - } - try{ - if(root._.tag.auth){ // auth handle might not be registered yet - (root._).on('auth', at) // TODO: Deprecate this, emit on user instead! Update docs when you do. - } else { setTimeout(function(){ (root._).on('auth', at) },1) } // if not, hackily add a timeout. - //at.on('auth', at) // Arrgh, this doesn't work without event "merge" code, but "merge" code causes stack overflow and crashes after logging in & trying to write data. - }catch(e){ - Gun.log("Your 'auth' callback crashed with:", e); - } - } - act.h = function(data){ - if(!data){ return act.b() } - alias = data.alias - if(!alias) - alias = data.alias = "~" + pair.pub - if(!data.auth){ - return act.g(pair); - } - pair = null; - act.c((act.data = data).auth); - } - act.z = function(){ - // password update so encrypt private key using new pwd + salt - act.salt = String.random(64); // pseudo-random - SEA.work(opt.change, act.salt, act.y); - } - act.y = function(proof){ - SEA.encrypt({priv: act.pair.priv, epriv: act.pair.epriv}, proof, act.x, {raw:1}); - } - act.x = function(auth){ - act.w(JSON.stringify({ek: auth, s: act.salt})); - } - act.w = function(auth){ - if(opt.shuffle){ // delete in future! - console.log('migrate core account from UTF8 & shuffle'); - var tmp = {}; Object.keys(act.data).forEach(function(k){ tmp[k] = act.data[k] }); - delete tmp._; - tmp.auth = auth; - root.get('~'+act.pair.pub).put(tmp); - } // end delete - root.get('~'+act.pair.pub).get('auth').put(auth, cb || noop); - } - act.err = function(e){ - var ack = {err: Gun.log(e || 'User cannot be found!')}; - cat.ing = false; - (cb || noop)(ack); - } - act.plugin = function(name){ - if(!(act.name = name)){ return act.err() } - var tmp = [name]; - if('~' !== name[0]){ - tmp[1] = '~'+name; - tmp[2] = '~@'+name; - } - act.b(tmp); - } - if(pair){ - if(pair.priv && pair.epriv) - act.g(pair); - else - root.get('~'+pair.pub).once(act.h); - } else - if(alias){ - root.get('~@'+alias).once(act.a); - } else - if(!alias && !pass){ - SEA.name(act.plugin); - } - return gun; - } - function obj_ify(o){ - if('string' != typeof o){ return o } - try{o = JSON.parse(o); - }catch(e){o={}}; - return o; - } - })(USE, './auth'); - - ;USE(function(module){ - var User = USE('./user'), SEA = User.SEA, Gun = User.GUN; - User.prototype.recall = function(opt, cb){ - var gun = this, root = gun.back(-1), tmp; - opt = opt || {}; - if(opt && opt.sessionStorage){ - if(SEA.window){ - try{ - var sS = {}; - sS = SEA.window.sessionStorage; // TODO: FIX BUG putting on `.is`! - if(sS){ - (root._).opt.remember = true; - ((gun.back('user')._).opt||opt).remember = true; - if(sS.recall || sS.pair) root.user().auth(JSON.parse(sS.pair), cb); // pair is more reliable than alias/pass - } - }catch(e){} - } - return gun; - } - /* - TODO: copy mhelander's expiry code back in. - Although, we should check with community, - should expiry be core or a plugin? - */ - return gun; - } - })(USE, './recall'); - - ;USE(function(module){ - var User = USE('./user'), SEA = User.SEA, Gun = User.GUN, noop = function(){}; - User.prototype.pair = function(){ - var user = this, proxy; // undeprecated, hiding with proxies. - try{ proxy = new Proxy({DANGER:'\u2620'}, {get: function(t,p,r){ - if(!user.is || !(user._||'').sea){ return } - return user._.sea[p]; - }})}catch(e){} - return proxy; - } - // If authenticated user wants to delete his/her account, let's support it! - User.prototype.delete = async function(alias, pass, cb){ - console.log("user.delete() IS DEPRECATED AND WILL BE MOVED TO A MODULE!!!"); - var gun = this, root = gun.back(-1), user = gun.back('user'); - try { - user.auth(alias, pass, function(ack){ - var pub = (user.is||{}).pub; - // Delete user data - user.map().once(function(){ this.put(null) }); - // Wipe user data from memory - user.leave(); - (cb || noop)({ok: 0}); - }); - } catch (e) { - Gun.log('User.delete failed! Error:', e); - } - return gun; - } - User.prototype.alive = async function(){ - console.log("user.alive() IS DEPRECATED!!!"); - const gunRoot = this.back(-1) - try { - // All is good. Should we do something more with actual recalled data? - await authRecall(gunRoot) - return gunRoot._.user._ - } catch (e) { - const err = 'No session!' - Gun.log(err) - throw { err } - } - } - User.prototype.trust = async function(user){ - console.log("`.trust` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); - // TODO: BUG!!! SEA `node` read listener needs to be async, which means core needs to be async too. - //gun.get('alice').get('age').trust(bob); - if (Gun.is(user)) { - user.get('pub').get((ctx, ev) => { - console.log(ctx, ev) - }) - } - user.get('trust').get(path).put(theirPubkey); - - // do a lookup on this gun chain directly (that gets bob's copy of the data) - // do a lookup on the metadata trust table for this path (that gets all the pubkeys allowed to write on this path) - // do a lookup on each of those pubKeys ON the path (to get the collab data "layers") - // THEN you perform Jachen's mix operation - // and return the result of that to... - } - User.prototype.grant = function(to, cb){ - console.log("`.grant` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); - var gun = this, user = gun.back(-1).user(), pair = user._.sea, path = ''; - gun.back(function(at){ if(at.is){ return } path += (at.get||'') }); - (async function(){ - var enc, sec = await user.get('grant').get(pair.pub).get(path).then(); - sec = await SEA.decrypt(sec, pair); - if(!sec){ - sec = SEA.random(16).toString(); - enc = await SEA.encrypt(sec, pair); - user.get('grant').get(pair.pub).get(path).put(enc); - } - var pub = to.get('pub').then(); - var epub = to.get('epub').then(); - pub = await pub; epub = await epub; - var dh = await SEA.secret(epub, pair); - enc = await SEA.encrypt(sec, dh); - user.get('grant').get(pub).get(path).put(enc, cb); - }()); - return gun; - } - User.prototype.secret = function(data, cb){ - console.log("`.secret` API MAY BE DELETED OR CHANGED OR RENAMED, DO NOT USE!"); - var gun = this, user = gun.back(-1).user(), pair = user.pair(), path = ''; - gun.back(function(at){ if(at.is){ return } path += (at.get||'') }); - (async function(){ - var enc, sec = await user.get('trust').get(pair.pub).get(path).then(); - sec = await SEA.decrypt(sec, pair); - if(!sec){ - sec = SEA.random(16).toString(); - enc = await SEA.encrypt(sec, pair); - user.get('trust').get(pair.pub).get(path).put(enc); - } - enc = await SEA.encrypt(data, sec); - gun.put(enc, cb); - }()); - return gun; - } - - /** - * returns the decrypted value, encrypted by secret - * @returns {Promise} - // Mark needs to review 1st before officially supported - User.prototype.decrypt = function(cb) { - let gun = this, - path = '' - gun.back(function(at) { - if (at.is) { - return - } - path += at.get || '' - }) - return gun - .then(async data => { - if (data == null) { - return - } - const user = gun.back(-1).user() - const pair = user.pair() - let sec = await user - .get('trust') - .get(pair.pub) - .get(path) - sec = await SEA.decrypt(sec, pair) - if (!sec) { - return data - } - let decrypted = await SEA.decrypt(data, sec) - return decrypted - }) - .then(res => { - cb && cb(res) - return res - }) - } - */ - module.exports = User - })(USE, './share'); - - ;USE(function(module){ - var SEA = USE('./sea'), S = USE('./settings'), noop = function() {}, u; - var Gun = (SEA.window||'').GUN || USE((''+u === typeof MODULE?'.':'')+'./gun', 1); - // After we have a GUN extension to make user registration/login easy, we then need to handle everything else. - - // We do this with a GUN adapter, we first listen to when a gun instance is created (and when its options change) - Gun.on('opt', function(at){ - if(!at.sea){ // only add SEA once per instance, on the "at" context. - at.sea = {own: {}}; - at.on('put', check, at); // SEA now runs its firewall on HAM diffs, not all i/o. - } - this.to.next(at); // make sure to call the "next" middleware adapter. - }); - - // Alright, this next adapter gets run at the per node level in the graph database. - // correction: 2020 it gets run on each key/value pair in a node upon a HAM diff. - // This will let us verify that every property on a node has a value signed by a public key we trust. - // If the signature does not match, the data is just `undefined` so it doesn't get passed on. - // If it does match, then we transform the in-memory "view" of the data into its plain value (without the signature). - // Now NOTE! Some data is "system" data, not user data. Example: List of public keys, aliases, etc. - // This data is self-enforced (the value can only match its ID), but that is handled in the `security` function. - // From the self-enforced data, we can see all the edges in the graph that belong to a public key. - // Example: ~ASDF is the ID of a node with ASDF as its public key, signed alias and salt, and - // its encrypted private key, but it might also have other signed values on it like `profile = ` edge. - // Using that directed edge's ID, we can then track (in memory) which IDs belong to which keys. - // Here is a problem: Multiple public keys can "claim" any node's ID, so this is dangerous! - // This means we should ONLY trust our "friends" (our key ring) public keys, not any ones. - // I have not yet added that to SEA yet in this alpha release. That is coming soon, but beware in the meanwhile! - - function check(msg){ // REVISE / IMPROVE, NO NEED TO PASS MSG/EVE EACH SUB? - var eve = this, at = eve.as, put = msg.put, soul = put['#'], key = put['.'], val = put[':'], state = put['>'], id = msg['#'], tmp; - if(!soul || !key){ return } - if((msg._||'').faith && (at.opt||'').faith && 'function' == typeof msg._){ - SEA.opt.pack(put, function(raw){ - SEA.verify(raw, false, function(data){ // this is synchronous if false - put['='] = SEA.opt.unpack(data); - eve.to.next(msg); - })}) - return - } - var no = function(why){ at.on('in', {'@': id, err: msg.err = why}) }; // exploit internal relay stun for now, maybe violates spec, but testing for now. // Note: this may be only the sharded message, not original batch. - //var no = function(why){ msg.ack(why) }; - (msg._||'').DBG && ((msg._||'').DBG.c = +new Date); - if(0 <= soul.indexOf(' { - if (certificate.m && certificate.s && certificant && pub) - // now verify certificate - return SEA.verify(certificate, pub, data => { // check if "pub" (of the graph owner) really issued this cert - if (u !== data && u !== data.e && msg.put['>'] && msg.put['>'] > parseFloat(data.e)) return no("Certificate expired.") // certificate expired - // "data.c" = a list of certificants/certified users - // "data.w" = lex WRITE permission, in the future, there will be "data.r" which means lex READ permission - if (u !== data && data.c && data.w && (data.c === certificant || data.c.indexOf('*' || certificant) > -1)) { - // ok, now "certificant" is in the "certificants" list, but is "path" allowed? Check path - let path = soul.indexOf('/') > -1 ? soul.replace(soul.substring(0, soul.indexOf('/') + 1), '') : '' - String.match = String.match || Gun.text.match - const w = Array.isArray(data.w) ? data.w : typeof data.w === 'object' || typeof data.w === 'string' ? [data.w] : [] - for (const lex of w) { - if ((String.match(path, lex['#']) && String.match(key, lex['.'])) || (!lex['.'] && String.match(path, lex['#'])) || (!lex['#'] && String.match(key, lex['.'])) || String.match((path ? path + '/' + key : key), lex['#'] || lex)) { - // is Certificant forced to present in Path - if (lex['+'] && lex['+'].indexOf('*') > -1 && path && path.indexOf(certificant) == -1 && key.indexOf(certificant) == -1) return no(`Path "${path}" or key "${key}" must contain string "${certificant}".`) - // path is allowed, but is there any WRITE block? Check it out - if (data.wb && (typeof data.wb === 'string' || ((data.wb || {})['#']))) { // "data.wb" = path to the WRITE block - var root = eve.as.root.$.back(-1) - if (typeof data.wb === 'string' && '~' !== data.wb.slice(0, 1)) root = root.get('~' + pub) - return root.get(data.wb).get(certificant).once(value => { // TODO: INTENT TO DEPRECATE. - if (value && (value === 1 || value === true)) return no(`Certificant ${certificant} blocked.`) - return cb(data) - }) - } - return cb(data) - } - } - return no("Certificate verification fail.") - } - }) - return - } - - if ('pub' === key && '~' + pub === soul) { - if (val === pub) return eve.to.next(msg) // the account MUST match `pub` property that equals the ID of the public key. - return no("Account not same!") - } - - if ((tmp = user.is) && tmp.pub && !raw['*'] && !raw['+'] && (pub === tmp.pub || (pub !== tmp.pub && ((msg._.msg || {}).opt || {}).cert))){ - SEA.opt.pack(msg.put, packed => { - SEA.sign(packed, (user._).sea, async function(data) { - if (u === data) return no(SEA.err || 'Signature fail.') - msg.put[':'] = {':': tmp = SEA.opt.unpack(data.m), '~': data.s} - msg.put['='] = tmp - - // if writing to own graph, just allow it - if (pub === user.is.pub) { - if (tmp = link_is(val)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 - JSON.stringifyAsync(msg.put[':'], function(err,s){ - if(err){ return no(err || "Stringify error.") } - msg.put[':'] = s; - return eve.to.next(msg); - }) - return - } - - // if writing to other's graph, check if cert exists then try to inject cert into put, also inject self pub so that everyone can verify the put - if (pub !== user.is.pub && ((msg._.msg || {}).opt || {}).cert) { - const cert = await S.parse(msg._.msg.opt.cert) - // even if cert exists, we must verify it - if (cert && cert.m && cert.s) - verify(cert, user.is.pub, _ => { - msg.put[':']['+'] = cert // '+' is a certificate - msg.put[':']['*'] = user.is.pub // '*' is pub of the user who puts - JSON.stringifyAsync(msg.put[':'], function(err,s){ - if(err){ return no(err || "Stringify error.") } - msg.put[':'] = s; - return eve.to.next(msg); - }) - return - }) - } - }, {raw: 1}) - }) - return; - } - - SEA.opt.pack(msg.put, packed => { - SEA.verify(packed, raw['*'] || pub, function(data){ var tmp; - data = SEA.opt.unpack(data); - if (u === data) return no("Unverified data.") // make sure the signature matches the account it claims to be on. // reject any updates that are signed with a mismatched account. - if ((tmp = link_is(data)) && pub === SEA.opt.pub(tmp)) (at.sea.own[tmp] = at.sea.own[tmp] || {})[pub] = 1 - - // check if cert ('+') and putter's pub ('*') exist - if (raw['+'] && raw['+']['m'] && raw['+']['s'] && raw['*']) - // now verify certificate - verify(raw['+'], raw['*'], _ => { - msg.put['='] = data; - return eve.to.next(msg); - }) - else { - msg.put['='] = data; - return eve.to.next(msg); - } - }); - }) - return - }; - check.any = function(eve, msg, val, key, soul, at, no, user){ var tmp, pub; - if(at.opt.secure){ return no("Soul missing public key at '" + key + "'.") } - // TODO: Ask community if should auto-sign non user-graph data. - at.on('secure', function(msg){ this.off(); - if(!at.opt.secure){ return eve.to.next(msg) } - no("Data cannot be changed."); - }).on.on('secure', msg); - return; - } - - var valid = Gun.valid, link_is = function(d,l){ return 'string' == typeof (l = valid(d)) && l }, state_ify = (Gun.state||'').ify; - - var pubcut = /[^\w_-]/; // anything not alphanumeric or _ - - SEA.opt.pub = function(s){ - if(!s){ return } - s = s.split('~'); - if(!s || !(s = s[1])){ return } - s = s.split(pubcut).slice(0,2); - if(!s || 2 != s.length){ return } - if('@' === (s[0]||'')[0]){ return } - s = s.slice(0,2).join('.'); - return s; - } - SEA.opt.stringy = function(t){ - // TODO: encrypt etc. need to check string primitive. Make as breaking change. - } - SEA.opt.pack = function(d,cb,k, n,s){ var tmp, f; // pack for verifying - if(SEA.opt.check(d)){ return cb(d) } - if(d && d['#'] && d['.'] && d['>']){ tmp = d[':']; f = 1 } - JSON.parseAsync(f? tmp : d, function(err, meta){ - var sig = ((u !== (meta||'')[':']) && (meta||'')['~']); // or just ~ check? - if(!sig){ cb(d); return } - cb({m: {'#':s||d['#'],'.':k||d['.'],':':(meta||'')[':'],'>':d['>']||Gun.state.is(n, k)}, s: sig}); - }); - } - var O = SEA.opt; - SEA.opt.unpack = function(d, k, n){ var tmp; - if(u === d){ return } - if(d && (u !== (tmp = d[':']))){ return tmp } - k = k || O.fall_key; if(!n && O.fall_val){ n = {}; n[k] = O.fall_val } - if(!k || !n){ return } - if(d === n[k]){ return d } - if(!SEA.opt.check(n[k])){ return d } - var soul = (n && n._ && n._['#']) || O.fall_soul, s = Gun.state.is(n, k) || O.fall_state; - if(d && 4 === d.length && soul === d[0] && k === d[1] && fl(s) === fl(d[3])){ - return d[2]; - } - if(s < SEA.opt.shuffle_attack){ - return d; - } - } - SEA.opt.shuffle_attack = 1546329600000; // Jan 1, 2019 - var fl = Math.floor; // TODO: Still need to fix inconsistent state issue. - // TODO: Potential bug? If pub/priv key starts with `-`? IDK how possible. - - })(USE, './index'); -}()); diff --git a/assets/static/simplemde.min.css b/assets/static/simplemde.min.css deleted file mode 100644 index d62f4d7..0000000 --- a/assets/static/simplemde.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/** - * simplemde v1.11.2 - * Copyright Next Step Webs, Inc. - * @link https://github.com/NextStepWebs/simplemde-markdown-editor - * @license MIT - */ -.CodeMirror{color:#000}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0!important;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;-moz-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-moz-keyframes blink{50%{background-color:transparent}}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-ruler{border-left:1px solid #ccc;position:absolute}.cm-s-default .cm-header{color:#00f}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:0;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;min-height:100%;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;vertical-align:top;margin-bottom:-30px}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:0 0!important;border:none!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;border-width:0;background:0 0;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent;-webkit-font-variant-ligatures:none;font-variant-ligatures:none}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-code{outline:0}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{-moz-box-sizing:content-box;box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected,.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:0 0}.CodeMirror{height:auto;min-height:300px;border:1px solid #ddd;border-bottom-left-radius:4px;border-bottom-right-radius:4px;padding:10px;font:inherit;z-index:1}.CodeMirror-scroll{min-height:300px}.CodeMirror-fullscreen{background:#fff;position:fixed!important;top:50px;left:0;right:0;bottom:0;height:auto;z-index:9}.CodeMirror-sided{width:50%!important}.editor-toolbar{position:relative;opacity:.6;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;padding:0 10px;border-top:1px solid #bbb;border-left:1px solid #bbb;border-right:1px solid #bbb;border-top-left-radius:4px;border-top-right-radius:4px}.editor-toolbar:after,.editor-toolbar:before{display:block;content:' ';height:1px}.editor-toolbar:before{margin-bottom:8px}.editor-toolbar:after{margin-top:8px}.editor-toolbar:hover,.editor-wrapper input.title:focus,.editor-wrapper input.title:hover{opacity:.8}.editor-toolbar.fullscreen{width:100%;height:50px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;padding-top:10px;padding-bottom:10px;box-sizing:border-box;background:#fff;border:0;position:fixed;top:0;left:0;opacity:1;z-index:9}.editor-toolbar.fullscreen::before{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,1)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);background:linear-gradient(to right,rgba(255,255,255,1) 0,rgba(255,255,255,0) 100%);position:fixed;top:0;left:0;margin:0;padding:0}.editor-toolbar.fullscreen::after{width:20px;height:50px;background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,0)),color-stop(100%,rgba(255,255,255,1)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,1) 100%);position:fixed;top:0;right:0;margin:0;padding:0}.editor-toolbar a{display:inline-block;text-align:center;text-decoration:none!important;color:#2c3e50!important;width:30px;height:30px;margin:0;border:1px solid transparent;border-radius:3px;cursor:pointer}.editor-toolbar a.active,.editor-toolbar a:hover{background:#fcfcfc;border-color:#95a5a6}.editor-toolbar a:before{line-height:30px}.editor-toolbar i.separator{display:inline-block;width:0;border-left:1px solid #d9d9d9;border-right:1px solid #fff;color:transparent;text-indent:-10px;margin:0 6px}.editor-toolbar a.fa-header-x:after{font-family:Arial,"Helvetica Neue",Helvetica,sans-serif;font-size:65%;vertical-align:text-bottom;position:relative;top:2px}.editor-toolbar a.fa-header-1:after{content:"1"}.editor-toolbar a.fa-header-2:after{content:"2"}.editor-toolbar a.fa-header-3:after{content:"3"}.editor-toolbar a.fa-header-bigger:after{content:"▲"}.editor-toolbar a.fa-header-smaller:after{content:"▼"}.editor-toolbar.disabled-for-preview a:not(.no-disable){pointer-events:none;background:#fff;border-color:transparent;text-shadow:inherit}@media only screen and (max-width:700px){.editor-toolbar a.no-mobile{display:none}}.editor-statusbar{padding:8px 10px;font-size:12px;color:#959694;text-align:right}.editor-statusbar span{display:inline-block;min-width:4em;margin-left:1em}.editor-preview,.editor-preview-side{padding:10px;background:#fafafa;overflow:auto;display:none;box-sizing:border-box}.editor-statusbar .lines:before{content:'lines: '}.editor-statusbar .words:before{content:'words: '}.editor-statusbar .characters:before{content:'characters: '}.editor-preview{position:absolute;width:100%;height:100%;top:0;left:0;z-index:7}.editor-preview-side{position:fixed;bottom:0;width:50%;top:50px;right:0;z-index:9;border:1px solid #ddd}.editor-preview-active,.editor-preview-active-side{display:block}.editor-preview-side>p,.editor-preview>p{margin-top:0}.editor-preview pre,.editor-preview-side pre{background:#eee;margin-bottom:10px}.editor-preview table td,.editor-preview table th,.editor-preview-side table td,.editor-preview-side table th{border:1px solid #ddd;padding:5px}.CodeMirror .CodeMirror-code .cm-tag{color:#63a35c}.CodeMirror .CodeMirror-code .cm-attribute{color:#795da3}.CodeMirror .CodeMirror-code .cm-string{color:#183691}.CodeMirror .CodeMirror-selected{background:#d9d9d9}.CodeMirror .CodeMirror-code .cm-header-1{font-size:200%;line-height:200%}.CodeMirror .CodeMirror-code .cm-header-2{font-size:160%;line-height:160%}.CodeMirror .CodeMirror-code .cm-header-3{font-size:125%;line-height:125%}.CodeMirror .CodeMirror-code .cm-header-4{font-size:110%;line-height:110%}.CodeMirror .CodeMirror-code .cm-comment{background:rgba(0,0,0,.05);border-radius:2px}.CodeMirror .CodeMirror-code .cm-link{color:#7f8c8d}.CodeMirror .CodeMirror-code .cm-url{color:#aab2b3}.CodeMirror .CodeMirror-code .cm-strikethrough{text-decoration:line-through}.CodeMirror .CodeMirror-placeholder{opacity:.5}.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){background:rgba(255,0,0,.15)} \ No newline at end of file diff --git a/assets/static/simplemde.min.js b/assets/static/simplemde.min.js deleted file mode 100644 index 50c624f..0000000 --- a/assets/static/simplemde.min.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * simplemde v1.11.2 - * Copyright Next Step Webs, Inc. - * @link https://github.com/NextStepWebs/simplemde-markdown-editor - * @license MIT - */ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.SimpleMDE=e()}}(function(){var e;return function t(e,n,r){function i(a,l){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!l&&s)return s(a,!0);if(o)return o(a,!0);var c=new Error("Cannot find module '"+a+"'");throw c.code="MODULE_NOT_FOUND",c}var u=n[a]={exports:{}};e[a][0].call(u.exports,function(t){var n=e[a][1][t];return i(n?n:t)},u,u.exports,t,e,n,r)}return n[a].exports}for(var o="function"==typeof require&&require,a=0;at;++t)s[t]=e[t],c[e.charCodeAt(t)]=t;c["-".charCodeAt(0)]=62,c["_".charCodeAt(0)]=63}function i(e){var t,n,r,i,o,a,l=e.length;if(l%4>0)throw new Error("Invalid string. Length must be a multiple of 4");o="="===e[l-2]?2:"="===e[l-1]?1:0,a=new u(3*l/4-o),r=o>0?l-4:l;var s=0;for(t=0,n=0;r>t;t+=4,n+=3)i=c[e.charCodeAt(t)]<<18|c[e.charCodeAt(t+1)]<<12|c[e.charCodeAt(t+2)]<<6|c[e.charCodeAt(t+3)],a[s++]=i>>16&255,a[s++]=i>>8&255,a[s++]=255&i;return 2===o?(i=c[e.charCodeAt(t)]<<2|c[e.charCodeAt(t+1)]>>4,a[s++]=255&i):1===o&&(i=c[e.charCodeAt(t)]<<10|c[e.charCodeAt(t+1)]<<4|c[e.charCodeAt(t+2)]>>2,a[s++]=i>>8&255,a[s++]=255&i),a}function o(e){return s[e>>18&63]+s[e>>12&63]+s[e>>6&63]+s[63&e]}function a(e,t,n){for(var r,i=[],a=t;n>a;a+=3)r=(e[a]<<16)+(e[a+1]<<8)+e[a+2],i.push(o(r));return i.join("")}function l(e){for(var t,n=e.length,r=n%3,i="",o=[],l=16383,c=0,u=n-r;u>c;c+=l)o.push(a(e,c,c+l>u?u:c+l));return 1===r?(t=e[n-1],i+=s[t>>2],i+=s[t<<4&63],i+="=="):2===r&&(t=(e[n-2]<<8)+e[n-1],i+=s[t>>10],i+=s[t>>4&63],i+=s[t<<2&63],i+="="),o.push(i),o.join("")}n.toByteArray=i,n.fromByteArray=l;var s=[],c=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array;r()},{}],2:[function(e,t,n){},{}],3:[function(e,t,n){(function(t){"use strict";function r(){try{var e=new Uint8Array(1);return e.foo=function(){return 42},42===e.foo()&&"function"==typeof e.subarray&&0===e.subarray(1,1).byteLength}catch(t){return!1}}function i(){return a.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function o(e,t){if(i()=t?o(e,t):void 0!==n?"string"==typeof r?o(e,t).fill(n,r):o(e,t).fill(n):o(e,t)}function u(e,t){if(s(t),e=o(e,0>t?0:0|m(t)),!a.TYPED_ARRAY_SUPPORT)for(var n=0;t>n;n++)e[n]=0;return e}function f(e,t,n){if("string"==typeof n&&""!==n||(n="utf8"),!a.isEncoding(n))throw new TypeError('"encoding" must be a valid string encoding');var r=0|v(t,n);return e=o(e,r),e.write(t,n),e}function h(e,t){var n=0|m(t.length);e=o(e,n);for(var r=0;n>r;r+=1)e[r]=255&t[r];return e}function d(e,t,n,r){if(t.byteLength,0>n||t.byteLength=i())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+i().toString(16)+" bytes");return 0|e}function g(e){return+e!=e&&(e=0),a.alloc(+e)}function v(e,t){if(a.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;"string"!=typeof e&&(e=""+e);var n=e.length;if(0===n)return 0;for(var r=!1;;)switch(t){case"ascii":case"binary":case"raw":case"raws":return n;case"utf8":case"utf-8":case void 0:return q(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return $(e).length;default:if(r)return q(e).length;t=(""+t).toLowerCase(),r=!0}}function y(e,t,n){var r=!1;if((void 0===t||0>t)&&(t=0),t>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),0>=n)return"";if(n>>>=0,t>>>=0,t>=n)return"";for(e||(e="utf8");;)switch(e){case"hex":return I(this,t,n);case"utf8":case"utf-8":return N(this,t,n);case"ascii":return E(this,t,n);case"binary":return O(this,t,n);case"base64":return M(this,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return P(this,t,n);default:if(r)throw new TypeError("Unknown encoding: "+e);e=(e+"").toLowerCase(),r=!0}}function x(e,t,n){var r=e[t];e[t]=e[n],e[n]=r}function b(e,t,n,r){function i(e,t){return 1===o?e[t]:e.readUInt16BE(t*o)}var o=1,a=e.length,l=t.length;if(void 0!==r&&(r=String(r).toLowerCase(),"ucs2"===r||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;o=2,a/=2,l/=2,n/=2}for(var s=-1,c=0;a>n+c;c++)if(i(e,n+c)===i(t,-1===s?0:c-s)){if(-1===s&&(s=c),c-s+1===l)return(n+s)*o}else-1!==s&&(c-=c-s),s=-1;return-1}function w(e,t,n,r){n=Number(n)||0;var i=e.length-n;r?(r=Number(r),r>i&&(r=i)):r=i;var o=t.length;if(o%2!==0)throw new Error("Invalid hex string");r>o/2&&(r=o/2);for(var a=0;r>a;a++){var l=parseInt(t.substr(2*a,2),16);if(isNaN(l))return a;e[n+a]=l}return a}function k(e,t,n,r){return V(q(t,e.length-n),e,n,r)}function S(e,t,n,r){return V(G(t),e,n,r)}function C(e,t,n,r){return S(e,t,n,r)}function L(e,t,n,r){return V($(t),e,n,r)}function T(e,t,n,r){return V(Y(t,e.length-n),e,n,r)}function M(e,t,n){return 0===t&&n===e.length?X.fromByteArray(e):X.fromByteArray(e.slice(t,n))}function N(e,t,n){n=Math.min(e.length,n);for(var r=[],i=t;n>i;){var o=e[i],a=null,l=o>239?4:o>223?3:o>191?2:1;if(n>=i+l){var s,c,u,f;switch(l){case 1:128>o&&(a=o);break;case 2:s=e[i+1],128===(192&s)&&(f=(31&o)<<6|63&s,f>127&&(a=f));break;case 3:s=e[i+1],c=e[i+2],128===(192&s)&&128===(192&c)&&(f=(15&o)<<12|(63&s)<<6|63&c,f>2047&&(55296>f||f>57343)&&(a=f));break;case 4:s=e[i+1],c=e[i+2],u=e[i+3],128===(192&s)&&128===(192&c)&&128===(192&u)&&(f=(15&o)<<18|(63&s)<<12|(63&c)<<6|63&u,f>65535&&1114112>f&&(a=f))}}null===a?(a=65533,l=1):a>65535&&(a-=65536,r.push(a>>>10&1023|55296),a=56320|1023&a),r.push(a),i+=l}return A(r)}function A(e){var t=e.length;if(Q>=t)return String.fromCharCode.apply(String,e);for(var n="",r=0;t>r;)n+=String.fromCharCode.apply(String,e.slice(r,r+=Q));return n}function E(e,t,n){var r="";n=Math.min(e.length,n);for(var i=t;n>i;i++)r+=String.fromCharCode(127&e[i]);return r}function O(e,t,n){var r="";n=Math.min(e.length,n);for(var i=t;n>i;i++)r+=String.fromCharCode(e[i]);return r}function I(e,t,n){var r=e.length;(!t||0>t)&&(t=0),(!n||0>n||n>r)&&(n=r);for(var i="",o=t;n>o;o++)i+=U(e[o]);return i}function P(e,t,n){for(var r=e.slice(t,n),i="",o=0;oe)throw new RangeError("offset is not uint");if(e+t>n)throw new RangeError("Trying to access beyond buffer length")}function D(e,t,n,r,i,o){if(!a.isBuffer(e))throw new TypeError('"buffer" argument must be a Buffer instance');if(t>i||o>t)throw new RangeError('"value" argument is out of bounds');if(n+r>e.length)throw new RangeError("Index out of range")}function H(e,t,n,r){0>t&&(t=65535+t+1);for(var i=0,o=Math.min(e.length-n,2);o>i;i++)e[n+i]=(t&255<<8*(r?i:1-i))>>>8*(r?i:1-i)}function W(e,t,n,r){0>t&&(t=4294967295+t+1);for(var i=0,o=Math.min(e.length-n,4);o>i;i++)e[n+i]=t>>>8*(r?i:3-i)&255}function B(e,t,n,r,i,o){if(n+r>e.length)throw new RangeError("Index out of range");if(0>n)throw new RangeError("Index out of range")}function _(e,t,n,r,i){return i||B(e,t,n,4,3.4028234663852886e38,-3.4028234663852886e38),Z.write(e,t,n,r,23,4),n+4}function F(e,t,n,r,i){return i||B(e,t,n,8,1.7976931348623157e308,-1.7976931348623157e308),Z.write(e,t,n,r,52,8),n+8}function z(e){if(e=j(e).replace(ee,""),e.length<2)return"";for(;e.length%4!==0;)e+="=";return e}function j(e){return e.trim?e.trim():e.replace(/^\s+|\s+$/g,"")}function U(e){return 16>e?"0"+e.toString(16):e.toString(16)}function q(e,t){t=t||1/0;for(var n,r=e.length,i=null,o=[],a=0;r>a;a++){if(n=e.charCodeAt(a),n>55295&&57344>n){if(!i){if(n>56319){(t-=3)>-1&&o.push(239,191,189);continue}if(a+1===r){(t-=3)>-1&&o.push(239,191,189);continue}i=n;continue}if(56320>n){(t-=3)>-1&&o.push(239,191,189),i=n;continue}n=(i-55296<<10|n-56320)+65536}else i&&(t-=3)>-1&&o.push(239,191,189);if(i=null,128>n){if((t-=1)<0)break;o.push(n)}else if(2048>n){if((t-=2)<0)break;o.push(n>>6|192,63&n|128)}else if(65536>n){if((t-=3)<0)break;o.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(1114112>n))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return o}function G(e){for(var t=[],n=0;n>8,i=n%256,o.push(i),o.push(r);return o}function $(e){return X.toByteArray(z(e))}function V(e,t,n,r){for(var i=0;r>i&&!(i+n>=t.length||i>=e.length);i++)t[i+n]=e[i];return i}function K(e){return e!==e}var X=e("base64-js"),Z=e("ieee754"),J=e("isarray");n.Buffer=a,n.SlowBuffer=g,n.INSPECT_MAX_BYTES=50,a.TYPED_ARRAY_SUPPORT=void 0!==t.TYPED_ARRAY_SUPPORT?t.TYPED_ARRAY_SUPPORT:r(),n.kMaxLength=i(),a.poolSize=8192,a._augment=function(e){return e.__proto__=a.prototype,e},a.from=function(e,t,n){return l(null,e,t,n)},a.TYPED_ARRAY_SUPPORT&&(a.prototype.__proto__=Uint8Array.prototype,a.__proto__=Uint8Array,"undefined"!=typeof Symbol&&Symbol.species&&a[Symbol.species]===a&&Object.defineProperty(a,Symbol.species,{value:null,configurable:!0})),a.alloc=function(e,t,n){return c(null,e,t,n)},a.allocUnsafe=function(e){return u(null,e)},a.allocUnsafeSlow=function(e){return u(null,e)},a.isBuffer=function(e){return!(null==e||!e._isBuffer)},a.compare=function(e,t){if(!a.isBuffer(e)||!a.isBuffer(t))throw new TypeError("Arguments must be Buffers");if(e===t)return 0;for(var n=e.length,r=t.length,i=0,o=Math.min(n,r);o>i;++i)if(e[i]!==t[i]){n=e[i],r=t[i];break}return r>n?-1:n>r?1:0},a.isEncoding=function(e){switch(String(e).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"raw":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},a.concat=function(e,t){if(!J(e))throw new TypeError('"list" argument must be an Array of Buffers');if(0===e.length)return a.alloc(0);var n;if(void 0===t)for(t=0,n=0;nt;t+=2)x(this,t,t+1);return this},a.prototype.swap32=function(){var e=this.length;if(e%4!==0)throw new RangeError("Buffer size must be a multiple of 32-bits");for(var t=0;e>t;t+=4)x(this,t,t+3),x(this,t+1,t+2);return this},a.prototype.toString=function(){var e=0|this.length;return 0===e?"":0===arguments.length?N(this,0,e):y.apply(this,arguments)},a.prototype.equals=function(e){if(!a.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e?!0:0===a.compare(this,e)},a.prototype.inspect=function(){var e="",t=n.INSPECT_MAX_BYTES;return this.length>0&&(e=this.toString("hex",0,t).match(/.{2}/g).join(" "),this.length>t&&(e+=" ... ")),""},a.prototype.compare=function(e,t,n,r,i){if(!a.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===t&&(t=0),void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===i&&(i=this.length),0>t||n>e.length||0>r||i>this.length)throw new RangeError("out of range index");if(r>=i&&t>=n)return 0;if(r>=i)return-1;if(t>=n)return 1;if(t>>>=0,n>>>=0,r>>>=0,i>>>=0,this===e)return 0;for(var o=i-r,l=n-t,s=Math.min(o,l),c=this.slice(r,i),u=e.slice(t,n),f=0;s>f;++f)if(c[f]!==u[f]){o=c[f],l=u[f];break}return l>o?-1:o>l?1:0},a.prototype.indexOf=function(e,t,n){if("string"==typeof t?(n=t,t=0):t>2147483647?t=2147483647:-2147483648>t&&(t=-2147483648),t>>=0,0===this.length)return-1;if(t>=this.length)return-1;if(0>t&&(t=Math.max(this.length+t,0)),"string"==typeof e&&(e=a.from(e,n)),a.isBuffer(e))return 0===e.length?-1:b(this,e,t,n);if("number"==typeof e)return a.TYPED_ARRAY_SUPPORT&&"function"===Uint8Array.prototype.indexOf?Uint8Array.prototype.indexOf.call(this,e,t):b(this,[e],t,n);throw new TypeError("val must be string, number or Buffer")},a.prototype.includes=function(e,t,n){return-1!==this.indexOf(e,t,n)},a.prototype.write=function(e,t,n,r){if(void 0===t)r="utf8",n=this.length,t=0;else if(void 0===n&&"string"==typeof t)r=t,n=this.length,t=0;else{if(!isFinite(t))throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");t=0|t,isFinite(n)?(n=0|n,void 0===r&&(r="utf8")):(r=n,n=void 0)}var i=this.length-t;if((void 0===n||n>i)&&(n=i),e.length>0&&(0>n||0>t)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var o=!1;;)switch(r){case"hex":return w(this,e,t,n);case"utf8":case"utf-8":return k(this,e,t,n);case"ascii":return S(this,e,t,n);case"binary":return C(this,e,t,n);case"base64":return L(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return T(this,e,t,n);default:if(o)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),o=!0}},a.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var Q=4096;a.prototype.slice=function(e,t){var n=this.length;e=~~e,t=void 0===t?n:~~t,0>e?(e+=n,0>e&&(e=0)):e>n&&(e=n),0>t?(t+=n,0>t&&(t=0)):t>n&&(t=n),e>t&&(t=e);var r;if(a.TYPED_ARRAY_SUPPORT)r=this.subarray(e,t),r.__proto__=a.prototype;else{var i=t-e;r=new a(i,void 0);for(var o=0;i>o;o++)r[o]=this[o+e]}return r},a.prototype.readUIntLE=function(e,t,n){e=0|e,t=0|t,n||R(e,t,this.length);for(var r=this[e],i=1,o=0;++o0&&(i*=256);)r+=this[e+--t]*i;return r},a.prototype.readUInt8=function(e,t){return t||R(e,1,this.length),this[e]},a.prototype.readUInt16LE=function(e,t){return t||R(e,2,this.length),this[e]|this[e+1]<<8},a.prototype.readUInt16BE=function(e,t){return t||R(e,2,this.length),this[e]<<8|this[e+1]},a.prototype.readUInt32LE=function(e,t){return t||R(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},a.prototype.readUInt32BE=function(e,t){return t||R(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},a.prototype.readIntLE=function(e,t,n){e=0|e,t=0|t,n||R(e,t,this.length);for(var r=this[e],i=1,o=0;++o=i&&(r-=Math.pow(2,8*t)),r},a.prototype.readIntBE=function(e,t,n){e=0|e,t=0|t,n||R(e,t,this.length);for(var r=t,i=1,o=this[e+--r];r>0&&(i*=256);)o+=this[e+--r]*i;return i*=128,o>=i&&(o-=Math.pow(2,8*t)),o},a.prototype.readInt8=function(e,t){return t||R(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},a.prototype.readInt16LE=function(e,t){t||R(e,2,this.length);var n=this[e]|this[e+1]<<8;return 32768&n?4294901760|n:n},a.prototype.readInt16BE=function(e,t){t||R(e,2,this.length);var n=this[e+1]|this[e]<<8;return 32768&n?4294901760|n:n},a.prototype.readInt32LE=function(e,t){return t||R(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},a.prototype.readInt32BE=function(e,t){return t||R(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},a.prototype.readFloatLE=function(e,t){return t||R(e,4,this.length),Z.read(this,e,!0,23,4)},a.prototype.readFloatBE=function(e,t){return t||R(e,4,this.length),Z.read(this,e,!1,23,4)},a.prototype.readDoubleLE=function(e,t){return t||R(e,8,this.length),Z.read(this,e,!0,52,8)},a.prototype.readDoubleBE=function(e,t){return t||R(e,8,this.length),Z.read(this,e,!1,52,8)},a.prototype.writeUIntLE=function(e,t,n,r){if(e=+e,t=0|t,n=0|n,!r){var i=Math.pow(2,8*n)-1;D(this,e,t,n,i,0)}var o=1,a=0;for(this[t]=255&e;++a=0&&(a*=256);)this[t+o]=e/a&255;return t+n},a.prototype.writeUInt8=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,1,255,0),a.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&e,t+1},a.prototype.writeUInt16LE=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,2,65535,0),a.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):H(this,e,t,!0),t+2},a.prototype.writeUInt16BE=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,2,65535,0),a.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):H(this,e,t,!1),t+2},a.prototype.writeUInt32LE=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,4,4294967295,0),a.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):W(this,e,t,!0),t+4},a.prototype.writeUInt32BE=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,4,4294967295,0),a.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):W(this,e,t,!1),t+4},a.prototype.writeIntLE=function(e,t,n,r){if(e=+e,t=0|t,!r){var i=Math.pow(2,8*n-1);D(this,e,t,n,i-1,-i)}var o=0,a=1,l=0;for(this[t]=255&e;++oe&&0===l&&0!==this[t+o-1]&&(l=1),this[t+o]=(e/a>>0)-l&255;return t+n},a.prototype.writeIntBE=function(e,t,n,r){if(e=+e,t=0|t,!r){var i=Math.pow(2,8*n-1);D(this,e,t,n,i-1,-i)}var o=n-1,a=1,l=0;for(this[t+o]=255&e;--o>=0&&(a*=256);)0>e&&0===l&&0!==this[t+o+1]&&(l=1),this[t+o]=(e/a>>0)-l&255;return t+n},a.prototype.writeInt8=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,1,127,-128),a.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),0>e&&(e=255+e+1),this[t]=255&e,t+1},a.prototype.writeInt16LE=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,2,32767,-32768),a.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):H(this,e,t,!0),t+2},a.prototype.writeInt16BE=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,2,32767,-32768),a.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):H(this,e,t,!1),t+2},a.prototype.writeInt32LE=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,4,2147483647,-2147483648),a.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):W(this,e,t,!0),t+4},a.prototype.writeInt32BE=function(e,t,n){return e=+e,t=0|t,n||D(this,e,t,4,2147483647,-2147483648),0>e&&(e=4294967295+e+1),a.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):W(this,e,t,!1),t+4},a.prototype.writeFloatLE=function(e,t,n){return _(this,e,t,!0,n)},a.prototype.writeFloatBE=function(e,t,n){return _(this,e,t,!1,n)},a.prototype.writeDoubleLE=function(e,t,n){return F(this,e,t,!0,n)},a.prototype.writeDoubleBE=function(e,t,n){return F(this,e,t,!1,n)},a.prototype.copy=function(e,t,n,r){if(n||(n=0),r||0===r||(r=this.length),t>=e.length&&(t=e.length),t||(t=0),r>0&&n>r&&(r=n),r===n)return 0;if(0===e.length||0===this.length)return 0;if(0>t)throw new RangeError("targetStart out of bounds");if(0>n||n>=this.length)throw new RangeError("sourceStart out of bounds");if(0>r)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),e.length-tn&&r>t)for(i=o-1;i>=0;i--)e[i+t]=this[i+n];else if(1e3>o||!a.TYPED_ARRAY_SUPPORT)for(i=0;o>i;i++)e[i+t]=this[i+n];else Uint8Array.prototype.set.call(e,this.subarray(n,n+o),t);return o},a.prototype.fill=function(e,t,n,r){if("string"==typeof e){if("string"==typeof t?(r=t,t=0,n=this.length):"string"==typeof n&&(r=n,n=this.length),1===e.length){var i=e.charCodeAt(0);256>i&&(e=i)}if(void 0!==r&&"string"!=typeof r)throw new TypeError("encoding must be a string");if("string"==typeof r&&!a.isEncoding(r))throw new TypeError("Unknown encoding: "+r)}else"number"==typeof e&&(e=255&e);if(0>t||this.length=n)return this;t>>>=0,n=void 0===n?this.length:n>>>0,e||(e=0);var o;if("number"==typeof e)for(o=t;n>o;o++)this[o]=e;else{var l=a.isBuffer(e)?e:q(new a(e,r).toString()),s=l.length;for(o=0;n-t>o;o++)this[o+t]=l[o%s]}return this};var ee=/[^+\/0-9A-Za-z-_]/g}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"base64-js":1,ieee754:15,isarray:16}],4:[function(e,t,n){"use strict";function r(e){return e=e||{},"function"!=typeof e.codeMirrorInstance||"function"!=typeof e.codeMirrorInstance.defineMode?void console.log("CodeMirror Spell Checker: You must provide an instance of CodeMirror via the option `codeMirrorInstance`"):(String.prototype.includes||(String.prototype.includes=function(){return-1!==String.prototype.indexOf.apply(this,arguments)}),void e.codeMirrorInstance.defineMode("spell-checker",function(t){if(!r.aff_loading){r.aff_loading=!0;var n=new XMLHttpRequest;n.open("GET","https://cdn.jsdelivr.net/codemirror.spell-checker/latest/en_US.aff",!0),n.onload=function(){4===n.readyState&&200===n.status&&(r.aff_data=n.responseText,r.num_loaded++,2==r.num_loaded&&(r.typo=new i("en_US",r.aff_data,r.dic_data,{platform:"any"})))},n.send(null)}if(!r.dic_loading){r.dic_loading=!0;var o=new XMLHttpRequest;o.open("GET","https://cdn.jsdelivr.net/codemirror.spell-checker/latest/en_US.dic",!0),o.onload=function(){4===o.readyState&&200===o.status&&(r.dic_data=o.responseText,r.num_loaded++,2==r.num_loaded&&(r.typo=new i("en_US",r.aff_data,r.dic_data,{platform:"any"})))},o.send(null)}var a='!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~ ',l={token:function(e){var t=e.peek(),n="";if(a.includes(t))return e.next(),null;for(;null!=(t=e.peek())&&!a.includes(t);)n+=t,e.next();return r.typo&&!r.typo.check(n)?"spell-error":null}},s=e.codeMirrorInstance.getMode(t,t.backdrop||"text/plain");return e.codeMirrorInstance.overlayMode(s,l,!0)}))}var i=e("typo-js");r.num_loaded=0,r.aff_loading=!1,r.dic_loading=!1,r.aff_data="",r.dic_data="",r.typo,t.exports=r},{"typo-js":18}],5:[function(t,n,r){!function(i){"object"==typeof r&&"object"==typeof n?i(t("../../lib/codemirror")):"function"==typeof e&&e.amd?e(["../../lib/codemirror"],i):i(CodeMirror)}(function(e){"use strict";function t(e){var t=e.getWrapperElement();e.state.fullScreenRestore={scrollTop:window.pageYOffset,scrollLeft:window.pageXOffset,width:t.style.width,height:t.style.height},t.style.width="",t.style.height="auto",t.className+=" CodeMirror-fullscreen",document.documentElement.style.overflow="hidden",e.refresh()}function n(e){var t=e.getWrapperElement();t.className=t.className.replace(/\s*CodeMirror-fullscreen\b/,""),document.documentElement.style.overflow="";var n=e.state.fullScreenRestore;t.style.width=n.width,t.style.height=n.height,window.scrollTo(n.scrollLeft,n.scrollTop),e.refresh()}e.defineOption("fullScreen",!1,function(r,i,o){o==e.Init&&(o=!1),!o!=!i&&(i?t(r):n(r))})})},{"../../lib/codemirror":10}],6:[function(t,n,r){!function(i){"object"==typeof r&&"object"==typeof n?i(t("../../lib/codemirror")):"function"==typeof e&&e.amd?e(["../../lib/codemirror"],i):i(CodeMirror)}(function(e){function t(e){e.state.placeholder&&(e.state.placeholder.parentNode.removeChild(e.state.placeholder),e.state.placeholder=null)}function n(e){t(e);var n=e.state.placeholder=document.createElement("pre");n.style.cssText="height: 0; overflow: visible",n.className="CodeMirror-placeholder";var r=e.getOption("placeholder");"string"==typeof r&&(r=document.createTextNode(r)),n.appendChild(r),e.display.lineSpace.insertBefore(n,e.display.lineSpace.firstChild)}function r(e){o(e)&&n(e)}function i(e){var r=e.getWrapperElement(),i=o(e);r.className=r.className.replace(" CodeMirror-empty","")+(i?" CodeMirror-empty":""),i?n(e):t(e)}function o(e){return 1===e.lineCount()&&""===e.getLine(0)}e.defineOption("placeholder","",function(n,o,a){var l=a&&a!=e.Init;if(o&&!l)n.on("blur",r),n.on("change",i),n.on("swapDoc",i),i(n);else if(!o&&l){n.off("blur",r),n.off("change",i),n.off("swapDoc",i),t(n);var s=n.getWrapperElement();s.className=s.className.replace(" CodeMirror-empty","")}o&&!n.hasFocus()&&r(n)})})},{"../../lib/codemirror":10}],7:[function(t,n,r){!function(i){"object"==typeof r&&"object"==typeof n?i(t("../../lib/codemirror")):"function"==typeof e&&e.amd?e(["../../lib/codemirror"],i):i(CodeMirror)}(function(e){"use strict";var t=/^(\s*)(>[> ]*|[*+-]\s|(\d+)([.)]))(\s*)/,n=/^(\s*)(>[> ]*|[*+-]|(\d+)[.)])(\s*)$/,r=/[*+-]\s/;e.commands.newlineAndIndentContinueMarkdownList=function(i){if(i.getOption("disableInput"))return e.Pass;for(var o=i.listSelections(),a=[],l=0;l")>=0?d[2]:parseInt(d[3],10)+1+d[4];a[l]="\n"+p+g+m}}i.replaceSelections(a)}})},{"../../lib/codemirror":10}],8:[function(t,n,r){!function(i){"object"==typeof r&&"object"==typeof n?i(t("../../lib/codemirror")):"function"==typeof e&&e.amd?e(["../../lib/codemirror"],i):i(CodeMirror)}(function(e){"use strict";e.overlayMode=function(t,n,r){return{startState:function(){return{base:e.startState(t),overlay:e.startState(n),basePos:0,baseCur:null,overlayPos:0,overlayCur:null,streamSeen:null}},copyState:function(r){return{base:e.copyState(t,r.base),overlay:e.copyState(n,r.overlay),basePos:r.basePos,baseCur:null,overlayPos:r.overlayPos,overlayCur:null}},token:function(e,i){return(e!=i.streamSeen||Math.min(i.basePos,i.overlayPos)=n.line,d=h?n:s(f,0),p=e.markText(u,d,{className:o});if(null==r?i.push(p):i.splice(r++,0,p),h)break;a=f}}function i(e){for(var t=e.state.markedSelection,n=0;n1)return o(e);var t=e.getCursor("start"),n=e.getCursor("end"),a=e.state.markedSelection;if(!a.length)return r(e,t,n);var s=a[0].find(),u=a[a.length-1].find();if(!s||!u||n.line-t.line=0||c(n,s.from)<=0)return o(e);for(;c(t,s.from)>0;)a.shift().clear(),s=a[0].find();for(c(t,s.from)<0&&(s.to.line-t.line0&&(n.line-u.from.linebo&&setTimeout(function(){s.display.input.reset(!0)},20),jt(this),Ki(),bt(this),this.curOp.forceUpdate=!0,Xr(this,i),r.autofocus&&!Ao||s.hasFocus()?setTimeout(Bi(vn,this),20):yn(this);for(var u in ta)ta.hasOwnProperty(u)&&ta[u](this,r[u],na);k(this),r.finishInit&&r.finishInit(this);for(var f=0;fbo&&(r.gutters.style.zIndex=-1,r.scroller.style.paddingRight=0),wo||go&&Ao||(r.scroller.draggable=!0),e&&(e.appendChild?e.appendChild(r.wrapper):e(r.wrapper)),r.viewFrom=r.viewTo=t.first,r.reportedViewFrom=r.reportedViewTo=t.first,r.view=[],r.renderedView=null,r.externalMeasured=null,r.viewOffset=0,r.lastWrapHeight=r.lastWrapWidth=0,r.updateLineNumbers=null,r.nativeBarWidth=r.barHeight=r.barWidth=0,r.scrollbarsClipped=!1,r.lineNumWidth=r.lineNumInnerWidth=r.lineNumChars=null,r.alignWidgets=!1,r.cachedCharWidth=r.cachedTextHeight=r.cachedPaddingH=null, -r.maxLine=null,r.maxLineLength=0,r.maxLineChanged=!1,r.wheelDX=r.wheelDY=r.wheelStartX=r.wheelStartY=null,r.shift=!1,r.selForContextMenu=null,r.activeTouch=null,n.init(r)}function n(t){t.doc.mode=e.getMode(t.options,t.doc.modeOption),r(t)}function r(e){e.doc.iter(function(e){e.stateAfter&&(e.stateAfter=null),e.styles&&(e.styles=null)}),e.doc.frontier=e.doc.first,_e(e,100),e.state.modeGen++,e.curOp&&Dt(e)}function i(e){e.options.lineWrapping?(Ja(e.display.wrapper,"CodeMirror-wrap"),e.display.sizer.style.minWidth="",e.display.sizerWidth=null):(Za(e.display.wrapper,"CodeMirror-wrap"),h(e)),a(e),Dt(e),lt(e),setTimeout(function(){y(e)},100)}function o(e){var t=yt(e.display),n=e.options.lineWrapping,r=n&&Math.max(5,e.display.scroller.clientWidth/xt(e.display)-3);return function(i){if(kr(e.doc,i))return 0;var o=0;if(i.widgets)for(var a=0;at.maxLineLength&&(t.maxLineLength=n,t.maxLine=e)})}function d(e){var t=Pi(e.gutters,"CodeMirror-linenumbers");-1==t&&e.lineNumbers?e.gutters=e.gutters.concat(["CodeMirror-linenumbers"]):t>-1&&!e.lineNumbers&&(e.gutters=e.gutters.slice(0),e.gutters.splice(t,1))}function p(e){var t=e.display,n=t.gutters.offsetWidth,r=Math.round(e.doc.height+qe(e.display));return{clientHeight:t.scroller.clientHeight,viewHeight:t.wrapper.clientHeight,scrollWidth:t.scroller.scrollWidth,clientWidth:t.scroller.clientWidth,viewWidth:t.wrapper.clientWidth,barLeft:e.options.fixedGutter?n:0,docHeight:r,scrollHeight:r+Ye(e)+t.barHeight,nativeBarWidth:t.nativeBarWidth,gutterWidth:n}}function m(e,t,n){this.cm=n;var r=this.vert=ji("div",[ji("div",null,null,"min-width: 1px")],"CodeMirror-vscrollbar"),i=this.horiz=ji("div",[ji("div",null,null,"height: 100%; min-height: 1px")],"CodeMirror-hscrollbar");e(r),e(i),Ea(r,"scroll",function(){r.clientHeight&&t(r.scrollTop,"vertical")}),Ea(i,"scroll",function(){i.clientWidth&&t(i.scrollLeft,"horizontal")}),this.checkedZeroWidth=!1,xo&&8>bo&&(this.horiz.style.minHeight=this.vert.style.minWidth="18px")}function g(){}function v(t){t.display.scrollbars&&(t.display.scrollbars.clear(),t.display.scrollbars.addClass&&Za(t.display.wrapper,t.display.scrollbars.addClass)),t.display.scrollbars=new e.scrollbarModel[t.options.scrollbarStyle](function(e){t.display.wrapper.insertBefore(e,t.display.scrollbarFiller),Ea(e,"mousedown",function(){t.state.focused&&setTimeout(function(){t.display.input.focus()},0)}),e.setAttribute("cm-not-content","true")},function(e,n){"horizontal"==n?on(t,e):rn(t,e)},t),t.display.scrollbars.addClass&&Ja(t.display.wrapper,t.display.scrollbars.addClass)}function y(e,t){t||(t=p(e));var n=e.display.barWidth,r=e.display.barHeight;x(e,t);for(var i=0;4>i&&n!=e.display.barWidth||r!=e.display.barHeight;i++)n!=e.display.barWidth&&e.options.lineWrapping&&O(e),x(e,p(e)),n=e.display.barWidth,r=e.display.barHeight}function x(e,t){var n=e.display,r=n.scrollbars.update(t);n.sizer.style.paddingRight=(n.barWidth=r.right)+"px",n.sizer.style.paddingBottom=(n.barHeight=r.bottom)+"px",n.heightForcer.style.borderBottom=r.bottom+"px solid transparent",r.right&&r.bottom?(n.scrollbarFiller.style.display="block",n.scrollbarFiller.style.height=r.bottom+"px",n.scrollbarFiller.style.width=r.right+"px"):n.scrollbarFiller.style.display="",r.bottom&&e.options.coverGutterNextToScrollbar&&e.options.fixedGutter?(n.gutterFiller.style.display="block",n.gutterFiller.style.height=r.bottom+"px",n.gutterFiller.style.width=t.gutterWidth+"px"):n.gutterFiller.style.display=""}function b(e,t,n){var r=n&&null!=n.top?Math.max(0,n.top):e.scroller.scrollTop;r=Math.floor(r-Ue(e));var i=n&&null!=n.bottom?n.bottom:r+e.wrapper.clientHeight,o=ni(t,r),a=ni(t,i);if(n&&n.ensure){var l=n.ensure.from.line,s=n.ensure.to.line;o>l?(o=l,a=ni(t,ri(Zr(t,l))+e.wrapper.clientHeight)):Math.min(s,t.lastLine())>=a&&(o=ni(t,ri(Zr(t,s))-e.wrapper.clientHeight),a=s)}return{from:o,to:Math.max(a,o+1)}}function w(e){var t=e.display,n=t.view;if(t.alignWidgets||t.gutters.firstChild&&e.options.fixedGutter){for(var r=C(t)-t.scroller.scrollLeft+e.doc.scrollLeft,i=t.gutters.offsetWidth,o=r+"px",a=0;a=n.viewFrom&&t.visible.to<=n.viewTo&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo)&&n.renderedView==n.view&&0==zt(e))return!1;k(e)&&(Wt(e),t.dims=P(e));var i=r.first+r.size,o=Math.max(t.visible.from-e.options.viewportMargin,r.first),a=Math.min(i,t.visible.to+e.options.viewportMargin);n.viewFroma&&n.viewTo-a<20&&(a=Math.min(i,n.viewTo)),Wo&&(o=br(e.doc,o),a=wr(e.doc,a));var l=o!=n.viewFrom||a!=n.viewTo||n.lastWrapHeight!=t.wrapperHeight||n.lastWrapWidth!=t.wrapperWidth;Ft(e,o,a),n.viewOffset=ri(Zr(e.doc,n.viewFrom)),e.display.mover.style.top=n.viewOffset+"px";var s=zt(e);if(!l&&0==s&&!t.force&&n.renderedView==n.view&&(null==n.updateLineNumbers||n.updateLineNumbers>=n.viewTo))return!1;var c=Gi();return s>4&&(n.lineDiv.style.display="none"),R(e,n.updateLineNumbers,t.dims),s>4&&(n.lineDiv.style.display=""),n.renderedView=n.view,c&&Gi()!=c&&c.offsetHeight&&c.focus(),Ui(n.cursorDiv),Ui(n.selectionDiv),n.gutters.style.height=n.sizer.style.minHeight=0,l&&(n.lastWrapHeight=t.wrapperHeight,n.lastWrapWidth=t.wrapperWidth,_e(e,400)),n.updateLineNumbers=null,!0}function N(e,t){for(var n=t.viewport,r=!0;(r&&e.options.lineWrapping&&t.oldDisplayWidth!=$e(e)||(n&&null!=n.top&&(n={top:Math.min(e.doc.height+qe(e.display)-Ve(e),n.top)}),t.visible=b(e.display,e.doc,n),!(t.visible.from>=e.display.viewFrom&&t.visible.to<=e.display.viewTo)))&&M(e,t);r=!1){O(e);var i=p(e);Re(e),y(e,i),E(e,i)}t.signal(e,"update",e),e.display.viewFrom==e.display.reportedViewFrom&&e.display.viewTo==e.display.reportedViewTo||(t.signal(e,"viewportChange",e,e.display.viewFrom,e.display.viewTo),e.display.reportedViewFrom=e.display.viewFrom,e.display.reportedViewTo=e.display.viewTo)}function A(e,t){var n=new L(e,t);if(M(e,n)){O(e),N(e,n);var r=p(e);Re(e),y(e,r),E(e,r),n.finish()}}function E(e,t){e.display.sizer.style.minHeight=t.docHeight+"px",e.display.heightForcer.style.top=t.docHeight+"px",e.display.gutters.style.height=t.docHeight+e.display.barHeight+Ye(e)+"px"}function O(e){for(var t=e.display,n=t.lineDiv.offsetTop,r=0;rbo){var a=o.node.offsetTop+o.node.offsetHeight;i=a-n,n=a}else{var l=o.node.getBoundingClientRect();i=l.bottom-l.top}var s=o.line.height-i;if(2>i&&(i=yt(t)),(s>.001||-.001>s)&&(ei(o.line,i),I(o.line),o.rest))for(var c=0;c=t&&f.lineNumber;f.changes&&(Pi(f.changes,"gutter")>-1&&(h=!1),D(e,f,c,n)),h&&(Ui(f.lineNumber),f.lineNumber.appendChild(document.createTextNode(S(e.options,c)))),l=f.node.nextSibling}else{var d=U(e,f,c,n);a.insertBefore(d,l)}c+=f.size}for(;l;)l=r(l)}function D(e,t,n,r){for(var i=0;ibo&&(e.node.style.zIndex=2)),e.node}function W(e){var t=e.bgClass?e.bgClass+" "+(e.line.bgClass||""):e.line.bgClass;if(t&&(t+=" CodeMirror-linebackground"),e.background)t?e.background.className=t:(e.background.parentNode.removeChild(e.background),e.background=null);else if(t){var n=H(e);e.background=n.insertBefore(ji("div",null,t),n.firstChild)}}function B(e,t){var n=e.display.externalMeasured;return n&&n.line==t.line?(e.display.externalMeasured=null,t.measure=n.measure,n.built):Br(e,t)}function _(e,t){var n=t.text.className,r=B(e,t);t.text==t.node&&(t.node=r.pre),t.text.parentNode.replaceChild(r.pre,t.text),t.text=r.pre,r.bgClass!=t.bgClass||r.textClass!=t.textClass?(t.bgClass=r.bgClass,t.textClass=r.textClass,F(t)):n&&(t.text.className=n)}function F(e){W(e),e.line.wrapClass?H(e).className=e.line.wrapClass:e.node!=e.text&&(e.node.className="");var t=e.textClass?e.textClass+" "+(e.line.textClass||""):e.line.textClass;e.text.className=t||""}function z(e,t,n,r){if(t.gutter&&(t.node.removeChild(t.gutter),t.gutter=null),t.gutterBackground&&(t.node.removeChild(t.gutterBackground),t.gutterBackground=null),t.line.gutterClass){var i=H(t);t.gutterBackground=ji("div",null,"CodeMirror-gutter-background "+t.line.gutterClass,"left: "+(e.options.fixedGutter?r.fixedPos:-r.gutterTotalWidth)+"px; width: "+r.gutterTotalWidth+"px"),i.insertBefore(t.gutterBackground,t.text)}var o=t.line.gutterMarkers;if(e.options.lineNumbers||o){var i=H(t),a=t.gutter=ji("div",null,"CodeMirror-gutter-wrapper","left: "+(e.options.fixedGutter?r.fixedPos:-r.gutterTotalWidth)+"px");if(e.display.input.setUneditable(a),i.insertBefore(a,t.text),t.line.gutterClass&&(a.className+=" "+t.line.gutterClass),!e.options.lineNumbers||o&&o["CodeMirror-linenumbers"]||(t.lineNumber=a.appendChild(ji("div",S(e.options,n),"CodeMirror-linenumber CodeMirror-gutter-elt","left: "+r.gutterLeft["CodeMirror-linenumbers"]+"px; width: "+e.display.lineNumInnerWidth+"px"))),o)for(var l=0;l1)if(Fo&&Fo.text.join("\n")==t){if(r.ranges.length%Fo.text.length==0){s=[];for(var c=0;c=0;c--){var u=r.ranges[c],f=u.from(),h=u.to();u.empty()&&(n&&n>0?f=Bo(f.line,f.ch-n):e.state.overwrite&&!a?h=Bo(h.line,Math.min(Zr(o,h.line).text.length,h.ch+Ii(l).length)):Fo&&Fo.lineWise&&Fo.text.join("\n")==t&&(f=h=Bo(f.line,0)));var d=e.curOp.updateInput,p={from:f,to:h,text:s?s[c%s.length]:l,origin:i||(a?"paste":e.state.cutIncoming?"cut":"+input")};Tn(e.doc,p),Ci(e,"inputRead",e,p)}t&&!a&&Q(e,t),Bn(e),e.curOp.updateInput=d,e.curOp.typing=!0,e.state.pasteIncoming=e.state.cutIncoming=!1}function J(e,t){var n=e.clipboardData&&e.clipboardData.getData("text/plain");return n?(e.preventDefault(),t.isReadOnly()||t.options.disableInput||At(t,function(){Z(t,n,0,null,"paste")}),!0):void 0}function Q(e,t){if(e.options.electricChars&&e.options.smartIndent)for(var n=e.doc.sel,r=n.ranges.length-1;r>=0;r--){var i=n.ranges[r];if(!(i.head.ch>100||r&&n.ranges[r-1].head.line==i.head.line)){var o=e.getModeAt(i.head),a=!1;if(o.electricChars){for(var l=0;l-1){a=Fn(e,i.head.line,"smart");break}}else o.electricInput&&o.electricInput.test(Zr(e.doc,i.head.line).text.slice(0,i.head.ch))&&(a=Fn(e,i.head.line,"smart"));a&&Ci(e,"electricInput",e,i.head.line)}}}function ee(e){for(var t=[],n=[],r=0;ri?c.map:u[i],a=0;ai?e.line:e.rest[i]),f=o[a]+r;return(0>r||l!=t)&&(f=o[a+(r?1:0)]),Bo(s,f)}}}var i=e.text.firstChild,o=!1;if(!t||!Va(i,t))return ae(Bo(ti(e.line),0),!0);if(t==i&&(o=!0,t=i.childNodes[n],n=0,!t)){var a=e.rest?Ii(e.rest):e.line;return ae(Bo(ti(a),a.text.length),o)}var l=3==t.nodeType?t:null,s=t;for(l||1!=t.childNodes.length||3!=t.firstChild.nodeType||(l=t.firstChild,n&&(n=l.nodeValue.length));s.parentNode!=i;)s=s.parentNode;var c=e.measure,u=c.maps,f=r(l,s,n);if(f)return ae(f,o);for(var h=s.nextSibling,d=l?l.nodeValue.length-n:0;h;h=h.nextSibling){if(f=r(h,h.firstChild,0))return ae(Bo(f.line,f.ch-d),o);d+=h.textContent.length}for(var p=s.previousSibling,d=n;p;p=p.previousSibling){if(f=r(p,p.firstChild,-1))return ae(Bo(f.line,f.ch+d),o);d+=h.textContent.length}}function ce(e,t,n,r,i){function o(e){return function(t){return t.id==e}}function a(t){if(1==t.nodeType){var n=t.getAttribute("cm-text");if(null!=n)return""==n&&(n=t.textContent.replace(/\u200b/g,"")),void(l+=n);var u,f=t.getAttribute("cm-marker");if(f){var h=e.findMarks(Bo(r,0),Bo(i+1,0),o(+f));return void(h.length&&(u=h[0].find())&&(l+=Jr(e.doc,u.from,u.to).join(c)))}if("false"==t.getAttribute("contenteditable"))return;for(var d=0;d=0){var a=K(o.from(),i.from()),l=V(o.to(),i.to()),s=o.empty()?i.from()==i.head:o.from()==o.head;t>=r&&--t,e.splice(--r,2,new fe(s?l:a,s?a:l))}}return new ue(e,t)}function de(e,t){return new ue([new fe(e,t||e)],0)}function pe(e,t){return Math.max(e.first,Math.min(t,e.first+e.size-1))}function me(e,t){if(t.linen?Bo(n,Zr(e,n).text.length):ge(t,Zr(e,t.line).text.length)}function ge(e,t){var n=e.ch;return null==n||n>t?Bo(e.line,t):0>n?Bo(e.line,0):e}function ve(e,t){return t>=e.first&&t=t.ch:l.to>t.ch))){if(i&&(Pa(s,"beforeCursorEnter"),s.explicitlyCleared)){if(o.markedSpans){--a;continue}break}if(!s.atomic)continue;if(n){var c,u=s.find(0>r?1:-1);if((0>r?s.inclusiveRight:s.inclusiveLeft)&&(u=Pe(e,u,-r,u&&u.line==t.line?o:null)),u&&u.line==t.line&&(c=_o(u,n))&&(0>r?0>c:c>0))return Oe(e,u,t,r,i)}var f=s.find(0>r?-1:1);return(0>r?s.inclusiveLeft:s.inclusiveRight)&&(f=Pe(e,f,r,f.line==t.line?o:null)),f?Oe(e,f,t,r,i):null}}return t}function Ie(e,t,n,r,i){var o=r||1,a=Oe(e,t,n,o,i)||!i&&Oe(e,t,n,o,!0)||Oe(e,t,n,-o,i)||!i&&Oe(e,t,n,-o,!0);return a?a:(e.cantEdit=!0,Bo(e.first,0))}function Pe(e,t,n,r){return 0>n&&0==t.ch?t.line>e.first?me(e,Bo(t.line-1)):null:n>0&&t.ch==(r||Zr(e,t.line)).text.length?t.line=e.display.viewTo||l.to().linet&&(t=0),t=Math.round(t),r=Math.round(r),l.appendChild(ji("div",null,"CodeMirror-selected","position: absolute; left: "+e+"px; top: "+t+"px; width: "+(null==n?u-e:n)+"px; height: "+(r-t)+"px"))}function i(t,n,i){function o(n,r){return ht(e,Bo(t,n),"div",f,r)}var l,s,f=Zr(a,t),h=f.text.length;return eo(ii(f),n||0,null==i?h:i,function(e,t,a){var f,d,p,m=o(e,"left");if(e==t)f=m,d=p=m.left;else{if(f=o(t-1,"right"),"rtl"==a){var g=m;m=f,f=g}d=m.left,p=f.right}null==n&&0==e&&(d=c),f.top-m.top>3&&(r(d,m.top,null,m.bottom),d=c,m.bottoms.bottom||f.bottom==s.bottom&&f.right>s.right)&&(s=f),c+1>d&&(d=c),r(d,f.top,p-d,f.bottom)}),{start:l,end:s}}var o=e.display,a=e.doc,l=document.createDocumentFragment(),s=Ge(e.display),c=s.left,u=Math.max(o.sizerWidth,$e(e)-o.sizer.offsetLeft)-s.right,f=t.from(),h=t.to();if(f.line==h.line)i(f.line,f.ch,h.ch);else{var d=Zr(a,f.line),p=Zr(a,h.line),m=yr(d)==yr(p),g=i(f.line,f.ch,m?d.text.length+1:null).end,v=i(h.line,m?0:null,h.ch).start;m&&(g.top0?t.blinker=setInterval(function(){t.cursorDiv.style.visibility=(n=!n)?"":"hidden"},e.options.cursorBlinkRate):e.options.cursorBlinkRate<0&&(t.cursorDiv.style.visibility="hidden")}}function _e(e,t){e.doc.mode.startState&&e.doc.frontier=e.display.viewTo)){var n=+new Date+e.options.workTime,r=sa(t.mode,je(e,t.frontier)),i=[];t.iter(t.frontier,Math.min(t.first+t.size,e.display.viewTo+500),function(o){if(t.frontier>=e.display.viewFrom){var a=o.styles,l=o.text.length>e.options.maxHighlightLength,s=Rr(e,o,l?sa(t.mode,r):r,!0);o.styles=s.styles;var c=o.styleClasses,u=s.classes;u?o.styleClasses=u:c&&(o.styleClasses=null);for(var f=!a||a.length!=o.styles.length||c!=u&&(!c||!u||c.bgClass!=u.bgClass||c.textClass!=u.textClass),h=0;!f&&hn?(_e(e,e.options.workDelay),!0):void 0}),i.length&&At(e,function(){for(var t=0;ta;--l){if(l<=o.first)return o.first;var s=Zr(o,l-1);if(s.stateAfter&&(!n||l<=o.frontier))return l;var c=Fa(s.text,null,e.options.tabSize);(null==i||r>c)&&(i=l-1,r=c)}return i}function je(e,t,n){var r=e.doc,i=e.display;if(!r.mode.startState)return!0;var o=ze(e,t,n),a=o>r.first&&Zr(r,o-1).stateAfter;return a=a?sa(r.mode,a):ca(r.mode),r.iter(o,t,function(n){Hr(e,n.text,a);var l=o==t-1||o%5==0||o>=i.viewFrom&&o2&&o.push((s.bottom+c.top)/2-n.top)}}o.push(n.bottom-n.top)}}function Xe(e,t,n){if(e.line==t)return{map:e.measure.map,cache:e.measure.cache};for(var r=0;rn)return{map:e.measure.maps[r],cache:e.measure.caches[r],before:!0}}function Ze(e,t){t=yr(t);var n=ti(t),r=e.display.externalMeasured=new Pt(e.doc,t,n);r.lineN=n;var i=r.built=Br(e,r);return r.text=i.pre,qi(e.display.lineMeasure,i.pre),r}function Je(e,t,n,r){return tt(e,et(e,t),n,r)}function Qe(e,t){if(t>=e.display.viewFrom&&t=n.lineN&&tt?(i=0,o=1,a="left"):c>t?(i=t-s,o=i+1):(l==e.length-3||t==c&&e[l+3]>t)&&(o=c-s,i=o-1,t>=c&&(a="right")),null!=i){if(r=e[l+2],s==c&&n==(r.insertLeft?"left":"right")&&(a=n),"left"==n&&0==i)for(;l&&e[l-2]==e[l-3]&&e[l-1].insertLeft;)r=e[(l-=3)+2],a="left";if("right"==n&&i==c-s)for(;lu;u++){for(;l&&zi(t.line.text.charAt(o.coverStart+l));)--l;for(;o.coverStart+sbo&&0==l&&s==o.coverEnd-o.coverStart)i=a.parentNode.getBoundingClientRect();else if(xo&&e.options.lineWrapping){var f=qa(a,l,s).getClientRects();i=f.length?f["right"==r?f.length-1:0]:qo}else i=qa(a,l,s).getBoundingClientRect()||qo;if(i.left||i.right||0==l)break;s=l,l-=1,c="right"}xo&&11>bo&&(i=it(e.display.measure,i))}else{l>0&&(c=r="right");var f;i=e.options.lineWrapping&&(f=a.getClientRects()).length>1?f["right"==r?f.length-1:0]:a.getBoundingClientRect()}if(xo&&9>bo&&!l&&(!i||!i.left&&!i.right)){var h=a.parentNode.getClientRects()[0];i=h?{left:h.left,right:h.left+xt(e.display),top:h.top,bottom:h.bottom}:qo}for(var d=i.top-t.rect.top,p=i.bottom-t.rect.top,m=(d+p)/2,g=t.view.measure.heights,u=0;un.from?a(e-1):a(e,r)}r=r||Zr(e.doc,t.line),i||(i=et(e,r));var s=ii(r),c=t.ch;if(!s)return a(c);var u=co(s,c),f=l(c,u);return null!=al&&(f.other=l(c,al)),f}function pt(e,t){var n=0,t=me(e.doc,t);e.options.lineWrapping||(n=xt(e.display)*t.ch);var r=Zr(e.doc,t.line),i=ri(r)+Ue(e.display);return{left:n,right:n,top:i,bottom:i+r.height}}function mt(e,t,n,r){var i=Bo(e,t);return i.xRel=r,n&&(i.outside=!0),i}function gt(e,t,n){var r=e.doc;if(n+=e.display.viewOffset,0>n)return mt(r.first,0,!0,-1);var i=ni(r,n),o=r.first+r.size-1;if(i>o)return mt(r.first+r.size-1,Zr(r,o).text.length,!0,1);0>t&&(t=0);for(var a=Zr(r,i);;){var l=vt(e,a,i,t,n),s=gr(a),c=s&&s.find(0,!0);if(!s||!(l.ch>c.from.ch||l.ch==c.from.ch&&l.xRel>0))return l;i=ti(a=c.to.line)}}function vt(e,t,n,r,i){function o(r){var i=dt(e,Bo(n,r),"line",t,c);return l=!0,a>i.bottom?i.left-s:ag)return mt(n,d,v,1);for(;;){if(u?d==h||d==fo(t,h,1):1>=d-h){for(var y=p>r||g-r>=r-p?h:d,x=r-(y==h?p:g);zi(t.text.charAt(y));)++y;var b=mt(n,y,y==h?m:v,-1>x?-1:x>1?1:0);return b}var w=Math.ceil(f/2),k=h+w;if(u){k=h;for(var S=0;w>S;++S)k=fo(t,k,1)}var C=o(k);C>r?(d=k,g=C,(v=l)&&(g+=1e3),f=w):(h=k,p=C,m=l,f-=w)}}function yt(e){if(null!=e.cachedTextHeight)return e.cachedTextHeight;if(null==zo){zo=ji("pre");for(var t=0;49>t;++t)zo.appendChild(document.createTextNode("x")),zo.appendChild(ji("br"));zo.appendChild(document.createTextNode("x"))}qi(e.measure,zo);var n=zo.offsetHeight/50;return n>3&&(e.cachedTextHeight=n),Ui(e.measure),n||1}function xt(e){if(null!=e.cachedCharWidth)return e.cachedCharWidth;var t=ji("span","xxxxxxxxxx"),n=ji("pre",[t]);qi(e.measure,n);var r=t.getBoundingClientRect(),i=(r.right-r.left)/10;return i>2&&(e.cachedCharWidth=i),i||10}function bt(e){e.curOp={cm:e,viewChanged:!1,startHeight:e.doc.height,forceUpdate:!1,updateInput:null,typing:!1,changeObjs:null,cursorActivityHandlers:null,cursorActivityCalled:0,selectionChanged:!1,updateMaxLine:!1,scrollLeft:null,scrollTop:null,scrollToPos:null,focus:!1,id:++Yo},Go?Go.ops.push(e.curOp):e.curOp.ownsGroup=Go={ops:[e.curOp],delayedCallbacks:[]}}function wt(e){var t=e.delayedCallbacks,n=0;do{for(;n=n.viewTo)||n.maxLineChanged&&t.options.lineWrapping,e.update=e.mustUpdate&&new L(t,e.mustUpdate&&{top:e.scrollTop,ensure:e.scrollToPos},e.forceUpdate)}function Lt(e){e.updatedDisplay=e.mustUpdate&&M(e.cm,e.update)}function Tt(e){var t=e.cm,n=t.display;e.updatedDisplay&&O(t),e.barMeasure=p(t),n.maxLineChanged&&!t.options.lineWrapping&&(e.adjustWidthTo=Je(t,n.maxLine,n.maxLine.text.length).left+3,t.display.sizerWidth=e.adjustWidthTo,e.barMeasure.scrollWidth=Math.max(n.scroller.clientWidth,n.sizer.offsetLeft+e.adjustWidthTo+Ye(t)+t.display.barWidth),e.maxScrollLeft=Math.max(0,n.sizer.offsetLeft+e.adjustWidthTo-$e(t))),(e.updatedDisplay||e.selectionChanged)&&(e.preparedSelection=n.input.prepareSelection(e.focus))}function Mt(e){var t=e.cm;null!=e.adjustWidthTo&&(t.display.sizer.style.minWidth=e.adjustWidthTo+"px",e.maxScrollLefto;o=r){var a=new Pt(e.doc,Zr(e.doc,o),o);r=o+a.size,i.push(a)}return i}function Dt(e,t,n,r){null==t&&(t=e.doc.first),null==n&&(n=e.doc.first+e.doc.size),r||(r=0);var i=e.display;if(r&&nt)&&(i.updateLineNumbers=t),e.curOp.viewChanged=!0,t>=i.viewTo)Wo&&br(e.doc,t)i.viewFrom?Wt(e):(i.viewFrom+=r,i.viewTo+=r);else if(t<=i.viewFrom&&n>=i.viewTo)Wt(e);else if(t<=i.viewFrom){var o=_t(e,n,n+r,1);o?(i.view=i.view.slice(o.index),i.viewFrom=o.lineN,i.viewTo+=r):Wt(e)}else if(n>=i.viewTo){var o=_t(e,t,t,-1);o?(i.view=i.view.slice(0,o.index),i.viewTo=o.lineN):Wt(e)}else{var a=_t(e,t,t,-1),l=_t(e,n,n+r,1);a&&l?(i.view=i.view.slice(0,a.index).concat(Rt(e,a.lineN,l.lineN)).concat(i.view.slice(l.index)),i.viewTo+=r):Wt(e)}var s=i.externalMeasured;s&&(n=i.lineN&&t=r.viewTo)){var o=r.view[Bt(e,t)];if(null!=o.node){var a=o.changes||(o.changes=[]);-1==Pi(a,n)&&a.push(n)}}}function Wt(e){e.display.viewFrom=e.display.viewTo=e.doc.first,e.display.view=[],e.display.viewOffset=0}function Bt(e,t){if(t>=e.display.viewTo)return null;if(t-=e.display.viewFrom,0>t)return null;for(var n=e.display.view,r=0;rt)return r}function _t(e,t,n,r){var i,o=Bt(e,t),a=e.display.view;if(!Wo||n==e.doc.first+e.doc.size)return{index:o,lineN:n};for(var l=0,s=e.display.viewFrom;o>l;l++)s+=a[l].size;if(s!=t){if(r>0){if(o==a.length-1)return null;i=s+a[o].size-t,o++}else i=s-t;t+=i,n+=i}for(;br(e.doc,n)!=n;){if(o==(0>r?0:a.length-1))return null;n+=r*a[o-(0>r?1:0)].size,o+=r}return{index:o,lineN:n}}function Ft(e,t,n){var r=e.display,i=r.view;0==i.length||t>=r.viewTo||n<=r.viewFrom?(r.view=Rt(e,t,n),r.viewFrom=t):(r.viewFrom>t?r.view=Rt(e,t,r.viewFrom).concat(r.view):r.viewFromn&&(r.view=r.view.slice(0,Bt(e,n)))),r.viewTo=n}function zt(e){for(var t=e.display.view,n=0,r=0;r400}var i=e.display;Ea(i.scroller,"mousedown",Et(e,$t)),xo&&11>bo?Ea(i.scroller,"dblclick",Et(e,function(t){if(!Ti(e,t)){var n=Yt(e,t);if(n&&!Jt(e,t)&&!Gt(e.display,t)){Ma(t);var r=e.findWordAt(n);be(e.doc,r.anchor,r.head)}}})):Ea(i.scroller,"dblclick",function(t){Ti(e,t)||Ma(t)}),Do||Ea(i.scroller,"contextmenu",function(t){xn(e,t)});var o,a={end:0};Ea(i.scroller,"touchstart",function(t){if(!Ti(e,t)&&!n(t)){clearTimeout(o);var r=+new Date;i.activeTouch={start:r,moved:!1,prev:r-a.end<=300?a:null},1==t.touches.length&&(i.activeTouch.left=t.touches[0].pageX,i.activeTouch.top=t.touches[0].pageY)}}),Ea(i.scroller,"touchmove",function(){i.activeTouch&&(i.activeTouch.moved=!0)}),Ea(i.scroller,"touchend",function(n){var o=i.activeTouch;if(o&&!Gt(i,n)&&null!=o.left&&!o.moved&&new Date-o.start<300){var a,l=e.coordsChar(i.activeTouch,"page");a=!o.prev||r(o,o.prev)?new fe(l,l):!o.prev.prev||r(o,o.prev.prev)?e.findWordAt(l):new fe(Bo(l.line,0),me(e.doc,Bo(l.line+1,0))),e.setSelection(a.anchor,a.head),e.focus(),Ma(n)}t()}),Ea(i.scroller,"touchcancel",t),Ea(i.scroller,"scroll",function(){i.scroller.clientHeight&&(rn(e,i.scroller.scrollTop),on(e,i.scroller.scrollLeft,!0),Pa(e,"scroll",e))}),Ea(i.scroller,"mousewheel",function(t){an(e,t)}),Ea(i.scroller,"DOMMouseScroll",function(t){an(e,t)}),Ea(i.wrapper,"scroll",function(){i.wrapper.scrollTop=i.wrapper.scrollLeft=0}),i.dragFunctions={enter:function(t){Ti(e,t)||Aa(t)},over:function(t){Ti(e,t)||(tn(e,t),Aa(t))},start:function(t){en(e,t)},drop:Et(e,Qt),leave:function(t){Ti(e,t)||nn(e)}};var l=i.input.getField();Ea(l,"keyup",function(t){pn.call(e,t)}),Ea(l,"keydown",Et(e,hn)),Ea(l,"keypress",Et(e,mn)),Ea(l,"focus",Bi(vn,e)),Ea(l,"blur",Bi(yn,e))}function Ut(t,n,r){var i=r&&r!=e.Init;if(!n!=!i){var o=t.display.dragFunctions,a=n?Ea:Ia;a(t.display.scroller,"dragstart",o.start),a(t.display.scroller,"dragenter",o.enter),a(t.display.scroller,"dragover",o.over),a(t.display.scroller,"dragleave",o.leave),a(t.display.scroller,"drop",o.drop)}}function qt(e){var t=e.display;t.lastWrapHeight==t.wrapper.clientHeight&&t.lastWrapWidth==t.wrapper.clientWidth||(t.cachedCharWidth=t.cachedTextHeight=t.cachedPaddingH=null,t.scrollbarsClipped=!1,e.setSize())}function Gt(e,t){for(var n=wi(t);n!=e.wrapper;n=n.parentNode)if(!n||1==n.nodeType&&"true"==n.getAttribute("cm-ignore-events")||n.parentNode==e.sizer&&n!=e.mover)return!0}function Yt(e,t,n,r){var i=e.display;if(!n&&"true"==wi(t).getAttribute("cm-not-content"))return null;var o,a,l=i.lineSpace.getBoundingClientRect();try{o=t.clientX-l.left,a=t.clientY-l.top}catch(t){return null}var s,c=gt(e,o,a);if(r&&1==c.xRel&&(s=Zr(e.doc,c.line).text).length==c.ch){var u=Fa(s,s.length,e.options.tabSize)-s.length;c=Bo(c.line,Math.max(0,Math.round((o-Ge(e.display).left)/xt(e.display))-u))}return c}function $t(e){var t=this,n=t.display;if(!(Ti(t,e)||n.activeTouch&&n.input.supportsTouch())){if(n.shift=e.shiftKey,Gt(n,e))return void(wo||(n.scroller.draggable=!1,setTimeout(function(){n.scroller.draggable=!0},100)));if(!Jt(t,e)){var r=Yt(t,e);switch(window.focus(),ki(e)){case 1:t.state.selectingText?t.state.selectingText(e):r?Vt(t,e,r):wi(e)==n.scroller&&Ma(e);break;case 2:wo&&(t.state.lastMiddleDown=+new Date),r&&be(t.doc,r),setTimeout(function(){n.input.focus()},20),Ma(e);break;case 3:Do?xn(t,e):gn(t)}}}}function Vt(e,t,n){xo?setTimeout(Bi(X,e),0):e.curOp.focus=Gi();var r,i=+new Date;Uo&&Uo.time>i-400&&0==_o(Uo.pos,n)?r="triple":jo&&jo.time>i-400&&0==_o(jo.pos,n)?(r="double",Uo={time:i,pos:n}):(r="single",jo={time:i,pos:n});var o,a=e.doc.sel,l=Eo?t.metaKey:t.ctrlKey;e.options.dragDrop&&el&&!e.isReadOnly()&&"single"==r&&(o=a.contains(n))>-1&&(_o((o=a.ranges[o]).from(),n)<0||n.xRel>0)&&(_o(o.to(),n)>0||n.xRel<0)?Kt(e,t,n,l):Xt(e,t,n,r,l)}function Kt(e,t,n,r){var i=e.display,o=+new Date,a=Et(e,function(l){wo&&(i.scroller.draggable=!1),e.state.draggingText=!1,Ia(document,"mouseup",a),Ia(i.scroller,"drop",a),Math.abs(t.clientX-l.clientX)+Math.abs(t.clientY-l.clientY)<10&&(Ma(l),!r&&+new Date-200=p;p++){var v=Zr(c,p).text,y=za(v,s,o);s==d?i.push(new fe(Bo(p,y),Bo(p,y))):v.length>y&&i.push(new fe(Bo(p,y),Bo(p,za(v,d,o))))}i.length||i.push(new fe(n,n)),Te(c,he(h.ranges.slice(0,f).concat(i),f),{origin:"*mouse",scroll:!1}),e.scrollIntoView(t)}else{var x=u,b=x.anchor,w=t;if("single"!=r){if("double"==r)var k=e.findWordAt(t);else var k=new fe(Bo(t.line,0),me(c,Bo(t.line+1,0)));_o(k.anchor,b)>0?(w=k.head,b=K(x.from(),k.anchor)):(w=k.anchor,b=V(x.to(),k.head))}var i=h.ranges.slice(0);i[f]=new fe(me(c,b),w),Te(c,he(i,f),Ba)}}function a(t){var n=++y,i=Yt(e,t,!0,"rect"==r);if(i)if(0!=_o(i,g)){e.curOp.focus=Gi(),o(i);var l=b(s,c);(i.line>=l.to||i.linev.bottom?20:0;u&&setTimeout(Et(e,function(){y==n&&(s.scroller.scrollTop+=u,a(t))}),50)}}function l(t){e.state.selectingText=!1,y=1/0,Ma(t),s.input.focus(),Ia(document,"mousemove",x),Ia(document,"mouseup",w),c.history.lastSelOrigin=null}var s=e.display,c=e.doc;Ma(t);var u,f,h=c.sel,d=h.ranges;if(i&&!t.shiftKey?(f=c.sel.contains(n),u=f>-1?d[f]:new fe(n,n)):(u=c.sel.primary(),f=c.sel.primIndex),Oo?t.shiftKey&&t.metaKey:t.altKey)r="rect",i||(u=new fe(n,n)),n=Yt(e,t,!0,!0),f=-1;else if("double"==r){var p=e.findWordAt(n);u=e.display.shift||c.extend?xe(c,u,p.anchor,p.head):p}else if("triple"==r){var m=new fe(Bo(n.line,0),me(c,Bo(n.line+1,0)));u=e.display.shift||c.extend?xe(c,u,m.anchor,m.head):m}else u=xe(c,u,n);i?-1==f?(f=d.length,Te(c,he(d.concat([u]),f),{scroll:!1,origin:"*mouse"})):d.length>1&&d[f].empty()&&"single"==r&&!t.shiftKey?(Te(c,he(d.slice(0,f).concat(d.slice(f+1)),0),{scroll:!1,origin:"*mouse"}),h=c.sel):ke(c,f,u,Ba):(f=0,Te(c,new ue([u],0),Ba),h=c.sel);var g=n,v=s.wrapper.getBoundingClientRect(),y=0,x=Et(e,function(e){ki(e)?a(e):l(e)}),w=Et(e,l);e.state.selectingText=w,Ea(document,"mousemove",x),Ea(document,"mouseup",w)}function Zt(e,t,n,r){try{var i=t.clientX,o=t.clientY}catch(t){return!1}if(i>=Math.floor(e.display.gutters.getBoundingClientRect().right))return!1;r&&Ma(t);var a=e.display,l=a.lineDiv.getBoundingClientRect();if(o>l.bottom||!Ni(e,n))return bi(t);o-=l.top-a.viewOffset;for(var s=0;s=i){var u=ni(e.doc,o),f=e.options.gutters[s];return Pa(e,n,e,u,f,t),bi(t)}}}function Jt(e,t){return Zt(e,t,"gutterClick",!0)}function Qt(e){var t=this;if(nn(t),!Ti(t,e)&&!Gt(t.display,e)){Ma(e),xo&&($o=+new Date);var n=Yt(t,e,!0),r=e.dataTransfer.files;if(n&&!t.isReadOnly())if(r&&r.length&&window.FileReader&&window.File)for(var i=r.length,o=Array(i),a=0,l=function(e,r){if(!t.options.allowDropFileTypes||-1!=Pi(t.options.allowDropFileTypes,e.type)){var l=new FileReader;l.onload=Et(t,function(){var e=l.result;if(/[\x00-\x08\x0e-\x1f]{2}/.test(e)&&(e=""),o[r]=e,++a==i){n=me(t.doc,n);var s={from:n,to:n,text:t.doc.splitLines(o.join(t.doc.lineSeparator())),origin:"paste"};Tn(t.doc,s),Le(t.doc,de(n,Qo(s)))}}),l.readAsText(e)}},s=0;i>s;++s)l(r[s],s);else{if(t.state.draggingText&&t.doc.sel.contains(n)>-1)return t.state.draggingText(e),void setTimeout(function(){t.display.input.focus()},20);try{var o=e.dataTransfer.getData("Text");if(o){if(t.state.draggingText&&!(Eo?e.altKey:e.ctrlKey))var c=t.listSelections();if(Me(t.doc,de(n,n)),c)for(var s=0;sa.clientWidth,s=a.scrollHeight>a.clientHeight;if(r&&l||i&&s){if(i&&Eo&&wo)e:for(var c=t.target,u=o.view;c!=a;c=c.parentNode)for(var f=0;fh?d=Math.max(0,d+h-50):p=Math.min(e.doc.height,p+h+50),A(e,{top:d,bottom:p})}20>Vo&&(null==o.wheelStartX?(o.wheelStartX=a.scrollLeft,o.wheelStartY=a.scrollTop,o.wheelDX=r,o.wheelDY=i,setTimeout(function(){if(null!=o.wheelStartX){var e=a.scrollLeft-o.wheelStartX,t=a.scrollTop-o.wheelStartY,n=t&&o.wheelDY&&t/o.wheelDY||e&&o.wheelDX&&e/o.wheelDX;o.wheelStartX=o.wheelStartY=null,n&&(Ko=(Ko*Vo+n)/(Vo+1),++Vo)}},200)):(o.wheelDX+=r,o.wheelDY+=i))}}function ln(e,t,n){if("string"==typeof t&&(t=ua[t],!t))return!1;e.display.input.ensurePolled();var r=e.display.shift,i=!1;try{e.isReadOnly()&&(e.state.suppressEdits=!0),n&&(e.display.shift=!1),i=t(e)!=Ha}finally{e.display.shift=r,e.state.suppressEdits=!1}return i}function sn(e,t,n){for(var r=0;rbo&&27==e.keyCode&&(e.returnValue=!1);var n=e.keyCode;t.display.shift=16==n||e.shiftKey;var r=un(t,e);Co&&(Jo=r?n:null,!r&&88==n&&!rl&&(Eo?e.metaKey:e.ctrlKey)&&t.replaceSelection("",null,"cut")),18!=n||/\bCodeMirror-crosshair\b/.test(t.display.lineDiv.className)||dn(t)}}function dn(e){function t(e){18!=e.keyCode&&e.altKey||(Za(n,"CodeMirror-crosshair"),Ia(document,"keyup",t),Ia(document,"mouseover",t))}var n=e.display.lineDiv;Ja(n,"CodeMirror-crosshair"),Ea(document,"keyup",t),Ea(document,"mouseover",t)}function pn(e){16==e.keyCode&&(this.doc.sel.shift=!1),Ti(this,e)}function mn(e){var t=this;if(!(Gt(t.display,e)||Ti(t,e)||e.ctrlKey&&!e.altKey||Eo&&e.metaKey)){var n=e.keyCode,r=e.charCode;if(Co&&n==Jo)return Jo=null,void Ma(e);if(!Co||e.which&&!(e.which<10)||!un(t,e)){var i=String.fromCharCode(null==r?n:r);fn(t,e,i)||t.display.input.onKeyPress(e)}}}function gn(e){e.state.delayingBlurEvent=!0,setTimeout(function(){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1,yn(e))},100)}function vn(e){e.state.delayingBlurEvent&&(e.state.delayingBlurEvent=!1),"nocursor"!=e.options.readOnly&&(e.state.focused||(Pa(e,"focus",e),e.state.focused=!0,Ja(e.display.wrapper,"CodeMirror-focused"),e.curOp||e.display.selForContextMenu==e.doc.sel||(e.display.input.reset(),wo&&setTimeout(function(){e.display.input.reset(!0)},20)),e.display.input.receivedFocus()),Be(e))}function yn(e){e.state.delayingBlurEvent||(e.state.focused&&(Pa(e,"blur",e),e.state.focused=!1,Za(e.display.wrapper,"CodeMirror-focused")),clearInterval(e.display.blinker),setTimeout(function(){e.state.focused||(e.display.shift=!1)},150))}function xn(e,t){Gt(e.display,t)||bn(e,t)||Ti(e,t,"contextmenu")||e.display.input.onContextMenu(t)}function bn(e,t){return Ni(e,"gutterContextMenu")?Zt(e,t,"gutterContextMenu",!1):!1}function wn(e,t){if(_o(e,t.from)<0)return e;if(_o(e,t.to)<=0)return Qo(t);var n=e.line+t.text.length-(t.to.line-t.from.line)-1,r=e.ch;return e.line==t.to.line&&(r+=Qo(t).ch-t.to.ch),Bo(n,r)}function kn(e,t){for(var n=[],r=0;r=0;--i)Mn(e,{from:r[i].from,to:r[i].to,text:i?[""]:t.text});else Mn(e,t)}}function Mn(e,t){if(1!=t.text.length||""!=t.text[0]||0!=_o(t.from,t.to)){var n=kn(e,t);ci(e,t,n,e.cm?e.cm.curOp.id:NaN),En(e,t,n,or(e,t));var r=[];Kr(e,function(e,n){n||-1!=Pi(r,e.history)||(xi(e.history,t),r.push(e.history)),En(e,t,null,or(e,t))})}}function Nn(e,t,n){if(!e.cm||!e.cm.state.suppressEdits){for(var r,i=e.history,o=e.sel,a="undo"==t?i.done:i.undone,l="undo"==t?i.undone:i.done,s=0;s=0;--s){var f=r.changes[s];if(f.origin=t,u&&!Ln(e,f,!1))return void(a.length=0);c.push(ai(e,f));var h=s?kn(e,f):Ii(a);En(e,f,h,lr(e,f)),!s&&e.cm&&e.cm.scrollIntoView({from:f.from,to:Qo(f)});var d=[];Kr(e,function(e,t){t||-1!=Pi(d,e.history)||(xi(e.history,f),d.push(e.history)),En(e,f,null,lr(e,f))})}}}}function An(e,t){if(0!=t&&(e.first+=t,e.sel=new ue(Ri(e.sel.ranges,function(e){return new fe(Bo(e.anchor.line+t,e.anchor.ch),Bo(e.head.line+t,e.head.ch))}),e.sel.primIndex),e.cm)){Dt(e.cm,e.first,e.first-t,t);for(var n=e.cm.display,r=n.viewFrom;re.lastLine())){if(t.from.lineo&&(t={from:t.from,to:Bo(o,Zr(e,o).text.length),text:[t.text[0]],origin:t.origin}),t.removed=Jr(e,t.from,t.to),n||(n=kn(e,t)),e.cm?On(e.cm,t,r):Yr(e,t,r),Me(e,n,Wa)}}function On(e,t,n){var r=e.doc,i=e.display,a=t.from,l=t.to,s=!1,c=a.line;e.options.lineWrapping||(c=ti(yr(Zr(r,a.line))),r.iter(c,l.line+1,function(e){return e==i.maxLine?(s=!0,!0):void 0})),r.sel.contains(t.from,t.to)>-1&&Mi(e),Yr(r,t,n,o(e)),e.options.lineWrapping||(r.iter(c,a.line+t.text.length,function(e){var t=f(e);t>i.maxLineLength&&(i.maxLine=e,i.maxLineLength=t,i.maxLineChanged=!0,s=!1)}),s&&(e.curOp.updateMaxLine=!0)),r.frontier=Math.min(r.frontier,a.line),_e(e,400);var u=t.text.length-(l.line-a.line)-1;t.full?Dt(e):a.line!=l.line||1!=t.text.length||Gr(e.doc,t)?Dt(e,a.line,l.line+1,u):Ht(e,a.line,"text");var h=Ni(e,"changes"),d=Ni(e,"change");if(d||h){var p={from:a,to:l,text:t.text,removed:t.removed,origin:t.origin};d&&Ci(e,"change",e,p),h&&(e.curOp.changeObjs||(e.curOp.changeObjs=[])).push(p)}e.display.selForContextMenu=null}function In(e,t,n,r,i){if(r||(r=n),_o(r,n)<0){var o=r;r=n,n=o}"string"==typeof t&&(t=e.splitLines(t)),Tn(e,{from:n,to:r,text:t,origin:i})}function Pn(e,t){if(!Ti(e,"scrollCursorIntoView")){var n=e.display,r=n.sizer.getBoundingClientRect(),i=null;if(t.top+r.top<0?i=!0:t.bottom+r.top>(window.innerHeight||document.documentElement.clientHeight)&&(i=!1),null!=i&&!Mo){var o=ji("div","​",null,"position: absolute; top: "+(t.top-n.viewOffset-Ue(e.display))+"px; height: "+(t.bottom-t.top+Ye(e)+n.barHeight)+"px; left: "+t.left+"px; width: 2px;");e.display.lineSpace.appendChild(o),o.scrollIntoView(i),e.display.lineSpace.removeChild(o)}}}function Rn(e,t,n,r){null==r&&(r=0);for(var i=0;5>i;i++){var o=!1,a=dt(e,t),l=n&&n!=t?dt(e,n):a,s=Hn(e,Math.min(a.left,l.left),Math.min(a.top,l.top)-r,Math.max(a.left,l.left),Math.max(a.bottom,l.bottom)+r),c=e.doc.scrollTop,u=e.doc.scrollLeft;if(null!=s.scrollTop&&(rn(e,s.scrollTop),Math.abs(e.doc.scrollTop-c)>1&&(o=!0)),null!=s.scrollLeft&&(on(e,s.scrollLeft),Math.abs(e.doc.scrollLeft-u)>1&&(o=!0)),!o)break}return a}function Dn(e,t,n,r,i){var o=Hn(e,t,n,r,i);null!=o.scrollTop&&rn(e,o.scrollTop),null!=o.scrollLeft&&on(e,o.scrollLeft)}function Hn(e,t,n,r,i){var o=e.display,a=yt(e.display);0>n&&(n=0);var l=e.curOp&&null!=e.curOp.scrollTop?e.curOp.scrollTop:o.scroller.scrollTop,s=Ve(e),c={};i-n>s&&(i=n+s);var u=e.doc.height+qe(o),f=a>n,h=i>u-a;if(l>n)c.scrollTop=f?0:n;else if(i>l+s){var d=Math.min(n,(h?u:i)-s);d!=l&&(c.scrollTop=d)}var p=e.curOp&&null!=e.curOp.scrollLeft?e.curOp.scrollLeft:o.scroller.scrollLeft,m=$e(e)-(e.options.fixedGutter?o.gutters.offsetWidth:0),g=r-t>m;return g&&(r=t+m),10>t?c.scrollLeft=0:p>t?c.scrollLeft=Math.max(0,t-(g?0:10)):r>m+p-3&&(c.scrollLeft=r+(g?0:10)-m),c}function Wn(e,t,n){null==t&&null==n||_n(e),null!=t&&(e.curOp.scrollLeft=(null==e.curOp.scrollLeft?e.doc.scrollLeft:e.curOp.scrollLeft)+t),null!=n&&(e.curOp.scrollTop=(null==e.curOp.scrollTop?e.doc.scrollTop:e.curOp.scrollTop)+n)}function Bn(e){_n(e);var t=e.getCursor(),n=t,r=t;e.options.lineWrapping||(n=t.ch?Bo(t.line,t.ch-1):t,r=Bo(t.line,t.ch+1)),e.curOp.scrollToPos={from:n,to:r,margin:e.options.cursorScrollMargin,isCursor:!0}}function _n(e){var t=e.curOp.scrollToPos;if(t){e.curOp.scrollToPos=null;var n=pt(e,t.from),r=pt(e,t.to),i=Hn(e,Math.min(n.left,r.left),Math.min(n.top,r.top)-t.margin,Math.max(n.right,r.right),Math.max(n.bottom,r.bottom)+t.margin);e.scrollTo(i.scrollLeft,i.scrollTop)}}function Fn(e,t,n,r){var i,o=e.doc;null==n&&(n="add"),"smart"==n&&(o.mode.indent?i=je(e,t):n="prev");var a=e.options.tabSize,l=Zr(o,t),s=Fa(l.text,null,a);l.stateAfter&&(l.stateAfter=null);var c,u=l.text.match(/^\s*/)[0];if(r||/\S/.test(l.text)){if("smart"==n&&(c=o.mode.indent(i,l.text.slice(u.length),l.text),c==Ha||c>150)){if(!r)return;n="prev"}}else c=0,n="not";"prev"==n?c=t>o.first?Fa(Zr(o,t-1).text,null,a):0:"add"==n?c=s+e.options.indentUnit:"subtract"==n?c=s-e.options.indentUnit:"number"==typeof n&&(c=s+n),c=Math.max(0,c);var f="",h=0;if(e.options.indentWithTabs)for(var d=Math.floor(c/a);d;--d)h+=a,f+=" ";if(c>h&&(f+=Oi(c-h)),f!=u)return In(o,f,Bo(t,0),Bo(t,u.length),"+input"),l.stateAfter=null,!0;for(var d=0;d=0;t--)In(e.doc,"",r[t].from,r[t].to,"+delete");Bn(e)})}function Un(e,t,n,r,i){function o(){var t=l+n;return t=e.first+e.size?!1:(l=t,u=Zr(e,t))}function a(e){var t=(i?fo:ho)(u,s,n,!0);if(null==t){if(e||!o())return!1;s=i?(0>n?io:ro)(u):0>n?u.text.length:0}else s=t;return!0}var l=t.line,s=t.ch,c=n,u=Zr(e,l);if("char"==r)a();else if("column"==r)a(!0);else if("word"==r||"group"==r)for(var f=null,h="group"==r,d=e.cm&&e.cm.getHelper(t,"wordChars"),p=!0;!(0>n)||a(!p);p=!1){var m=u.text.charAt(s)||"\n",g=_i(m,d)?"w":h&&"\n"==m?"n":!h||/\s/.test(m)?null:"p";if(!h||p||g||(g="s"),f&&f!=g){0>n&&(n=1,a());break}if(g&&(f=g),n>0&&!a(!p))break}var v=Ie(e,Bo(l,s),t,c,!0);return _o(t,v)||(v.hitSide=!0),v}function qn(e,t,n,r){var i,o=e.doc,a=t.left;if("page"==r){var l=Math.min(e.display.wrapper.clientHeight,window.innerHeight||document.documentElement.clientHeight);i=t.top+n*(l-(0>n?1.5:.5)*yt(e.display))}else"line"==r&&(i=n>0?t.bottom+3:t.top-3);for(;;){var s=gt(e,a,i);if(!s.outside)break;if(0>n?0>=i:i>=o.height){s.hitSide=!0;break}i+=5*n}return s}function Gn(t,n,r,i){e.defaults[t]=n,r&&(ta[t]=i?function(e,t,n){n!=na&&r(e,t,n)}:r)}function Yn(e){for(var t,n,r,i,o=e.split(/-(?!$)/),e=o[o.length-1],a=0;a0||0==a&&o.clearWhenEmpty!==!1)return o;if(o.replacedWith&&(o.collapsed=!0,o.widgetNode=ji("span",[o.replacedWith],"CodeMirror-widget"),r.handleMouseEvents||o.widgetNode.setAttribute("cm-ignore-events","true"),r.insertLeft&&(o.widgetNode.insertLeft=!0)),o.collapsed){if(vr(e,t.line,t,n,o)||t.line!=n.line&&vr(e,n.line,t,n,o))throw new Error("Inserting collapsed marker partially overlapping an existing one");Wo=!0}o.addToHistory&&ci(e,{from:t,to:n,origin:"markText"},e.sel,NaN);var l,s=t.line,c=e.cm;if(e.iter(s,n.line+1,function(e){c&&o.collapsed&&!c.options.lineWrapping&&yr(e)==c.display.maxLine&&(l=!0),o.collapsed&&s!=t.line&&ei(e,0),nr(e,new Qn(o,s==t.line?t.ch:null,s==n.line?n.ch:null)),++s}),o.collapsed&&e.iter(t.line,n.line+1,function(t){kr(e,t)&&ei(t,0)}),o.clearOnEnter&&Ea(o,"beforeCursorEnter",function(){o.clear()}),o.readOnly&&(Ho=!0,(e.history.done.length||e.history.undone.length)&&e.clearHistory()),o.collapsed&&(o.id=++ga,o.atomic=!0),c){if(l&&(c.curOp.updateMaxLine=!0),o.collapsed)Dt(c,t.line,n.line+1);else if(o.className||o.title||o.startStyle||o.endStyle||o.css)for(var u=t.line;u<=n.line;u++)Ht(c,u,"text");o.atomic&&Ae(c.doc),Ci(c,"markerAdded",c,o)}return o}function Kn(e,t,n,r,i){r=Wi(r),r.shared=!1;var o=[Vn(e,t,n,r,i)],a=o[0],l=r.widgetNode;return Kr(e,function(e){l&&(r.widgetNode=l.cloneNode(!0)),o.push(Vn(e,me(e,t),me(e,n),r,i));for(var s=0;s=t:o.to>t);(r||(r=[])).push(new Qn(a,o.from,s?null:o.to))}}return r}function ir(e,t,n){if(e)for(var r,i=0;i=t:o.to>t);if(l||o.from==t&&"bookmark"==a.type&&(!n||o.marker.insertLeft)){var s=null==o.from||(a.inclusiveLeft?o.from<=t:o.from0&&l)for(var f=0;ff;++f)p.push(m);p.push(s)}return p}function ar(e){for(var t=0;t0)){var u=[s,1],f=_o(c.from,l.from),h=_o(c.to,l.to);(0>f||!a.inclusiveLeft&&!f)&&u.push({from:c.from,to:l.from}),(h>0||!a.inclusiveRight&&!h)&&u.push({from:l.to,to:c.to}),i.splice.apply(i,u),s+=u.length-1}}return i}function cr(e){var t=e.markedSpans;if(t){for(var n=0;n=0&&0>=f||0>=u&&f>=0)&&(0>=u&&(s.marker.inclusiveRight&&i.inclusiveLeft?_o(c.to,n)>=0:_o(c.to,n)>0)||u>=0&&(s.marker.inclusiveRight&&i.inclusiveLeft?_o(c.from,r)<=0:_o(c.from,r)<0)))return!0}}}function yr(e){for(var t;t=mr(e);)e=t.find(-1,!0).line;return e}function xr(e){for(var t,n;t=gr(e);)e=t.find(1,!0).line,(n||(n=[])).push(e);return n}function br(e,t){var n=Zr(e,t),r=yr(n);return n==r?t:ti(r)}function wr(e,t){if(t>e.lastLine())return t;var n,r=Zr(e,t);if(!kr(e,r))return t;for(;n=gr(r);)r=n.find(1,!0).line;return ti(r)+1}function kr(e,t){var n=Wo&&t.markedSpans;if(n)for(var r,i=0;io;o++){i&&(i[0]=e.innerMode(t,r).mode);var a=t.token(n,r);if(n.pos>n.start)return a}throw new Error("Mode "+t.name+" failed to advance stream.")}function Ir(e,t,n,r){function i(e){return{start:f.start,end:f.pos,string:f.current(),type:o||null,state:e?sa(a.mode,u):u}}var o,a=e.doc,l=a.mode;t=me(a,t);var s,c=Zr(a,t.line),u=je(e,t.line,n),f=new ma(c.text,e.options.tabSize);for(r&&(s=[]);(r||f.pose.options.maxHighlightLength?(l=!1,a&&Hr(e,t,r,f.pos),f.pos=t.length,s=null):s=Ar(Or(n,f,r,h),o),h){var d=h[0].name;d&&(s="m-"+(s?d+" "+s:d))}if(!l||u!=s){for(;cc;){var r=i[s];r>e&&i.splice(s,1,e,i[s+1],r),s+=2,c=Math.min(e,r)}if(t)if(l.opaque)i.splice(n,s-n,e,"cm-overlay "+t),s=n+2;else for(;s>n;n+=2){var o=i[n+1];i[n+1]=(o?o+" ":"")+"cm-overlay "+t}},o)}return{styles:i,classes:o.bgClass||o.textClass?o:null}}function Dr(e,t,n){if(!t.styles||t.styles[0]!=e.state.modeGen){var r=je(e,ti(t)),i=Rr(e,t,t.text.length>e.options.maxHighlightLength?sa(e.doc.mode,r):r);t.stateAfter=r,t.styles=i.styles,i.classes?t.styleClasses=i.classes:t.styleClasses&&(t.styleClasses=null),n===e.doc.frontier&&e.doc.frontier++}return t.styles}function Hr(e,t,n,r){var i=e.doc.mode,o=new ma(t,e.options.tabSize);for(o.start=o.pos=r||0,""==t&&Er(i,n);!o.eol();)Or(i,o,n),o.start=o.pos}function Wr(e,t){if(!e||/^\s*$/.test(e))return null;var n=t.addModeClass?ka:wa;return n[e]||(n[e]=e.replace(/\S+/g,"cm-$&"))}function Br(e,t){var n=ji("span",null,null,wo?"padding-right: .1px":null),r={pre:ji("pre",[n],"CodeMirror-line"),content:n,col:0,pos:0,cm:e,splitSpaces:(xo||wo)&&e.getOption("lineWrapping")};t.measure={};for(var i=0;i<=(t.rest?t.rest.length:0);i++){var o,a=i?t.rest[i-1]:t.line;r.pos=0,r.addToken=Fr,Ji(e.display.measure)&&(o=ii(a))&&(r.addToken=jr(r.addToken,o)),r.map=[];var l=t!=e.display.externalMeasured&&ti(a);qr(a,r,Dr(e,a,l)),a.styleClasses&&(a.styleClasses.bgClass&&(r.bgClass=$i(a.styleClasses.bgClass,r.bgClass||"")),a.styleClasses.textClass&&(r.textClass=$i(a.styleClasses.textClass,r.textClass||""))),0==r.map.length&&r.map.push(0,0,r.content.appendChild(Zi(e.display.measure))),0==i?(t.measure.map=r.map,t.measure.cache={}):((t.measure.maps||(t.measure.maps=[])).push(r.map),(t.measure.caches||(t.measure.caches=[])).push({}))}if(wo){var s=r.content.lastChild;(/\bcm-tab\b/.test(s.className)||s.querySelector&&s.querySelector(".cm-tab"))&&(r.content.className="cm-tab-wrap-hack")}return Pa(e,"renderLine",e,t.line,r.pre),r.pre.className&&(r.textClass=$i(r.pre.className,r.textClass||"")),r}function _r(e){var t=ji("span","•","cm-invalidchar");return t.title="\\u"+e.charCodeAt(0).toString(16),t.setAttribute("aria-label",t.title),t}function Fr(e,t,n,r,i,o,a){if(t){var l=e.splitSpaces?t.replace(/ {3,}/g,zr):t,s=e.cm.state.specialChars,c=!1;if(s.test(t))for(var u=document.createDocumentFragment(),f=0;;){s.lastIndex=f;var h=s.exec(t),d=h?h.index-f:t.length-f;if(d){var p=document.createTextNode(l.slice(f,f+d));xo&&9>bo?u.appendChild(ji("span",[p])):u.appendChild(p),e.map.push(e.pos,e.pos+d,p),e.col+=d,e.pos+=d}if(!h)break;if(f+=d+1," "==h[0]){var m=e.cm.options.tabSize,g=m-e.col%m,p=u.appendChild(ji("span",Oi(g),"cm-tab"));p.setAttribute("role","presentation"),p.setAttribute("cm-text"," "),e.col+=g}else if("\r"==h[0]||"\n"==h[0]){var p=u.appendChild(ji("span","\r"==h[0]?"␍":"␤","cm-invalidchar"));p.setAttribute("cm-text",h[0]),e.col+=1}else{var p=e.cm.options.specialCharPlaceholder(h[0]);p.setAttribute("cm-text",h[0]),xo&&9>bo?u.appendChild(ji("span",[p])):u.appendChild(p),e.col+=1}e.map.push(e.pos,e.pos+1,p),e.pos++}else{e.col+=t.length;var u=document.createTextNode(l);e.map.push(e.pos,e.pos+t.length,u),xo&&9>bo&&(c=!0),e.pos+=t.length}if(n||r||i||c||a){var v=n||"";r&&(v+=r),i&&(v+=i);var y=ji("span",[u],v,a);return o&&(y.title=o),e.content.appendChild(y)}e.content.appendChild(u)}}function zr(e){for(var t=" ",n=0;nc&&h.from<=c)break}if(h.to>=u)return e(n,r,i,o,a,l,s);e(n,r.slice(0,h.to-c),i,o,null,l,s),o=null,r=r.slice(h.to-c),c=h.to}}}function Ur(e,t,n,r){var i=!r&&n.widgetNode;i&&e.map.push(e.pos,e.pos+t,i),!r&&e.cm.display.input.needsContentAttribute&&(i||(i=e.content.appendChild(document.createElement("span"))),i.setAttribute("cm-marker",n.id)),i&&(e.cm.display.input.setUneditable(i),e.content.appendChild(i)),e.pos+=t}function qr(e,t,n){var r=e.markedSpans,i=e.text,o=0;if(r)for(var a,l,s,c,u,f,h,d=i.length,p=0,m=1,g="",v=0;;){if(v==p){s=c=u=f=l="",h=null,v=1/0;for(var y,x=[],b=0;bp||k.collapsed&&w.to==p&&w.from==p)?(null!=w.to&&w.to!=p&&v>w.to&&(v=w.to,c=""),k.className&&(s+=" "+k.className),k.css&&(l=(l?l+";":"")+k.css),k.startStyle&&w.from==p&&(u+=" "+k.startStyle),k.endStyle&&w.to==v&&(y||(y=[])).push(k.endStyle,w.to),k.title&&!f&&(f=k.title),k.collapsed&&(!h||dr(h.marker,k)<0)&&(h=w)):w.from>p&&v>w.from&&(v=w.from)}if(y)for(var b=0;b=d)break;for(var S=Math.min(d,v);;){if(g){var C=p+g.length;if(!h){var L=C>S?g.slice(0,S-p):g;t.addToken(t,L,a?a+s:s,u,p+L.length==v?c:"",f,l)}if(C>=S){g=g.slice(S-p),p=S;break}p=C,u=""}g=i.slice(o,o=n[m++]),a=Wr(n[m++],t.cm.options)}}else for(var m=1;mn;++n)o.push(new ba(c[n],i(n),r));return o}var l=t.from,s=t.to,c=t.text,u=Zr(e,l.line),f=Zr(e,s.line),h=Ii(c),d=i(c.length-1),p=s.line-l.line;if(t.full)e.insert(0,a(0,c.length)),e.remove(c.length,e.size-c.length);else if(Gr(e,t)){var m=a(0,c.length-1);o(f,f.text,d),p&&e.remove(l.line,p),m.length&&e.insert(l.line,m)}else if(u==f)if(1==c.length)o(u,u.text.slice(0,l.ch)+h+u.text.slice(s.ch),d);else{var m=a(1,c.length-1);m.push(new ba(h+u.text.slice(s.ch),d,r)),o(u,u.text.slice(0,l.ch)+c[0],i(0)),e.insert(l.line+1,m)}else if(1==c.length)o(u,u.text.slice(0,l.ch)+c[0]+f.text.slice(s.ch),i(0)),e.remove(l.line+1,p);else{o(u,u.text.slice(0,l.ch)+c[0],i(0)),o(f,h+f.text.slice(s.ch),d);var m=a(1,c.length-1);p>1&&e.remove(l.line+1,p-1),e.insert(l.line+1,m)}Ci(e,"change",e,t)}function $r(e){this.lines=e,this.parent=null;for(var t=0,n=0;tt||t>=e.size)throw new Error("There is no line "+(t+e.first)+" in the document.");for(var n=e;!n.lines;)for(var r=0;;++r){var i=n.children[r],o=i.chunkSize();if(o>t){n=i;break}t-=o}return n.lines[t]}function Jr(e,t,n){var r=[],i=t.line;return e.iter(t.line,n.line+1,function(e){var o=e.text;i==n.line&&(o=o.slice(0,n.ch)),i==t.line&&(o=o.slice(t.ch)),r.push(o),++i}),r}function Qr(e,t,n){var r=[];return e.iter(t,n,function(e){r.push(e.text)}),r}function ei(e,t){var n=t-e.height;if(n)for(var r=e;r;r=r.parent)r.height+=n}function ti(e){if(null==e.parent)return null;for(var t=e.parent,n=Pi(t.lines,e),r=t.parent;r;t=r,r=r.parent)for(var i=0;r.children[i]!=t;++i)n+=r.children[i].chunkSize();return n+t.first}function ni(e,t){var n=e.first;e:do{for(var r=0;rt){e=i;continue e}t-=o,n+=i.chunkSize()}return n}while(!e.lines);for(var r=0;rt)break;t-=l}return n+r}function ri(e){e=yr(e);for(var t=0,n=e.parent,r=0;r1&&!e.done[e.done.length-2].ranges?(e.done.pop(),Ii(e.done)):void 0}function ci(e,t,n,r){var i=e.history;i.undone.length=0;var o,a=+new Date;if((i.lastOp==r||i.lastOrigin==t.origin&&t.origin&&("+"==t.origin.charAt(0)&&e.cm&&i.lastModTime>a-e.cm.options.historyEventDelay||"*"==t.origin.charAt(0)))&&(o=si(i,i.lastOp==r))){var l=Ii(o.changes);0==_o(t.from,t.to)&&0==_o(t.from,l.to)?l.to=Qo(t):o.changes.push(ai(e,t))}else{var s=Ii(i.done);for(s&&s.ranges||hi(e.sel,i.done),o={changes:[ai(e,t)],generation:i.generation},i.done.push(o);i.done.length>i.undoDepth;)i.done.shift(),i.done[0].ranges||i.done.shift()}i.done.push(n),i.generation=++i.maxGeneration,i.lastModTime=i.lastSelTime=a,i.lastOp=i.lastSelOp=r,i.lastOrigin=i.lastSelOrigin=t.origin,l||Pa(e,"historyAdded")}function ui(e,t,n,r){var i=t.charAt(0);return"*"==i||"+"==i&&n.ranges.length==r.ranges.length&&n.somethingSelected()==r.somethingSelected()&&new Date-e.history.lastSelTime<=(e.cm?e.cm.options.historyEventDelay:500)}function fi(e,t,n,r){var i=e.history,o=r&&r.origin;n==i.lastSelOp||o&&i.lastSelOrigin==o&&(i.lastModTime==i.lastSelTime&&i.lastOrigin==o||ui(e,o,Ii(i.done),t))?i.done[i.done.length-1]=t:hi(t,i.done),i.lastSelTime=+new Date,i.lastSelOrigin=o,i.lastSelOp=n,r&&r.clearRedo!==!1&&li(i.undone)}function hi(e,t){var n=Ii(t);n&&n.ranges&&n.equals(e)||t.push(e)}function di(e,t,n,r){var i=t["spans_"+e.id],o=0;e.iter(Math.max(e.first,n),Math.min(e.first+e.size,r),function(n){n.markedSpans&&((i||(i=t["spans_"+e.id]={}))[o]=n.markedSpans),++o})}function pi(e){if(!e)return null;for(var t,n=0;n-1&&(Ii(l)[f]=u[f],delete u[f])}}}return i}function vi(e,t,n,r){n0?r.slice():Oa:r||Oa}function Ci(e,t){function n(e){return function(){e.apply(null,o)}}var r=Si(e,t,!1);if(r.length){var i,o=Array.prototype.slice.call(arguments,2);Go?i=Go.delayedCallbacks:Ra?i=Ra:(i=Ra=[],setTimeout(Li,0));for(var a=0;a0}function Ai(e){e.prototype.on=function(e,t){Ea(this,e,t)},e.prototype.off=function(e,t){Ia(this,e,t)}}function Ei(){this.id=null}function Oi(e){for(;ja.length<=e;)ja.push(Ii(ja)+" ");return ja[e]}function Ii(e){return e[e.length-1]}function Pi(e,t){for(var n=0;n-1&&Ya(e)?!0:t.test(e):Ya(e)}function Fi(e){for(var t in e)if(e.hasOwnProperty(t)&&e[t])return!1;return!0}function zi(e){return e.charCodeAt(0)>=768&&$a.test(e)}function ji(e,t,n,r){var i=document.createElement(e);if(n&&(i.className=n),r&&(i.style.cssText=r),"string"==typeof t)i.appendChild(document.createTextNode(t));else if(t)for(var o=0;o0;--t)e.removeChild(e.firstChild);return e}function qi(e,t){return Ui(e).appendChild(t)}function Gi(){for(var e=document.activeElement;e&&e.root&&e.root.activeElement;)e=e.root.activeElement;return e}function Yi(e){return new RegExp("(^|\\s)"+e+"(?:$|\\s)\\s*")}function $i(e,t){for(var n=e.split(" "),r=0;r2&&!(xo&&8>bo))}var n=Ka?ji("span","​"):ji("span"," ",null,"display: inline-block; width: 1px; margin-right: -1px");return n.setAttribute("cm-text",""),n}function Ji(e){if(null!=Xa)return Xa;var t=qi(e,document.createTextNode("AخA")),n=qa(t,0,1).getBoundingClientRect();if(!n||n.left==n.right)return!1;var r=qa(t,1,2).getBoundingClientRect();return Xa=r.right-n.right<3}function Qi(e){if(null!=il)return il;var t=qi(e,ji("span","x")),n=t.getBoundingClientRect(),r=qa(t,0,1).getBoundingClientRect();return il=Math.abs(n.left-r.left)>1}function eo(e,t,n,r){if(!e)return r(t,n,"ltr");for(var i=!1,o=0;ot||t==n&&a.to==t)&&(r(Math.max(a.from,t),Math.min(a.to,n),1==a.level?"rtl":"ltr"),i=!0)}i||r(t,n,"ltr")}function to(e){return e.level%2?e.to:e.from}function no(e){return e.level%2?e.from:e.to}function ro(e){var t=ii(e);return t?to(t[0]):0}function io(e){var t=ii(e);return t?no(Ii(t)):e.text.length}function oo(e,t){var n=Zr(e.doc,t),r=yr(n);r!=n&&(t=ti(r));var i=ii(r),o=i?i[0].level%2?io(r):ro(r):0;return Bo(t,o)}function ao(e,t){for(var n,r=Zr(e.doc,t);n=gr(r);)r=n.find(1,!0).line,t=null;var i=ii(r),o=i?i[0].level%2?ro(r):io(r):r.text.length;return Bo(null==t?ti(r):t,o)}function lo(e,t){var n=oo(e,t.line),r=Zr(e.doc,n.line),i=ii(r);if(!i||0==i[0].level){var o=Math.max(0,r.text.search(/\S/)),a=t.line==n.line&&t.ch<=o&&t.ch;return Bo(n.line,a?0:o)}return n}function so(e,t,n){var r=e[0].level;return t==r?!0:n==r?!1:n>t}function co(e,t){al=null;for(var n,r=0;rt)return r;if(i.from==t||i.to==t){if(null!=n)return so(e,i.level,e[n].level)?(i.from!=i.to&&(al=n),r):(i.from!=i.to&&(al=r),n);n=r}}return n}function uo(e,t,n,r){if(!r)return t+n;do t+=n;while(t>0&&zi(e.text.charAt(t)));return t}function fo(e,t,n,r){var i=ii(e);if(!i)return ho(e,t,n,r);for(var o=co(i,t),a=i[o],l=uo(e,t,a.level%2?-n:n,r);;){if(l>a.from&&l0==a.level%2?a.to:a.from);if(a=i[o+=n],!a)return null;l=n>0==a.level%2?uo(e,a.to,-1,r):uo(e,a.from,1,r)}}function ho(e,t,n,r){var i=t+n;if(r)for(;i>0&&zi(e.text.charAt(i));)i+=n;return 0>i||i>e.text.length?null:i}var po=navigator.userAgent,mo=navigator.platform,go=/gecko\/\d/i.test(po),vo=/MSIE \d/.test(po),yo=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(po),xo=vo||yo,bo=xo&&(vo?document.documentMode||6:yo[1]),wo=/WebKit\//.test(po),ko=wo&&/Qt\/\d+\.\d+/.test(po),So=/Chrome\//.test(po),Co=/Opera\//.test(po),Lo=/Apple Computer/.test(navigator.vendor),To=/Mac OS X 1\d\D([8-9]|\d\d)\D/.test(po),Mo=/PhantomJS/.test(po),No=/AppleWebKit/.test(po)&&/Mobile\/\w+/.test(po),Ao=No||/Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(po),Eo=No||/Mac/.test(mo),Oo=/\bCrOS\b/.test(po),Io=/win/i.test(mo),Po=Co&&po.match(/Version\/(\d*\.\d*)/);Po&&(Po=Number(Po[1])),Po&&Po>=15&&(Co=!1,wo=!0);var Ro=Eo&&(ko||Co&&(null==Po||12.11>Po)),Do=go||xo&&bo>=9,Ho=!1,Wo=!1;m.prototype=Wi({update:function(e){var t=e.scrollWidth>e.clientWidth+1,n=e.scrollHeight>e.clientHeight+1,r=e.nativeBarWidth;if(n){this.vert.style.display="block",this.vert.style.bottom=t?r+"px":"0";var i=e.viewHeight-(t?r:0);this.vert.firstChild.style.height=Math.max(0,e.scrollHeight-e.clientHeight+i)+"px"}else this.vert.style.display="",this.vert.firstChild.style.height="0";if(t){this.horiz.style.display="block",this.horiz.style.right=n?r+"px":"0",this.horiz.style.left=e.barLeft+"px";var o=e.viewWidth-e.barLeft-(n?r:0);this.horiz.firstChild.style.width=e.scrollWidth-e.clientWidth+o+"px"}else this.horiz.style.display="",this.horiz.firstChild.style.width="0";return!this.checkedZeroWidth&&e.clientHeight>0&&(0==r&&this.zeroWidthHack(),this.checkedZeroWidth=!0),{right:n?r:0,bottom:t?r:0}},setScrollLeft:function(e){this.horiz.scrollLeft!=e&&(this.horiz.scrollLeft=e),this.disableHoriz&&this.enableZeroWidthBar(this.horiz,this.disableHoriz)},setScrollTop:function(e){this.vert.scrollTop!=e&&(this.vert.scrollTop=e),this.disableVert&&this.enableZeroWidthBar(this.vert,this.disableVert)},zeroWidthHack:function(){var e=Eo&&!To?"12px":"18px";this.horiz.style.height=this.vert.style.width=e,this.horiz.style.pointerEvents=this.vert.style.pointerEvents="none",this.disableHoriz=new Ei,this.disableVert=new Ei},enableZeroWidthBar:function(e,t){function n(){var r=e.getBoundingClientRect(),i=document.elementFromPoint(r.left+1,r.bottom-1);i!=e?e.style.pointerEvents="none":t.set(1e3,n)}e.style.pointerEvents="auto",t.set(1e3,n)},clear:function(){var e=this.horiz.parentNode;e.removeChild(this.horiz),e.removeChild(this.vert)}},m.prototype),g.prototype=Wi({update:function(){return{bottom:0,right:0}},setScrollLeft:function(){},setScrollTop:function(){},clear:function(){}},g.prototype),e.scrollbarModel={"native":m,"null":g},L.prototype.signal=function(e,t){Ni(e,t)&&this.events.push(arguments)},L.prototype.finish=function(){for(var e=0;e=9&&n.hasSelection&&(n.hasSelection=null),n.poll()}),Ea(o,"paste",function(e){Ti(r,e)||J(e,r)||(r.state.pasteIncoming=!0,n.fastPoll())}),Ea(o,"cut",t),Ea(o,"copy",t),Ea(e.scroller,"paste",function(t){Gt(e,t)||Ti(r,t)||(r.state.pasteIncoming=!0,n.focus())}),Ea(e.lineSpace,"selectstart",function(t){Gt(e,t)||Ma(t)}),Ea(o,"compositionstart",function(){var e=r.getCursor("from");n.composing&&n.composing.range.clear(),n.composing={start:e,range:r.markText(e,r.getCursor("to"),{className:"CodeMirror-composing"})}}),Ea(o,"compositionend",function(){n.composing&&(n.poll(),n.composing.range.clear(),n.composing=null)})},prepareSelection:function(){var e=this.cm,t=e.display,n=e.doc,r=De(e);if(e.options.moveInputWithCursor){var i=dt(e,n.sel.primary().head,"div"),o=t.wrapper.getBoundingClientRect(),a=t.lineDiv.getBoundingClientRect();r.teTop=Math.max(0,Math.min(t.wrapper.clientHeight-10,i.top+a.top-o.top)),r.teLeft=Math.max(0,Math.min(t.wrapper.clientWidth-10,i.left+a.left-o.left))}return r},showSelection:function(e){var t=this.cm,n=t.display;qi(n.cursorDiv,e.cursors),qi(n.selectionDiv,e.selection),null!=e.teTop&&(this.wrapper.style.top=e.teTop+"px",this.wrapper.style.left=e.teLeft+"px")},reset:function(e){if(!this.contextMenuPending){var t,n,r=this.cm,i=r.doc;if(r.somethingSelected()){this.prevInput="";var o=i.sel.primary();t=rl&&(o.to().line-o.from().line>100||(n=r.getSelection()).length>1e3);var a=t?"-":n||r.getSelection();this.textarea.value=a,r.state.focused&&Ua(this.textarea),xo&&bo>=9&&(this.hasSelection=a)}else e||(this.prevInput=this.textarea.value="",xo&&bo>=9&&(this.hasSelection=null));this.inaccurateSelection=t}},getField:function(){return this.textarea},supportsTouch:function(){return!1},focus:function(){if("nocursor"!=this.cm.options.readOnly&&(!Ao||Gi()!=this.textarea))try{this.textarea.focus()}catch(e){}},blur:function(){this.textarea.blur()},resetPosition:function(){this.wrapper.style.top=this.wrapper.style.left=0; -},receivedFocus:function(){this.slowPoll()},slowPoll:function(){var e=this;e.pollingFast||e.polling.set(this.cm.options.pollInterval,function(){e.poll(),e.cm.state.focused&&e.slowPoll()})},fastPoll:function(){function e(){var r=n.poll();r||t?(n.pollingFast=!1,n.slowPoll()):(t=!0,n.polling.set(60,e))}var t=!1,n=this;n.pollingFast=!0,n.polling.set(20,e)},poll:function(){var e=this.cm,t=this.textarea,n=this.prevInput;if(this.contextMenuPending||!e.state.focused||nl(t)&&!n&&!this.composing||e.isReadOnly()||e.options.disableInput||e.state.keySeq)return!1;var r=t.value;if(r==n&&!e.somethingSelected())return!1;if(xo&&bo>=9&&this.hasSelection===r||Eo&&/[\uf700-\uf7ff]/.test(r))return e.display.input.reset(),!1;if(e.doc.sel==e.display.selForContextMenu){var i=r.charCodeAt(0);if(8203!=i||n||(n="​"),8666==i)return this.reset(),this.cm.execCommand("undo")}for(var o=0,a=Math.min(n.length,r.length);a>o&&n.charCodeAt(o)==r.charCodeAt(o);)++o;var l=this;return At(e,function(){Z(e,r.slice(o),n.length-o,null,l.composing?"*compose":null),r.length>1e3||r.indexOf("\n")>-1?t.value=l.prevInput="":l.prevInput=r,l.composing&&(l.composing.range.clear(),l.composing.range=e.markText(l.composing.start,e.getCursor("to"),{className:"CodeMirror-composing"}))}),!0},ensurePolled:function(){this.pollingFast&&this.poll()&&(this.pollingFast=!1)},onKeyPress:function(){xo&&bo>=9&&(this.hasSelection=null),this.fastPoll()},onContextMenu:function(e){function t(){if(null!=a.selectionStart){var e=i.somethingSelected(),t="​"+(e?a.value:"");a.value="⇚",a.value=t,r.prevInput=e?"":"​",a.selectionStart=1,a.selectionEnd=t.length,o.selForContextMenu=i.doc.sel}}function n(){if(r.contextMenuPending=!1,r.wrapper.style.cssText=f,a.style.cssText=u,xo&&9>bo&&o.scrollbars.setScrollTop(o.scroller.scrollTop=s),null!=a.selectionStart){(!xo||xo&&9>bo)&&t();var e=0,n=function(){o.selForContextMenu==i.doc.sel&&0==a.selectionStart&&a.selectionEnd>0&&"​"==r.prevInput?Et(i,ua.selectAll)(i):e++<10?o.detectingSelectAll=setTimeout(n,500):o.input.reset()};o.detectingSelectAll=setTimeout(n,200)}}var r=this,i=r.cm,o=i.display,a=r.textarea,l=Yt(i,e),s=o.scroller.scrollTop;if(l&&!Co){var c=i.options.resetSelectionOnContextMenu;c&&-1==i.doc.sel.contains(l)&&Et(i,Te)(i.doc,de(l),Wa);var u=a.style.cssText,f=r.wrapper.style.cssText;r.wrapper.style.cssText="position: absolute";var h=r.wrapper.getBoundingClientRect();if(a.style.cssText="position: absolute; width: 30px; height: 30px; top: "+(e.clientY-h.top-5)+"px; left: "+(e.clientX-h.left-5)+"px; z-index: 1000; background: "+(xo?"rgba(255, 255, 255, .05)":"transparent")+"; outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);",wo)var d=window.scrollY;if(o.input.focus(),wo&&window.scrollTo(null,d),o.input.reset(),i.somethingSelected()||(a.value=r.prevInput=" "),r.contextMenuPending=!0,o.selForContextMenu=i.doc.sel,clearTimeout(o.detectingSelectAll),xo&&bo>=9&&t(),Do){Aa(e);var p=function(){Ia(window,"mouseup",p),setTimeout(n,20)};Ea(window,"mouseup",p)}else setTimeout(n,50)}},readOnlyChanged:function(e){e||this.reset()},setUneditable:Di,needsContentAttribute:!1},ne.prototype),ie.prototype=Wi({init:function(e){function t(e){if(!Ti(r,e)){if(r.somethingSelected())Fo={lineWise:!1,text:r.getSelections()},"cut"==e.type&&r.replaceSelection("",null,"cut");else{if(!r.options.lineWiseCopyCut)return;var t=ee(r);Fo={lineWise:!0,text:t.text},"cut"==e.type&&r.operation(function(){r.setSelections(t.ranges,0,Wa),r.replaceSelection("",null,"cut")})}if(e.clipboardData&&!No)e.preventDefault(),e.clipboardData.clearData(),e.clipboardData.setData("text/plain",Fo.text.join("\n"));else{var n=re(),i=n.firstChild;r.display.lineSpace.insertBefore(n,r.display.lineSpace.firstChild),i.value=Fo.text.join("\n");var o=document.activeElement;Ua(i),setTimeout(function(){r.display.lineSpace.removeChild(n),o.focus()},50)}}}var n=this,r=n.cm,i=n.div=e.lineDiv;te(i),Ea(i,"paste",function(e){Ti(r,e)||J(e,r)}),Ea(i,"compositionstart",function(e){var t=e.data;if(n.composing={sel:r.doc.sel,data:t,startData:t},t){var i=r.doc.sel.primary(),o=r.getLine(i.head.line),a=o.indexOf(t,Math.max(0,i.head.ch-t.length));a>-1&&a<=i.head.ch&&(n.composing.sel=de(Bo(i.head.line,a),Bo(i.head.line,a+t.length)))}}),Ea(i,"compositionupdate",function(e){n.composing.data=e.data}),Ea(i,"compositionend",function(e){var t=n.composing;t&&(e.data==t.startData||/\u200b/.test(e.data)||(t.data=e.data),setTimeout(function(){t.handled||n.applyComposition(t),n.composing==t&&(n.composing=null)},50))}),Ea(i,"touchstart",function(){n.forceCompositionEnd()}),Ea(i,"input",function(){n.composing||!r.isReadOnly()&&n.pollContent()||At(n.cm,function(){Dt(r)})}),Ea(i,"copy",t),Ea(i,"cut",t)},prepareSelection:function(){var e=De(this.cm,!1);return e.focus=this.cm.state.focused,e},showSelection:function(e,t){e&&this.cm.display.view.length&&((e.focus||t)&&this.showPrimarySelection(),this.showMultipleSelections(e))},showPrimarySelection:function(){var e=window.getSelection(),t=this.cm.doc.sel.primary(),n=le(this.cm,e.anchorNode,e.anchorOffset),r=le(this.cm,e.focusNode,e.focusOffset);if(!n||n.bad||!r||r.bad||0!=_o(K(n,r),t.from())||0!=_o(V(n,r),t.to())){var i=oe(this.cm,t.from()),o=oe(this.cm,t.to());if(i||o){var a=this.cm.display.view,l=e.rangeCount&&e.getRangeAt(0);if(i){if(!o){var s=a[a.length-1].measure,c=s.maps?s.maps[s.maps.length-1]:s.map;o={node:c[c.length-1],offset:c[c.length-2]-c[c.length-3]}}}else i={node:a[0].measure.map[2],offset:0};try{var u=qa(i.node,i.offset,o.offset,o.node)}catch(f){}u&&(!go&&this.cm.state.focused?(e.collapse(i.node,i.offset),u.collapsed||e.addRange(u)):(e.removeAllRanges(),e.addRange(u)),l&&null==e.anchorNode?e.addRange(l):go&&this.startGracePeriod()),this.rememberSelection()}}},startGracePeriod:function(){var e=this;clearTimeout(this.gracePeriod),this.gracePeriod=setTimeout(function(){e.gracePeriod=!1,e.selectionChanged()&&e.cm.operation(function(){e.cm.curOp.selectionChanged=!0})},20)},showMultipleSelections:function(e){qi(this.cm.display.cursorDiv,e.cursors),qi(this.cm.display.selectionDiv,e.selection)},rememberSelection:function(){var e=window.getSelection();this.lastAnchorNode=e.anchorNode,this.lastAnchorOffset=e.anchorOffset,this.lastFocusNode=e.focusNode,this.lastFocusOffset=e.focusOffset},selectionInEditor:function(){var e=window.getSelection();if(!e.rangeCount)return!1;var t=e.getRangeAt(0).commonAncestorContainer;return Va(this.div,t)},focus:function(){"nocursor"!=this.cm.options.readOnly&&this.div.focus()},blur:function(){this.div.blur()},getField:function(){return this.div},supportsTouch:function(){return!0},receivedFocus:function(){function e(){t.cm.state.focused&&(t.pollSelection(),t.polling.set(t.cm.options.pollInterval,e))}var t=this;this.selectionInEditor()?this.pollSelection():At(this.cm,function(){t.cm.curOp.selectionChanged=!0}),this.polling.set(this.cm.options.pollInterval,e)},selectionChanged:function(){var e=window.getSelection();return e.anchorNode!=this.lastAnchorNode||e.anchorOffset!=this.lastAnchorOffset||e.focusNode!=this.lastFocusNode||e.focusOffset!=this.lastFocusOffset},pollSelection:function(){if(!this.composing&&!this.gracePeriod&&this.selectionChanged()){var e=window.getSelection(),t=this.cm;this.rememberSelection();var n=le(t,e.anchorNode,e.anchorOffset),r=le(t,e.focusNode,e.focusOffset);n&&r&&At(t,function(){Te(t.doc,de(n,r),Wa),(n.bad||r.bad)&&(t.curOp.selectionChanged=!0)})}},pollContent:function(){var e=this.cm,t=e.display,n=e.doc.sel.primary(),r=n.from(),i=n.to();if(r.linet.viewTo-1)return!1;var o;if(r.line==t.viewFrom||0==(o=Bt(e,r.line)))var a=ti(t.view[0].line),l=t.view[0].node;else var a=ti(t.view[o].line),l=t.view[o-1].node.nextSibling;var s=Bt(e,i.line);if(s==t.view.length-1)var c=t.viewTo-1,u=t.lineDiv.lastChild;else var c=ti(t.view[s+1].line)-1,u=t.view[s+1].node.previousSibling;for(var f=e.doc.splitLines(ce(e,l,u,a,c)),h=Jr(e.doc,Bo(a,0),Bo(c,Zr(e.doc,c).text.length));f.length>1&&h.length>1;)if(Ii(f)==Ii(h))f.pop(),h.pop(),c--;else{if(f[0]!=h[0])break;f.shift(),h.shift(),a++}for(var d=0,p=0,m=f[0],g=h[0],v=Math.min(m.length,g.length);v>d&&m.charCodeAt(d)==g.charCodeAt(d);)++d;for(var y=Ii(f),x=Ii(h),b=Math.min(y.length-(1==f.length?d:0),x.length-(1==h.length?d:0));b>p&&y.charCodeAt(y.length-p-1)==x.charCodeAt(x.length-p-1);)++p;f[f.length-1]=y.slice(0,y.length-p),f[0]=f[0].slice(d);var w=Bo(a,d),k=Bo(c,h.length?Ii(h).length-p:0);return f.length>1||f[0]||_o(w,k)?(In(e.doc,f,w,k,"+input"),!0):void 0},ensurePolled:function(){this.forceCompositionEnd()},reset:function(){this.forceCompositionEnd()},forceCompositionEnd:function(){this.composing&&!this.composing.handled&&(this.applyComposition(this.composing),this.composing.handled=!0,this.div.blur(),this.div.focus())},applyComposition:function(e){this.cm.isReadOnly()?Et(this.cm,Dt)(this.cm):e.data&&e.data!=e.startData&&Et(this.cm,Z)(this.cm,e.data,0,e.sel)},setUneditable:function(e){e.contentEditable="false"},onKeyPress:function(e){e.preventDefault(),this.cm.isReadOnly()||Et(this.cm,Z)(this.cm,String.fromCharCode(null==e.charCode?e.keyCode:e.charCode),0)},readOnlyChanged:function(e){this.div.contentEditable=String("nocursor"!=e)},onContextMenu:Di,resetPosition:Di,needsContentAttribute:!0},ie.prototype),e.inputStyles={textarea:ne,contenteditable:ie},ue.prototype={primary:function(){return this.ranges[this.primIndex]},equals:function(e){if(e==this)return!0;if(e.primIndex!=this.primIndex||e.ranges.length!=this.ranges.length)return!1;for(var t=0;t=0&&_o(e,r.to())<=0)return n}return-1}},fe.prototype={from:function(){return K(this.anchor,this.head)},to:function(){return V(this.anchor,this.head)},empty:function(){return this.head.line==this.anchor.line&&this.head.ch==this.anchor.ch}};var zo,jo,Uo,qo={left:0,right:0,top:0,bottom:0},Go=null,Yo=0,$o=0,Vo=0,Ko=null;xo?Ko=-.53:go?Ko=15:So?Ko=-.7:Lo&&(Ko=-1/3);var Xo=function(e){var t=e.wheelDeltaX,n=e.wheelDeltaY;return null==t&&e.detail&&e.axis==e.HORIZONTAL_AXIS&&(t=e.detail),null==n&&e.detail&&e.axis==e.VERTICAL_AXIS?n=e.detail:null==n&&(n=e.wheelDelta),{x:t,y:n}};e.wheelEventPixels=function(e){var t=Xo(e);return t.x*=Ko,t.y*=Ko,t};var Zo=new Ei,Jo=null,Qo=e.changeEnd=function(e){return e.text?Bo(e.from.line+e.text.length-1,Ii(e.text).length+(1==e.text.length?e.from.ch:0)):e.to};e.prototype={constructor:e,focus:function(){window.focus(),this.display.input.focus()},setOption:function(e,t){var n=this.options,r=n[e];n[e]==t&&"mode"!=e||(n[e]=t,ta.hasOwnProperty(e)&&Et(this,ta[e])(this,t,r))},getOption:function(e){return this.options[e]},getDoc:function(){return this.doc},addKeyMap:function(e,t){this.state.keyMaps[t?"push":"unshift"]($n(e))},removeKeyMap:function(e){for(var t=this.state.keyMaps,n=0;nn&&(Fn(this,i.head.line,e,!0),n=i.head.line,r==this.doc.sel.primIndex&&Bn(this));else{var o=i.from(),a=i.to(),l=Math.max(n,o.line);n=Math.min(this.lastLine(),a.line-(a.ch?0:1))+1;for(var s=l;n>s;++s)Fn(this,s,e);var c=this.doc.sel.ranges;0==o.ch&&t.length==c.length&&c[r].from().ch>0&&ke(this.doc,r,new fe(o,c[r].to()),Wa)}}}),getTokenAt:function(e,t){return Ir(this,e,t)},getLineTokens:function(e,t){return Ir(this,Bo(e),t,!0)},getTokenTypeAt:function(e){e=me(this.doc,e);var t,n=Dr(this,Zr(this.doc,e.line)),r=0,i=(n.length-1)/2,o=e.ch;if(0==o)t=n[2];else for(;;){var a=r+i>>1;if((a?n[2*a-1]:0)>=o)i=a;else{if(!(n[2*a+1]l?t:0==l?null:t.slice(0,l-1)},getModeAt:function(t){var n=this.doc.mode;return n.innerMode?e.innerMode(n,this.getTokenAt(t).state).mode:n},getHelper:function(e,t){return this.getHelpers(e,t)[0]},getHelpers:function(e,t){var n=[];if(!la.hasOwnProperty(t))return n;var r=la[t],i=this.getModeAt(e);if("string"==typeof i[t])r[i[t]]&&n.push(r[i[t]]);else if(i[t])for(var o=0;oi&&(e=i,r=!0),n=Zr(this.doc,e)}else n=e;return ut(this,n,{top:0,left:0},t||"page").top+(r?this.doc.height-ri(n):0)},defaultTextHeight:function(){return yt(this.display)},defaultCharWidth:function(){return xt(this.display)},setGutterMarker:Ot(function(e,t,n){return zn(this.doc,e,"gutter",function(e){var r=e.gutterMarkers||(e.gutterMarkers={});return r[t]=n,!n&&Fi(r)&&(e.gutterMarkers=null),!0})}),clearGutter:Ot(function(e){var t=this,n=t.doc,r=n.first;n.iter(function(n){n.gutterMarkers&&n.gutterMarkers[e]&&(n.gutterMarkers[e]=null,Ht(t,r,"gutter"),Fi(n.gutterMarkers)&&(n.gutterMarkers=null)),++r})}),lineInfo:function(e){if("number"==typeof e){if(!ve(this.doc,e))return null;var t=e;if(e=Zr(this.doc,e),!e)return null}else{var t=ti(e);if(null==t)return null}return{line:t,handle:e,text:e.text,gutterMarkers:e.gutterMarkers,textClass:e.textClass,bgClass:e.bgClass,wrapClass:e.wrapClass,widgets:e.widgets}},getViewport:function(){return{from:this.display.viewFrom,to:this.display.viewTo}},addWidget:function(e,t,n,r,i){var o=this.display;e=dt(this,me(this.doc,e));var a=e.bottom,l=e.left;if(t.style.position="absolute",t.setAttribute("cm-ignore-events","true"),this.display.input.setUneditable(t),o.sizer.appendChild(t),"over"==r)a=e.top;else if("above"==r||"near"==r){var s=Math.max(o.wrapper.clientHeight,this.doc.height),c=Math.max(o.sizer.clientWidth,o.lineSpace.clientWidth);("above"==r||e.bottom+t.offsetHeight>s)&&e.top>t.offsetHeight?a=e.top-t.offsetHeight:e.bottom+t.offsetHeight<=s&&(a=e.bottom),l+t.offsetWidth>c&&(l=c-t.offsetWidth)}t.style.top=a+"px",t.style.left=t.style.right="","right"==i?(l=o.sizer.clientWidth-t.offsetWidth,t.style.right="0px"):("left"==i?l=0:"middle"==i&&(l=(o.sizer.clientWidth-t.offsetWidth)/2),t.style.left=l+"px"),n&&Dn(this,l,a,l+t.offsetWidth,a+t.offsetHeight)},triggerOnKeyDown:Ot(hn),triggerOnKeyPress:Ot(mn),triggerOnKeyUp:pn,execCommand:function(e){return ua.hasOwnProperty(e)?ua[e].call(null,this):void 0},triggerElectric:Ot(function(e){Q(this,e)}),findPosH:function(e,t,n,r){var i=1;0>t&&(i=-1,t=-t);for(var o=0,a=me(this.doc,e);t>o&&(a=Un(this.doc,a,i,n,r),!a.hitSide);++o);return a},moveH:Ot(function(e,t){var n=this;n.extendSelectionsBy(function(r){return n.display.shift||n.doc.extend||r.empty()?Un(n.doc,r.head,e,t,n.options.rtlMoveVisually):0>e?r.from():r.to()},_a)}),deleteH:Ot(function(e,t){var n=this.doc.sel,r=this.doc;n.somethingSelected()?r.replaceSelection("",null,"+delete"):jn(this,function(n){var i=Un(r,n.head,e,t,!1);return 0>e?{from:i,to:n.head}:{from:n.head,to:i}})}),findPosV:function(e,t,n,r){var i=1,o=r;0>t&&(i=-1,t=-t);for(var a=0,l=me(this.doc,e);t>a;++a){var s=dt(this,l,"div");if(null==o?o=s.left:s.left=o,l=qn(this,s,i,n),l.hitSide)break}return l},moveV:Ot(function(e,t){var n=this,r=this.doc,i=[],o=!n.display.shift&&!r.extend&&r.sel.somethingSelected();if(r.extendSelectionsBy(function(a){if(o)return 0>e?a.from():a.to();var l=dt(n,a.head,"div");null!=a.goalColumn&&(l.left=a.goalColumn),i.push(l.left);var s=qn(n,l,e,t);return"page"==t&&a==r.sel.primary()&&Wn(n,null,ht(n,s,"div").top-l.top),s},_a),i.length)for(var a=0;a0&&l(n.charAt(r-1));)--r;for(;i.5)&&a(this),Pa(this,"refresh",this)}),swapDoc:Ot(function(e){var t=this.doc;return t.cm=null,Xr(this,e),lt(this),this.display.input.reset(),this.scrollTo(e.scrollLeft,e.scrollTop),this.curOp.forceScroll=!0,Ci(this,"swapDoc",this,t),t}),getInputField:function(){return this.display.input.getField()},getWrapperElement:function(){return this.display.wrapper},getScrollerElement:function(){return this.display.scroller},getGutterElement:function(){return this.display.gutters}},Ai(e);var ea=e.defaults={},ta=e.optionHandlers={},na=e.Init={toString:function(){return"CodeMirror.Init"}};Gn("value","",function(e,t){e.setValue(t)},!0),Gn("mode",null,function(e,t){e.doc.modeOption=t,n(e)},!0),Gn("indentUnit",2,n,!0),Gn("indentWithTabs",!1),Gn("smartIndent",!0),Gn("tabSize",4,function(e){r(e),lt(e),Dt(e)},!0),Gn("lineSeparator",null,function(e,t){if(e.doc.lineSep=t,t){var n=[],r=e.doc.first;e.doc.iter(function(e){for(var i=0;;){var o=e.text.indexOf(t,i);if(-1==o)break;i=o+t.length,n.push(Bo(r,o))}r++});for(var i=n.length-1;i>=0;i--)In(e.doc,t,n[i],Bo(n[i].line,n[i].ch+t.length))}}),Gn("specialChars",/[\u0000-\u001f\u007f\u00ad\u200b-\u200f\u2028\u2029\ufeff]/g,function(t,n,r){t.state.specialChars=new RegExp(n.source+(n.test(" ")?"":"| "),"g"),r!=e.Init&&t.refresh()}),Gn("specialCharPlaceholder",_r,function(e){e.refresh()},!0),Gn("electricChars",!0),Gn("inputStyle",Ao?"contenteditable":"textarea",function(){throw new Error("inputStyle can not (yet) be changed in a running editor")},!0),Gn("rtlMoveVisually",!Io),Gn("wholeLineUpdateBefore",!0),Gn("theme","default",function(e){l(e),s(e)},!0),Gn("keyMap","default",function(t,n,r){var i=$n(n),o=r!=e.Init&&$n(r);o&&o.detach&&o.detach(t,i),i.attach&&i.attach(t,o||null)}),Gn("extraKeys",null),Gn("lineWrapping",!1,i,!0),Gn("gutters",[],function(e){d(e.options),s(e)},!0),Gn("fixedGutter",!0,function(e,t){e.display.gutters.style.left=t?C(e.display)+"px":"0",e.refresh()},!0),Gn("coverGutterNextToScrollbar",!1,function(e){y(e)},!0),Gn("scrollbarStyle","native",function(e){v(e),y(e),e.display.scrollbars.setScrollTop(e.doc.scrollTop),e.display.scrollbars.setScrollLeft(e.doc.scrollLeft)},!0),Gn("lineNumbers",!1,function(e){d(e.options),s(e)},!0),Gn("firstLineNumber",1,s,!0),Gn("lineNumberFormatter",function(e){return e},s,!0),Gn("showCursorWhenSelecting",!1,Re,!0),Gn("resetSelectionOnContextMenu",!0),Gn("lineWiseCopyCut",!0),Gn("readOnly",!1,function(e,t){"nocursor"==t?(yn(e),e.display.input.blur(),e.display.disabled=!0):e.display.disabled=!1,e.display.input.readOnlyChanged(t)}),Gn("disableInput",!1,function(e,t){t||e.display.input.reset()},!0),Gn("dragDrop",!0,Ut),Gn("allowDropFileTypes",null),Gn("cursorBlinkRate",530),Gn("cursorScrollMargin",0),Gn("cursorHeight",1,Re,!0),Gn("singleCursorHeightPerLine",!0,Re,!0),Gn("workTime",100),Gn("workDelay",100),Gn("flattenSpans",!0,r,!0),Gn("addModeClass",!1,r,!0),Gn("pollInterval",100),Gn("undoDepth",200,function(e,t){e.doc.history.undoDepth=t}),Gn("historyEventDelay",1250),Gn("viewportMargin",10,function(e){e.refresh()},!0),Gn("maxHighlightLength",1e4,r,!0),Gn("moveInputWithCursor",!0,function(e,t){t||e.display.input.resetPosition()}),Gn("tabindex",null,function(e,t){e.display.input.getField().tabIndex=t||""}),Gn("autofocus",null);var ra=e.modes={},ia=e.mimeModes={};e.defineMode=function(t,n){e.defaults.mode||"null"==t||(e.defaults.mode=t),arguments.length>2&&(n.dependencies=Array.prototype.slice.call(arguments,2)),ra[t]=n},e.defineMIME=function(e,t){ia[e]=t},e.resolveMode=function(t){if("string"==typeof t&&ia.hasOwnProperty(t))t=ia[t];else if(t&&"string"==typeof t.name&&ia.hasOwnProperty(t.name)){var n=ia[t.name];"string"==typeof n&&(n={name:n}),t=Hi(n,t),t.name=n.name}else if("string"==typeof t&&/^[\w\-]+\/[\w\-]+\+xml$/.test(t))return e.resolveMode("application/xml");return"string"==typeof t?{name:t}:t||{name:"null"}},e.getMode=function(t,n){var n=e.resolveMode(n),r=ra[n.name];if(!r)return e.getMode(t,"text/plain");var i=r(t,n);if(oa.hasOwnProperty(n.name)){var o=oa[n.name];for(var a in o)o.hasOwnProperty(a)&&(i.hasOwnProperty(a)&&(i["_"+a]=i[a]),i[a]=o[a])}if(i.name=n.name,n.helperType&&(i.helperType=n.helperType),n.modeProps)for(var a in n.modeProps)i[a]=n.modeProps[a];return i},e.defineMode("null",function(){return{token:function(e){e.skipToEnd()}}}),e.defineMIME("text/plain","null");var oa=e.modeExtensions={};e.extendMode=function(e,t){var n=oa.hasOwnProperty(e)?oa[e]:oa[e]={};Wi(t,n)},e.defineExtension=function(t,n){e.prototype[t]=n},e.defineDocExtension=function(e,t){Ca.prototype[e]=t},e.defineOption=Gn;var aa=[];e.defineInitHook=function(e){aa.push(e)};var la=e.helpers={};e.registerHelper=function(t,n,r){la.hasOwnProperty(t)||(la[t]=e[t]={_global:[]}),la[t][n]=r},e.registerGlobalHelper=function(t,n,r,i){e.registerHelper(t,n,i),la[t]._global.push({pred:r,val:i})};var sa=e.copyState=function(e,t){if(t===!0)return t;if(e.copyState)return e.copyState(t);var n={};for(var r in t){var i=t[r];i instanceof Array&&(i=i.concat([])),n[r]=i}return n},ca=e.startState=function(e,t,n){return e.startState?e.startState(t,n):!0};e.innerMode=function(e,t){for(;e.innerMode;){var n=e.innerMode(t);if(!n||n.mode==e)break;t=n.state,e=n.mode}return n||{mode:e,state:t}};var ua=e.commands={selectAll:function(e){e.setSelection(Bo(e.firstLine(),0),Bo(e.lastLine()),Wa)},singleSelection:function(e){e.setSelection(e.getCursor("anchor"),e.getCursor("head"),Wa)},killLine:function(e){jn(e,function(t){if(t.empty()){var n=Zr(e.doc,t.head.line).text.length;return t.head.ch==n&&t.head.line0)i=new Bo(i.line,i.ch+1),e.replaceRange(o.charAt(i.ch-1)+o.charAt(i.ch-2),Bo(i.line,i.ch-2),i,"+transpose");else if(i.line>e.doc.first){var a=Zr(e.doc,i.line-1).text;a&&e.replaceRange(o.charAt(0)+e.doc.lineSeparator()+a.charAt(a.length-1),Bo(i.line-1,a.length-1),Bo(i.line,1),"+transpose")}n.push(new fe(i,i))}e.setSelections(n)})},newlineAndIndent:function(e){At(e,function(){for(var t=e.listSelections().length,n=0;t>n;n++){var r=e.listSelections()[n];e.replaceRange(e.doc.lineSeparator(),r.anchor,r.head,"+input"),e.indentLine(r.from().line+1,null,!0)}Bn(e)})},openLine:function(e){e.replaceSelection("\n","start")},toggleOverwrite:function(e){e.toggleOverwrite()}},fa=e.keyMap={};fa.basic={Left:"goCharLeft",Right:"goCharRight",Up:"goLineUp",Down:"goLineDown",End:"goLineEnd",Home:"goLineStartSmart",PageUp:"goPageUp",PageDown:"goPageDown",Delete:"delCharAfter",Backspace:"delCharBefore","Shift-Backspace":"delCharBefore",Tab:"defaultTab","Shift-Tab":"indentAuto",Enter:"newlineAndIndent",Insert:"toggleOverwrite",Esc:"singleSelection"},fa.pcDefault={"Ctrl-A":"selectAll","Ctrl-D":"deleteLine","Ctrl-Z":"undo","Shift-Ctrl-Z":"redo","Ctrl-Y":"redo","Ctrl-Home":"goDocStart","Ctrl-End":"goDocEnd","Ctrl-Up":"goLineUp","Ctrl-Down":"goLineDown","Ctrl-Left":"goGroupLeft","Ctrl-Right":"goGroupRight","Alt-Left":"goLineStart","Alt-Right":"goLineEnd","Ctrl-Backspace":"delGroupBefore","Ctrl-Delete":"delGroupAfter","Ctrl-S":"save","Ctrl-F":"find","Ctrl-G":"findNext","Shift-Ctrl-G":"findPrev","Shift-Ctrl-F":"replace","Shift-Ctrl-R":"replaceAll","Ctrl-[":"indentLess","Ctrl-]":"indentMore","Ctrl-U":"undoSelection","Shift-Ctrl-U":"redoSelection","Alt-U":"redoSelection",fallthrough:"basic"},fa.emacsy={"Ctrl-F":"goCharRight","Ctrl-B":"goCharLeft","Ctrl-P":"goLineUp","Ctrl-N":"goLineDown","Alt-F":"goWordRight","Alt-B":"goWordLeft","Ctrl-A":"goLineStart","Ctrl-E":"goLineEnd","Ctrl-V":"goPageDown","Shift-Ctrl-V":"goPageUp","Ctrl-D":"delCharAfter","Ctrl-H":"delCharBefore","Alt-D":"delWordAfter","Alt-Backspace":"delWordBefore","Ctrl-K":"killLine","Ctrl-T":"transposeChars","Ctrl-O":"openLine"},fa.macDefault={"Cmd-A":"selectAll","Cmd-D":"deleteLine","Cmd-Z":"undo","Shift-Cmd-Z":"redo","Cmd-Y":"redo","Cmd-Home":"goDocStart","Cmd-Up":"goDocStart","Cmd-End":"goDocEnd","Cmd-Down":"goDocEnd","Alt-Left":"goGroupLeft","Alt-Right":"goGroupRight","Cmd-Left":"goLineLeft","Cmd-Right":"goLineRight","Alt-Backspace":"delGroupBefore","Ctrl-Alt-Backspace":"delGroupAfter","Alt-Delete":"delGroupAfter","Cmd-S":"save","Cmd-F":"find","Cmd-G":"findNext","Shift-Cmd-G":"findPrev","Cmd-Alt-F":"replace","Shift-Cmd-Alt-F":"replaceAll","Cmd-[":"indentLess","Cmd-]":"indentMore","Cmd-Backspace":"delWrappedLineLeft","Cmd-Delete":"delWrappedLineRight","Cmd-U":"undoSelection","Shift-Cmd-U":"redoSelection","Ctrl-Up":"goDocStart","Ctrl-Down":"goDocEnd",fallthrough:["basic","emacsy"]},fa["default"]=Eo?fa.macDefault:fa.pcDefault,e.normalizeKeyMap=function(e){var t={};for(var n in e)if(e.hasOwnProperty(n)){var r=e[n];if(/^(name|fallthrough|(de|at)tach)$/.test(n))continue;if("..."==r){delete e[n];continue}for(var i=Ri(n.split(" "),Yn),o=0;o=this.string.length},sol:function(){return this.pos==this.lineStart},peek:function(){return this.string.charAt(this.pos)||void 0},next:function(){return this.post},eatSpace:function(){for(var e=this.pos;/[\s\u00a0]/.test(this.string.charAt(this.pos));)++this.pos;return this.pos>e},skipToEnd:function(){this.pos=this.string.length},skipTo:function(e){var t=this.string.indexOf(e,this.pos);return t>-1?(this.pos=t,!0):void 0},backUp:function(e){this.pos-=e},column:function(){return this.lastColumnPos0?null:(r&&t!==!1&&(this.pos+=r[0].length),r)}var i=function(e){return n?e.toLowerCase():e},o=this.string.substr(this.pos,e.length);return i(o)==i(e)?(t!==!1&&(this.pos+=e.length),!0):void 0},current:function(){return this.string.slice(this.start,this.pos)},hideFirstChars:function(e,t){this.lineStart+=e;try{return t()}finally{this.lineStart-=e}}};var ga=0,va=e.TextMarker=function(e,t){this.lines=[],this.type=t,this.doc=e,this.id=++ga};Ai(va),va.prototype.clear=function(){if(!this.explicitlyCleared){var e=this.doc.cm,t=e&&!e.curOp;if(t&&bt(e),Ni(this,"clear")){var n=this.find();n&&Ci(this,"clear",n.from,n.to)}for(var r=null,i=null,o=0;oe.display.maxLineLength&&(e.display.maxLine=s,e.display.maxLineLength=c,e.display.maxLineChanged=!0)}null!=r&&e&&this.collapsed&&Dt(e,r,i+1),this.lines.length=0,this.explicitlyCleared=!0,this.atomic&&this.doc.cantEdit&&(this.doc.cantEdit=!1,e&&Ae(e.doc)),e&&Ci(e,"markerCleared",e,this),t&&kt(e),this.parent&&this.parent.clear()}},va.prototype.find=function(e,t){null==e&&"bookmark"==this.type&&(e=1);for(var n,r,i=0;in;++n){var i=this.lines[n];this.height-=i.height,Nr(i),Ci(i,"delete")}this.lines.splice(e,t)},collapse:function(e){e.push.apply(e,this.lines)},insertInner:function(e,t,n){this.height+=n,this.lines=this.lines.slice(0,e).concat(t).concat(this.lines.slice(e));for(var r=0;re;++e)if(n(this.lines[e]))return!0}},Vr.prototype={chunkSize:function(){return this.size},removeInner:function(e,t){this.size-=t;for(var n=0;ne){var o=Math.min(t,i-e),a=r.height;if(r.removeInner(e,o),this.height-=a-r.height,i==o&&(this.children.splice(n--,1),r.parent=null),0==(t-=o))break;e=0}else e-=i}if(this.size-t<25&&(this.children.length>1||!(this.children[0]instanceof $r))){var l=[];this.collapse(l),this.children=[new $r(l)],this.children[0].parent=this}},collapse:function(e){for(var t=0;t=e){if(i.insertInner(e,t,n),i.lines&&i.lines.length>50){for(var a=i.lines.length%25+25,l=a;l10);e.parent.maybeSpill()}},iterN:function(e,t,n){for(var r=0;re){var a=Math.min(t,o-e);if(i.iterN(e,a,n))return!0;if(0==(t-=a))break;e=0}else e-=o}}};var Sa=0,Ca=e.Doc=function(e,t,n,r){if(!(this instanceof Ca))return new Ca(e,t,n,r);null==n&&(n=0),Vr.call(this,[new $r([new ba("",null)])]),this.first=n,this.scrollTop=this.scrollLeft=0,this.cantEdit=!1,this.cleanGeneration=1,this.frontier=n;var i=Bo(n,0);this.sel=de(i),this.history=new oi(null),this.id=++Sa,this.modeOption=t,this.lineSep=r,this.extend=!1,"string"==typeof e&&(e=this.splitLines(e)),Yr(this,{from:i,to:i,text:e}),Te(this,de(i),Wa)};Ca.prototype=Hi(Vr.prototype,{constructor:Ca,iter:function(e,t,n){n?this.iterN(e-this.first,t-e,n):this.iterN(this.first,this.first+this.size,e)},insert:function(e,t){for(var n=0,r=0;r=0;o--)Tn(this,r[o]);l?Le(this,l):this.cm&&Bn(this.cm)}),undo:It(function(){Nn(this,"undo")}),redo:It(function(){Nn(this,"redo")}),undoSelection:It(function(){Nn(this,"undo",!0)}),redoSelection:It(function(){Nn(this,"redo",!0)}),setExtending:function(e){this.extend=e},getExtending:function(){return this.extend},historySize:function(){for(var e=this.history,t=0,n=0,r=0;r=e.ch)&&t.push(i.marker.parent||i.marker)}return t},findMarks:function(e,t,n){e=me(this,e),t=me(this,t);var r=[],i=e.line;return this.iter(e.line,t.line+1,function(o){var a=o.markedSpans;if(a)for(var l=0;l=s.to||null==s.from&&i!=e.line||null!=s.from&&i==t.line&&s.from>=t.ch||n&&!n(s.marker)||r.push(s.marker.parent||s.marker)}++i}),r},getAllMarks:function(){var e=[];return this.iter(function(t){var n=t.markedSpans;if(n)for(var r=0;re?(t=e,!0):(e-=o,void++n)}),me(this,Bo(n,t))},indexFromPos:function(e){e=me(this,e);var t=e.ch;if(e.linet&&(t=e.from),null!=e.to&&e.tol||l>=t)return a+(t-o);a+=l-o,a+=n-a%n,o=l+1}},za=e.findColumn=function(e,t,n){for(var r=0,i=0;;){var o=e.indexOf(" ",r);-1==o&&(o=e.length);var a=o-r;if(o==e.length||i+a>=t)return r+Math.min(a,t-i);if(i+=o-r,i+=n-i%n,r=o+1,i>=t)return r}},ja=[""],Ua=function(e){e.select()};No?Ua=function(e){e.selectionStart=0,e.selectionEnd=e.value.length}:xo&&(Ua=function(e){try{e.select()}catch(t){}});var qa,Ga=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/,Ya=e.isWordChar=function(e){return/\w/.test(e)||e>"€"&&(e.toUpperCase()!=e.toLowerCase()||Ga.test(e))},$a=/[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/;qa=document.createRange?function(e,t,n,r){var i=document.createRange();return i.setEnd(r||e,n),i.setStart(e,t),i}:function(e,t,n){var r=document.body.createTextRange();try{r.moveToElementText(e.parentNode)}catch(i){return r}return r.collapse(!0),r.moveEnd("character",n),r.moveStart("character",t),r};var Va=e.contains=function(e,t){if(3==t.nodeType&&(t=t.parentNode),e.contains)return e.contains(t);do if(11==t.nodeType&&(t=t.host),t==e)return!0;while(t=t.parentNode)};xo&&11>bo&&(Gi=function(){try{return document.activeElement}catch(e){return document.body}});var Ka,Xa,Za=e.rmClass=function(e,t){var n=e.className,r=Yi(t).exec(n);if(r){var i=n.slice(r.index+r[0].length);e.className=n.slice(0,r.index)+(i?r[1]+i:"")}},Ja=e.addClass=function(e,t){var n=e.className;Yi(t).test(n)||(e.className+=(n?" ":"")+t)},Qa=!1,el=function(){if(xo&&9>bo)return!1;var e=ji("div");return"draggable"in e||"dragDrop"in e}(),tl=e.splitLines=3!="\n\nb".split(/\n/).length?function(e){for(var t=0,n=[],r=e.length;r>=t;){var i=e.indexOf("\n",t);-1==i&&(i=e.length);var o=e.slice(t,"\r"==e.charAt(i-1)?i-1:i),a=o.indexOf("\r");-1!=a?(n.push(o.slice(0,a)),t+=a+1):(n.push(o),t=i+1)}return n}:function(e){return e.split(/\r\n?|\n/)},nl=window.getSelection?function(e){try{return e.selectionStart!=e.selectionEnd}catch(t){return!1}}:function(e){try{var t=e.ownerDocument.selection.createRange()}catch(n){}return t&&t.parentElement()==e?0!=t.compareEndPoints("StartToEnd",t):!1},rl=function(){var e=ji("div");return"oncopy"in e?!0:(e.setAttribute("oncopy","return;"),"function"==typeof e.oncopy)}(),il=null,ol=e.keyNames={3:"Enter",8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"CapsLock",27:"Esc",32:"Space",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",44:"PrintScrn",45:"Insert",46:"Delete",59:";",61:"=",91:"Mod",92:"Mod",93:"Mod",106:"*",107:"=",109:"-",110:".",111:"/",127:"Delete",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",63232:"Up",63233:"Down",63234:"Left",63235:"Right",63272:"Delete",63273:"Home",63275:"End",63276:"PageUp",63277:"PageDown",63302:"Insert"};!function(){for(var e=0;10>e;e++)ol[e+48]=ol[e+96]=String(e);for(var e=65;90>=e;e++)ol[e]=String.fromCharCode(e);for(var e=1;12>=e;e++)ol[e+111]=ol[e+63235]="F"+e}();var al,ll=function(){function e(e){return 247>=e?n.charAt(e):e>=1424&&1524>=e?"R":e>=1536&&1773>=e?r.charAt(e-1536):e>=1774&&2220>=e?"r":e>=8192&&8203>=e?"w":8204==e?"b":"L"}function t(e,t,n){this.level=e,this.from=t,this.to=n}var n="bbbbbbbbbtstwsbbbbbbbbbbbbbbssstwNN%%%NNNNNN,N,N1111111111NNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNbbbbbbsbbbbbbbbbbbbbbbbbbbbbbbbbb,N%%%%NNNNLNNNNN%%11NLNNN1LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN",r="rrrrrrrrrrrr,rNNmmmmmmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmrrrrrrrnnnnnnnnnn%nnrrrmrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmmmmmmmmmmmmmmmmmmmNmmmm",i=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/,o=/[stwN]/,a=/[LRr]/,l=/[Lb1n]/,s=/[1n]/,c="L";return function(n){if(!i.test(n))return!1;for(var r,u=n.length,f=[],h=0;u>h;++h)f.push(r=e(n.charCodeAt(h)));for(var h=0,d=c;u>h;++h){var r=f[h];"m"==r?f[h]=d:d=r}for(var h=0,p=c;u>h;++h){var r=f[h];"1"==r&&"r"==p?f[h]="n":a.test(r)&&(p=r,"r"==r&&(f[h]="R"))}for(var h=1,d=f[0];u-1>h;++h){var r=f[h];"+"==r&&"1"==d&&"1"==f[h+1]?f[h]="1":","!=r||d!=f[h+1]||"1"!=d&&"n"!=d||(f[h]=d),d=r}for(var h=0;u>h;++h){var r=f[h];if(","==r)f[h]="N";else if("%"==r){for(var m=h+1;u>m&&"%"==f[m];++m);for(var g=h&&"!"==f[h-1]||u>m&&"1"==f[m]?"1":"N",v=h;m>v;++v)f[v]=g;h=m-1}}for(var h=0,p=c;u>h;++h){var r=f[h];"L"==p&&"1"==r?f[h]="L":a.test(r)&&(p=r)}for(var h=0;u>h;++h)if(o.test(f[h])){for(var m=h+1;u>m&&o.test(f[m]);++m);for(var y="L"==(h?f[h-1]:c),x="L"==(u>m?f[m]:c),g=y||x?"L":"R",v=h;m>v;++v)f[v]=g;h=m-1}for(var b,w=[],h=0;u>h;)if(l.test(f[h])){var k=h;for(++h;u>h&&l.test(f[h]);++h);w.push(new t(0,k,h))}else{var S=h,C=w.length;for(++h;u>h&&"L"!=f[h];++h);for(var v=S;h>v;)if(s.test(f[v])){v>S&&w.splice(C,0,new t(1,S,v));var L=v;for(++v;h>v&&s.test(f[v]);++v);w.splice(C,0,new t(2,L,v)),S=v}else++v;h>S&&w.splice(C,0,new t(1,S,h))}return 1==w[0].level&&(b=n.match(/^\s+/))&&(w[0].from=b[0].length,w.unshift(new t(0,0,b[0].length))),1==Ii(w).level&&(b=n.match(/\s+$/))&&(Ii(w).to-=b[0].length,w.push(new t(0,u-b[0].length,u))),2==w[0].level&&w.unshift(new t(1,w[0].to,w[0].to)),w[0].level!=Ii(w).level&&w.push(new t(w[0].level,u,u)),w}}();return e.version="5.15.2",e})},{}],11:[function(t,n,r){!function(i){"object"==typeof r&&"object"==typeof n?i(t("../../lib/codemirror"),t("../markdown/markdown"),t("../../addon/mode/overlay")):"function"==typeof e&&e.amd?e(["../../lib/codemirror","../markdown/markdown","../../addon/mode/overlay"],i):i(CodeMirror)}(function(e){"use strict";var t=/^((?:(?:aaas?|about|acap|adiumxtra|af[ps]|aim|apt|attachment|aw|beshare|bitcoin|bolo|callto|cap|chrome(?:-extension)?|cid|coap|com-eventbrite-attendee|content|crid|cvs|data|dav|dict|dlna-(?:playcontainer|playsingle)|dns|doi|dtn|dvb|ed2k|facetime|feed|file|finger|fish|ftp|geo|gg|git|gizmoproject|go|gopher|gtalk|h323|hcp|https?|iax|icap|icon|im|imap|info|ipn|ipp|irc[6s]?|iris(?:\.beep|\.lwz|\.xpc|\.xpcs)?|itms|jar|javascript|jms|keyparc|lastfm|ldaps?|magnet|mailto|maps|market|message|mid|mms|ms-help|msnim|msrps?|mtqp|mumble|mupdate|mvn|news|nfs|nih?|nntp|notes|oid|opaquelocktoken|palm|paparazzi|platform|pop|pres|proxy|psyc|query|res(?:ource)?|rmi|rsync|rtmp|rtsp|secondlife|service|session|sftp|sgn|shttp|sieve|sips?|skype|sm[bs]|snmp|soap\.beeps?|soldat|spotify|ssh|steam|svn|tag|teamspeak|tel(?:net)?|tftp|things|thismessage|tip|tn3270|tv|udp|unreal|urn|ut2004|vemmi|ventrilo|view-source|webcal|wss?|wtai|wyciwyg|xcon(?:-userid)?|xfire|xmlrpc\.beeps?|xmpp|xri|ymsgr|z39\.50[rs]?):(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`*!()\[\]{};:'".,<>?«»“”‘’]))/i;e.defineMode("gfm",function(n,r){function i(e){return e.code=!1,null}var o=0,a={startState:function(){return{code:!1,codeBlock:!1,ateSpace:!1}},copyState:function(e){return{code:e.code,codeBlock:e.codeBlock,ateSpace:e.ateSpace}},token:function(e,n){if(n.combineTokens=null,n.codeBlock)return e.match(/^```+/)?(n.codeBlock=!1,null):(e.skipToEnd(),null);if(e.sol()&&(n.code=!1),e.sol()&&e.match(/^```+/))return e.skipToEnd(),n.codeBlock=!0,null;if("`"===e.peek()){e.next();var i=e.pos;e.eatWhile("`");var a=1+e.pos-i;return n.code?a===o&&(n.code=!1):(o=a,n.code=!0),null}if(n.code)return e.next(),null;if(e.eatSpace())return n.ateSpace=!0,null;if((e.sol()||n.ateSpace)&&(n.ateSpace=!1,r.gitHubSpice!==!1)){if(e.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/))return n.combineTokens=!0,"link";if(e.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/))return n.combineTokens=!0,"link"}return e.match(t)&&"]("!=e.string.slice(e.start-2,e.start)&&(0==e.start||/\W/.test(e.string.charAt(e.start-1)))?(n.combineTokens=!0,"link"):(e.next(),null)},blankLine:i},l={underscoresBreakWords:!1,taskLists:!0,fencedCodeBlocks:"```",strikethrough:!0};for(var s in r)l[s]=r[s];return l.name="markdown",e.overlayMode(e.getMode(n,l),a)},"markdown"),e.defineMIME("text/x-gfm","gfm")})},{"../../addon/mode/overlay":8,"../../lib/codemirror":10,"../markdown/markdown":12}],12:[function(t,n,r){!function(i){"object"==typeof r&&"object"==typeof n?i(t("../../lib/codemirror"),t("../xml/xml"),t("../meta")):"function"==typeof e&&e.amd?e(["../../lib/codemirror","../xml/xml","../meta"],i):i(CodeMirror)}(function(e){"use strict";e.defineMode("markdown",function(t,n){function r(n){if(e.findModeByName){var r=e.findModeByName(n);r&&(n=r.mime||r.mimes[0])}var i=e.getMode(t,n);return"null"==i.name?null:i}function i(e,t,n){return t.f=t.inline=n,n(e,t)}function o(e,t,n){return t.f=t.block=n,n(e,t)}function a(e){return!e||!/\S/.test(e.string)}function l(e){return e.linkTitle=!1,e.em=!1,e.strong=!1,e.strikethrough=!1,e.quote=0,e.indentedCode=!1,k&&e.f==c&&(e.f=p,e.block=s),e.trailingSpace=0,e.trailingSpaceNewLine=!1,e.prevLine=e.thisLine,e.thisLine=null,null}function s(t,o){var l=t.sol(),s=o.list!==!1,c=o.indentedCode;o.indentedCode=!1,s&&(o.indentationDiff>=0?(o.indentationDiff<4&&(o.indentation-=o.indentationDiff),o.list=null):o.indentation>0?o.list=null:o.list=!1);var f=null;if(o.indentationDiff>=4)return t.skipToEnd(),c||a(o.prevLine)?(o.indentation-=4,o.indentedCode=!0,S.code):null;if(t.eatSpace())return null;if((f=t.match(A))&&f[1].length<=6)return o.header=f[1].length,n.highlightFormatting&&(o.formatting="header"),o.f=o.inline,h(o);if(!(a(o.prevLine)||o.quote||s||c)&&(f=t.match(E)))return o.header="="==f[0].charAt(0)?1:2,n.highlightFormatting&&(o.formatting="header"),o.f=o.inline,h(o);if(t.eat(">"))return o.quote=l?1:o.quote+1,n.highlightFormatting&&(o.formatting="quote"),t.eatSpace(),h(o);if("["===t.peek())return i(t,o,y);if(t.match(L,!0))return o.hr=!0,S.hr;if((a(o.prevLine)||s)&&(t.match(T,!1)||t.match(M,!1))){var d=null;for(t.match(T,!0)?d="ul":(t.match(M,!0),d="ol"),o.indentation=t.column()+t.current().length,o.list=!0;o.listStack&&t.column()")>-1)&&(n.f=p,n.block=s,n.htmlState=null)}return r}function u(e,t){return t.fencedChars&&e.match(t.fencedChars,!1)?(t.localMode=t.localState=null,t.f=t.block=f,null):t.localMode?t.localMode.token(e,t.localState):(e.skipToEnd(),S.code)}function f(e,t){e.match(t.fencedChars),t.block=s,t.f=p,t.fencedChars=null,n.highlightFormatting&&(t.formatting="code-block"),t.code=1;var r=h(t);return t.code=0,r}function h(e){var t=[];if(e.formatting){t.push(S.formatting),"string"==typeof e.formatting&&(e.formatting=[e.formatting]);for(var r=0;r=e.quote?t.push(S.formatting+"-"+e.formatting[r]+"-"+e.quote):t.push("error"))}if(e.taskOpen)return t.push("meta"),t.length?t.join(" "):null;if(e.taskClosed)return t.push("property"),t.length?t.join(" "):null;if(e.linkHref?t.push(S.linkHref,"url"):(e.strong&&t.push(S.strong),e.em&&t.push(S.em),e.strikethrough&&t.push(S.strikethrough),e.linkText&&t.push(S.linkText),e.code&&t.push(S.code)),e.header&&t.push(S.header,S.header+"-"+e.header),e.quote&&(t.push(S.quote),!n.maxBlockquoteDepth||n.maxBlockquoteDepth>=e.quote?t.push(S.quote+"-"+e.quote):t.push(S.quote+"-"+n.maxBlockquoteDepth)),e.list!==!1){var i=(e.listStack.length-1)%3;i?1===i?t.push(S.list2):t.push(S.list3):t.push(S.list1)}return e.trailingSpaceNewLine?t.push("trailing-space-new-line"):e.trailingSpace&&t.push("trailing-space-"+(e.trailingSpace%2?"a":"b")),t.length?t.join(" "):null}function d(e,t){return e.match(O,!0)?h(t):void 0}function p(t,r){var i=r.text(t,r);if("undefined"!=typeof i)return i;if(r.list)return r.list=null,h(r);if(r.taskList){var a="x"!==t.match(N,!0)[1];return a?r.taskOpen=!0:r.taskClosed=!0,n.highlightFormatting&&(r.formatting="task"),r.taskList=!1,h(r)}if(r.taskOpen=!1,r.taskClosed=!1,r.header&&t.match(/^#+$/,!0))return n.highlightFormatting&&(r.formatting="header"), -h(r);var l=t.sol(),s=t.next();if(r.linkTitle){r.linkTitle=!1;var u=s;"("===s&&(u=")"),u=(u+"").replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1");var f="^\\s*(?:[^"+u+"\\\\]+|\\\\\\\\|\\\\.)"+u;if(t.match(new RegExp(f),!0))return S.linkHref}if("`"===s){var d=r.formatting;n.highlightFormatting&&(r.formatting="code"),t.eatWhile("`");var p=t.current().length;if(0==r.code)return r.code=p,h(r);if(p==r.code){var v=h(r);return r.code=0,v}return r.formatting=d,h(r)}if(r.code)return h(r);if("\\"===s&&(t.next(),n.highlightFormatting)){var y=h(r),x=S.formatting+"-escape";return y?y+" "+x:x}if("!"===s&&t.match(/\[[^\]]*\] ?(?:\(|\[)/,!1))return t.match(/\[[^\]]*\]/),r.inline=r.f=g,S.image;if("["===s&&t.match(/[^\]]*\](\(.*\)| ?\[.*?\])/,!1))return r.linkText=!0,n.highlightFormatting&&(r.formatting="link"),h(r);if("]"===s&&r.linkText&&t.match(/\(.*?\)| ?\[.*?\]/,!1)){n.highlightFormatting&&(r.formatting="link");var y=h(r);return r.linkText=!1,r.inline=r.f=g,y}if("<"===s&&t.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/,!1)){r.f=r.inline=m,n.highlightFormatting&&(r.formatting="link");var y=h(r);return y?y+=" ":y="",y+S.linkInline}if("<"===s&&t.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/,!1)){r.f=r.inline=m,n.highlightFormatting&&(r.formatting="link");var y=h(r);return y?y+=" ":y="",y+S.linkEmail}if("<"===s&&t.match(/^(!--|\w)/,!1)){var b=t.string.indexOf(">",t.pos);if(-1!=b){var k=t.string.substring(t.start,b);/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(k)&&(r.md_inside=!0)}return t.backUp(1),r.htmlState=e.startState(w),o(t,r,c)}if("<"===s&&t.match(/^\/\w*?>/))return r.md_inside=!1,"tag";var C=!1;if(!n.underscoresBreakWords&&"_"===s&&"_"!==t.peek()&&t.match(/(\w)/,!1)){var L=t.pos-2;if(L>=0){var T=t.string.charAt(L);"_"!==T&&T.match(/(\w)/,!1)&&(C=!0)}}if("*"===s||"_"===s&&!C)if(l&&" "===t.peek());else{if(r.strong===s&&t.eat(s)){n.highlightFormatting&&(r.formatting="strong");var v=h(r);return r.strong=!1,v}if(!r.strong&&t.eat(s))return r.strong=s,n.highlightFormatting&&(r.formatting="strong"),h(r);if(r.em===s){n.highlightFormatting&&(r.formatting="em");var v=h(r);return r.em=!1,v}if(!r.em)return r.em=s,n.highlightFormatting&&(r.formatting="em"),h(r)}else if(" "===s&&(t.eat("*")||t.eat("_"))){if(" "===t.peek())return h(r);t.backUp(1)}if(n.strikethrough)if("~"===s&&t.eatWhile(s)){if(r.strikethrough){n.highlightFormatting&&(r.formatting="strikethrough");var v=h(r);return r.strikethrough=!1,v}if(t.match(/^[^\s]/,!1))return r.strikethrough=!0,n.highlightFormatting&&(r.formatting="strikethrough"),h(r)}else if(" "===s&&t.match(/^~~/,!0)){if(" "===t.peek())return h(r);t.backUp(2)}return" "===s&&(t.match(/ +$/,!1)?r.trailingSpace++:r.trailingSpace&&(r.trailingSpaceNewLine=!0)),h(r)}function m(e,t){var r=e.next();if(">"===r){t.f=t.inline=p,n.highlightFormatting&&(t.formatting="link");var i=h(t);return i?i+=" ":i="",i+S.linkInline}return e.match(/^[^>]+/,!0),S.linkInline}function g(e,t){if(e.eatSpace())return null;var r=e.next();return"("===r||"["===r?(t.f=t.inline=v("("===r?")":"]",0),n.highlightFormatting&&(t.formatting="link-string"),t.linkHref=!0,h(t)):"error"}function v(e){return function(t,r){var i=t.next();if(i===e){r.f=r.inline=p,n.highlightFormatting&&(r.formatting="link-string");var o=h(r);return r.linkHref=!1,o}return t.match(P[e]),r.linkHref=!0,h(r)}}function y(e,t){return e.match(/^([^\]\\]|\\.)*\]:/,!1)?(t.f=x,e.next(),n.highlightFormatting&&(t.formatting="link"),t.linkText=!0,h(t)):i(e,t,p)}function x(e,t){if(e.match(/^\]:/,!0)){t.f=t.inline=b,n.highlightFormatting&&(t.formatting="link");var r=h(t);return t.linkText=!1,r}return e.match(/^([^\]\\]|\\.)+/,!0),S.linkText}function b(e,t){return e.eatSpace()?null:(e.match(/^[^\s]+/,!0),void 0===e.peek()?t.linkTitle=!0:e.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/,!0),t.f=t.inline=p,S.linkHref+" url")}var w=e.getMode(t,"text/html"),k="null"==w.name;void 0===n.highlightFormatting&&(n.highlightFormatting=!1),void 0===n.maxBlockquoteDepth&&(n.maxBlockquoteDepth=0),void 0===n.underscoresBreakWords&&(n.underscoresBreakWords=!0),void 0===n.taskLists&&(n.taskLists=!1),void 0===n.strikethrough&&(n.strikethrough=!1),void 0===n.tokenTypeOverrides&&(n.tokenTypeOverrides={});var S={header:"header",code:"comment",quote:"quote",list1:"variable-2",list2:"variable-3",list3:"keyword",hr:"hr",image:"tag",formatting:"formatting",linkInline:"link",linkEmail:"link",linkText:"link",linkHref:"string",em:"em",strong:"strong",strikethrough:"strikethrough"};for(var C in S)S.hasOwnProperty(C)&&n.tokenTypeOverrides[C]&&(S[C]=n.tokenTypeOverrides[C]);var L=/^([*\-_])(?:\s*\1){2,}\s*$/,T=/^[*\-+]\s+/,M=/^[0-9]+([.)])\s+/,N=/^\[(x| )\](?=\s)/,A=n.allowAtxHeaderWithoutSpace?/^(#+)/:/^(#+)(?: |$)/,E=/^ *(?:\={1,}|-{1,})\s*$/,O=/^[^#!\[\]*_\\<>` "'(~]+/,I=new RegExp("^("+(n.fencedCodeBlocks===!0?"~~~+|```+":n.fencedCodeBlocks)+")[ \\t]*([\\w+#-]*)"),P={")":/^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,"]":/^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\\]]|\\.)*\])*?(?=\])/},R={startState:function(){return{f:s,prevLine:null,thisLine:null,block:s,htmlState:null,indentation:0,inline:p,text:d,formatting:!1,linkText:!1,linkHref:!1,linkTitle:!1,code:0,em:!1,strong:!1,header:0,hr:!1,taskList:!1,list:!1,listStack:[],quote:0,trailingSpace:0,trailingSpaceNewLine:!1,strikethrough:!1,fencedChars:null}},copyState:function(t){return{f:t.f,prevLine:t.prevLine,thisLine:t.thisLine,block:t.block,htmlState:t.htmlState&&e.copyState(w,t.htmlState),indentation:t.indentation,localMode:t.localMode,localState:t.localMode?e.copyState(t.localMode,t.localState):null,inline:t.inline,text:t.text,formatting:!1,linkTitle:t.linkTitle,code:t.code,em:t.em,strong:t.strong,strikethrough:t.strikethrough,header:t.header,hr:t.hr,taskList:t.taskList,list:t.list,listStack:t.listStack.slice(0),quote:t.quote,indentedCode:t.indentedCode,trailingSpace:t.trailingSpace,trailingSpaceNewLine:t.trailingSpaceNewLine,md_inside:t.md_inside,fencedChars:t.fencedChars}},token:function(e,t){if(t.formatting=!1,e!=t.thisLine){var n=t.header||t.hr;if(t.header=0,t.hr=!1,e.match(/^\s*$/,!0)||n){if(l(t),!n)return null;t.prevLine=null}t.prevLine=t.thisLine,t.thisLine=e,t.taskList=!1,t.trailingSpace=0,t.trailingSpaceNewLine=!1,t.f=t.block;var r=e.match(/^\s*/,!0)[0].replace(/\t/g," ").length;if(t.indentationDiff=Math.min(r-t.indentation,4),t.indentation=t.indentation+t.indentationDiff,r>0)return null}return t.f(e,t)},innerMode:function(e){return e.block==c?{state:e.htmlState,mode:w}:e.localState?{state:e.localState,mode:e.localMode}:{state:e,mode:R}},blankLine:l,getType:h,fold:"markdown"};return R},"xml"),e.defineMIME("text/x-markdown","markdown")})},{"../../lib/codemirror":10,"../meta":13,"../xml/xml":14}],13:[function(t,n,r){!function(i){"object"==typeof r&&"object"==typeof n?i(t("../lib/codemirror")):"function"==typeof e&&e.amd?e(["../lib/codemirror"],i):i(CodeMirror)}(function(e){"use strict";e.modeInfo=[{name:"APL",mime:"text/apl",mode:"apl",ext:["dyalog","apl"]},{name:"PGP",mimes:["application/pgp","application/pgp-keys","application/pgp-signature"],mode:"asciiarmor",ext:["pgp"]},{name:"ASN.1",mime:"text/x-ttcn-asn",mode:"asn.1",ext:["asn","asn1"]},{name:"Asterisk",mime:"text/x-asterisk",mode:"asterisk",file:/^extensions\.conf$/i},{name:"Brainfuck",mime:"text/x-brainfuck",mode:"brainfuck",ext:["b","bf"]},{name:"C",mime:"text/x-csrc",mode:"clike",ext:["c","h"]},{name:"C++",mime:"text/x-c++src",mode:"clike",ext:["cpp","c++","cc","cxx","hpp","h++","hh","hxx"],alias:["cpp"]},{name:"Cobol",mime:"text/x-cobol",mode:"cobol",ext:["cob","cpy"]},{name:"C#",mime:"text/x-csharp",mode:"clike",ext:["cs"],alias:["csharp"]},{name:"Clojure",mime:"text/x-clojure",mode:"clojure",ext:["clj","cljc","cljx"]},{name:"ClojureScript",mime:"text/x-clojurescript",mode:"clojure",ext:["cljs"]},{name:"Closure Stylesheets (GSS)",mime:"text/x-gss",mode:"css",ext:["gss"]},{name:"CMake",mime:"text/x-cmake",mode:"cmake",ext:["cmake","cmake.in"],file:/^CMakeLists.txt$/},{name:"CoffeeScript",mime:"text/x-coffeescript",mode:"coffeescript",ext:["coffee"],alias:["coffee","coffee-script"]},{name:"Common Lisp",mime:"text/x-common-lisp",mode:"commonlisp",ext:["cl","lisp","el"],alias:["lisp"]},{name:"Cypher",mime:"application/x-cypher-query",mode:"cypher",ext:["cyp","cypher"]},{name:"Cython",mime:"text/x-cython",mode:"python",ext:["pyx","pxd","pxi"]},{name:"Crystal",mime:"text/x-crystal",mode:"crystal",ext:["cr"]},{name:"CSS",mime:"text/css",mode:"css",ext:["css"]},{name:"CQL",mime:"text/x-cassandra",mode:"sql",ext:["cql"]},{name:"D",mime:"text/x-d",mode:"d",ext:["d"]},{name:"Dart",mimes:["application/dart","text/x-dart"],mode:"dart",ext:["dart"]},{name:"diff",mime:"text/x-diff",mode:"diff",ext:["diff","patch"]},{name:"Django",mime:"text/x-django",mode:"django"},{name:"Dockerfile",mime:"text/x-dockerfile",mode:"dockerfile",file:/^Dockerfile$/},{name:"DTD",mime:"application/xml-dtd",mode:"dtd",ext:["dtd"]},{name:"Dylan",mime:"text/x-dylan",mode:"dylan",ext:["dylan","dyl","intr"]},{name:"EBNF",mime:"text/x-ebnf",mode:"ebnf"},{name:"ECL",mime:"text/x-ecl",mode:"ecl",ext:["ecl"]},{name:"edn",mime:"application/edn",mode:"clojure",ext:["edn"]},{name:"Eiffel",mime:"text/x-eiffel",mode:"eiffel",ext:["e"]},{name:"Elm",mime:"text/x-elm",mode:"elm",ext:["elm"]},{name:"Embedded Javascript",mime:"application/x-ejs",mode:"htmlembedded",ext:["ejs"]},{name:"Embedded Ruby",mime:"application/x-erb",mode:"htmlembedded",ext:["erb"]},{name:"Erlang",mime:"text/x-erlang",mode:"erlang",ext:["erl"]},{name:"Factor",mime:"text/x-factor",mode:"factor",ext:["factor"]},{name:"FCL",mime:"text/x-fcl",mode:"fcl"},{name:"Forth",mime:"text/x-forth",mode:"forth",ext:["forth","fth","4th"]},{name:"Fortran",mime:"text/x-fortran",mode:"fortran",ext:["f","for","f77","f90"]},{name:"F#",mime:"text/x-fsharp",mode:"mllike",ext:["fs"],alias:["fsharp"]},{name:"Gas",mime:"text/x-gas",mode:"gas",ext:["s"]},{name:"Gherkin",mime:"text/x-feature",mode:"gherkin",ext:["feature"]},{name:"GitHub Flavored Markdown",mime:"text/x-gfm",mode:"gfm",file:/^(readme|contributing|history).md$/i},{name:"Go",mime:"text/x-go",mode:"go",ext:["go"]},{name:"Groovy",mime:"text/x-groovy",mode:"groovy",ext:["groovy","gradle"]},{name:"HAML",mime:"text/x-haml",mode:"haml",ext:["haml"]},{name:"Haskell",mime:"text/x-haskell",mode:"haskell",ext:["hs"]},{name:"Haskell (Literate)",mime:"text/x-literate-haskell",mode:"haskell-literate",ext:["lhs"]},{name:"Haxe",mime:"text/x-haxe",mode:"haxe",ext:["hx"]},{name:"HXML",mime:"text/x-hxml",mode:"haxe",ext:["hxml"]},{name:"ASP.NET",mime:"application/x-aspx",mode:"htmlembedded",ext:["aspx"],alias:["asp","aspx"]},{name:"HTML",mime:"text/html",mode:"htmlmixed",ext:["html","htm"],alias:["xhtml"]},{name:"HTTP",mime:"message/http",mode:"http"},{name:"IDL",mime:"text/x-idl",mode:"idl",ext:["pro"]},{name:"Jade",mime:"text/x-jade",mode:"jade",ext:["jade"]},{name:"Java",mime:"text/x-java",mode:"clike",ext:["java"]},{name:"Java Server Pages",mime:"application/x-jsp",mode:"htmlembedded",ext:["jsp"],alias:["jsp"]},{name:"JavaScript",mimes:["text/javascript","text/ecmascript","application/javascript","application/x-javascript","application/ecmascript"],mode:"javascript",ext:["js"],alias:["ecmascript","js","node"]},{name:"JSON",mimes:["application/json","application/x-json"],mode:"javascript",ext:["json","map"],alias:["json5"]},{name:"JSON-LD",mime:"application/ld+json",mode:"javascript",ext:["jsonld"],alias:["jsonld"]},{name:"JSX",mime:"text/jsx",mode:"jsx",ext:["jsx"]},{name:"Jinja2",mime:"null",mode:"jinja2"},{name:"Julia",mime:"text/x-julia",mode:"julia",ext:["jl"]},{name:"Kotlin",mime:"text/x-kotlin",mode:"clike",ext:["kt"]},{name:"LESS",mime:"text/x-less",mode:"css",ext:["less"]},{name:"LiveScript",mime:"text/x-livescript",mode:"livescript",ext:["ls"],alias:["ls"]},{name:"Lua",mime:"text/x-lua",mode:"lua",ext:["lua"]},{name:"Markdown",mime:"text/x-markdown",mode:"markdown",ext:["markdown","md","mkd"]},{name:"mIRC",mime:"text/mirc",mode:"mirc"},{name:"MariaDB SQL",mime:"text/x-mariadb",mode:"sql"},{name:"Mathematica",mime:"text/x-mathematica",mode:"mathematica",ext:["m","nb"]},{name:"Modelica",mime:"text/x-modelica",mode:"modelica",ext:["mo"]},{name:"MUMPS",mime:"text/x-mumps",mode:"mumps",ext:["mps"]},{name:"MS SQL",mime:"text/x-mssql",mode:"sql"},{name:"mbox",mime:"application/mbox",mode:"mbox",ext:["mbox"]},{name:"MySQL",mime:"text/x-mysql",mode:"sql"},{name:"Nginx",mime:"text/x-nginx-conf",mode:"nginx",file:/nginx.*\.conf$/i},{name:"NSIS",mime:"text/x-nsis",mode:"nsis",ext:["nsh","nsi"]},{name:"NTriples",mime:"text/n-triples",mode:"ntriples",ext:["nt"]},{name:"Objective C",mime:"text/x-objectivec",mode:"clike",ext:["m","mm"],alias:["objective-c","objc"]},{name:"OCaml",mime:"text/x-ocaml",mode:"mllike",ext:["ml","mli","mll","mly"]},{name:"Octave",mime:"text/x-octave",mode:"octave",ext:["m"]},{name:"Oz",mime:"text/x-oz",mode:"oz",ext:["oz"]},{name:"Pascal",mime:"text/x-pascal",mode:"pascal",ext:["p","pas"]},{name:"PEG.js",mime:"null",mode:"pegjs",ext:["jsonld"]},{name:"Perl",mime:"text/x-perl",mode:"perl",ext:["pl","pm"]},{name:"PHP",mime:"application/x-httpd-php",mode:"php",ext:["php","php3","php4","php5","phtml"]},{name:"Pig",mime:"text/x-pig",mode:"pig",ext:["pig"]},{name:"Plain Text",mime:"text/plain",mode:"null",ext:["txt","text","conf","def","list","log"]},{name:"PLSQL",mime:"text/x-plsql",mode:"sql",ext:["pls"]},{name:"PowerShell",mime:"application/x-powershell",mode:"powershell",ext:["ps1","psd1","psm1"]},{name:"Properties files",mime:"text/x-properties",mode:"properties",ext:["properties","ini","in"],alias:["ini","properties"]},{name:"ProtoBuf",mime:"text/x-protobuf",mode:"protobuf",ext:["proto"]},{name:"Python",mime:"text/x-python",mode:"python",ext:["BUILD","bzl","py","pyw"],file:/^(BUCK|BUILD)$/},{name:"Puppet",mime:"text/x-puppet",mode:"puppet",ext:["pp"]},{name:"Q",mime:"text/x-q",mode:"q",ext:["q"]},{name:"R",mime:"text/x-rsrc",mode:"r",ext:["r"],alias:["rscript"]},{name:"reStructuredText",mime:"text/x-rst",mode:"rst",ext:["rst"],alias:["rst"]},{name:"RPM Changes",mime:"text/x-rpm-changes",mode:"rpm"},{name:"RPM Spec",mime:"text/x-rpm-spec",mode:"rpm",ext:["spec"]},{name:"Ruby",mime:"text/x-ruby",mode:"ruby",ext:["rb"],alias:["jruby","macruby","rake","rb","rbx"]},{name:"Rust",mime:"text/x-rustsrc",mode:"rust",ext:["rs"]},{name:"SAS",mime:"text/x-sas",mode:"sas",ext:["sas"]},{name:"Sass",mime:"text/x-sass",mode:"sass",ext:["sass"]},{name:"Scala",mime:"text/x-scala",mode:"clike",ext:["scala"]},{name:"Scheme",mime:"text/x-scheme",mode:"scheme",ext:["scm","ss"]},{name:"SCSS",mime:"text/x-scss",mode:"css",ext:["scss"]},{name:"Shell",mime:"text/x-sh",mode:"shell",ext:["sh","ksh","bash"],alias:["bash","sh","zsh"],file:/^PKGBUILD$/},{name:"Sieve",mime:"application/sieve",mode:"sieve",ext:["siv","sieve"]},{name:"Slim",mimes:["text/x-slim","application/x-slim"],mode:"slim",ext:["slim"]},{name:"Smalltalk",mime:"text/x-stsrc",mode:"smalltalk",ext:["st"]},{name:"Smarty",mime:"text/x-smarty",mode:"smarty",ext:["tpl"]},{name:"Solr",mime:"text/x-solr",mode:"solr"},{name:"Soy",mime:"text/x-soy",mode:"soy",ext:["soy"],alias:["closure template"]},{name:"SPARQL",mime:"application/sparql-query",mode:"sparql",ext:["rq","sparql"],alias:["sparul"]},{name:"Spreadsheet",mime:"text/x-spreadsheet",mode:"spreadsheet",alias:["excel","formula"]},{name:"SQL",mime:"text/x-sql",mode:"sql",ext:["sql"]},{name:"Squirrel",mime:"text/x-squirrel",mode:"clike",ext:["nut"]},{name:"Swift",mime:"text/x-swift",mode:"swift",ext:["swift"]},{name:"sTeX",mime:"text/x-stex",mode:"stex"},{name:"LaTeX",mime:"text/x-latex",mode:"stex",ext:["text","ltx"],alias:["tex"]},{name:"SystemVerilog",mime:"text/x-systemverilog",mode:"verilog",ext:["v"]},{name:"Tcl",mime:"text/x-tcl",mode:"tcl",ext:["tcl"]},{name:"Textile",mime:"text/x-textile",mode:"textile",ext:["textile"]},{name:"TiddlyWiki ",mime:"text/x-tiddlywiki",mode:"tiddlywiki"},{name:"Tiki wiki",mime:"text/tiki",mode:"tiki"},{name:"TOML",mime:"text/x-toml",mode:"toml",ext:["toml"]},{name:"Tornado",mime:"text/x-tornado",mode:"tornado"},{name:"troff",mime:"text/troff",mode:"troff",ext:["1","2","3","4","5","6","7","8","9"]},{name:"TTCN",mime:"text/x-ttcn",mode:"ttcn",ext:["ttcn","ttcn3","ttcnpp"]},{name:"TTCN_CFG",mime:"text/x-ttcn-cfg",mode:"ttcn-cfg",ext:["cfg"]},{name:"Turtle",mime:"text/turtle",mode:"turtle",ext:["ttl"]},{name:"TypeScript",mime:"application/typescript",mode:"javascript",ext:["ts"],alias:["ts"]},{name:"Twig",mime:"text/x-twig",mode:"twig"},{name:"Web IDL",mime:"text/x-webidl",mode:"webidl",ext:["webidl"]},{name:"VB.NET",mime:"text/x-vb",mode:"vb",ext:["vb"]},{name:"VBScript",mime:"text/vbscript",mode:"vbscript",ext:["vbs"]},{name:"Velocity",mime:"text/velocity",mode:"velocity",ext:["vtl"]},{name:"Verilog",mime:"text/x-verilog",mode:"verilog",ext:["v"]},{name:"VHDL",mime:"text/x-vhdl",mode:"vhdl",ext:["vhd","vhdl"]},{name:"XML",mimes:["application/xml","text/xml"],mode:"xml",ext:["xml","xsl","xsd"],alias:["rss","wsdl","xsd"]},{name:"XQuery",mime:"application/xquery",mode:"xquery",ext:["xy","xquery"]},{name:"Yacas",mime:"text/x-yacas",mode:"yacas",ext:["ys"]},{name:"YAML",mime:"text/x-yaml",mode:"yaml",ext:["yaml","yml"],alias:["yml"]},{name:"Z80",mime:"text/x-z80",mode:"z80",ext:["z80"]},{name:"mscgen",mime:"text/x-mscgen",mode:"mscgen",ext:["mscgen","mscin","msc"]},{name:"xu",mime:"text/x-xu",mode:"mscgen",ext:["xu"]},{name:"msgenny",mime:"text/x-msgenny",mode:"mscgen",ext:["msgenny"]}];for(var t=0;t-1&&t.substring(i+1,t.length);return o?e.findModeByExtension(o):void 0},e.findModeByName=function(t){t=t.toLowerCase();for(var n=0;n")):null:e.match("--")?n(s("comment","-->")):e.match("DOCTYPE",!0,!0)?(e.eatWhile(/[\w\._\-]/),n(c(1))):null:e.eat("?")?(e.eatWhile(/[\w\._\-]/),t.tokenize=s("meta","?>"),"meta"):(T=e.eat("/")?"closeTag":"openTag",t.tokenize=a,"tag bracket");if("&"==r){var i;return i=e.eat("#")?e.eat("x")?e.eatWhile(/[a-fA-F\d]/)&&e.eat(";"):e.eatWhile(/[\d]/)&&e.eat(";"):e.eatWhile(/[\w\.\-:]/)&&e.eat(";"),i?"atom":"error"}return e.eatWhile(/[^&<]/),null}function a(e,t){var n=e.next();if(">"==n||"/"==n&&e.eat(">"))return t.tokenize=o,T=">"==n?"endTag":"selfcloseTag","tag bracket";if("="==n)return T="equals",null;if("<"==n){t.tokenize=o,t.state=d,t.tagName=t.tagStart=null;var r=t.tokenize(e,t);return r?r+" tag error":"tag error"}return/[\'\"]/.test(n)?(t.tokenize=l(n),t.stringStartCol=e.column(),t.tokenize(e,t)):(e.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/),"word")}function l(e){var t=function(t,n){for(;!t.eol();)if(t.next()==e){n.tokenize=a;break}return"string"};return t.isInAttribute=!0,t}function s(e,t){return function(n,r){for(;!n.eol();){if(n.match(t)){r.tokenize=o;break}n.next()}return e}}function c(e){return function(t,n){for(var r;null!=(r=t.next());){if("<"==r)return n.tokenize=c(e+1),n.tokenize(t,n);if(">"==r){if(1==e){n.tokenize=o;break}return n.tokenize=c(e-1),n.tokenize(t,n)}}return"meta"}}function u(e,t,n){this.prev=e.context,this.tagName=t,this.indent=e.indented,this.startOfLine=n,(S.doNotIndent.hasOwnProperty(t)||e.context&&e.context.noIndent)&&(this.noIndent=!0)}function f(e){e.context&&(e.context=e.context.prev)}function h(e,t){for(var n;;){if(!e.context)return;if(n=e.context.tagName,!S.contextGrabbers.hasOwnProperty(n)||!S.contextGrabbers[n].hasOwnProperty(t))return;f(e)}}function d(e,t,n){return"openTag"==e?(n.tagStart=t.column(),p):"closeTag"==e?m:d}function p(e,t,n){return"word"==e?(n.tagName=t.current(),M="tag",y):(M="error",p)}function m(e,t,n){if("word"==e){var r=t.current();return n.context&&n.context.tagName!=r&&S.implicitlyClosed.hasOwnProperty(n.context.tagName)&&f(n),n.context&&n.context.tagName==r||S.matchClosing===!1?(M="tag",g):(M="tag error",v)}return M="error",v}function g(e,t,n){return"endTag"!=e?(M="error",g):(f(n),d)}function v(e,t,n){return M="error",g(e,t,n)}function y(e,t,n){if("word"==e)return M="attribute",x;if("endTag"==e||"selfcloseTag"==e){var r=n.tagName,i=n.tagStart;return n.tagName=n.tagStart=null,"selfcloseTag"==e||S.autoSelfClosers.hasOwnProperty(r)?h(n,r):(h(n,r),n.context=new u(n,r,i==n.indented)),d}return M="error",y}function x(e,t,n){return"equals"==e?b:(S.allowMissing||(M="error"),y(e,t,n))}function b(e,t,n){return"string"==e?w:"word"==e&&S.allowUnquoted?(M="string",y):(M="error",y(e,t,n))}function w(e,t,n){return"string"==e?w:y(e,t,n)}var k=r.indentUnit,S={},C=i.htmlMode?t:n;for(var L in C)S[L]=C[L];for(var L in i)S[L]=i[L];var T,M;return o.isInText=!0,{startState:function(e){var t={tokenize:o,state:d,indented:e||0,tagName:null,tagStart:null,context:null};return null!=e&&(t.baseIndent=e),t},token:function(e,t){if(!t.tagName&&e.sol()&&(t.indented=e.indentation()),e.eatSpace())return null;T=null;var n=t.tokenize(e,t);return(n||T)&&"comment"!=n&&(M=null,t.state=t.state(T||n,e,t),M&&(n="error"==M?n+" error":M)),n},indent:function(t,n,r){var i=t.context;if(t.tokenize.isInAttribute)return t.tagStart==t.indented?t.stringStartCol+1:t.indented+k;if(i&&i.noIndent)return e.Pass;if(t.tokenize!=a&&t.tokenize!=o)return r?r.match(/^(\s*)/)[0].length:0;if(t.tagName)return S.multilineTagIndentPastTag!==!1?t.tagStart+t.tagName.length+2:t.tagStart+k*(S.multilineTagIndentFactor||1);if(S.alignCDATA&&/$/,blockCommentStart:"",configuration:S.htmlMode?"html":"xml",helperType:S.htmlMode?"html":"xml",skipAttribute:function(e){e.state==b&&(e.state=y)}}}),e.defineMIME("text/xml","xml"),e.defineMIME("application/xml","xml"),e.mimeModes.hasOwnProperty("text/html")||e.defineMIME("text/html",{name:"xml",htmlMode:!0})})},{"../../lib/codemirror":10}],15:[function(e,t,n){n.read=function(e,t,n,r,i){var o,a,l=8*i-r-1,s=(1<>1,u=-7,f=n?i-1:0,h=n?-1:1,d=e[t+f];for(f+=h,o=d&(1<<-u)-1,d>>=-u,u+=l;u>0;o=256*o+e[t+f],f+=h,u-=8);for(a=o&(1<<-u)-1,o>>=-u,u+=r;u>0;a=256*a+e[t+f],f+=h,u-=8);if(0===o)o=1-c;else{if(o===s)return a?NaN:(d?-1:1)*(1/0);a+=Math.pow(2,r),o-=c}return(d?-1:1)*a*Math.pow(2,o-r)},n.write=function(e,t,n,r,i,o){var a,l,s,c=8*o-i-1,u=(1<>1,h=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,d=r?0:o-1,p=r?1:-1,m=0>t||0===t&&0>1/t?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(l=isNaN(t)?1:0,a=u):(a=Math.floor(Math.log(t)/Math.LN2),t*(s=Math.pow(2,-a))<1&&(a--,s*=2),t+=a+f>=1?h/s:h*Math.pow(2,1-f),t*s>=2&&(a++,s/=2),a+f>=u?(l=0,a=u):a+f>=1?(l=(t*s-1)*Math.pow(2,i),a+=f):(l=t*Math.pow(2,f-1)*Math.pow(2,i),a=0));i>=8;e[n+d]=255&l,d+=p,l/=256,i-=8);for(a=a<0;e[n+d]=255&a,d+=p,a/=256,c-=8);e[n+d-p]|=128*m}},{}],16:[function(e,t,n){var r={}.toString;t.exports=Array.isArray||function(e){return"[object Array]"==r.call(e)}},{}],17:[function(t,n,r){(function(t){(function(){function t(e){this.tokens=[],this.tokens.links={},this.options=e||h.defaults,this.rules=d.normal,this.options.gfm&&(this.options.tables?this.rules=d.tables:this.rules=d.gfm)}function i(e,t){if(this.options=t||h.defaults,this.links=e,this.rules=p.normal,this.renderer=this.options.renderer||new o,this.renderer.options=this.options,!this.links)throw new Error("Tokens array requires a `links` property.");this.options.gfm?this.options.breaks?this.rules=p.breaks:this.rules=p.gfm:this.options.pedantic&&(this.rules=p.pedantic)}function o(e){this.options=e||{}}function a(e){this.tokens=[],this.token=null,this.options=e||h.defaults,this.options.renderer=this.options.renderer||new o,this.renderer=this.options.renderer,this.renderer.options=this.options}function l(e,t){return e.replace(t?/&/g:/&(?!#?\w+;)/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function s(e){return e.replace(/&([#\w]+);/g,function(e,t){return t=t.toLowerCase(),"colon"===t?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}function c(e,t){return e=e.source,t=t||"",function n(r,i){return r?(i=i.source||i,i=i.replace(/(^|[^\[])\^/g,"$1"),e=e.replace(r,i),n):new RegExp(e,t)}}function u(){}function f(e){for(var t,n,r=1;rAn error occured:

"+l(u.message+"",!0)+"
";throw u}}var d={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:u,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:u,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:u,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};d.bullet=/(?:[*+-]|\d+\.)/,d.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,d.item=c(d.item,"gm")(/bull/g,d.bullet)(),d.list=c(d.list)(/bull/g,d.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+d.def.source+")")(),d.blockquote=c(d.blockquote)("def",d.def)(),d._tag="(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b",d.html=c(d.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,d._tag)(),d.paragraph=c(d.paragraph)("hr",d.hr)("heading",d.heading)("lheading",d.lheading)("blockquote",d.blockquote)("tag","<"+d._tag)("def",d.def)(),d.normal=f({},d),d.gfm=f({},d.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),d.gfm.paragraph=c(d.paragraph)("(?!","(?!"+d.gfm.fences.source.replace("\\1","\\2")+"|"+d.list.source.replace("\\1","\\3")+"|")(),d.tables=f({},d.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/}),t.rules=d,t.lex=function(e,n){var r=new t(n);return r.lex(e)},t.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},t.prototype.token=function(e,t,n){for(var r,i,o,a,l,s,c,u,f,e=e.replace(/^ +$/gm,"");e;)if((o=this.rules.newline.exec(e))&&(e=e.substring(o[0].length),o[0].length>1&&this.tokens.push({type:"space"})),o=this.rules.code.exec(e))e=e.substring(o[0].length),o=o[0].replace(/^ {4}/gm,""),this.tokens.push({type:"code",text:this.options.pedantic?o:o.replace(/\n+$/,"")});else if(o=this.rules.fences.exec(e))e=e.substring(o[0].length),this.tokens.push({type:"code",lang:o[2],text:o[3]||""});else if(o=this.rules.heading.exec(e))e=e.substring(o[0].length),this.tokens.push({type:"heading",depth:o[1].length,text:o[2]});else if(t&&(o=this.rules.nptable.exec(e))){for(e=e.substring(o[0].length),s={type:"table",header:o[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:o[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:o[3].replace(/\n$/,"").split("\n")},u=0;u ?/gm,""),this.token(o,t,!0),this.tokens.push({type:"blockquote_end"});else if(o=this.rules.list.exec(e)){for(e=e.substring(o[0].length),a=o[2],this.tokens.push({type:"list_start",ordered:a.length>1}),o=o[0].match(this.rules.item),r=!1,f=o.length,u=0;f>u;u++)s=o[u],c=s.length,s=s.replace(/^ *([*+-]|\d+\.) +/,""),~s.indexOf("\n ")&&(c-=s.length,s=this.options.pedantic?s.replace(/^ {1,4}/gm,""):s.replace(new RegExp("^ {1,"+c+"}","gm"),"")),this.options.smartLists&&u!==f-1&&(l=d.bullet.exec(o[u+1])[0],a===l||a.length>1&&l.length>1||(e=o.slice(u+1).join("\n")+e,u=f-1)),i=r||/\n\n(?!\s*$)/.test(s),u!==f-1&&(r="\n"===s.charAt(s.length-1),i||(i=r)),this.tokens.push({type:i?"loose_item_start":"list_item_start"}),this.token(s,!1,n),this.tokens.push({type:"list_item_end"});this.tokens.push({type:"list_end"})}else if(o=this.rules.html.exec(e))e=e.substring(o[0].length),this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&("pre"===o[1]||"script"===o[1]||"style"===o[1]),text:o[0]});else if(!n&&t&&(o=this.rules.def.exec(e)))e=e.substring(o[0].length),this.tokens.links[o[1].toLowerCase()]={href:o[2],title:o[3]};else if(t&&(o=this.rules.table.exec(e))){for(e=e.substring(o[0].length),s={type:"table", -header:o[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:o[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:o[3].replace(/(?: *\| *)?\n$/,"").split("\n")},u=0;u])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:u,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:u,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/,p.link=c(p.link)("inside",p._inside)("href",p._href)(),p.reflink=c(p.reflink)("inside",p._inside)(),p.normal=f({},p),p.pedantic=f({},p.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/}),p.gfm=f({},p.normal,{escape:c(p.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:c(p.text)("]|","~]|")("|","|https?://|")()}),p.breaks=f({},p.gfm,{br:c(p.br)("{2,}","*")(),text:c(p.gfm.text)("{2,}","*")()}),i.rules=p,i.output=function(e,t,n){var r=new i(t,n);return r.output(e)},i.prototype.output=function(e){for(var t,n,r,i,o="";e;)if(i=this.rules.escape.exec(e))e=e.substring(i[0].length),o+=i[1];else if(i=this.rules.autolink.exec(e))e=e.substring(i[0].length),"@"===i[2]?(n=":"===i[1].charAt(6)?this.mangle(i[1].substring(7)):this.mangle(i[1]),r=this.mangle("mailto:")+n):(n=l(i[1]),r=n),o+=this.renderer.link(r,null,n);else if(this.inLink||!(i=this.rules.url.exec(e))){if(i=this.rules.tag.exec(e))!this.inLink&&/^/i.test(i[0])&&(this.inLink=!1),e=e.substring(i[0].length),o+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):l(i[0]):i[0];else if(i=this.rules.link.exec(e))e=e.substring(i[0].length),this.inLink=!0,o+=this.outputLink(i,{href:i[2],title:i[3]}),this.inLink=!1;else if((i=this.rules.reflink.exec(e))||(i=this.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\s+/g," "),t=this.links[t.toLowerCase()],!t||!t.href){o+=i[0].charAt(0),e=i[0].substring(1)+e;continue}this.inLink=!0,o+=this.outputLink(i,t),this.inLink=!1}else if(i=this.rules.strong.exec(e))e=e.substring(i[0].length),o+=this.renderer.strong(this.output(i[2]||i[1]));else if(i=this.rules.em.exec(e))e=e.substring(i[0].length),o+=this.renderer.em(this.output(i[2]||i[1]));else if(i=this.rules.code.exec(e))e=e.substring(i[0].length),o+=this.renderer.codespan(l(i[2],!0));else if(i=this.rules.br.exec(e))e=e.substring(i[0].length),o+=this.renderer.br();else if(i=this.rules.del.exec(e))e=e.substring(i[0].length),o+=this.renderer.del(this.output(i[1]));else if(i=this.rules.text.exec(e))e=e.substring(i[0].length),o+=this.renderer.text(l(this.smartypants(i[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else e=e.substring(i[0].length),n=l(i[1]),r=n,o+=this.renderer.link(r,null,n);return o},i.prototype.outputLink=function(e,t){var n=l(t.href),r=t.title?l(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,l(e[1]))},i.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014\/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014\/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},i.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,i=0;r>i;i++)t=e.charCodeAt(i),Math.random()>.5&&(t="x"+t.toString(16)),n+="&#"+t+";";return n},o.prototype.code=function(e,t,n){if(this.options.highlight){var r=this.options.highlight(e,t);null!=r&&r!==e&&(n=!0,e=r)}return t?'
'+(n?e:l(e,!0))+"\n
\n":"
"+(n?e:l(e,!0))+"\n
"},o.prototype.blockquote=function(e){return"
\n"+e+"
\n"},o.prototype.html=function(e){return e},o.prototype.heading=function(e,t,n){return"'+e+"\n"},o.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"},o.prototype.list=function(e,t){var n=t?"ol":"ul";return"<"+n+">\n"+e+"\n"},o.prototype.listitem=function(e){return"
  • "+e+"
  • \n"},o.prototype.paragraph=function(e){return"

    "+e+"

    \n"},o.prototype.table=function(e,t){return"\n\n"+e+"\n\n"+t+"\n
    \n"},o.prototype.tablerow=function(e){return"\n"+e+"\n"},o.prototype.tablecell=function(e,t){var n=t.header?"th":"td",r=t.align?"<"+n+' style="text-align:'+t.align+'">':"<"+n+">";return r+e+"\n"},o.prototype.strong=function(e){return""+e+""},o.prototype.em=function(e){return""+e+""},o.prototype.codespan=function(e){return""+e+""},o.prototype.br=function(){return this.options.xhtml?"
    ":"
    "},o.prototype.del=function(e){return""+e+""},o.prototype.link=function(e,t,n){if(this.options.sanitize){try{var r=decodeURIComponent(s(e)).replace(/[^\w:]/g,"").toLowerCase()}catch(i){return""}if(0===r.indexOf("javascript:")||0===r.indexOf("vbscript:"))return""}var o='
    "},o.prototype.image=function(e,t,n){var r=''+n+'":">"},o.prototype.text=function(e){return e},a.parse=function(e,t,n){var r=new a(t,n);return r.parse(e)},a.prototype.parse=function(e){this.inline=new i(e.links,this.options,this.renderer),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},a.prototype.next=function(){return this.token=this.tokens.pop()},a.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},a.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},a.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text);case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,i,o="",a="";for(n="",e=0;ea;a++)for(var s=this.compoundRules[a],c=0,u=s.length;u>c;c++)this.compoundRuleCodes[s[c]]=[];"ONLYINCOMPOUND"in this.flags&&(this.compoundRuleCodes[this.flags.ONLYINCOMPOUND]=[]),this.dictionaryTable=this._parseDIC(n);for(var a in this.compoundRuleCodes)0==this.compoundRuleCodes[a].length&&delete this.compoundRuleCodes[a];for(var a=0,l=this.compoundRules.length;l>a;a++){for(var f=this.compoundRules[a],h="",c=0,u=f.length;u>c;c++){var d=f[c];h+=d in this.compoundRuleCodes?"("+this.compoundRuleCodes[d].join("|")+")":d}this.compoundRules[a]=new RegExp(h,"i")}}return this};i.prototype={load:function(e){for(var t in e)this[t]=e[t];return this},_readFile:function(t,r){if(r||(r="utf8"),"undefined"!=typeof XMLHttpRequest){var i=new XMLHttpRequest;return i.open("GET",t,!1),i.overrideMimeType&&i.overrideMimeType("text/plain; charset="+r),i.send(null),i.responseText}if("undefined"!=typeof e){var o=e("fs");try{if(o.existsSync(t)){var a=o.statSync(t),l=o.openSync(t,"r"),s=new n(a.size);return o.readSync(l,s,0,s.length,null),s.toString(r,0,s.length)}console.log("Path "+t+" does not exist.")}catch(c){return console.log(c),""}}},_parseAFF:function(e){var t={};e=this._removeAffixComments(e);for(var n=e.split("\n"),r=0,i=n.length;i>r;r++){var o=n[r],a=o.split(/\s+/),l=a[0];if("PFX"==l||"SFX"==l){for(var s=a[1],c=a[2],u=parseInt(a[3],10),f=[],h=r+1,d=r+1+u;d>h;h++){var o=n[h],p=o.split(/\s+/),m=p[2],g=p[3].split("/"),v=g[0];"0"===v&&(v="");var y=this.parseRuleCodes(g[1]),x=p[4],b={};b.add=v,y.length>0&&(b.continuationClasses=y),"."!==x&&("SFX"===l?b.match=new RegExp(x+"$"):b.match=new RegExp("^"+x)),"0"!=m&&("SFX"===l?b.remove=new RegExp(m+"$"):b.remove=m),f.push(b)}t[s]={type:l,combineable:"Y"==c,entries:f},r+=u}else if("COMPOUNDRULE"===l){for(var u=parseInt(a[1],10),h=r+1,d=r+1+u;d>h;h++){var o=n[h],p=o.split(/\s+/);this.compoundRules.push(p[1])}r+=u}else if("REP"===l){var p=o.split(/\s+/);3===p.length&&this.replacementTable.push([p[1],p[2]])}else this.flags[l]=a[1]}return t},_removeAffixComments:function(e){return e=e.replace(/#.*$/gm,""),e=e.replace(/^\s\s*/m,"").replace(/\s\s*$/m,""),e=e.replace(/\n{2,}/g,"\n"),e=e.replace(/^\s\s*/,"").replace(/\s\s*$/,"")},_parseDIC:function(e){function t(e,t){e in r&&"object"==typeof r[e]||(r[e]=[]),r[e].push(t)}e=this._removeDicComments(e);for(var n=e.split("\n"),r={},i=1,o=n.length;o>i;i++){var a=n[i],l=a.split("/",2),s=l[0];if(l.length>1){var c=this.parseRuleCodes(l[1]);"NEEDAFFIX"in this.flags&&-1!=c.indexOf(this.flags.NEEDAFFIX)||t(s,c);for(var u=0,f=c.length;f>u;u++){var h=c[u],d=this.rules[h];if(d)for(var p=this._applyRule(s,d),m=0,g=p.length;g>m;m++){var v=p[m];if(t(v,[]),d.combineable)for(var y=u+1;f>y;y++){var x=c[y],b=this.rules[x];if(b&&b.combineable&&d.type!=b.type)for(var w=this._applyRule(v,b),k=0,S=w.length;S>k;k++){var C=w[k];t(C,[])}}}h in this.compoundRuleCodes&&this.compoundRuleCodes[h].push(s)}}else t(s.trim(),[])}return r},_removeDicComments:function(e){return e=e.replace(/^\t.*$/gm,"")},parseRuleCodes:function(e){if(!e)return[];if(!("FLAG"in this.flags))return e.split("");if("long"===this.flags.FLAG){for(var t=[],n=0,r=e.length;r>n;n+=2)t.push(e.substr(n,2));return t}return"num"===this.flags.FLAG?textCode.split(","):void 0},_applyRule:function(e,t){for(var n=t.entries,r=[],i=0,o=n.length;o>i;i++){var a=n[i];if(!a.match||e.match(a.match)){var l=e;if(a.remove&&(l=l.replace(a.remove,"")),"SFX"===t.type?l+=a.add:l=a.add+l,r.push(l),"continuationClasses"in a)for(var s=0,c=a.continuationClasses.length;c>s;s++){var u=this.rules[a.continuationClasses[s]];u&&(r=r.concat(this._applyRule(l,u)))}}}return r},check:function(e){var t=e.replace(/^\s\s*/,"").replace(/\s\s*$/,"");if(this.checkExact(t))return!0;if(t.toUpperCase()===t){var n=t[0]+t.substring(1).toLowerCase();if(this.hasFlag(n,"KEEPCASE"))return!1;if(this.checkExact(n))return!0}var r=t.toLowerCase();if(r!==t){if(this.hasFlag(r,"KEEPCASE"))return!1;if(this.checkExact(r))return!0}return!1},checkExact:function(e){var t=this.dictionaryTable[e];if("undefined"==typeof t){if("COMPOUNDMIN"in this.flags&&e.length>=this.flags.COMPOUNDMIN)for(var n=0,r=this.compoundRules.length;r>n;n++)if(e.match(this.compoundRules[n]))return!0;return!1}if("object"==typeof t){for(var n=0,r=t.length;r>n;n++)if(!this.hasFlag(e,"ONLYINCOMPOUND",t[n]))return!0;return!1}},hasFlag:function(e,t,n){if(t in this.flags){if("undefined"==typeof n)var n=Array.prototype.concat.apply([],this.dictionaryTable[e]);if(n&&-1!==n.indexOf(this.flags[t]))return!0}return!1},alphabet:"",suggest:function(e,t){function n(e){for(var t=[],n=0,r=e.length;r>n;n++){for(var i=e[n],o=[],a=0,l=i.length+1;l>a;a++)o.push([i.substring(0,a),i.substring(a,i.length)]);for(var s=[],a=0,l=o.length;l>a;a++){var u=o[a];u[1]&&s.push(u[0]+u[1].substring(1))}for(var f=[],a=0,l=o.length;l>a;a++){var u=o[a];u[1].length>1&&f.push(u[0]+u[1][1]+u[1][0]+u[1].substring(2))}for(var h=[],a=0,l=o.length;l>a;a++){var u=o[a];if(u[1])for(var d=0,p=c.alphabet.length;p>d;d++)h.push(u[0]+c.alphabet[d]+u[1].substring(1))}for(var m=[],a=0,l=o.length;l>a;a++){var u=o[a];if(u[1])for(var d=0,p=c.alphabet.length;p>d;d++)h.push(u[0]+c.alphabet[d]+u[1])}t=t.concat(s),t=t.concat(f),t=t.concat(h),t=t.concat(m)}return t}function r(e){for(var t=[],n=0;nu;u++)l[u]in s?s[l[u]]+=1:s[l[u]]=1;var h=[];for(var u in s)h.push([u,s[u]]);h.sort(i).reverse();for(var d=[],u=0,f=Math.min(t,h.length);f>u;u++)c.hasFlag(h[u][0],"NOSUGGEST")||d.push(h[u][0]);return d}if(t||(t=5),this.check(e))return[];for(var o=0,a=this.replacementTable.length;a>o;o++){var l=this.replacementTable[o];if(-1!==e.indexOf(l[0])){var s=e.replace(l[0],l[1]);if(this.check(s))return[s]}}var c=this;return c.alphabet="abcdefghijklmnopqrstuvwxyz",i(e)}},"undefined"!=typeof t&&(t.exports=i)}).call(this,e("buffer").Buffer,"/node_modules/typo-js")},{buffer:3,fs:2}],19:[function(e,t,n){var r=e("codemirror");r.commands.tabAndIndentMarkdownList=function(e){var t=e.listSelections(),n=t[0].head,r=e.getStateAfter(n.line),i=r.list!==!1;if(i)return void e.execCommand("indentMore");if(e.options.indentWithTabs)e.execCommand("insertTab");else{var o=Array(e.options.tabSize+1).join(" ");e.replaceSelection(o)}},r.commands.shiftTabAndUnindentMarkdownList=function(e){var t=e.listSelections(),n=t[0].head,r=e.getStateAfter(n.line),i=r.list!==!1;if(i)return void e.execCommand("indentLess");if(e.options.indentWithTabs)e.execCommand("insertTab");else{var o=Array(e.options.tabSize+1).join(" ");e.replaceSelection(o)}}},{codemirror:10}],20:[function(e,t,n){"use strict";function r(e){return e=U?e.replace("Ctrl","Cmd"):e.replace("Cmd","Ctrl")}function i(e,t,n){e=e||{};var r=document.createElement("a");return t=void 0==t?!0:t,e.title&&t&&(r.title=a(e.title,e.action,n),U&&(r.title=r.title.replace("Ctrl","⌘"),r.title=r.title.replace("Alt","⌥"))),r.tabIndex=-1,r.className=e.className,r}function o(){var e=document.createElement("i");return e.className="separator",e.innerHTML="|",e}function a(e,t,n){var i,o=e;return t&&(i=Y(t),n[i]&&(o+=" ("+r(n[i])+")")),o}function l(e,t){t=t||e.getCursor("start");var n=e.getTokenAt(t);if(!n.type)return{};for(var r,i,o=n.type.split(" "),a={},l=0;l=0&&(d=c.getLineHandle(o),!t(d));o--);var v,y,x,b,w=c.getTokenAt({line:o,ch:1}),k=n(w).fencedChars;t(c.getLineHandle(u.line))?(v="",y=u.line):t(c.getLineHandle(u.line-1))?(v="",y=u.line-1):(v=k+"\n",y=u.line),t(c.getLineHandle(f.line))?(x="",b=f.line,0===f.ch&&(b+=1)):0!==f.ch&&t(c.getLineHandle(f.line+1))?(x="",b=f.line+1):(x=k+"\n",b=f.line+1),0===f.ch&&(b-=1),c.operation(function(){c.replaceRange(x,{line:b,ch:0},{line:b+(x?0:1),ch:0}),c.replaceRange(v,{line:y,ch:0},{line:y+(v?0:1),ch:0})}),c.setSelection({line:y+(v?1:0),ch:0},{line:b+(v?1:-1),ch:0}),c.focus()}else{var S=u.line;if(t(c.getLineHandle(u.line))&&("fenced"===r(c,u.line+1)?(o=u.line,S=u.line+1):(a=u.line,S=u.line-1)),void 0===o)for(o=S;o>=0&&(d=c.getLineHandle(o),!t(d));o--);if(void 0===a)for(l=c.lineCount(),a=S;l>a&&(d=c.getLineHandle(a),!t(d));a++);c.operation(function(){c.replaceRange("",{line:o,ch:0},{line:o+1,ch:0}),c.replaceRange("",{line:a-1,ch:0},{line:a,ch:0})}),c.focus()}else if("indented"===p){if(u.line!==f.line||u.ch!==f.ch)o=u.line,a=f.line,0===f.ch&&a--;else{for(o=u.line;o>=0;o--)if(d=c.getLineHandle(o),!d.text.match(/^\s*$/)&&"indented"!==r(c,o,d)){o+=1;break}for(l=c.lineCount(),a=u.line;l>a;a++)if(d=c.getLineHandle(a),!d.text.match(/^\s*$/)&&"indented"!==r(c,a,d)){a-=1;break}}var C=c.getLineHandle(a+1),L=C&&c.getTokenAt({line:a+1,ch:C.text.length-1}),T=L&&n(L).indentedCode;T&&c.replaceRange("\n",{line:a+1,ch:0});for(var M=o;a>=M;M++)c.indentLine(M,"subtract");c.focus()}else{var N=u.line===f.line&&u.ch===f.ch&&0===u.ch,A=u.line!==f.line;N||A?i(c,u,f,s):E(c,!1,["`","`"])}}function d(e){var t=e.codemirror;I(t,"quote")}function p(e){var t=e.codemirror;O(t,"smaller")}function m(e){var t=e.codemirror;O(t,"bigger")}function g(e){var t=e.codemirror;O(t,void 0,1)}function v(e){var t=e.codemirror;O(t,void 0,2)}function y(e){var t=e.codemirror;O(t,void 0,3)}function x(e){var t=e.codemirror;I(t,"unordered-list")}function b(e){var t=e.codemirror;I(t,"ordered-list")}function w(e){var t=e.codemirror;R(t)}function k(e){var t=e.codemirror,n=l(t),r=e.options,i="http://";return r.promptURLs&&(i=prompt(r.promptTexts.link),!i)?!1:void E(t,n.link,r.insertTexts.link,i)}function S(e){var t=e.codemirror,n=l(t),r=e.options,i="http://";return r.promptURLs&&(i=prompt(r.promptTexts.image),!i)?!1:void E(t,n.image,r.insertTexts.image,i)}function C(e){var t=e.codemirror,n=l(t),r=e.options;E(t,n.table,r.insertTexts.table)}function L(e){var t=e.codemirror,n=l(t),r=e.options;E(t,n.image,r.insertTexts.horizontalRule)}function T(e){var t=e.codemirror;t.undo(),t.focus()}function M(e){var t=e.codemirror;t.redo(),t.focus()}function N(e){var t=e.codemirror,n=t.getWrapperElement(),r=n.nextSibling,i=e.toolbarElements["side-by-side"],o=!1;/editor-preview-active-side/.test(r.className)?(r.className=r.className.replace(/\s*editor-preview-active-side\s*/g,""),i.className=i.className.replace(/\s*active\s*/g,""),n.className=n.className.replace(/\s*CodeMirror-sided\s*/g," ")):(setTimeout(function(){t.getOption("fullScreen")||s(e),r.className+=" editor-preview-active-side"},1),i.className+=" active",n.className+=" CodeMirror-sided",o=!0);var a=n.lastChild;if(/editor-preview-active/.test(a.className)){a.className=a.className.replace(/\s*editor-preview-active\s*/g,"");var l=e.toolbarElements.preview,c=n.previousSibling;l.className=l.className.replace(/\s*active\s*/g,""),c.className=c.className.replace(/\s*disabled-for-preview*/g,"")}var u=function(){r.innerHTML=e.options.previewRender(e.value(),r)};t.sideBySideRenderingFunction||(t.sideBySideRenderingFunction=u),o?(r.innerHTML=e.options.previewRender(e.value(),r),t.on("update",t.sideBySideRenderingFunction)):t.off("update",t.sideBySideRenderingFunction),t.refresh()}function A(e){var t=e.codemirror,n=t.getWrapperElement(),r=n.previousSibling,i=e.options.toolbar?e.toolbarElements.preview:!1,o=n.lastChild;o&&/editor-preview/.test(o.className)||(o=document.createElement("div"),o.className="editor-preview",n.appendChild(o)),/editor-preview-active/.test(o.className)?(o.className=o.className.replace(/\s*editor-preview-active\s*/g,""),i&&(i.className=i.className.replace(/\s*active\s*/g,""),r.className=r.className.replace(/\s*disabled-for-preview*/g,""))):(setTimeout(function(){o.className+=" editor-preview-active"},1),i&&(i.className+=" active",r.className+=" disabled-for-preview")),o.innerHTML=e.options.previewRender(e.value(),o);var a=t.getWrapperElement().nextSibling;/editor-preview-active-side/.test(a.className)&&N(e)}function E(e,t,n,r){if(!/editor-preview-active/.test(e.getWrapperElement().lastChild.className)){var i,o=n[0],a=n[1],l=e.getCursor("start"),s=e.getCursor("end");r&&(a=a.replace("#url#",r)),t?(i=e.getLine(l.line),o=i.slice(0,l.ch),a=i.slice(l.ch),e.replaceRange(o+a,{line:l.line,ch:0})):(i=e.getSelection(),e.replaceSelection(o+i+a),l.ch+=o.length,l!==s&&(s.ch+=o.length)),e.setSelection(l,s),e.focus()}}function O(e,t,n){if(!/editor-preview-active/.test(e.getWrapperElement().lastChild.className)){for(var r=e.getCursor("start"),i=e.getCursor("end"),o=r.line;o<=i.line;o++)!function(r){var i=e.getLine(r),o=i.search(/[^#]/);i=void 0!==t?0>=o?"bigger"==t?"###### "+i:"# "+i:6==o&&"smaller"==t?i.substr(7):1==o&&"bigger"==t?i.substr(2):"bigger"==t?i.substr(1):"#"+i:1==n?0>=o?"# "+i:o==n?i.substr(o+1):"# "+i.substr(o+1):2==n?0>=o?"## "+i:o==n?i.substr(o+1):"## "+i.substr(o+1):0>=o?"### "+i:o==n?i.substr(o+1):"### "+i.substr(o+1),e.replaceRange(i,{line:r,ch:0},{line:r,ch:99999999999999})}(o);e.focus()}}function I(e,t){if(!/editor-preview-active/.test(e.getWrapperElement().lastChild.className)){for(var n=l(e),r=e.getCursor("start"),i=e.getCursor("end"),o={quote:/^(\s*)\>\s+/,"unordered-list":/^(\s*)(\*|\-|\+)\s+/,"ordered-list":/^(\s*)\d+\.\s+/},a={quote:"> ","unordered-list":"* ","ordered-list":"1. "},s=r.line;s<=i.line;s++)!function(r){var i=e.getLine(r);i=n[t]?i.replace(o[t],"$1"):a[t]+i,e.replaceRange(i,{line:r,ch:0},{line:r,ch:99999999999999})}(s);e.focus()}}function P(e,t,n,r){if(!/editor-preview-active/.test(e.codemirror.getWrapperElement().lastChild.className)){r="undefined"==typeof r?n:r;var i,o=e.codemirror,a=l(o),s=n,c=r,u=o.getCursor("start"),f=o.getCursor("end");a[t]?(i=o.getLine(u.line),s=i.slice(0,u.ch),c=i.slice(u.ch),"bold"==t?(s=s.replace(/(\*\*|__)(?![\s\S]*(\*\*|__))/,""),c=c.replace(/(\*\*|__)/,"")):"italic"==t?(s=s.replace(/(\*|_)(?![\s\S]*(\*|_))/,""),c=c.replace(/(\*|_)/,"")):"strikethrough"==t&&(s=s.replace(/(\*\*|~~)(?![\s\S]*(\*\*|~~))/,""),c=c.replace(/(\*\*|~~)/,"")),o.replaceRange(s+c,{line:u.line,ch:0},{line:u.line,ch:99999999999999}),"bold"==t||"strikethrough"==t?(u.ch-=2,u!==f&&(f.ch-=2)):"italic"==t&&(u.ch-=1,u!==f&&(f.ch-=1))):(i=o.getSelection(),"bold"==t?(i=i.split("**").join(""),i=i.split("__").join("")):"italic"==t?(i=i.split("*").join(""),i=i.split("_").join("")):"strikethrough"==t&&(i=i.split("~~").join("")),o.replaceSelection(s+i+c),u.ch+=n.length,f.ch=u.ch+i.length),o.setSelection(u,f),o.focus()}}function R(e){if(!/editor-preview-active/.test(e.getWrapperElement().lastChild.className))for(var t,n=e.getCursor("start"),r=e.getCursor("end"),i=n.line;i<=r.line;i++)t=e.getLine(i),t=t.replace(/^[ ]*([# ]+|\*|\-|[> ]+|[0-9]+(.|\)))[ ]*/,""),e.replaceRange(t,{line:i,ch:0},{line:i,ch:99999999999999})}function D(e,t){for(var n in t)t.hasOwnProperty(n)&&(t[n]instanceof Array?e[n]=t[n].concat(e[n]instanceof Array?e[n]:[]):null!==t[n]&&"object"==typeof t[n]&&t[n].constructor===Object?e[n]=D(e[n]||{},t[n]):e[n]=t[n]);return e}function H(e){for(var t=1;t=19968?n[i].length:1;return r}function B(e){e=e||{},e.parent=this;var t=!0;if(e.autoDownloadFontAwesome===!1&&(t=!1),e.autoDownloadFontAwesome!==!0)for(var n=document.styleSheets,r=0;r-1&&(t=!1);if(t){var i=document.createElement("link");i.rel="stylesheet",i.href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css",document.getElementsByTagName("head")[0].appendChild(i)}if(e.element)this.element=e.element;else if(null===e.element)return void console.log("SimpleMDE: Error. No element was found.");if(void 0===e.toolbar){e.toolbar=[];for(var o in K)K.hasOwnProperty(o)&&(-1!=o.indexOf("separator-")&&e.toolbar.push("|"),(K[o]["default"]===!0||e.showIcons&&e.showIcons.constructor===Array&&-1!=e.showIcons.indexOf(o))&&e.toolbar.push(o))}e.hasOwnProperty("status")||(e.status=["autosave","lines","words","cursor"]),e.previewRender||(e.previewRender=function(e){return this.parent.markdown(e)}),e.parsingConfig=H({highlightFormatting:!0},e.parsingConfig||{}),e.insertTexts=H({},X,e.insertTexts||{}),e.promptTexts=Z,e.blockStyles=H({},J,e.blockStyles||{}),e.shortcuts=H({},G,e.shortcuts||{}),void 0!=e.autosave&&void 0!=e.autosave.unique_id&&""!=e.autosave.unique_id&&(e.autosave.uniqueId=e.autosave.unique_id),this.options=e,this.render(),!e.initialValue||this.options.autosave&&this.options.autosave.foundSavedValue===!0||this.value(e.initialValue)}function _(){if("object"!=typeof localStorage)return!1;try{localStorage.setItem("smde_localStorage",1),localStorage.removeItem("smde_localStorage")}catch(e){return!1}return!0}var F=e("codemirror");e("codemirror/addon/edit/continuelist.js"),e("./codemirror/tablist"),e("codemirror/addon/display/fullscreen.js"),e("codemirror/mode/markdown/markdown.js"),e("codemirror/addon/mode/overlay.js"),e("codemirror/addon/display/placeholder.js"),e("codemirror/addon/selection/mark-selection.js"),e("codemirror/mode/gfm/gfm.js"),e("codemirror/mode/xml/xml.js");var z=e("codemirror-spell-checker"),j=e("marked"),U=/Mac/.test(navigator.platform),q={toggleBold:c,toggleItalic:u,drawLink:k,toggleHeadingSmaller:p,toggleHeadingBigger:m,drawImage:S,toggleBlockquote:d,toggleOrderedList:b,toggleUnorderedList:x,toggleCodeBlock:h,togglePreview:A,toggleStrikethrough:f,toggleHeading1:g,toggleHeading2:v,toggleHeading3:y,cleanBlock:w,drawTable:C,drawHorizontalRule:L,undo:T,redo:M,toggleSideBySide:N,toggleFullScreen:s},G={toggleBold:"Cmd-B",toggleItalic:"Cmd-I",drawLink:"Cmd-K",toggleHeadingSmaller:"Cmd-H",toggleHeadingBigger:"Shift-Cmd-H",cleanBlock:"Cmd-E",drawImage:"Cmd-Alt-I",toggleBlockquote:"Cmd-'",toggleOrderedList:"Cmd-Alt-L",toggleUnorderedList:"Cmd-L",toggleCodeBlock:"Cmd-Alt-C",togglePreview:"Cmd-P",toggleSideBySide:"F9",toggleFullScreen:"F11"},Y=function(e){for(var t in q)if(q[t]===e)return t;return null},$=function(){var e=!1;return function(t){(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(t)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(t.substr(0,4)))&&(e=!0); -}(navigator.userAgent||navigator.vendor||window.opera),e},V="",K={bold:{name:"bold",action:c,className:"fa fa-bold",title:"Bold","default":!0},italic:{name:"italic",action:u,className:"fa fa-italic",title:"Italic","default":!0},strikethrough:{name:"strikethrough",action:f,className:"fa fa-strikethrough",title:"Strikethrough"},heading:{name:"heading",action:p,className:"fa fa-header",title:"Heading","default":!0},"heading-smaller":{name:"heading-smaller",action:p,className:"fa fa-header fa-header-x fa-header-smaller",title:"Smaller Heading"},"heading-bigger":{name:"heading-bigger",action:m,className:"fa fa-header fa-header-x fa-header-bigger",title:"Bigger Heading"},"heading-1":{name:"heading-1",action:g,className:"fa fa-header fa-header-x fa-header-1",title:"Big Heading"},"heading-2":{name:"heading-2",action:v,className:"fa fa-header fa-header-x fa-header-2",title:"Medium Heading"},"heading-3":{name:"heading-3",action:y,className:"fa fa-header fa-header-x fa-header-3",title:"Small Heading"},"separator-1":{name:"separator-1"},code:{name:"code",action:h,className:"fa fa-code",title:"Code"},quote:{name:"quote",action:d,className:"fa fa-quote-left",title:"Quote","default":!0},"unordered-list":{name:"unordered-list",action:x,className:"fa fa-list-ul",title:"Generic List","default":!0},"ordered-list":{name:"ordered-list",action:b,className:"fa fa-list-ol",title:"Numbered List","default":!0},"clean-block":{name:"clean-block",action:w,className:"fa fa-eraser fa-clean-block",title:"Clean block"},"separator-2":{name:"separator-2"},link:{name:"link",action:k,className:"fa fa-link",title:"Create Link","default":!0},image:{name:"image",action:S,className:"fa fa-picture-o",title:"Insert Image","default":!0},table:{name:"table",action:C,className:"fa fa-table",title:"Insert Table"},"horizontal-rule":{name:"horizontal-rule",action:L,className:"fa fa-minus",title:"Insert Horizontal Line"},"separator-3":{name:"separator-3"},preview:{name:"preview",action:A,className:"fa fa-eye no-disable",title:"Toggle Preview","default":!0},"side-by-side":{name:"side-by-side",action:N,className:"fa fa-columns no-disable no-mobile",title:"Toggle Side by Side","default":!0},fullscreen:{name:"fullscreen",action:s,className:"fa fa-arrows-alt no-disable no-mobile",title:"Toggle Fullscreen","default":!0},"separator-4":{name:"separator-4"},guide:{name:"guide",action:"https://simplemde.com/markdown-guide",className:"fa fa-question-circle",title:"Markdown Guide","default":!0},"separator-5":{name:"separator-5"},undo:{name:"undo",action:T,className:"fa fa-undo no-disable",title:"Undo"},redo:{name:"redo",action:M,className:"fa fa-repeat no-disable",title:"Redo"}},X={link:["[","](#url#)"],image:["![](","#url#)"],table:["","\n\n| Column 1 | Column 2 | Column 3 |\n| -------- | -------- | -------- |\n| Text | Text | Text |\n\n"],horizontalRule:["","\n\n-----\n\n"]},Z={link:"URL for the link:",image:"URL of the image:"},J={bold:"**",code:"```",italic:"*"};B.prototype.markdown=function(e){if(j){var t={};return this.options&&this.options.renderingConfig&&this.options.renderingConfig.singleLineBreaks===!1?t.breaks=!1:t.breaks=!0,this.options&&this.options.renderingConfig&&this.options.renderingConfig.codeSyntaxHighlighting===!0&&window.hljs&&(t.highlight=function(e){return window.hljs.highlightAuto(e).value}),j.setOptions(t),j(e)}},B.prototype.render=function(e){if(e||(e=this.element||document.getElementsByTagName("textarea")[0]),!this._rendered||this._rendered!==e){this.element=e;var t=this.options,n=this,i={};for(var o in t.shortcuts)null!==t.shortcuts[o]&&null!==q[o]&&!function(e){i[r(t.shortcuts[e])]=function(){q[e](n)}}(o);i.Enter="newlineAndIndentContinueMarkdownList",i.Tab="tabAndIndentMarkdownList",i["Shift-Tab"]="shiftTabAndUnindentMarkdownList",i.Esc=function(e){e.getOption("fullScreen")&&s(n)},document.addEventListener("keydown",function(e){e=e||window.event,27==e.keyCode&&n.codemirror.getOption("fullScreen")&&s(n)},!1);var a,l;if(t.spellChecker!==!1?(a="spell-checker",l=t.parsingConfig,l.name="gfm",l.gitHubSpice=!1,z({codeMirrorInstance:F})):(a=t.parsingConfig,a.name="gfm",a.gitHubSpice=!1),this.codemirror=F.fromTextArea(e,{mode:a,backdrop:l,theme:"paper",tabSize:void 0!=t.tabSize?t.tabSize:2,indentUnit:void 0!=t.tabSize?t.tabSize:2,indentWithTabs:t.indentWithTabs!==!1,lineNumbers:!1,autofocus:t.autofocus===!0,extraKeys:i,lineWrapping:t.lineWrapping!==!1,allowDropFileTypes:["text/plain"],placeholder:t.placeholder||e.getAttribute("placeholder")||"",styleSelectedText:void 0!=t.styleSelectedText?t.styleSelectedText:!0}),t.forceSync===!0){var c=this.codemirror;c.on("change",function(){c.save()})}this.gui={},t.toolbar!==!1&&(this.gui.toolbar=this.createToolbar()),t.status!==!1&&(this.gui.statusbar=this.createStatusbar()),void 0!=t.autosave&&t.autosave.enabled===!0&&this.autosave(),this.gui.sideBySide=this.createSideBySide(),this._rendered=this.element;var u=this.codemirror;setTimeout(function(){u.refresh()}.bind(u),0)}},B.prototype.autosave=function(){if(_()){var e=this;if(void 0==this.options.autosave.uniqueId||""==this.options.autosave.uniqueId)return void console.log("SimpleMDE: You must set a uniqueId to use the autosave feature");null!=e.element.form&&void 0!=e.element.form&&e.element.form.addEventListener("submit",function(){localStorage.removeItem("smde_"+e.options.autosave.uniqueId)}),this.options.autosave.loaded!==!0&&("string"==typeof localStorage.getItem("smde_"+this.options.autosave.uniqueId)&&""!=localStorage.getItem("smde_"+this.options.autosave.uniqueId)&&(this.codemirror.setValue(localStorage.getItem("smde_"+this.options.autosave.uniqueId)),this.options.autosave.foundSavedValue=!0),this.options.autosave.loaded=!0),localStorage.setItem("smde_"+this.options.autosave.uniqueId,e.value());var t=document.getElementById("autosaved");if(null!=t&&void 0!=t&&""!=t){var n=new Date,r=n.getHours(),i=n.getMinutes(),o="am",a=r;a>=12&&(a=r-12,o="pm"),0==a&&(a=12),i=10>i?"0"+i:i,t.innerHTML="Autosaved: "+a+":"+i+" "+o}this.autosaveTimeoutId=setTimeout(function(){e.autosave()},this.options.autosave.delay||1e4)}else console.log("SimpleMDE: localStorage not available, cannot autosave")},B.prototype.clearAutosavedValue=function(){if(_()){if(void 0==this.options.autosave||void 0==this.options.autosave.uniqueId||""==this.options.autosave.uniqueId)return void console.log("SimpleMDE: You must set a uniqueId to clear the autosave value");localStorage.removeItem("smde_"+this.options.autosave.uniqueId)}else console.log("SimpleMDE: localStorage not available, cannot autosave")},B.prototype.createSideBySide=function(){var e=this.codemirror,t=e.getWrapperElement(),n=t.nextSibling;n&&/editor-preview-side/.test(n.className)||(n=document.createElement("div"),n.className="editor-preview-side",t.parentNode.insertBefore(n,t.nextSibling));var r=!1,i=!1;return e.on("scroll",function(e){if(r)return void(r=!1);i=!0;var t=e.getScrollInfo().height-e.getScrollInfo().clientHeight,o=parseFloat(e.getScrollInfo().top)/t,a=(n.scrollHeight-n.clientHeight)*o;n.scrollTop=a}),n.onscroll=function(){if(i)return void(i=!1);r=!0;var t=n.scrollHeight-n.clientHeight,o=parseFloat(n.scrollTop)/t,a=(e.getScrollInfo().height-e.getScrollInfo().clientHeight)*o;e.scrollTo(0,a)},n},B.prototype.createToolbar=function(e){if(e=e||this.options.toolbar,e&&0!==e.length){var t;for(t=0;t'], tmp; - var DBG = (msg._||'').DBG; DBG && (DBG.sp = DBG.sp || +new Date); - //var lot = (msg._||'').lot||''; count[id] = (count[id] || 0) + 1; - var S = (msg._||'').RPS || ((msg._||'').RPS = +new Date); - //console.log("PUT ------->>>", soul,key, val, state); - //dare(soul+esc+key, {':': val, '>': state}, dare.one[id] || function(err, ok){ - dare(soul+esc+key, {':': val, '>': state}, function(err, ok){ - //console.log("<<<------- PAT", soul,key, val, state, 'in', +new Date - S); - DBG && (DBG.spd = DBG.spd || +new Date); - console.STAT && console.STAT(S, +new Date - S, 'put'); - //if(!err && count[id] !== lot.s){ console.log(err = "Disk count not same as ram count."); console.STAT && console.STAT(+new Date, lot.s - count[id], 'put ack != count') } delete count[id]; - if(err){ root.on('in', {'@': id, err: err, DBG: DBG}); return } - root.on('in', {'@': id, ok: ok, DBG: DBG}); - //}, id, DBG && (DBG.r = DBG.r || {})); - }, false && id, DBG && (DBG.r = DBG.r || {})); - DBG && (DBG.sps = DBG.sps || +new Date); - }); - var count = {}, obj_empty = Object.empty; - - root.on('get', function(msg){ - this.to.next(msg); - var ctx = msg._||'', DBG = ctx.DBG = msg.DBG; DBG && (DBG.sg = +new Date); - var id = msg['#'], get = msg.get, soul = msg.get['#'], has = msg.get['.']||'', o = {}, graph, lex, key, tmp, force; - if('string' == typeof soul){ - key = soul; - } else - if(soul){ - if(u !== (tmp = soul['*'])){ o.limit = force = 1 } - if(u !== soul['>']){ o.start = soul['>'] } - if(u !== soul['<']){ o.end = soul['<'] } - key = force? (''+tmp) : tmp || soul['=']; - force = null; - } - if(key && !o.limit){ // a soul.has must be on a soul, and not during soul* - if('string' == typeof has){ - key = key+esc+(o.atom = has); - } else - if(has){ - if(u !== has['>']){ o.start = has['>']; o.limit = 1 } - if(u !== has['<']){ o.end = has['<']; o.limit = 1 } - if(u !== (tmp = has['*'])){ o.limit = force = 1 } - if(key){ key = key+esc + (force? (''+(tmp||'')) : tmp || (o.atom = has['='] || '')) } - } - } - if((tmp = get['%']) || o.limit){ - o.limit = (tmp <= (o.pack || (1000 * 100)))? tmp : 1; - } - if(has['-'] || (soul||{})['-'] || get['-']){ o.reverse = true } - if((tmp = (root.next||'')[soul]) && tmp.put){ - if(o.atom){ - tmp = (tmp.next||'')[o.atom] ; - if(tmp && tmp.root && tmp.root.graph && tmp.root.graph[soul] && tmp.root.graph[soul][o.atom]){ return } - } else - if(tmp && tmp.rad){ return } - } - var now = Gun.state(); - var S = (+new Date), C = 0, SPT = 0; // STATS! - DBG && (DBG.sgm = S); - //var GID = String.random(3); console.log("GET ------->>>", GID, key, o, '?', get); - dare(key||'', function(err, data, info){ - //console.log("<<<------- GOT", GID, +new Date - S, err, data); - DBG && (DBG.sgr = +new Date); - DBG && (DBG.sgi = info); - try{opt.store.stats.get.time[statg % 50] = (+new Date) - S; ++statg; - opt.store.stats.get.count++; - if(err){ opt.store.stats.get.err = err } - }catch(e){} // STATS! - //if(u === data && info.chunks > 1){ return } // if we already sent a chunk, ignore ending empty responses. // this causes tests to fail. - console.STAT && console.STAT(S, +new Date - S, 'got', JSON.stringify(key)); S = +new Date; - info = info || ''; - var va, ve; - if(info.unit && data && u !== (va = data[':']) && u !== (ve = data['>'])){ // new format - var tmp = key.split(esc), so = tmp[0], ha = tmp[1]; - (graph = graph || {})[so] = Gun.state.ify(graph[so], ha, ve, va, so); - root.$.get(so).get(ha)._.rad = now; - // REMEMBER TO ADD _rad TO NODE/SOUL QUERY! - } else - if(data){ // old code path - if(typeof data !== 'string'){ - if(o.atom){ - data = u; - } else { - Radix.map(data, each, o); // IS A RADIX TREE, NOT FUNCTION! - } - } - if(!graph && data){ each(data, '') } - // TODO: !has what about soul lookups? - if(!o.atom && !has & 'string' == typeof soul && !o.limit && !o.more){ - root.$.get(soul)._.rad = now; - } - } - DBG && (DBG.sgp = +new Date); - // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps? - // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps? - // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps? - // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps? - // TODO: PERF NOTES! This is like 0.2s, but for each ack, or all? Can you cache these preps? - // Or benchmark by reusing first start date. - if(console.STAT && (ST = +new Date - S) > 9){ console.STAT(S, ST, 'got prep time'); console.STAT(S, C, 'got prep #') } SPT += ST; C = 0; S = +new Date; - var faith = function(){}; faith.faith = true; faith.rad = get; // HNPERF: We're testing performance improvement by skipping going through security again, but this should be audited. - root.on('in', {'@': id, put: graph, '%': info.more? 1 : u, err: err? err : u, _: faith, DBG: DBG}); - console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'got emit', Object.keys(graph||{}).length); - graph = u; // each is outside our scope, we have to reset graph to nothing! - }, o, DBG && (DBG.r = DBG.r || {})); - DBG && (DBG.sgd = +new Date); - console.STAT && (ST = +new Date - S) > 9 && console.STAT(S, ST, 'get call'); // TODO: Perf: this was half a second?????? - function each(val, has, a,b){ // TODO: THIS CODE NEEDS TO BE FASTER!!!! - C++; - if(!val){ return } - has = (key+has).split(esc); - var soul = has.slice(0,1)[0]; - has = has.slice(-1)[0]; - if(o.limit && o.limit <= o.count){ return true } - var va, ve, so = soul, ha = has; - //if(u !== (va = val[':']) && u !== (ve = val['>'])){ // THIS HANDLES NEW CODE! - if('string' != typeof val){ // THIS HANDLES NEW CODE! - va = val[':']; ve = val['>']; - (graph = graph || {})[so] = Gun.state.ify(graph[so], ha, ve, va, so); - //root.$.get(so).get(ha)._.rad = now; - o.count = (o.count || 0) + ((va||'').length || 9); - return; - } - o.count = (o.count || 0) + val.length; - var tmp = val.lastIndexOf('>'); - var state = Radisk.decode(val.slice(tmp+1), null, esc); - val = Radisk.decode(val.slice(0,tmp), null, esc); - (graph = graph || {})[soul] = Gun.state.ify(graph[soul], has, state, val, soul); - } - }); - var val_is = Gun.valid; - (opt.store||{}).stats = {get:{time:{}, count:0}, put: {time:{}, count:0}}; // STATS! - var statg = 0, statp = 0; // STATS! -}); \ No newline at end of file diff --git a/assets/static/synchronous.js b/assets/static/synchronous.js deleted file mode 100644 index 0a6fbe7..0000000 --- a/assets/static/synchronous.js +++ /dev/null @@ -1,58 +0,0 @@ -(function (env) { - var Gun; - if(typeof module !== "undefined" && module.exports){ Gun = require('gun/gun') } - if(typeof window !== "undefined"){ Gun = window.Gun } - - Gun.chain.sync = function (obj, opt, cb, o) { - var gun = this; - if (!Gun.obj.is(obj)) { - console.log('First param is not an object'); - return gun; - } - if (Gun.bi.is(opt)) { - opt = { - meta: opt - }; - } - if(Gun.fn.is(opt)){ - cb = opt; - opt = null; - } - cb = cb || function(){}; - opt = opt || {}; - opt.ctx = opt.ctx || {}; - gun.on(function (change, field) { - Gun.obj.map(change, function (val, field) { - if (!obj) { - return; - } - if (field === '_' || field === '#') { - if (opt.meta) { - obj[field] = val; - } - return; - } - if (Gun.obj.is(val)) { - var soul = Gun.val.rel.is(val); - if (opt.ctx[soul + field]) { - // don't re-subscribe. - return; - } - // unique subscribe! - opt.ctx[soul + field] = true; - this.path(field).sync( - obj[field] = (obj[field] || {}), - Gun.obj.copy(opt), - cb, - o || obj - ); - return; - } - obj[field] = val; - }, this); - cb(o || obj); - }); - return gun; - }; - -}()); diff --git a/assets/static/webrtc.js b/assets/static/webrtc.js deleted file mode 100644 index 91faf10..0000000 --- a/assets/static/webrtc.js +++ /dev/null @@ -1,134 +0,0 @@ -;(function(){ - var GUN = (typeof window !== "undefined")? window.Gun : require('../gun'); - GUN.on('opt', function(root){ - this.to.next(root); - var opt = root.opt; - if(root.once){ return } - if(!GUN.Mesh){ return } - if(false === opt.RTCPeerConnection){ return } - - var env; - if(typeof window !== "undefined"){ env = window } - if(typeof global !== "undefined"){ env = global } - env = env || {}; - - var rtcpc = opt.RTCPeerConnection || env.RTCPeerConnection || env.webkitRTCPeerConnection || env.mozRTCPeerConnection; - var rtcsd = opt.RTCSessionDescription || env.RTCSessionDescription || env.webkitRTCSessionDescription || env.mozRTCSessionDescription; - var rtcic = opt.RTCIceCandidate || env.RTCIceCandidate || env.webkitRTCIceCandidate || env.mozRTCIceCandidate; - if(!rtcpc || !rtcsd || !rtcic){ return } - opt.RTCPeerConnection = rtcpc; - opt.RTCSessionDescription = rtcsd; - opt.RTCIceCandidate = rtcic; - opt.rtc = opt.rtc || {'iceServers': [ - {urls: 'stun:stun.l.google.com:19302'}, - {urls: 'stun:stun.cloudflare.com:3478'}/*, - {urls: "stun:stun.sipgate.net:3478"}, - {urls: "stun:stun.stunprotocol.org"}, - {urls: "stun:stun.sipgate.net:10000"}, - {urls: "stun:217.10.68.152:10000"}, - {urls: 'stun:stun.services.mozilla.com'}*/ - ]}; - // TODO: Select the most appropriate stuns. - // FIXME: Find the wire throwing ICE Failed - // The above change corrects at least firefox RTC Peer handler where it **throws** on over 6 ice servers, and updates url: to urls: removing deprecation warning - opt.rtc.dataChannel = opt.rtc.dataChannel || {ordered: false, maxRetransmits: 2}; - opt.rtc.sdp = opt.rtc.sdp || {mandatory: {OfferToReceiveAudio: false, OfferToReceiveVideo: false}}; - opt.rtc.max = opt.rtc.max || 55; // is this a magic number? // For Future WebRTC notes: Chrome 500 max limit, however 256 likely - FF "none", webtorrent does 55 per torrent. - opt.rtc.room = opt.rtc.room || GUN.window && (window.rtcRoom || location.hash.slice(1) || location.pathname.slice(1)); - opt.announce = function(to){ - opt.rtc.start = +new Date; // handle room logic: - root.$.get('/RTC/'+opt.rtc.room+' msg.put['>']){ return } - plan({'#': ''+msg['#'], ok: {rtc: {id: last}}}); - }); - }; - - var mesh = opt.mesh = opt.mesh || GUN.Mesh(root), wired = mesh.wire; - mesh.hear['rtc'] = plan; - mesh.wire = function(media){ try{ wired && wired(media); - if(!(media instanceof MediaStream)){ return } - (open.media = open.media||{})[media.id] = media; - for(var p in opt.peers){ p = opt.peers[p]||''; - p.addTrack && media.getTracks().forEach(track => { - p.addTrack(track, media); - }); - p.createOffer && p.createOffer(function(offer){ - p.setLocalDescription(offer); - mesh.say({'#': root.ask(plan), dam: 'rtc', ok: {rtc: {offer: offer, id: opt.pid}}}, p); - }, function(){}, opt.rtc.sdp); - } - } catch(e){console.log(e)} } - root.on('create', function(at){ - this.to.next(at); - setTimeout(opt.announce, 1); - }); - - function plan(msg){ - if(!msg.ok){ return } - var rtc = msg.ok.rtc, peer, tmp; - if(!rtc || !rtc.id || rtc.id === opt.pid){ return } - peer = open(msg, rtc); - if(tmp = rtc.candidate){ - return peer.addIceCandidate(new opt.RTCIceCandidate(tmp)); - } - if(tmp = rtc.answer){ - tmp.sdp = tmp.sdp.replace(/\\r\\n/g, '\r\n'); - return peer.setRemoteDescription(peer.remoteSet = new opt.RTCSessionDescription(tmp)); - } - if(tmp = rtc.offer){ - rtc.offer.sdp = rtc.offer.sdp.replace(/\\r\\n/g, '\r\n'); - peer.setRemoteDescription(new opt.RTCSessionDescription(tmp)); - return peer.createAnswer(function(answer){ - peer.setLocalDescription(answer); - root.on('out', {'@': msg['#'], ok: {rtc: {answer: answer, id: opt.pid}}}); - }, function(){}, opt.rtc.sdp); - } - } - function open(msg, rtc, peer){ - if(peer = opt.peers[rtc.id] || open[rtc.id]){ return peer } - (peer = new opt.RTCPeerConnection(opt.rtc)).id = rtc.id; - var wire = peer.wire = peer.createDataChannel('dc', opt.rtc.dataChannel); - function rtceve(eve){ eve.peer = peer; gun.on('rtc', eve) } - peer.$ = gun; - open[rtc.id] = peer; - peer.ontrack = rtceve; - peer.onremovetrack = rtceve; - peer.onconnectionstatechange = rtceve; - wire.to = setTimeout(function(){delete open[rtc.id]},1000*60); - wire.onclose = function(){ mesh.bye(peer) }; - wire.onerror = function(err){ }; - wire.onopen = function(e){ - delete open[rtc.id]; - mesh.hi(peer); - } - wire.onmessage = function(msg){ - if(!msg){ return } - mesh.hear(msg.data || msg, peer); - }; - peer.onicecandidate = function(e){ rtceve(e); - if(!e.candidate){ return } - root.on('out', {'@': (msg||'')['#'], '#': root.ask(plan), ok: {rtc: {candidate: e.candidate, id: opt.pid}}}); - } - peer.ondatachannel = function(e){ rtceve(e); - var rc = e.channel; - rc.onmessage = wire.onmessage; - rc.onopen = wire.onopen; - rc.onclose = wire.onclose; - } - if(rtc.offer){ return peer } - for(var m in open.media){ m = open.media[m]; - m.getTracks().forEach(track => { - peer.addTrack(track, m); - }); - } - peer.createOffer(function(offer){ - peer.setLocalDescription(offer); - root.on('out', {'@': (msg||'')['#'], '#': root.ask(plan), ok: {rtc: {offer: offer, id: opt.pid}}}); - }, function(){}, opt.rtc.sdp); - return peer; - } - }); -}()); \ No newline at end of file diff --git a/assets/static/yson.js b/assets/static/yson.js deleted file mode 100644 index 623f7c2..0000000 --- a/assets/static/yson.js +++ /dev/null @@ -1,244 +0,0 @@ -;(function(){ -// JSON: JavaScript Object Notation -// YSON: Yielding javaScript Object Notation -var yson = {}, u, sI = setTimeout.turn || (typeof setImmediate != ''+u && setImmediate) || setTimeout; - -yson.parseAsync = function(text, done, revive, M){ - if('string' != typeof text){ try{ done(u,JSON.parse(text)) }catch(e){ done(e) } return } - var ctx = {i: 0, text: text, done: done, l: text.length, up: []}; - //M = 1024 * 1024 * 100; - //M = M || 1024 * 64; - M = M || 1024 * 32; - parse(); - function parse(){ - //var S = +new Date; - var s = ctx.text; - var i = ctx.i, l = ctx.l, j = 0; - var w = ctx.w, b, tmp; - while(j++ < M){ - var c = s[i++]; - if(i > l){ - ctx.end = true; - break; - } - if(w){ - i = s.indexOf('"', i-1); c = s[i]; - tmp = 0; while('\\' == s[i-(++tmp)]){}; tmp = !(tmp % 2);//tmp = ('\\' == s[i-1]); // json is stupid - b = b || tmp; - if('"' == c && !tmp){ - w = u; - tmp = ctx.s; - if(ctx.a){ - tmp = s.slice(ctx.sl, i); - if(b || (1+tmp.indexOf('\\'))){ tmp = JSON.parse('"'+tmp+'"') } // escape + unicode :( handling - if(ctx.at instanceof Array){ - ctx.at.push(ctx.s = tmp); - } else { - if(!ctx.at){ ctx.end = j = M; tmp = u } - (ctx.at||{})[ctx.s] = ctx.s = tmp; - } - ctx.s = u; - } else { - ctx.s = s.slice(ctx.sl, i); - if(b || (1+ctx.s.indexOf('\\'))){ ctx.s = JSON.parse('"'+ctx.s+'"'); } // escape + unicode :( handling - } - ctx.a = b = u; - } - ++i; - } else { - switch(c){ - case '"': - ctx.sl = i; - w = true; - break; - case ':': - ctx.ai = i; - ctx.a = true; - break; - case ',': - if(ctx.a || ctx.at instanceof Array){ - if(tmp = s.slice(ctx.ai, i-1)){ - if(u !== (tmp = value(tmp))){ - if(ctx.at instanceof Array){ - ctx.at.push(tmp); - } else { - ctx.at[ctx.s] = tmp; - } - } - } - } - ctx.a = u; - if(ctx.at instanceof Array){ - ctx.a = true; - ctx.ai = i; - } - break; - case '{': - ctx.up.push(ctx.at||(ctx.at = {})); - if(ctx.at instanceof Array){ - ctx.at.push(ctx.at = {}); - } else - if(u !== (tmp = ctx.s)){ - ctx.at[tmp] = ctx.at = {}; - } - ctx.a = u; - break; - case '}': - if(ctx.a){ - if(tmp = s.slice(ctx.ai, i-1)){ - if(u !== (tmp = value(tmp))){ - if(ctx.at instanceof Array){ - ctx.at.push(tmp); - } else { - if(!ctx.at){ ctx.end = j = M; tmp = u } - (ctx.at||{})[ctx.s] = tmp; - } - } - } - } - ctx.a = u; - ctx.at = ctx.up.pop(); - break; - case '[': - if(u !== (tmp = ctx.s)){ - ctx.up.push(ctx.at); - ctx.at[tmp] = ctx.at = []; - } else - if(!ctx.at){ - ctx.up.push(ctx.at = []); - } - ctx.a = true; - ctx.ai = i; - break; - case ']': - if(ctx.a){ - if(tmp = s.slice(ctx.ai, i-1)){ - if(u !== (tmp = value(tmp))){ - if(ctx.at instanceof Array){ - ctx.at.push(tmp); - } else { - ctx.at[ctx.s] = tmp; - } - } - } - } - ctx.a = u; - ctx.at = ctx.up.pop(); - break; - } - } - } - ctx.s = u; - ctx.i = i; - ctx.w = w; - if(ctx.end){ - tmp = ctx.at; - if(u === tmp){ - try{ tmp = JSON.parse(text) - }catch(e){ return ctx.done(e) } - } - ctx.done(u, tmp); - } else { - sI(parse); - } - } -} -function value(s){ - var n = parseFloat(s); - if(!isNaN(n)){ - return n; - } - s = s.trim(); - if('true' == s){ - return true; - } - if('false' == s){ - return false; - } - if('null' == s){ - return null; - } -} - -yson.stringifyAsync = function(data, done, replacer, space, ctx){ - //try{done(u, JSON.stringify(data, replacer, space))}catch(e){done(e)}return; - ctx = ctx || {}; - ctx.text = ctx.text || ""; - ctx.up = [ctx.at = {d: data}]; - ctx.done = done; - ctx.i = 0; - var j = 0; - ify(); - function ify(){ - var at = ctx.at, data = at.d, add = '', tmp; - if(at.i && (at.i - at.j) > 0){ add += ',' } - if(u !== (tmp = at.k)){ add += JSON.stringify(tmp) + ':' } //'"'+tmp+'":' } // only if backslash - switch(typeof data){ - case 'boolean': - add += ''+data; - break; - case 'string': - add += JSON.stringify(data); //ctx.text += '"'+data+'"';//JSON.stringify(data); // only if backslash - break; - case 'number': - add += (isNaN(data)? 'null' : data); - break; - case 'object': - if(!data){ - add += 'null'; - break; - } - if(data instanceof Array){ - add += '['; - at = {i: -1, as: data, up: at, j: 0}; - at.l = data.length; - ctx.up.push(ctx.at = at); - break; - } - if('function' != typeof (data||'').toJSON){ - add += '{'; - at = {i: -1, ok: Object.keys(data).sort(), as: data, up: at, j: 0}; - at.l = at.ok.length; - ctx.up.push(ctx.at = at); - break; - } - if(tmp = data.toJSON()){ - add += tmp; - break; - } - // let this & below pass into default case... - case 'function': - if(at.as instanceof Array){ - add += 'null'; - break; - } - default: // handle wrongly added leading `,` if previous item not JSON-able. - add = ''; - at.j++; - } - ctx.text += add; - while(1+at.i >= at.l){ - ctx.text += (at.ok? '}' : ']'); - at = ctx.at = at.up; - } - if(++at.i < at.l){ - if(tmp = at.ok){ - at.d = at.as[at.k = tmp[at.i]]; - } else { - at.d = at.as[at.i]; - } - if(++j < 9){ return ify() } else { j = 0 } - sI(ify); - return; - } - ctx.done(u, ctx.text); - } -} -if(typeof window != ''+u){ window.YSON = yson } -try{ if(typeof module != ''+u){ module.exports = yson } }catch(e){} -if(typeof JSON != ''+u){ - JSON.parseAsync = yson.parseAsync; - JSON.stringifyAsync = yson.stringifyAsync; -} - -}()); \ No newline at end of file diff --git a/build.py b/build.py index a3b976a..75ab305 100644 --- a/build.py +++ b/build.py @@ -14,7 +14,7 @@ def get_all_files(directory): return files PREFETCH = "" -VERSIONCO = "2025-08" +VERSIONCO = "2026-02" HANDLEPARSE = get_all_files("src") TITLE = os.environ.get("TELESEC_TITLE", "TeleSec") HOSTER = os.environ.get("TELESEC_HOSTER", "EuskadiTech") @@ -35,7 +35,7 @@ shutil.copytree("assets","dist", dirs_exist_ok=True) def replace_handles(string): string = string.replace("%%PREFETCH%%", PREFETCH) string = string.replace("%%VERSIONCO%%", VERSIONCO) - string = string.replace("%%TITLE%%", "TeleSec") + string = string.replace("%%TITLE%%", TITLE) string = string.replace("%%HOSTER%%", HOSTER) string = string.replace("%%ASSETSJSON%%", json.dumps(ASSETS, ensure_ascii=False)) return string diff --git a/src/app_logic.js b/src/app_logic.js index 07744be..8bbe116 100644 --- a/src/app_logic.js +++ b/src/app_logic.js @@ -1,33 +1,33 @@ function fixfloat(number) { - return (parseFloat(number).toPrecision(8)); + return parseFloat(number).toPrecision(8); } function tableScroll(query) { $(query).doubleScroll(); } //var secretTokenEl = document.getElementById("secretToken"); -var container = document.getElementById("container"); +var container = document.getElementById('container'); function open_page(params) { // Clear stored event listeners and timers EventListeners.GunJS = []; - EventListeners.Timeout.forEach(ev => clearTimeout(ev)); + EventListeners.Timeout.forEach((ev) => clearTimeout(ev)); EventListeners.Timeout = []; - EventListeners.Interval.forEach(ev => clearInterval(ev)); + EventListeners.Interval.forEach((ev) => clearInterval(ev)); EventListeners.Interval = []; - EventListeners.QRScanner.forEach(ev => ev.clear()); + EventListeners.QRScanner.forEach((ev) => ev.clear()); EventListeners.QRScanner = []; - EventListeners.Custom.forEach(ev => ev()); + EventListeners.Custom.forEach((ev) => ev()); EventListeners.Custom = []; - if (SUB_LOGGED_IN != true && params != "login,setup" && !params.startsWith("login,onboarding")) { - PAGES["login"].index(); + if (SUB_LOGGED_IN != true && params != 'login,setup' && !params.startsWith('login,onboarding')) { + PAGES['login'].index(); return; } - if (params == "") { - params = "index"; + if (params == '') { + params = 'index'; } - var path = params.split(","); + var path = params.split(','); var app = path[0]; if (path[1] == undefined) { PAGES[app].index(); @@ -37,8 +37,8 @@ function open_page(params) { } function setUrlHash(hash) { - location.hash = "#" + hash; - + location.hash = '#' + hash; + // Handle quick search transfer if (hash === 'buscar') { const quickSearchInput = document.getElementById('quickSearchInput'); @@ -50,62 +50,57 @@ function setUrlHash(hash) { } } window.onhashchange = () => { - open_page(location.hash.replace("#", "")); + open_page(location.hash.replace('#', '')); }; function download(filename, text) { - var element = document.createElement("a"); + var element = document.createElement('a'); element.setAttribute( - "href", - "data:application/octet-stream;charset=utf-8," + encodeURIComponent(text) + 'href', + 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(text) ); - element.setAttribute("download", filename); - - element.style.display = "none"; + element.setAttribute('download', filename); + + element.style.display = 'none'; document.body.appendChild(element); - + element.click(); - + document.body.removeChild(element); } -function resizeInputImage( - file, - callback, - targetHeight = 256, - targetQuality = 0.75 -) { +function resizeInputImage(file, callback, targetHeight = 256, targetQuality = 0.75) { const reader = new FileReader(); - - reader.onload = function(event) { + + reader.onload = function (event) { const img = new Image(); - img.onload = function() { + img.onload = function () { const aspectRatio = img.width / img.height; const targetWidth = targetHeight * aspectRatio; - - const canvas = document.createElement("canvas"); + + const canvas = document.createElement('canvas'); canvas.width = targetWidth; canvas.height = targetHeight; - - const ctx = canvas.getContext("2d"); - - ctx.fillStyle = "#ffffff"; + + const ctx = canvas.getContext('2d'); + + ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); - + ctx.drawImage(img, 0, 0, targetWidth, targetHeight); - + // Get resized image as Blob - const dataURL = canvas.toDataURL("image/jpeg", targetQuality); + const dataURL = canvas.toDataURL('image/jpeg', targetQuality); callback(dataURL); }; img.src = event.target.result; }; - + reader.readAsDataURL(file); } function CurrentISODate() { - return new Date().toISOString().split("T")[0].replace("T", " "); + return new Date().toISOString().split('T')[0].replace('T', ' '); } function CurrentISOTime() { @@ -113,7 +108,7 @@ function CurrentISOTime() { } function fixGunLocalStorage() { - localStorage.removeItem("radata"); + localStorage.removeItem('radata'); removeCache(); location.reload(); } @@ -125,45 +120,34 @@ function fixGunLocalStorage() { // } // }, 5000); - function betterSorter(a, b) { - // 1. Fecha (ascending) - if (a.Fecha && b.Fecha && a.Fecha !== b.Fecha) { - return a.Fecha > b.Fecha ? -1 : 1; - } - // 2. Region (ascending, from SC_Personas if Persona exists) - const regionA = - a.Persona && SC_Personas[a.Persona] - ? SC_Personas[a.Persona].Region || "" - : a.Region || ""; - const regionB = - b.Persona && SC_Personas[b.Persona] - ? SC_Personas[b.Persona].Region || "" - : b.Region || ""; - if (regionA !== regionB) { - return regionA.toLowerCase() < regionB.toLowerCase() ? -1 : 1; - } - // 3. Persona (Nombre, ascending, from SC_Personas if Persona exists) - const nombrePersonaA = - a.Persona && SC_Personas[a.Persona] - ? SC_Personas[a.Persona].Nombre || "" - : ""; - const nombrePersonaB = - b.Persona && SC_Personas[b.Persona] - ? SC_Personas[b.Persona].Nombre || "" - : ""; - if (nombrePersonaA !== nombrePersonaB) { - return nombrePersonaA.toLowerCase() < nombrePersonaB.toLowerCase() - ? -1 - : 1; - } - // 4. Nombre (ascending, from a.Nombre/b.Nombre) - if (a.Nombre && b.Nombre && a.Nombre !== b.Nombre) { - return a.Nombre.toLowerCase() < b.Nombre.toLowerCase() ? -1 : 1; - } - // 5. Asunto (ascending, from a.Asunto/b.Asunto) - if (a.Asunto && b.Asunto && a.Asunto !== b.Asunto) { - return a.Asunto.toLowerCase() < b.Asunto.toLowerCase() ? -1 : 1; - } - return 0; + // 1. Fecha (ascending) + if (a.Fecha && b.Fecha && a.Fecha !== b.Fecha) { + return a.Fecha > b.Fecha ? -1 : 1; } + // 2. Region (ascending, from SC_Personas if Persona exists) + const regionA = + a.Persona && SC_Personas[a.Persona] ? SC_Personas[a.Persona].Region || '' : a.Region || ''; + const regionB = + b.Persona && SC_Personas[b.Persona] ? SC_Personas[b.Persona].Region || '' : b.Region || ''; + if (regionA !== regionB) { + return regionA.toLowerCase() < regionB.toLowerCase() ? -1 : 1; + } + // 3. Persona (Nombre, ascending, from SC_Personas if Persona exists) + const nombrePersonaA = + a.Persona && SC_Personas[a.Persona] ? SC_Personas[a.Persona].Nombre || '' : ''; + const nombrePersonaB = + b.Persona && SC_Personas[b.Persona] ? SC_Personas[b.Persona].Nombre || '' : ''; + if (nombrePersonaA !== nombrePersonaB) { + return nombrePersonaA.toLowerCase() < nombrePersonaB.toLowerCase() ? -1 : 1; + } + // 4. Nombre (ascending, from a.Nombre/b.Nombre) + if (a.Nombre && b.Nombre && a.Nombre !== b.Nombre) { + return a.Nombre.toLowerCase() < b.Nombre.toLowerCase() ? -1 : 1; + } + // 5. Asunto (ascending, from a.Asunto/b.Asunto) + if (a.Asunto && b.Asunto && a.Asunto !== b.Asunto) { + return a.Asunto.toLowerCase() < b.Asunto.toLowerCase() ? -1 : 1; + } + return 0; +} diff --git a/src/app_modules.js b/src/app_modules.js index b2afe46..11cf4f0 100644 --- a/src/app_modules.js +++ b/src/app_modules.js @@ -1,7 +1,7 @@ try { - navigator.wakeLock.request("screen"); + navigator.wakeLock.request('screen'); } catch { - console.log("ScreenLock Failed"); + console.log('ScreenLock Failed'); } const debounce = (id, callback, wait, args) => { // debounce with trailing callback @@ -33,31 +33,31 @@ const debounce = (id, callback, wait, args) => { const wheelcolors = [ // Your original custom colors - "#ff0000", - "#ff00ff", - "#00ff00", - "#0000ff", - "#00ffff", - "#000000", - "#69DDFF", - "#7FB800", - "#963484", - "#FF1D15", - "#FF8600", + '#ff0000', + '#ff00ff', + '#00ff00', + '#0000ff', + '#00ffff', + '#000000', + '#69DDFF', + '#7FB800', + '#963484', + '#FF1D15', + '#FF8600', // Precomputed 30° hue-step colors (12 steps, 70% saturation, 50% lightness) - "#bf3f3f", // 0° - "#bf9f3f", // 30° - "#bfff3f", // 60° - "#7fff3f", // 90° - "#3fff5f", // 120° - "#3fffbf", // 150° - "#3fafff", // 180° - "#3f3fff", // 210° - "#9f3fff", // 240° - "#ff3fff", // 270° - "#ff3f7f", // 300° - "#ff3f3f", // 330° + '#bf3f3f', // 0° + '#bf9f3f', // 30° + '#bfff3f', // 60° + '#7fff3f', // 90° + '#3fff5f', // 120° + '#3fffbf', // 150° + '#3fafff', // 180° + '#3f3fff', // 210° + '#9f3fff', // 240° + '#ff3fff', // 270° + '#ff3f7f', // 300° + '#ff3f3f', // 330° ]; // String prototype using the precomputed array @@ -74,7 +74,7 @@ function stringToColour(str) { } function colorIsDarkAdvanced(bgColor) { - let color = bgColor.charAt(0) === "#" ? bgColor.substring(1, 7) : bgColor; + let color = bgColor.charAt(0) === '#' ? bgColor.substring(1, 7) : bgColor; let r = parseInt(color.substring(0, 2), 16); // hexToR let g = parseInt(color.substring(2, 4), 16); // hexToG let b = parseInt(color.substring(4, 6), 16); // hexToB @@ -86,33 +86,33 @@ function colorIsDarkAdvanced(bgColor) { return Math.pow((col + 0.055) / 1.055, 2.4); }); let L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; - return L <= 0.179 ? "#FFFFFF" : "#000000"; + return L <= 0.179 ? '#FFFFFF' : '#000000'; } function setLayeredImages(comanda, key) { // Base paths for each layer type (adjust paths as needed) const basePaths = { - Selección: "static/ico/layered1/", - Café: "static/ico/layered1/", - Endulzante: "static/ico/layered1/", - Cafeina: "static/ico/layered1/", - Leche: "static/ico/layered1/", + Selección: 'static/ico/layered1/', + Café: 'static/ico/layered1/', + Endulzante: 'static/ico/layered1/', + Cafeina: 'static/ico/layered1/', + Leche: 'static/ico/layered1/', }; // Map for Selección to filenames const selectionMap = { - "ColaCao con leche": "Selección-ColaCao.png", - Infusión: "Selección-Infusion.png", - "Café con leche": "Selección-CaféLeche.png", - "Solo Leche": "Selección-Leche.png", - "Solo café (sin leche)": "Selección-CaféSolo.png", + 'ColaCao con leche': 'Selección-ColaCao.png', + Infusión: 'Selección-Infusion.png', + 'Café con leche': 'Selección-CaféLeche.png', + 'Solo Leche': 'Selección-Leche.png', + 'Solo café (sin leche)': 'Selección-CaféSolo.png', }; // Start div with relative positioning for layering let html = `
    `; // Layer 1: Selección image - const selection = comanda["Selección"]; + const selection = comanda['Selección']; if (selectionMap[selection]) { html += `"; + html += '
    '; return html; } -function addCategory( - parent, - name, - icon, - options, - values, - change_cb = () => {} -) { - var details_0 = document.createElement("details"); // children: img_0, summary_0 +function addCategory(parent, name, icon, options, values, change_cb = () => {}) { + var details_0 = document.createElement('details'); // children: img_0, summary_0 //details_0.open = true; - var img_0 = document.createElement("img"); - img_0.src = "static/ico/checkbox_unchecked.png"; - img_0.style.height = "30px"; + var img_0 = document.createElement('img'); + img_0.src = 'static/ico/checkbox_unchecked.png'; + img_0.style.height = '30px'; if (values[name] != undefined) { //details_0.open = true; - img_0.src = "static/ico/checkbox.png"; + img_0.src = 'static/ico/checkbox.png'; } - var summary_0 = document.createElement("summary"); - var span_0 = document.createElement("span"); - span_0.style.float = "right"; - span_0.append(values[name] || "", " ", img_0); + var summary_0 = document.createElement('summary'); + var span_0 = document.createElement('span'); + span_0.style.float = 'right'; + span_0.append(values[name] || '', ' ', img_0); summary_0.append(name, span_0); - details_0.append(summary_0, document.createElement("br")); - details_0.style.textAlign = "center"; - details_0.style.margin = "5px"; - details_0.style.padding = "5px"; - details_0.style.border = "2px solid black"; - details_0.style.borderRadius = "5px"; - details_0.style.backgroundColor = "white"; - details_0.style.cursor = "pointer"; - details_0.style.width = "calc(100% - 25px)"; - details_0.style.display = "inline-block"; - summary_0.style.padding = "10px"; + details_0.append(summary_0, document.createElement('br')); + details_0.style.textAlign = 'center'; + details_0.style.margin = '5px'; + details_0.style.padding = '5px'; + details_0.style.border = '2px solid black'; + details_0.style.borderRadius = '5px'; + details_0.style.backgroundColor = 'white'; + details_0.style.cursor = 'pointer'; + details_0.style.width = 'calc(100% - 25px)'; + details_0.style.display = 'inline-block'; + summary_0.style.padding = '10px'; // background image at the start of summary_0: summary_0.style.backgroundImage = "url('" + icon + "')"; - summary_0.style.backgroundSize = "contain"; - summary_0.style.backgroundPosition = "left"; - summary_0.style.backgroundRepeat = "no-repeat"; - summary_0.style.textAlign = "left"; - summary_0.style.paddingLeft = "55px"; + summary_0.style.backgroundSize = 'contain'; + summary_0.style.backgroundPosition = 'left'; + summary_0.style.backgroundRepeat = 'no-repeat'; + summary_0.style.textAlign = 'left'; + summary_0.style.paddingLeft = '55px'; parent.append(details_0); options.forEach((option) => { - var btn = document.createElement("button"); - var br1 = document.createElement("br"); + var btn = document.createElement('button'); + var br1 = document.createElement('br'); //btn.innerText = option.key + ": " + option.value btn.append(option.value); // for each image in option.img: if (option.img) { - var br2 = document.createElement("br"); + var br2 = document.createElement('br'); btn.append(br2); option.img.forEach((imgsrc) => { - var img = document.createElement("img"); + var img = document.createElement('img'); img.src = imgsrc; - img.style.height = "50px"; - img.style.padding = "5px"; - img.style.backgroundColor = "white"; - btn.append(img, " "); + img.style.height = '50px'; + img.style.padding = '5px'; + img.style.backgroundColor = 'white'; + btn.append(img, ' '); }); } btn.className = option.className; if (values[option.key] == option.value) { - btn.classList.add("activeSCButton"); + btn.classList.add('activeSCButton'); } btn.onclick = (event) => { - var items = details_0.getElementsByClassName("activeSCButton"); + var items = details_0.getElementsByClassName('activeSCButton'); for (var i = 0; i < items.length; i++) { - items[i].classList.remove("activeSCButton"); + items[i].classList.remove('activeSCButton'); } - btn.classList.add("activeSCButton"); + btn.classList.add('activeSCButton'); values[option.key] = option.value; span_0.innerText = option.value; change_cb(values); - img_0.src = "static/ico/checkbox.png"; + img_0.src = 'static/ico/checkbox.png'; //details_0.open = false; // Disabled due to request }; - btn.style.borderRadius = "20px"; + btn.style.borderRadius = '20px'; //btn.style.fontSize="17.5px" details_0.append(btn); }); @@ -240,325 +233,329 @@ function addCategory_Personas( options, defaultval, change_cb = () => {}, - label = "Persona", + label = 'Persona', open_default = false, - default_empty_text = "- Lista Vacia -" + default_empty_text = '- Lista Vacia -' ) { - var details_0 = document.createElement("details"); // children: img_0, summary_0 + var details_0 = document.createElement('details'); // children: img_0, summary_0 //details_0.open = true; - var img_0 = document.createElement("img"); - img_0.src = "static/ico/checkbox_unchecked.png"; - img_0.style.height = "30px"; - if (defaultval != "") { + var img_0 = document.createElement('img'); + img_0.src = 'static/ico/checkbox_unchecked.png'; + img_0.style.height = '30px'; + if (defaultval != '') { details_0.open = false; - img_0.src = "static/ico/checkbox.png"; + img_0.src = 'static/ico/checkbox.png'; } - var summary_0 = document.createElement("summary"); - var span_0 = document.createElement("span"); - span_0.style.float = "right"; + var summary_0 = document.createElement('summary'); + var span_0 = document.createElement('span'); + span_0.style.float = 'right'; var p = SC_Personas[defaultval] || {}; - span_0.append(p.Nombre || "", " ", img_0); + span_0.append(p.Nombre || '', ' ', img_0); summary_0.append(label, span_0); - details_0.append(summary_0, document.createElement("br")); + details_0.append(summary_0, document.createElement('br')); if (open_default == true) { details_0.open = true; } - details_0.style.textAlign = "center"; - details_0.style.margin = "5px"; - details_0.style.padding = "5px"; - details_0.style.border = "2px solid black"; - details_0.style.borderRadius = "5px"; - details_0.style.backgroundColor = "white"; - details_0.style.cursor = "pointer"; - details_0.style.width = "calc(100% - 25px)"; - details_0.style.display = "inline-block"; - summary_0.style.padding = "10px"; + details_0.style.textAlign = 'center'; + details_0.style.margin = '5px'; + details_0.style.padding = '5px'; + details_0.style.border = '2px solid black'; + details_0.style.borderRadius = '5px'; + details_0.style.backgroundColor = 'white'; + details_0.style.cursor = 'pointer'; + details_0.style.width = 'calc(100% - 25px)'; + details_0.style.display = 'inline-block'; + summary_0.style.padding = '10px'; // background image at the start of summary_0: summary_0.style.backgroundImage = "url('static/ico/user.png')"; - summary_0.style.backgroundSize = "contain"; - summary_0.style.backgroundPosition = "left"; - summary_0.style.backgroundRepeat = "no-repeat"; - summary_0.style.textAlign = "left"; - summary_0.style.paddingLeft = "55px"; + summary_0.style.backgroundSize = 'contain'; + summary_0.style.backgroundPosition = 'left'; + summary_0.style.backgroundRepeat = 'no-repeat'; + summary_0.style.textAlign = 'left'; + summary_0.style.paddingLeft = '55px'; parent.append(details_0); - var lastreg = ""; + var lastreg = ''; Object.entries(options) .map(([_, data]) => { - data["_key"] = _; - return data + data['_key'] = _; + return data; }) .sort(betterSorter) .map((entry) => { - var key = entry["_key"]; + var key = entry['_key']; var value = entry; if (lastreg != value.Region.toUpperCase()) { lastreg = value.Region.toUpperCase(); - var h3_0 = document.createElement("h2"); - h3_0.style.margin = "0"; - h3_0.style.marginTop = "15px"; + var h3_0 = document.createElement('h2'); + h3_0.style.margin = '0'; + h3_0.style.marginTop = '15px'; h3_0.innerText = lastreg; details_0.append(h3_0); } var option = value.Nombre; - var btn = document.createElement("button"); - var br1 = document.createElement("br"); + var btn = document.createElement('button'); + var br1 = document.createElement('br'); //btn.innerText = option.key + ": " + option.value btn.append(option); - var br2 = document.createElement("br"); + var br2 = document.createElement('br'); btn.append(br2); - var img = document.createElement("img"); - img.src = value.Foto || "static/ico/user_generic.png"; + var img = document.createElement('img'); + img.src = value.Foto || 'static/ico/user_generic.png'; // Prefer attachment 'foto' for this persona try { const personaKey = key; if (personaKey) { - DB.getAttachment('personas', personaKey, 'foto').then((durl) => { - if (durl) img.src = durl; - }).catch(() => {}); + DB.getAttachment('personas', personaKey, 'foto') + .then((durl) => { + if (durl) img.src = durl; + }) + .catch(() => {}); } } catch (e) {} - img.style.height = "60px"; - img.style.padding = "5px"; - img.style.backgroundColor = "white"; - btn.append(img, " "); + img.style.height = '60px'; + img.style.padding = '5px'; + img.style.backgroundColor = 'white'; + btn.append(img, ' '); if (defaultval == key) { - btn.classList.add("activeSCButton"); + btn.classList.add('activeSCButton'); } btn.onclick = (event) => { - var items = details_0.getElementsByClassName("activeSCButton"); + var items = details_0.getElementsByClassName('activeSCButton'); for (var i = 0; i < items.length; i++) { - items[i].classList.remove("activeSCButton"); + items[i].classList.remove('activeSCButton'); } - btn.classList.add("activeSCButton"); + btn.classList.add('activeSCButton'); defaultval = key; - span_0.innerText = ""; - var img_5 = document.createElement("img"); - img_5.src = value.Foto || "static/ico/user_generic.png"; + span_0.innerText = ''; + var img_5 = document.createElement('img'); + img_5.src = value.Foto || 'static/ico/user_generic.png'; // Prefer attachment 'foto' when available try { const personaKey2 = key; if (personaKey2) { - DB.getAttachment('personas', personaKey2, 'foto').then((durl) => { - if (durl) img_5.src = durl; - }).catch(() => {}); + DB.getAttachment('personas', personaKey2, 'foto') + .then((durl) => { + if (durl) img_5.src = durl; + }) + .catch(() => {}); } } catch (e) {} - img_5.style.height = "30px"; + img_5.style.height = '30px'; span_0.append(img_5, value.Nombre); change_cb(defaultval); - img_0.src = "static/ico/checkbox.png"; + img_0.src = 'static/ico/checkbox.png'; //details_0.open = false; // Disabled due to request }; - btn.style.borderRadius = "20px"; + btn.style.borderRadius = '20px'; //btn.style.fontSize="17.5px" details_0.append(btn); }); if (Object.entries(options).length == 0) { - var btn = document.createElement("b"); + var btn = document.createElement('b'); btn.append(default_empty_text); details_0.append(btn); } } const SC_actions_icons = { - Tamaño: "static/ico/sizes.png", - Temperatura: "static/ico/thermometer2.png", - Leche: "static/ico/milk.png", - Selección: "static/ico/preferences.png", - Cafeina: "static/ico/coffee_bean.png", - Endulzante: "static/ico/lollipop.png", - Receta: "static/ico/cookies.png", + Tamaño: 'static/ico/sizes.png', + Temperatura: 'static/ico/thermometer2.png', + Leche: 'static/ico/milk.png', + Selección: 'static/ico/preferences.png', + Cafeina: 'static/ico/coffee_bean.png', + Endulzante: 'static/ico/lollipop.png', + Receta: 'static/ico/cookies.png', }; const SC_actions = { Selección: [ { - value: "Solo Leche", - key: "Selección", - className: "btn4", - img: ["static/ico/milk.png"], + value: 'Solo Leche', + key: 'Selección', + className: 'btn4', + img: ['static/ico/milk.png'], }, { - value: "Solo café (sin leche)", - key: "Selección", - className: "btn4", - img: ["static/ico/coffee_bean.png"], + value: 'Solo café (sin leche)', + key: 'Selección', + className: 'btn4', + img: ['static/ico/coffee_bean.png'], }, { - value: "Café con leche", - key: "Selección", - className: "btn4", - img: ["static/ico/coffee_bean.png", "static/ico/milk.png"], + value: 'Café con leche', + key: 'Selección', + className: 'btn4', + img: ['static/ico/coffee_bean.png', 'static/ico/milk.png'], }, { - value: "ColaCao con leche", - key: "Selección", - className: "btn4", - img: ["static/ico/colacao.jpg", "static/ico/milk.png"], + value: 'ColaCao con leche', + key: 'Selección', + className: 'btn4', + img: ['static/ico/colacao.jpg', 'static/ico/milk.png'], }, { - value: "Leche con cereales", - key: "Selección", - className: "btn4", - img: ["static/ico/cereales.png", "static/ico/milk.png"], + value: 'Leche con cereales', + key: 'Selección', + className: 'btn4', + img: ['static/ico/cereales.png', 'static/ico/milk.png'], }, { - value: "Infusión", - key: "Selección", - className: "btn4", - img: ["static/ico/tea_bag.png"], + value: 'Infusión', + key: 'Selección', + className: 'btn4', + img: ['static/ico/tea_bag.png'], }, ], Tamaño: [ { - value: "Grande", - key: "Tamaño", - className: "btn1", - img: ["static/ico/keyboard_key_g.png"], + value: 'Grande', + key: 'Tamaño', + className: 'btn1', + img: ['static/ico/keyboard_key_g.png'], }, { - value: "Pequeño", - key: "Tamaño", - className: "btn1", - img: ["static/ico/keyboard_key_p.png"], + value: 'Pequeño', + key: 'Tamaño', + className: 'btn1', + img: ['static/ico/keyboard_key_p.png'], }, ], Temperatura: [ { - value: "Caliente", - key: "Temperatura", - className: "btn2", - img: [ - "static/ico/thermometer2.png", - "static/ico/arrow_up_red.png", - "static/ico/fire.png", - ], + value: 'Caliente', + key: 'Temperatura', + className: 'btn2', + img: ['static/ico/thermometer2.png', 'static/ico/arrow_up_red.png', 'static/ico/fire.png'], }, { - value: "Templado", - key: "Temperatura", - className: "btn2", - img: ["static/ico/thermometer2.png", "static/ico/arrow_left_green.png"], + value: 'Templado', + key: 'Temperatura', + className: 'btn2', + img: ['static/ico/thermometer2.png', 'static/ico/arrow_left_green.png'], }, { - value: "Frio", - key: "Temperatura", - className: "btn2", + value: 'Frio', + key: 'Temperatura', + className: 'btn2', img: [ - "static/ico/thermometer2.png", - "static/ico/arrow_down_blue.png", - "static/ico/snowflake.png", + 'static/ico/thermometer2.png', + 'static/ico/arrow_down_blue.png', + 'static/ico/snowflake.png', ], }, ], Leche: [ { - value: "de Vaca", - key: "Leche", - className: "btn3", - img: ["static/ico/cow.png", "static/ico/add.png"], + value: 'de Vaca', + key: 'Leche', + className: 'btn3', + img: ['static/ico/cow.png', 'static/ico/add.png'], }, { - value: "Sin lactosa", - key: "Leche", - className: "btn3", - img: ["static/ico/cow.png", "static/ico/delete.png"], + value: 'Sin lactosa', + key: 'Leche', + className: 'btn3', + img: ['static/ico/cow.png', 'static/ico/delete.png'], }, { - value: "Vegetal", - key: "Leche", - className: "btn3", - img: ["static/ico/milk.png", "static/ico/wheat.png"], + value: 'Vegetal', + key: 'Leche', + className: 'btn3', + img: ['static/ico/milk.png', 'static/ico/wheat.png'], }, { - value: "Almendras", - key: "Leche", - className: "btn3", - img: ["static/ico/milk.png", "static/ico/almond.svg"], + value: 'Almendras', + key: 'Leche', + className: 'btn3', + img: ['static/ico/milk.png', 'static/ico/almond.svg'], }, { - value: "Agua", - key: "Leche", - className: "btn3", - img: ["static/ico/water_tap.png"], + value: 'Agua', + key: 'Leche', + className: 'btn3', + img: ['static/ico/water_tap.png'], }, ], Cafeina: [ { - value: "Con", - key: "Cafeina", - className: "btn5", - img: ["static/ico/coffee_bean.png", "static/ico/add.png"], + value: 'Con', + key: 'Cafeina', + className: 'btn5', + img: ['static/ico/coffee_bean.png', 'static/ico/add.png'], }, { - value: "Sin", - key: "Cafeina", - className: "btn5", - img: ["static/ico/coffee_bean.png", "static/ico/delete.png"], + value: 'Sin', + key: 'Cafeina', + className: 'btn5', + img: ['static/ico/coffee_bean.png', 'static/ico/delete.png'], }, ], Endulzante: [ { - value: "Az. Blanco", - key: "Endulzante", - className: "btn6", - img: ["static/ico/azucar-blanco.jpg"], + value: 'Az. Blanco', + key: 'Endulzante', + className: 'btn6', + img: ['static/ico/azucar-blanco.jpg'], }, { - value: "Az. Moreno", - key: "Endulzante", - className: "btn6", - img: ["static/ico/azucar-moreno.png"], + value: 'Az. Moreno', + key: 'Endulzante', + className: 'btn6', + img: ['static/ico/azucar-moreno.png'], }, { - value: "Sacarina", - key: "Endulzante", - className: "btn6", - img: ["static/ico/sacarina.jpg"], + value: 'Sacarina', + key: 'Endulzante', + className: 'btn6', + img: ['static/ico/sacarina.jpg'], }, { - value: "Stevia (Pastillas)", - key: "Endulzante", - className: "btn6", - img: ["static/ico/stevia.jpg"], + value: 'Stevia (Pastillas)', + key: 'Endulzante', + className: 'btn6', + img: ['static/ico/stevia.jpg'], }, { - value: "Stevia (Gotas)", - key: "Endulzante", - className: "btn6", - img: ["static/ico/stevia-gotas.webp"], + value: 'Stevia (Gotas)', + key: 'Endulzante', + className: 'btn6', + img: ['static/ico/stevia-gotas.webp'], }, { - value: "Sin", - key: "Endulzante", - className: "btn6", - img: ["static/ico/delete.png"], + value: 'Sin', + key: 'Endulzante', + className: 'btn6', + img: ['static/ico/delete.png'], }, ], Receta: [ { - value: "Si", - key: "Receta", - className: "btn7", - img: ["static/ico/add.png"], + value: 'Si', + key: 'Receta', + className: 'btn7', + img: ['static/ico/add.png'], }, { - value: "No", - key: "Receta", - className: "btn7", - img: ["static/ico/delete.png"], + value: 'No', + key: 'Receta', + className: 'btn7', + img: ['static/ico/delete.png'], }, ], }; function TS_decrypt(input, secret, callback, table, id) { // Accept objects or plaintext strings. Support AES-encrypted entries wrapped as RSA{...}. - if (typeof input !== "string") { - try { callback(input, false); } catch (e) { console.error(e); } + if (typeof input !== 'string') { + try { + callback(input, false); + } catch (e) { + console.error(e); + } return; } // Encrypted format marker: RSA{} where is CryptoJS AES output - if (input.startsWith("RSA{") && input.endsWith("}") && typeof CryptoJS !== 'undefined') { + if (input.startsWith('RSA{') && input.endsWith('}') && typeof CryptoJS !== 'undefined') { try { var data = input.slice(4, -1); var words = CryptoJS.AES.decrypt(data, secret); @@ -570,19 +567,29 @@ function TS_decrypt(input, secret, callback, table, id) { decryptedUtf8 = words.toString(CryptoJS.enc.Latin1); } catch (latinErr) { console.warn('TS_decrypt: failed to decode decrypted bytes', utfErr, latinErr); - try { callback(input, "error"); } catch (ee) { } + try { + callback(input, 'error'); + } catch (ee) {} return; } } var parsed = null; - try { - parsed = JSON.parse(decryptedUtf8); - } catch (pe) { - parsed = decryptedUtf8; - try { callback(parsed, "error2"); } catch (ee) { console.error(ee); } - return; + try { + parsed = JSON.parse(decryptedUtf8); + } catch (pe) { + parsed = decryptedUtf8; + try { + callback(parsed, 'error2'); + } catch (ee) { + console.error(ee); } - try { callback(parsed, true); } catch (e) { console.error(e); } + return; + } + try { + callback(parsed, true); + } catch (e) { + console.error(e); + } // Keep encrypted at-rest: if table/id provided, ensure DB stores encrypted payload (input) // if (table && id && window.DB && DB.put) { // DB.put(table, id, input).catch(() => {}); @@ -590,7 +597,9 @@ function TS_decrypt(input, secret, callback, table, id) { return; } catch (e) { console.error('TS_decrypt: invalid encrypted payload', e); - try { callback(input, "error"); } catch (ee) { } + try { + callback(input, 'error'); + } catch (ee) {} return; } } @@ -598,7 +607,11 @@ function TS_decrypt(input, secret, callback, table, id) { // Plain JSON stored as text -> parse and return, then re-encrypt in DB for at-rest protection try { var parsed = JSON.parse(input); - try { callback(parsed, false); } catch (e) { console.error(e); } + try { + callback(parsed, false); + } catch (e) { + console.error(e); + } if (table && id && window.DB && DB.put && typeof SECRET !== 'undefined') { TS_encrypt(parsed, SECRET, function (enc) { DB.put(table, id, enc).catch(() => {}); @@ -606,11 +619,15 @@ function TS_decrypt(input, secret, callback, table, id) { } } catch (e) { // Not JSON, return raw string - try { callback(input, false); } catch (err) { console.error(err); } + try { + callback(input, false); + } catch (err) { + console.error(err); + } } -} -function TS_encrypt(input, secret, callback, mode = "RSA") { - // Skip encryption +} +function TS_encrypt(input, secret, callback, mode = 'RSA') { + // Skip encryption //callback(input); //return; // Encrypt given value for at-rest storage using CryptoJS AES. @@ -618,44 +635,66 @@ function TS_encrypt(input, secret, callback, mode = "RSA") { try { if (typeof CryptoJS === 'undefined') { // CryptoJS not available — return plaintext - try { callback(input); } catch (e) { console.error(e); } + try { + callback(input); + } catch (e) { + console.error(e); + } return; } var payload = input; if (typeof input !== 'string') { - try { payload = JSON.stringify(input); } catch (e) { payload = String(input); } + try { + payload = JSON.stringify(input); + } catch (e) { + payload = String(input); + } } var encrypted = CryptoJS.AES.encrypt(payload, secret).toString(); var out = 'RSA{' + encrypted + '}'; - try { callback(out); } catch (e) { console.error(e); } + try { + callback(out); + } catch (e) { + console.error(e); + } } catch (e) { console.error('TS_encrypt: encryption failed', e); - try { callback(input); } catch (err) { console.error(err); } + try { + callback(input); + } catch (err) { + console.error(err); + } } -} +} // Listado precargado de personas: DB.map('personas', (data, key) => { function add_row(data, key) { if (data != null) { - data["_key"] = key; + data['_key'] = key; SC_Personas[key] = data; } else { delete SC_Personas[key]; } } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - add_row(data, key); - }, 'personas', key); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + add_row(data, key); + }, + 'personas', + key + ); } else { add_row(data, key); } }); function SC_parse(json) { - var out = ""; + var out = ''; Object.entries(json).forEach((entry) => { - out += entry[0] + ": " + entry[1] + "\n"; + out += entry[0] + ': ' + entry[1] + '\n'; }); return out; } @@ -664,89 +703,76 @@ function SC_parse_short(json) { var valores = "Servicio base (10c)\n"; Object.entries(json).forEach((entry) => { - valores += - "" + - entry[0] + - ": " + - entry[1] + - " "; - var combo = entry[0] + ";" + entry[1]; + valores += "" + entry[0] + ': ' + entry[1] + ' '; + var combo = entry[0] + ';' + entry[1]; switch (entry[0]) { - case "Leche": + case 'Leche': // Leche pequeña = 10c if ( - json["Tamaño"] == "Pequeño" && - ["de Vaca", "Sin lactosa", "Vegetal", "Almendras"].includes( - json["Leche"] - ) + json['Tamaño'] == 'Pequeño' && + ['de Vaca', 'Sin lactosa', 'Vegetal', 'Almendras'].includes(json['Leche']) ) { - valores += "(P = 10c)"; + valores += '(P = 10c)'; } // Leche grande = 20c if ( - json["Tamaño"] == "Grande" && - ["de Vaca", "Sin lactosa", "Vegetal", "Almendras"].includes( - json["Leche"] - ) + json['Tamaño'] == 'Grande' && + ['de Vaca', 'Sin lactosa', 'Vegetal', 'Almendras'].includes(json['Leche']) ) { - valores += "(G = 20c)"; + valores += '(G = 20c)'; } break; - case "Selección": + case 'Selección': // Café = 20c - if ( - ["Café con leche", "Solo café (sin leche)"].includes( - json["Selección"] - ) - ) { - valores += "(20c)"; + if (['Café con leche', 'Solo café (sin leche)'].includes(json['Selección'])) { + valores += '(20c)'; } // ColaCao = 20c - if (json["Selección"] == "ColaCao con leche") { - valores += "(20c)"; + if (json['Selección'] == 'ColaCao con leche') { + valores += '(20c)'; } default: break; } - valores += "\n"; + valores += '\n'; }); return valores; } function SC_priceCalc(json) { var precio = 0; - var valores = ""; + var valores = ''; // Servicio base = 10c precio += 10; - valores += "Servicio base = 10c\n"; + valores += 'Servicio base = 10c\n'; // Leche pequeña = 10c if ( - json["Tamaño"] == "Pequeño" && - ["de Vaca", "Sin lactosa", "Vegetal", "Almendras"].includes(json["Leche"]) + json['Tamaño'] == 'Pequeño' && + ['de Vaca', 'Sin lactosa', 'Vegetal', 'Almendras'].includes(json['Leche']) ) { precio += 15; - valores += "Leche pequeña = 15c\n"; + valores += 'Leche pequeña = 15c\n'; } // Leche grande = 20c if ( - json["Tamaño"] == "Grande" && - ["de Vaca", "Sin lactosa", "Vegetal", "Almendras"].includes(json["Leche"]) + json['Tamaño'] == 'Grande' && + ['de Vaca', 'Sin lactosa', 'Vegetal', 'Almendras'].includes(json['Leche']) ) { precio += 25; - valores += "Leche grande = 25c\n"; + valores += 'Leche grande = 25c\n'; } // Café = 20c - if (["Café con leche", "Solo café (sin leche)"].includes(json["Selección"])) { + if (['Café con leche', 'Solo café (sin leche)'].includes(json['Selección'])) { precio += 25; - valores += "Café = 25c\n"; + valores += 'Café = 25c\n'; } // ColaCao = 20c - if (json["Selección"] == "ColaCao con leche") { + if (json['Selección'] == 'ColaCao con leche') { precio += 25; - valores += "ColaCao = 25c\n"; + valores += 'ColaCao = 25c\n'; } - valores += "
    Total: " + precio + "c\n"; + valores += '
    Total: ' + precio + 'c\n'; return [precio, valores]; } @@ -772,34 +798,36 @@ function TS_IndexElement( var debounce_load = safeuuid(); // Create the container with search bar and table - container.innerHTML = ` + container.innerHTML = html`
    - - +
    - +
    - `; - tableScroll("#" + scrolltable); // id="scrolltable" + `; + tableScroll('#' + scrolltable); // id="scrolltable" var tablehead_EL = document.getElementById(tablehead); var tablebody_EL = document.getElementById(tablebody); var rows = {}; config.forEach((key) => { - tablehead_EL.innerHTML += `${key.label || ""}`; + tablehead_EL.innerHTML += `${key.label || ''}`; }); // Add search functionality const searchKeyEl = document.getElementById(searchKeyInput); - searchKeyEl.addEventListener("input", () => - debounce(debounce_search, render, 200, [rows]) - ); + searchKeyEl.addEventListener('input', () => debounce(debounce_search, render, 200, [rows])); function searchInData(data, searchValue, config) { if (!searchValue) return true; @@ -809,18 +837,16 @@ function TS_IndexElement( // Search in configured fields for (var field of config) { - const value = data[field.key] || field.default || ""; + const value = data[field.key] || field.default || ''; // Handle different field types switch (field.type) { - case "comanda": + case 'comanda': try { const comandaData = JSON.parse(data.Comanda); // Search in all comanda fields if ( - Object.values(comandaData).some((v) => - String(v).toLowerCase().includes(searchValue) - ) + Object.values(comandaData).some((v) => String(v).toLowerCase().includes(searchValue)) ) return true; } catch (e) { @@ -828,11 +854,11 @@ function TS_IndexElement( if (data.Comanda.toLowerCase().includes(searchValue)) return true; } break; - case "persona": - case "persona-nombre": - var persona = SC_Personas[value] || { Nombre: "", Region: "" }; + case 'persona': + case 'persona-nombre': + var persona = SC_Personas[value] || { Nombre: '', Region: '' }; if (field.self == true) { - persona = data || { Nombre: "", Region: "" }; + persona = data || { Nombre: '', Region: '' }; } if (persona) { // Search in persona fields @@ -840,11 +866,11 @@ function TS_IndexElement( if (persona.Region.toLowerCase().includes(searchValue)) return true; } break; - case "fecha": - case "fecha-iso": + case 'fecha': + case 'fecha-iso': // Format date as DD/MM/YYYY for searching if (value) { - const fechaArray = value.split("-"); + const fechaArray = value.split('-'); const formattedDate = `${fechaArray[2]}/${fechaArray[1]}/${fechaArray[0]}`; if (formattedDate.includes(searchValue)) return true; } @@ -858,17 +884,12 @@ function TS_IndexElement( } // --- Optimized render function --- - var lastSearchValue = ""; + var lastSearchValue = ''; var lastFilteredSorted = []; - function getFilteredSortedRows(searchValue) { // Only use cache if searchValue is not empty and cache is valid - if ( - searchValue && - searchValue === lastSearchValue && - lastFilteredSorted.length > 0 - ) { + if (searchValue && searchValue === lastSearchValue && lastFilteredSorted.length > 0) { return lastFilteredSorted; } const filtered = Object.entries(rows) @@ -890,60 +911,59 @@ function TS_IndexElement( if (canAddCallback != undefined && canAddCallback(data) === true) { continue; } - const new_tr = document.createElement("tr"); + const new_tr = document.createElement('tr'); if (rowCallback != undefined) { rowCallback(data, new_tr); } config.forEach((key) => { switch (key.type) { - case "_encrypted": { - const tdEncrypted = document.createElement("td"); - if (data["_encrypted__"] === true) { - tdEncrypted.innerText = "🔒" - } else if (data["_encrypted__"] === "error" || data["_encrypted__"] === "error2" || data["_encrypted__"] === undefined) { - tdEncrypted.innerText = "⚠️ Error"; + case '_encrypted': { + const tdEncrypted = document.createElement('td'); + if (data['_encrypted__'] === true) { + tdEncrypted.innerText = '🔒'; + } else if ( + data['_encrypted__'] === 'error' || + data['_encrypted__'] === 'error2' || + data['_encrypted__'] === undefined + ) { + tdEncrypted.innerText = '⚠️ Error'; } else { - tdEncrypted.innerText = ""; + tdEncrypted.innerText = ''; } new_tr.appendChild(tdEncrypted); break; } - case "raw": - case "text": { - const tdRaw = document.createElement("td"); - const rawContent = ( - String(data[key.key]) || - key.default || - "" - ).replace(/\n/g, "
    "); + case 'raw': + case 'text': { + const tdRaw = document.createElement('td'); + const rawContent = (String(data[key.key]) || key.default || '').replace(/\n/g, '
    '); tdRaw.innerHTML = rawContent; new_tr.appendChild(tdRaw); break; } - case "moneda": { - const tdMoneda = document.createElement("td"); + case 'moneda': { + const tdMoneda = document.createElement('td'); const valor = parseFloat(data[key.key]); if (!isNaN(valor)) { - tdMoneda.innerText = valor.toFixed(2) + " €"; + tdMoneda.innerText = valor.toFixed(2) + ' €'; } else { - tdMoneda.innerText = key.default || ""; + tdMoneda.innerText = key.default || ''; } new_tr.appendChild(tdMoneda); break; } - case "fecha": - case "fecha-iso": { - const tdFechaISO = document.createElement("td"); + case 'fecha': + case 'fecha-iso': { + const tdFechaISO = document.createElement('td'); if (data[key.key]) { - const fechaArray = data[key.key].split("-"); - tdFechaISO.innerText = - fechaArray[2] + "/" + fechaArray[1] + "/" + fechaArray[0]; + const fechaArray = data[key.key].split('-'); + tdFechaISO.innerText = fechaArray[2] + '/' + fechaArray[1] + '/' + fechaArray[0]; } new_tr.appendChild(tdFechaISO); break; } - case "fecha-diff": { - const tdFechaISO = document.createElement("td"); + case 'fecha-diff': { + const tdFechaISO = document.createElement('td'); if (data[key.key]) { const fecha = new Date(data[key.key]); const now = new Date(); @@ -951,96 +971,96 @@ function TS_IndexElement( const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); const diffMonths = Math.floor(diffDays / 30); const diffYears = Math.floor(diffDays / 365); - let diffString = ""; + let diffString = ''; if (diffYears > 0) { - diffString += diffYears + " año" + (diffYears > 1 ? "s " : " "); + diffString += diffYears + ' año' + (diffYears > 1 ? 's ' : ' '); } if (diffMonths % 12 > 0) { - diffString += - (diffMonths % 12) + - " mes" + - (diffMonths % 12 > 1 ? "es " : " "); + diffString += (diffMonths % 12) + ' mes' + (diffMonths % 12 > 1 ? 'es ' : ' '); } - + // if more than 3 months, show rgb(255, 192, 192) as background if (diffMonths >= 3) { - tdFechaISO.style.backgroundColor = "rgb(255, 192, 192)"; + tdFechaISO.style.backgroundColor = 'rgb(255, 192, 192)'; } else if (diffMonths >= 1) { - tdFechaISO.style.backgroundColor = "rgb(252, 252, 176)"; + tdFechaISO.style.backgroundColor = 'rgb(252, 252, 176)'; } tdFechaISO.innerText = diffString.trim(); } new_tr.appendChild(tdFechaISO); break; } - case "template": { - const tdCustomTemplate = document.createElement("td"); + case 'template': { + const tdCustomTemplate = document.createElement('td'); new_tr.appendChild(tdCustomTemplate); key.template(data, tdCustomTemplate); break; } - case "comanda": { - const tdComanda = document.createElement("td"); - tdComanda.style.verticalAlign = "top"; + case 'comanda': { + const tdComanda = document.createElement('td'); + tdComanda.style.verticalAlign = 'top'; const parsedComanda = JSON.parse(data.Comanda); const precio = SC_priceCalc(parsedComanda)[0]; - const tempDiv = document.createElement("div"); + const tempDiv = document.createElement('div'); tempDiv.innerHTML = setLayeredImages(parsedComanda, data._key); tdComanda.appendChild(tempDiv.firstChild); - const pre = document.createElement("pre"); - pre.style.fontSize = "15px"; - pre.style.display = "inline-block"; - pre.style.margin = "0"; - pre.style.verticalAlign = "top"; - pre.style.padding = "5px"; - pre.style.background = "rgba(255, 255, 0, 0.5)"; - pre.style.border = "1px solid rgba(0, 0, 0, 0.2)"; - pre.style.borderRadius = "5px"; - pre.style.boxShadow = "2px 2px 5px rgba(0, 0, 0, 0.1)"; - pre.style.height = "100%"; - const spanPrecio = document.createElement("span"); - spanPrecio.style.fontSize = "20px"; - spanPrecio.innerHTML = `Total: ${precio}c`; - pre.innerHTML = "Ticket de compra "; - pre.appendChild(document.createTextNode("\n")); - pre.innerHTML += - SC_parse_short(parsedComanda) + "
    " + data.Notas + "
    "; + const pre = document.createElement('pre'); + pre.style.fontSize = '15px'; + pre.style.display = 'inline-block'; + pre.style.margin = '0'; + pre.style.verticalAlign = 'top'; + pre.style.padding = '5px'; + pre.style.background = 'rgba(255, 255, 0, 0.5)'; + pre.style.border = '1px solid rgba(0, 0, 0, 0.2)'; + pre.style.borderRadius = '5px'; + pre.style.boxShadow = '2px 2px 5px rgba(0, 0, 0, 0.1)'; + pre.style.height = '100%'; + const spanPrecio = document.createElement('span'); + spanPrecio.style.fontSize = '20px'; + spanPrecio.innerHTML = html`Total: ${precio}c`; + pre.innerHTML = 'Ticket de compra '; + pre.appendChild(document.createTextNode('\n')); + pre.innerHTML += SC_parse_short(parsedComanda) + '
    ' + data.Notas + '
    '; pre.appendChild(spanPrecio); tdComanda.appendChild(pre); new_tr.appendChild(tdComanda); break; } - case "comanda-status": { - var sc_nobtn = ""; - if (urlParams.get("sc_nobtn") == "yes") { - sc_nobtn = "pointer-events: none; opacity: 0.5"; + case 'comanda-status': { + var sc_nobtn = ''; + if (urlParams.get('sc_nobtn') == 'yes') { + sc_nobtn = 'pointer-events: none; opacity: 0.5'; } - const td = document.createElement("td"); - td.style.fontSize = "17px"; + const td = document.createElement('td'); + td.style.fontSize = '17px'; if (sc_nobtn) { - td.style.pointerEvents = "none"; - td.style.opacity = "0.5"; + td.style.pointerEvents = 'none'; + td.style.opacity = '0.5'; } const createButton = (text, state) => { - const button = document.createElement("button"); + const button = document.createElement('button'); button.textContent = text; if (data.Estado === state) { - button.className = "rojo"; + button.className = 'rojo'; } button.onclick = (event) => { event.preventDefault(); event.stopPropagation(); data.Estado = state; if (typeof ref === 'string') { - DB.put(ref, data._key, data).then(() => { - toastr.success("Guardado!"); - render(); - }).catch((e) => { console.warn('DB.put error', e); }); + DB.put(ref, data._key, data) + .then(() => { + toastr.success('Guardado!'); + render(); + }) + .catch((e) => { + console.warn('DB.put error', e); + }); } else { try { // legacy ref.get(data._key).put(data); - toastr.success("Guardado!"); + toastr.success('Guardado!'); } catch (e) { console.warn('Could not save item', e); } @@ -1050,15 +1070,15 @@ function TS_IndexElement( return button; }; const buttons = [ - createButton("Pedido", "Pedido"), - createButton("En preparación", "En preparación"), - createButton("Listo", "Listo"), - createButton("Entregado", "Entregado"), - createButton("Deuda", "Deuda"), + createButton('Pedido', 'Pedido'), + createButton('En preparación', 'En preparación'), + createButton('Listo', 'Listo'), + createButton('Entregado', 'Entregado'), + createButton('Deuda', 'Deuda'), ]; - const paidButton = document.createElement("button"); - paidButton.textContent = "Pagado"; - paidButton.className = "btn5"; + const paidButton = document.createElement('button'); + paidButton.textContent = 'Pagado'; + paidButton.className = 'btn5'; paidButton.onclick = (event) => { event.preventDefault(); event.stopPropagation(); @@ -1070,108 +1090,109 @@ function TS_IndexElement( // Store prefilled data in sessionStorage for Pagos module var sdata = JSON.stringify({ - tipo: "Gasto", + tipo: 'Gasto', monto: precio / 100, // Convert cents to euros persona: personaId, - notas: - "Pago de comanda SuperCafé\n" + - SC_parse(JSON.parse(data.Comanda)), - origen: "SuperCafé", + notas: 'Pago de comanda SuperCafé\n' + SC_parse(JSON.parse(data.Comanda)), + origen: 'SuperCafé', origen_id: comandaId, }); // Navigate to datafono - setUrlHash("pagos,datafono_prefill," + btoa(sdata)); + setUrlHash('pagos,datafono_prefill,' + btoa(sdata)); return false; }; td.append(data.Fecha); - td.append(document.createElement("br")); + td.append(document.createElement('br')); buttons.forEach((button) => { td.appendChild(button); - td.appendChild(document.createElement("br")); + td.appendChild(document.createElement('br')); }); td.appendChild(paidButton); new_tr.appendChild(td); break; } - case "persona": { - let persona = - key.self === true ? data : SC_Personas[data[key.key]] || {}; - const regco = stringToColour((persona.Region || "?").toLowerCase()); - const tdPersona = document.createElement("td"); - tdPersona.style.textAlign = "center"; - tdPersona.style.fontSize = "20px"; + case 'persona': { + let persona = key.self === true ? data : SC_Personas[data[key.key]] || {}; + const regco = stringToColour((persona.Region || '?').toLowerCase()); + const tdPersona = document.createElement('td'); + tdPersona.style.textAlign = 'center'; + tdPersona.style.fontSize = '20px'; tdPersona.style.backgroundColor = regco; tdPersona.style.color = colorIsDarkAdvanced(regco); - const regionSpan = document.createElement("span"); - regionSpan.style.fontSize = "40px"; - regionSpan.style.textTransform = "capitalize"; - regionSpan.textContent = (persona.Region || "?").toLowerCase(); + const regionSpan = document.createElement('span'); + regionSpan.style.fontSize = '40px'; + regionSpan.style.textTransform = 'capitalize'; + regionSpan.textContent = (persona.Region || '?').toLowerCase(); tdPersona.appendChild(regionSpan); - tdPersona.appendChild(document.createElement("br")); - const infoSpan = document.createElement("span"); - infoSpan.style.backgroundColor = "white"; - infoSpan.style.border = "2px solid black"; - infoSpan.style.borderRadius = "5px"; - infoSpan.style.display = "inline-block"; - infoSpan.style.padding = "5px"; - infoSpan.style.color = "black"; - const img = document.createElement("img"); - img.src = persona.Foto || "static/ico/user_generic.png"; + tdPersona.appendChild(document.createElement('br')); + const infoSpan = document.createElement('span'); + infoSpan.style.backgroundColor = 'white'; + infoSpan.style.border = '2px solid black'; + infoSpan.style.borderRadius = '5px'; + infoSpan.style.display = 'inline-block'; + infoSpan.style.padding = '5px'; + infoSpan.style.color = 'black'; + const img = document.createElement('img'); + img.src = persona.Foto || 'static/ico/user_generic.png'; // Prefer attachment 'foto' stored in PouchDB if available try { - const personaId = key.self === true ? (data._key || data._id || data.id) : data[key.key]; + const personaId = + key.self === true ? data._key || data._id || data.id : data[key.key]; if (personaId) { - DB.getAttachment('personas', personaId, 'foto').then((durl) => { - if (durl) img.src = durl; - }).catch(() => {}); + DB.getAttachment('personas', personaId, 'foto') + .then((durl) => { + if (durl) img.src = durl; + }) + .catch(() => {}); } } catch (e) { // ignore } img.height = 70; infoSpan.appendChild(img); - infoSpan.appendChild(document.createElement("br")); - infoSpan.appendChild(document.createTextNode(persona.Nombre || "")); - infoSpan.appendChild(document.createElement("br")); - if (parseFloat(persona.Monedero_Balance || "0") != 0) { - const pointsSpan = document.createElement("span"); - pointsSpan.style.fontSize = "17px"; - pointsSpan.textContent = parseFloat(persona.Monedero_Balance || "0").toPrecision(2) + " €"; + infoSpan.appendChild(document.createElement('br')); + infoSpan.appendChild(document.createTextNode(persona.Nombre || '')); + infoSpan.appendChild(document.createElement('br')); + if (parseFloat(persona.Monedero_Balance || '0') != 0) { + const pointsSpan = document.createElement('span'); + pointsSpan.style.fontSize = '17px'; + pointsSpan.textContent = + parseFloat(persona.Monedero_Balance || '0').toPrecision(2) + ' €'; infoSpan.appendChild(pointsSpan); } tdPersona.appendChild(infoSpan); new_tr.appendChild(tdPersona); break; } - case "persona-nombre": { - let persona = - key.self === true ? data : SC_Personas[data[key.key]] || {}; - const tdPersonaNombre = document.createElement("td"); - tdPersonaNombre.style.textAlign = "center"; - tdPersonaNombre.style.fontSize = "20px"; - tdPersonaNombre.textContent = persona.Nombre || ""; + case 'persona-nombre': { + let persona = key.self === true ? data : SC_Personas[data[key.key]] || {}; + const tdPersonaNombre = document.createElement('td'); + tdPersonaNombre.style.textAlign = 'center'; + tdPersonaNombre.style.fontSize = '20px'; + tdPersonaNombre.textContent = persona.Nombre || ''; new_tr.appendChild(tdPersonaNombre); break; } - case "attachment-persona": { - const tdAttachment = document.createElement("td"); - const img = document.createElement("img"); - img.src = - data[key.key] || - "static/ico/user_generic.png"; - img.style.maxHeight = "80px"; - img.style.maxWidth = "80px"; + case 'attachment-persona': { + const tdAttachment = document.createElement('td'); + const img = document.createElement('img'); + img.src = data[key.key] || 'static/ico/user_generic.png'; + img.style.maxHeight = '80px'; + img.style.maxWidth = '80px'; tdAttachment.appendChild(img); new_tr.appendChild(tdAttachment); // Prefer attachment 'foto' stored in PouchDB if available try { - const personaId = key.self === true ? (data._key || data._id || data.id) : data[key.key]; + const personaId = + key.self === true ? data._key || data._id || data.id : data[key.key]; if (personaId) { - DB.getAttachment('personas', personaId, 'foto').then((durl) => { - if (durl) img.src = durl; - }).catch(() => {}); + DB.getAttachment('personas', personaId, 'foto') + .then((durl) => { + if (durl) img.src = durl; + }) + .catch(() => {}); } } catch (e) { // ignore @@ -1183,12 +1204,12 @@ function TS_IndexElement( } }); new_tr.onclick = (event) => { - setUrlHash(pageco + "," + data._key); + setUrlHash(pageco + ',' + data._key); }; fragment.appendChild(new_tr); } // Replace tbody in one operation - tablebody_EL.innerHTML = ""; + tablebody_EL.innerHTML = ''; tablebody_EL.appendChild(fragment); } // Subscribe to dataset updates using DB.map (PouchDB) when `ref` is a table name string @@ -1196,20 +1217,26 @@ function TS_IndexElement( DB.map(ref, (data, key) => { function add_row(data, key) { if (data != null) { - data["_key"] = key; + data['_key'] = key; rows[key] = data; } else { delete rows[key]; } debounce(debounce_load, render, 200, [rows]); } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - data["_encrypted__"] = wasEncrypted; - add_row(data, key); - }, ref, key); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + data['_encrypted__'] = wasEncrypted; + add_row(data, key); + }, + ref, + key + ); } else { - data["_encrypted__"] = false; + data['_encrypted__'] = false; add_row(data, key); } }); @@ -1219,20 +1246,26 @@ function TS_IndexElement( ref.map().on((data, key, _msg, _ev) => { function add_row(data, key) { if (data != null) { - data["_key"] = key; + data['_key'] = key; rows[key] = data; } else { delete rows[key]; } debounce(debounce_load, render, 200, [rows]); } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - data["_encrypted__"] = wasEncrypted; - add_row(data, key); - }, undefined, undefined); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + data['_encrypted__'] = wasEncrypted; + add_row(data, key); + }, + undefined, + undefined + ); } else { - data["_encrypted__"] = false; + data['_encrypted__'] = false; add_row(data, key); } }); @@ -1254,61 +1287,61 @@ function BuildQR(mid, label) { var PAGES = {}; var PERMS = { - ADMIN: "Administrador", + ADMIN: 'Administrador', }; function checkRole(role) { - var roles = SUB_LOGGED_IN_DETAILS.Roles || ""; - var rolesArr = roles.split(","); - if (rolesArr.includes("ADMIN") || rolesArr.includes(role) || AC_BYPASS) { + var roles = SUB_LOGGED_IN_DETAILS.Roles || ''; + var rolesArr = roles.split(','); + if (rolesArr.includes('ADMIN') || rolesArr.includes(role) || AC_BYPASS) { return true; } else { return false; } } function SetPages() { - document.getElementById("appendApps2").innerHTML = ""; + document.getElementById('appendApps2').innerHTML = ''; Object.keys(PAGES).forEach((key) => { if (PAGES[key].Esconder == true) { return; } if (PAGES[key].AccessControl == true) { - var roles = SUB_LOGGED_IN_DETAILS.Roles || ""; - var rolesArr = roles.split(","); - if (rolesArr.includes("ADMIN") || rolesArr.includes(key) || AC_BYPASS) { + var roles = SUB_LOGGED_IN_DETAILS.Roles || ''; + var rolesArr = roles.split(','); + if (rolesArr.includes('ADMIN') || rolesArr.includes(key) || AC_BYPASS) { } else { return; } } - var a = document.createElement("a"); - var img = document.createElement("img"); - var label = document.createElement("div"); - a.className = "ribbon-button"; - a.href = "#" + key; + var a = document.createElement('a'); + var img = document.createElement('img'); + var label = document.createElement('div'); + a.className = 'ribbon-button'; + a.href = '#' + key; label.innerText = PAGES[key].Title; - label.className = "label"; - img.src = PAGES[key].icon || "static/appico/application_enterprise.png"; + label.className = 'label'; + img.src = PAGES[key].icon || 'static/appico/application_enterprise.png'; a.append(img, label); - document.getElementById("appendApps2").append(a); + document.getElementById('appendApps2').append(a); }); - var a = document.createElement("a"); - var img = document.createElement("img"); - var label = document.createElement("div"); - a.className = "ribbon-button"; - a.href = "#index,qr"; - label.innerText = "Escanear QR"; - label.className = "label"; - img.src = "static/appico/barcode.png"; + var a = document.createElement('a'); + var img = document.createElement('img'); + var label = document.createElement('div'); + a.className = 'ribbon-button'; + a.href = '#index,qr'; + label.innerText = 'Escanear QR'; + label.className = 'label'; + img.src = 'static/appico/barcode.png'; a.append(img, label); - document.getElementById("appendApps2").append(a); + document.getElementById('appendApps2').append(a); } var Booted = false; var TimeoutBoot = 3; // in loops of 750ms var BootLoops = 0; // Get URL host for peer link display -var couchDatabase = localStorage.getItem("TELESEC_COUCH_DBNAME") || "telesec"; -var couchUrl = localStorage.getItem("TELESEC_COUCH_URL") || null; -var couchHost = ""; +var couchDatabase = localStorage.getItem('TELESEC_COUCH_DBNAME') || 'telesec'; +var couchUrl = localStorage.getItem('TELESEC_COUCH_URL') || null; +var couchHost = ''; try { var urlObj = new URL(couchUrl); couchHost = urlObj.host; @@ -1316,28 +1349,29 @@ try { couchHost = couchUrl; } if (couchHost) { - document.getElementById("peerLink").innerText = couchDatabase + "@" + couchHost; + document.getElementById('peerLink').innerText = couchDatabase + '@' + couchHost; } -const statusImg = document.getElementById("connectStatus"); +const statusImg = document.getElementById('connectStatus'); function updateStatusOrb() { const now = Date.now(); - const recentSync = window.TELESEC_LAST_SYNC && (now - window.TELESEC_LAST_SYNC <= 3000); + const recentSync = window.TELESEC_LAST_SYNC && now - window.TELESEC_LAST_SYNC <= 3000; if (recentSync) { if (statusImg) { - const syncColor = window.TELESEC_LAST_SYNC_COLOR || "hsl(200, 70%, 50%)"; + const syncColor = window.TELESEC_LAST_SYNC_COLOR || 'hsl(200, 70%, 50%)'; // Semicircle on the right side - statusImg.src = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEyIDIyYzUuNTIyIDAgMTAtNC40NzggMTAtMTBTMTcuNTIyIDIgMTIgMnMtdjJoLTJWM2gtMnYyaC0ydjJoLTJWM2gtMnYyaC0ydjJoLTJWM2gtMnYyaC0ydi0yaDJWM2gydjJoMnYyaDJ2MmgyVjNoMmwyIDJ2Mmgydi0yaDJ2MmgyVjNoMnYyYzAgNS41MjIgNC40NzggMTAgMTAgMTB6IiBmaWxsPSIjRkZGIi8+PC9zdmc+"; + statusImg.src = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEyIDIyYzUuNTIyIDAgMTAtNC40NzggMTAtMTBTMTcuNTIyIDIgMTIgMnMtdjJoLTJWM2gtMnYyaC0ydjJoLTJWM2gtMnYyaC0ydjJoLTJWM2gtMnYyaC0ydi0yaDJWM2gydjJoMnYyaDJ2MmgyVjNoMmwyIDJ2Mmgydi0yaDJ2MmgyVjNoMnYyYzAgNS41MjIgNC40NzggMTAgMTAgMTB6IiBmaWxsPSIjRkZGIi8+PC9zdmc+'; statusImg.style.backgroundColor = syncColor; - statusImg.style.borderRadius = "50%"; + statusImg.style.borderRadius = '50%'; } return; } if (window.navigator && window.navigator.onLine === false) { if (statusImg) { - statusImg.src = "static/ico/offline.svg"; - statusImg.style.backgroundColor = ""; - statusImg.style.borderRadius = ""; + statusImg.src = 'static/ico/offline.svg'; + statusImg.style.backgroundColor = ''; + statusImg.style.borderRadius = ''; } } else { if (statusImg) { @@ -1358,7 +1392,10 @@ var BootIntervalID = setInterval(() => { // Check if local DB is initialized and responsive const checkLocalDB = () => { if (window.DB && DB._internal && DB._internal.local) { - return DB._internal.local.info().then(() => true).catch(() => false); + return DB._internal.local + .info() + .then(() => true) + .catch(() => false); } return Promise.resolve(false); }; @@ -1367,12 +1404,10 @@ var BootIntervalID = setInterval(() => { // If offline, or DB ready, or we've waited long enough, proceed to boot the UI if ((dbReady || !isOnline || BootLoops >= TimeoutBoot) && !Booted) { Booted = true; - document.getElementById("loading").style.display = "none"; + document.getElementById('loading').style.display = 'none'; if (!isOnline) { - toastr.error( - "Sin conexión! Los cambios se sincronizarán cuando vuelvas a estar en línea." - ); + toastr.error('Sin conexión! Los cambios se sincronizarán cuando vuelvas a estar en línea.'); } if (!SUB_LOGGED_IN) { @@ -1380,67 +1415,80 @@ var BootIntervalID = setInterval(() => { // Auto-create or load a bypass persona and log in automatically const bypassId = localStorage.getItem('TELESEC_BYPASS_ID') || 'bypass-admin'; if (window.DB && DB.get) { - DB.get('personas', bypassId).then((data) => { - function finish(pdata, id) { - SUB_LOGGED_IN_ID = id || bypassId; - SUB_LOGGED_IN_DETAILS = pdata || {}; - SUB_LOGGED_IN = true; - localStorage.setItem('TELESEC_BYPASS_ID', SUB_LOGGED_IN_ID); - SetPages(); - open_page(location.hash.replace("#", "")); - } - if (!data) { - const persona = { Nombre: 'Admin (bypass)', Roles: 'ADMIN,' }; - DB.put('personas', bypassId, persona).then(() => finish(persona, bypassId)).catch((e) => { console.warn('AC_BYPASS create error', e); open_page('login'); }); - } else { - if (typeof data === 'string') { - TS_decrypt(data, SECRET, (pdata) => finish(pdata, bypassId), 'personas', bypassId); - } else { - finish(data, bypassId); + DB.get('personas', bypassId) + .then((data) => { + function finish(pdata, id) { + SUB_LOGGED_IN_ID = id || bypassId; + SUB_LOGGED_IN_DETAILS = pdata || {}; + SUB_LOGGED_IN = true; + localStorage.setItem('TELESEC_BYPASS_ID', SUB_LOGGED_IN_ID); + SetPages(); + open_page(location.hash.replace('#', '')); } - } - }).catch((e) => { - console.warn('AC_BYPASS persona check error', e); - open_page('login'); - }); + if (!data) { + const persona = { Nombre: 'Admin (bypass)', Roles: 'ADMIN,' }; + DB.put('personas', bypassId, persona) + .then(() => finish(persona, bypassId)) + .catch((e) => { + console.warn('AC_BYPASS create error', e); + open_page('login'); + }); + } else { + if (typeof data === 'string') { + TS_decrypt( + data, + SECRET, + (pdata) => finish(pdata, bypassId), + 'personas', + bypassId + ); + } else { + finish(data, bypassId); + } + } + }) + .catch((e) => { + console.warn('AC_BYPASS persona check error', e); + open_page('login'); + }); } else { // DB not ready, fallback to login page open_page('login'); } } else { - open_page("login"); + open_page('login'); } } else { SetPages(); - open_page(location.hash.replace("#", "")); + open_page(location.hash.replace('#', '')); } clearInterval(BootIntervalID); } }); }, 750); -const tabs = document.querySelectorAll(".ribbon-tab"); +const tabs = document.querySelectorAll('.ribbon-tab'); const detailTabs = { - modulos: document.getElementById("tab-modulos"), - buscar: document.getElementById("tab-buscar"), + modulos: document.getElementById('tab-modulos'), + buscar: document.getElementById('tab-buscar'), }; tabs.forEach((tab) => { - tab.addEventListener("click", () => { - const selected = tab.getAttribute("data-tab"); + tab.addEventListener('click', () => { + const selected = tab.getAttribute('data-tab'); // Toggle details for (const [key, detailsEl] of Object.entries(detailTabs)) { if (key === selected) { - detailsEl.setAttribute("open", ""); + detailsEl.setAttribute('open', ''); } else { - detailsEl.removeAttribute("open"); + detailsEl.removeAttribute('open'); } } // Toggle tab active class - tabs.forEach((t) => t.classList.remove("active")); - tab.classList.add("active"); + tabs.forEach((t) => t.classList.remove('active')); + tab.classList.add('active'); }); }); @@ -1449,60 +1497,60 @@ function GlobalSearch() { const searchData = {}; const allSearchableModules = [ { - role: "personas", - key: "personas", - title: "Personas", - icon: "static/appico/File_Person.svg", - fields: ["Nombre", "Region", "Notas", "email"], + role: 'personas', + key: 'personas', + title: 'Personas', + icon: 'static/appico/File_Person.svg', + fields: ['Nombre', 'Region', 'Notas', 'email'], }, { - role: "materiales", - key: "materiales", - title: "Materiales", - icon: "static/appico/Database.svg", - fields: ["Nombre", "Referencia", "Ubicacion", "Notas"], + role: 'materiales', + key: 'materiales', + title: 'Materiales', + icon: 'static/appico/Database.svg', + fields: ['Nombre', 'Referencia', 'Ubicacion', 'Notas'], }, { - role: "supercafe", - key: "supercafe", - title: "SuperCafé", - icon: "static/appico/Coffee.svg", - fields: ["Persona", "Comanda", "Estado"], + role: 'supercafe', + key: 'supercafe', + title: 'SuperCafé', + icon: 'static/appico/Coffee.svg', + fields: ['Persona', 'Comanda', 'Estado'], }, { - role: "comedor", - key: "comedor", - title: "Comedor", - icon: "static/appico/Meal.svg", - fields: ["Fecha", "Platos"], + role: 'comedor', + key: 'comedor', + title: 'Comedor', + icon: 'static/appico/Meal.svg', + fields: ['Fecha', 'Platos'], }, { - role: "notas", - key: "notas", - title: "Notas", - icon: "static/appico/Notepad.svg", - fields: ["Asunto", "Contenido", "Autor"], + role: 'notas', + key: 'notas', + title: 'Notas', + icon: 'static/appico/Notepad.svg', + fields: ['Asunto', 'Contenido', 'Autor'], }, { - role: "notificaciones", - key: "notificaciones", - title: "Avisos", - icon: "static/appico/Alert_Warning.svg", - fields: ["Asunto", "Mensaje", "Origen", "Destino"], + role: 'notificaciones', + key: 'notificaciones', + title: 'Avisos', + icon: 'static/appico/Alert_Warning.svg', + fields: ['Asunto', 'Mensaje', 'Origen', 'Destino'], }, { - role: "aulas", - key: "aulas_solicitudes", - title: "Solicitudes de Aulas", - icon: "static/appico/Classroom.svg", - fields: ["Asunto", "Contenido", "Solicitante"], + role: 'aulas', + key: 'aulas_solicitudes', + title: 'Solicitudes de Aulas', + icon: 'static/appico/Classroom.svg', + fields: ['Asunto', 'Contenido', 'Solicitante'], }, { - role: "aulas", - key: "aulas_informes", - title: "Informes de Aulas", - icon: "static/appico/Newspaper.svg", - fields: ["Asunto", "Contenido", "Autor", "Fecha"], + role: 'aulas', + key: 'aulas_informes', + title: 'Informes de Aulas', + icon: 'static/appico/Newspaper.svg', + fields: ['Asunto', 'Contenido', 'Autor', 'Fecha'], }, ]; @@ -1519,23 +1567,23 @@ function GlobalSearch() { if (!data) return; function processData(processedData) { - if (processedData && typeof processedData === "object") { + if (processedData && typeof processedData === 'object') { searchData[module.key][key] = { _key: key, _module: module.key, _title: module.title, _icon: module.icon, - ...processedData, - }; - } + ...processedData, + }; } + } - if (typeof data === "string") { - TS_decrypt(data, SECRET, processData); - } else { - processData(data); - } - }); + if (typeof data === 'string') { + TS_decrypt(data, SECRET, processData); + } else { + processData(data); + } + }); }); } @@ -1558,7 +1606,7 @@ function GlobalSearch() { // Search in key/ID if (item._key && item._key.toLowerCase().includes(searchLower)) { relevanceScore += 10; - matchedFields.push("ID"); + matchedFields.push('ID'); } // Search in configured fields @@ -1566,15 +1614,15 @@ function GlobalSearch() { const value = item[field]; if (!value) return; - let searchValue = ""; + let searchValue = ''; // Handle special field types - if (field === "Persona" && SC_Personas[value]) { - searchValue = SC_Personas[value].Nombre || ""; - } else if (field === "Comanda" && typeof value === "string") { + if (field === 'Persona' && SC_Personas[value]) { + searchValue = SC_Personas[value].Nombre || ''; + } else if (field === 'Comanda' && typeof value === 'string') { try { const comandaData = JSON.parse(value); - searchValue = Object.values(comandaData).join(" "); + searchValue = Object.values(comandaData).join(' '); } catch (e) { searchValue = value; } @@ -1583,7 +1631,7 @@ function GlobalSearch() { } if (searchValue.toLowerCase().includes(searchLower)) { - relevanceScore += field === "Nombre" || field === "Asunto" ? 5 : 2; + relevanceScore += field === 'Nombre' || field === 'Asunto' ? 5 : 2; matchedFields.push(field); } }); @@ -1604,7 +1652,7 @@ function GlobalSearch() { // Render search results function renderResults(results, container) { if (results.length === 0) { - container.innerHTML = ` + container.innerHTML = html`
    Sin resultados
    🚫 No se encontraron resultados
    @@ -1614,7 +1662,7 @@ function GlobalSearch() { return; } - let html = ""; + let html = ''; // Group by module const groupedResults = {}; @@ -1638,16 +1686,16 @@ function GlobalSearch() { moduleResults.slice(0, 5).forEach((result) => { let title = result.Nombre || result.Asunto || result._key; - let subtitle = ""; + let subtitle = ''; // Handle comedor specific display - if (result._module === "comedor") { + if (result._module === 'comedor') { title = result.Fecha - ? `Menú del ${result.Fecha.split("-").reverse().join("/")}` + ? `Menú del ${result.Fecha.split('-').reverse().join('/')}` : result._key; if (result.Platos) { subtitle = `🍽️ ${result.Platos.substring(0, 50)}${ - result.Platos.length > 50 ? "..." : "" + result.Platos.length > 50 ? '...' : '' }`; } } else { @@ -1656,33 +1704,29 @@ function GlobalSearch() { subtitle = `👤 ${SC_Personas[result.Persona].Nombre}`; } if (result.Fecha) { - const fecha = result.Fecha.split("-").reverse().join("/"); + const fecha = result.Fecha.split('-').reverse().join('/'); subtitle += subtitle ? ` • 📅 ${fecha}` : `📅 ${fecha}`; } if (result.Region) { - subtitle += subtitle - ? ` • 🌍 ${result.Region}` - : `🌍 ${result.Region}`; + subtitle += subtitle ? ` • 🌍 ${result.Region}` : `🌍 ${result.Region}`; } } html += ` - `; }); if (moduleResults.length > 5) { let moreLink = moduleKey; - if (moduleKey === "aulas_solicitudes") { - moreLink = "aulas,solicitudes"; - } else if (moduleKey === "aulas_informes") { - moreLink = "aulas,informes"; + if (moduleKey === 'aulas_solicitudes') { + moreLink = 'aulas,solicitudes'; + } else if (moduleKey === 'aulas_informes') { + moreLink = 'aulas,informes'; } html += ` @@ -1693,7 +1737,7 @@ function GlobalSearch() { `; } - html += "
    "; + html += ''; }); container.innerHTML = html; @@ -1710,13 +1754,13 @@ function GlobalSearch() { // Helper function to navigate to search results function navigateToResult(moduleKey, resultKey) { switch (moduleKey) { - case "aulas_solicitudes": - setUrlHash("aulas,solicitudes," + resultKey); + case 'aulas_solicitudes': + setUrlHash('aulas,solicitudes,' + resultKey); break; - case "aulas_informes": - setUrlHash("aulas,informes," + resultKey); + case 'aulas_informes': + setUrlHash('aulas,informes,' + resultKey); break; default: - setUrlHash(moduleKey + "," + resultKey); + setUrlHash(moduleKey + ',' + resultKey); } } diff --git a/src/config.js b/src/config.js index 5fa019a..80a5c27 100644 --- a/src/config.js +++ b/src/config.js @@ -1,3 +1,7 @@ +// Syntax helper for HTML template literals (e.g. html`
    ${content}
    `) +const html = (strings, ...values) => String.raw({ raw: strings }, ...values); + +// Global Event Listeners registry for cleanup on logout or other events. Each category can be used to track different types of listeners (e.g., GunJS events, timeouts, intervals, QRScanner events, custom events). var EventListeners = { GunJS: [], Timeout: [], @@ -6,60 +10,120 @@ var EventListeners = { Custom: [], }; -function safeuuid(prefix = "AXLUID_") { +// Safe UUID for html element IDs: generates a unique identifier with a specified prefix, ensuring it is safe for use in HTML element IDs. It uses crypto.randomUUID if available, with a fallback to a random string generation method for environments that do not support it. The generated ID is prefixed to avoid collisions and ensure uniqueness across the application. +function safeuuid(prefix = 'AXLUID_') { if (!crypto.randomUUID) { // Fallback for environments without crypto.randomUUID() const randomPart = Math.random().toString(36).substring(2, 10); return prefix + randomPart; } - return prefix + crypto.randomUUID().split("-")[4]; + return prefix + crypto.randomUUID().split('-')[4]; +} + +function parseURL(input) { + try { + return new URL(input); + } catch (e) { + try { + return new URL('https://' + input); + } catch (e2) { + return { hostname: '', username: '', password: '', pathname: '' }; + } + } } var urlParams = new URLSearchParams(location.search); var AC_BYPASS = false; -if (urlParams.get("ac_bypass") == "yes") { +if (urlParams.get('ac_bypass') == 'yes') { AC_BYPASS = true; } -if (urlParams.get("hidenav") != undefined) { - document.getElementById("header_hide_query").style.display = "none"; +if (urlParams.get('hidenav') != undefined) { + document.getElementById('header_hide_query').style.display = 'none'; } +// CouchDB URI generator from components: host, user, pass, dbname. Host can include protocol or not, but will be normalized to just hostname in the display. If host is empty, returns empty string. +function makeCouchURLDisplay(host, user, pass, dbname) { + if (!host) return ''; + var display = user + ':' + pass + '@' + host.replace(/^https?:\/\//, '') + '/' + dbname; + return display; +} +// Auto-configure CouchDB from ?couch= parameter +if (urlParams.get('couch') != null) { + try { + var couchURI = urlParams.get('couch'); + // Normalize URL: add https:// if no protocol specified + var normalizedUrl = couchURI; + if (!/^https?:\/\//i.test(couchURI)) { + normalizedUrl = 'https://' + couchURI; + } + var URL_PARSED = parseURL(normalizedUrl); + var user = URL_PARSED.username || ''; + var pass = URL_PARSED.password || ''; + var dbname = URL_PARSED.pathname ? URL_PARSED.pathname.replace(/^\//, '') : ''; + var host = URL_PARSED.hostname || normalizedUrl; + + // Extract secret from ?secret= parameter if provided + var secret = urlParams.get('secret') || ''; + + // Save to localStorage + localStorage.setItem('TELESEC_COUCH_URL', 'https://' + host); + localStorage.setItem('TELESEC_COUCH_DBNAME', dbname); + localStorage.setItem('TELESEC_COUCH_USER', user); + localStorage.setItem('TELESEC_COUCH_PASS', pass); + if (secret) { + localStorage.setItem('TELESEC_SECRET', secret.toUpperCase()); + } + + // Mark onboarding as complete since we have server config + localStorage.setItem('TELESEC_ONBOARDING_COMPLETE', 'true'); + + // Clean URL by removing the couch parameter + urlParams.delete('couch'); + urlParams.delete('secret'); + history.replaceState( + null, + '', + location.pathname + (urlParams.toString() ? '?' + urlParams.toString() : '') + location.hash + ); + + console.log('CouchDB auto-configured from URL parameter'); + } catch (e) { + console.error('Error auto-configuring CouchDB from URL:', e); + } +} + // getDBName: prefer explicit CouchDB dbname from settings. Single-group model: default to 'telesec' function getDBName() { const dbname = localStorage.getItem('TELESEC_COUCH_DBNAME') || ''; if (dbname && dbname.trim() !== '') return dbname.trim(); return 'telesec'; } -// const PUBLIC_KEY = "~cppGiuA4UFUPGTDoC-4r2izVC3F7MfpaCmF3iZdESN4.vntmjgbAVUpF_zfinYY6EKVFuuTYxh5xOrL4KmtdTmc" -// `TABLE` variable removed. The CouchDB database name should be configured via the login/setup form -// and passed to `DB.init({ dbname: '' })` so it becomes the app's primary DB. -// Legacy relay list removed (migrated to CouchDB/PouchDB) -const RELAYS = []; -var SECRET = ""; +var SECRET = ''; var SUB_LOGGED_IN = false; var SUB_LOGGED_IN_DETAILS = false; var SUB_LOGGED_IN_ID = false; var SAVE_WAIT = 500; var SC_Personas = {}; var PeerConnectionInterval = 5000; -if (urlParams.get("sublogin") != null) { +if (urlParams.get('sublogin') != null) { SUB_LOGGED_IN = true; - SUB_LOGGED_IN_ID = urlParams.get("sublogin"); + SUB_LOGGED_IN_ID = urlParams.get('sublogin'); SUB_LOGGED_IN_DETAILS = SC_Personas[SUB_LOGGED_IN_ID]; var sli = 15; var slii = setInterval(() => { SUB_LOGGED_IN_DETAILS = SC_Personas[SUB_LOGGED_IN_ID]; - sli-=1; + sli -= 1; if (sli < 0) { clearInterval(slii); } }, 500); } +// Logout function for sublogin: clears sublogin state and reloads the page without the sublogin parameter function LogOutTeleSec() { SUB_LOGGED_IN = false; SUB_LOGGED_IN_DETAILS = false; SUB_LOGGED_IN_ID = false; - document.getElementById("loading").style.display = "block"; + document.getElementById('loading').style.display = 'block'; //Remove sublogin from URL and reload - urlParams.delete("sublogin"); - history.replaceState(null, "", "?" + urlParams.toString()); + urlParams.delete('sublogin'); + history.replaceState(null, '', '?' + urlParams.toString()); location.reload(); } diff --git a/src/db.js b/src/db.js index 1a18293..3bba9e2 100644 --- a/src/db.js +++ b/src/db.js @@ -18,9 +18,13 @@ var DB = (function () { const localName = 'telesec'; local = new PouchDB(localName); if (changes) { - try { changes.cancel(); } catch (e) {} + try { + changes.cancel(); + } catch (e) {} } - changes = local.changes({ live: true, since: 'now', include_docs: true }).on('change', onChange); + changes = local + .changes({ live: true, since: 'now', include_docs: true }) + .on('change', onChange); } catch (e) { console.warn('ensureLocal error', e); } @@ -35,7 +39,9 @@ var DB = (function () { try { if (opts && opts.secret) { SECRET = opts.secret; - try { localStorage.setItem('TELESEC_SECRET', SECRET); } catch (e) {} + try { + localStorage.setItem('TELESEC_SECRET', SECRET); + } catch (e) {} } } catch (e) {} local = new PouchDB(localName); @@ -43,7 +49,7 @@ var DB = (function () { if (opts.remoteServer) { try { const server = opts.remoteServer.replace(/\/$/, ''); - const dbname = encodeURIComponent((opts.dbname || localName)); + const dbname = encodeURIComponent(opts.dbname || localName); let authPart = ''; if (opts.username) authPart = opts.username + ':' + (opts.password || '') + '@'; const remoteUrl = server.replace(/https?:\/\//, (m) => m) + '/' + dbname; @@ -56,25 +62,41 @@ var DB = (function () { } if (changes) changes.cancel(); - changes = local.changes({ live: true, since: 'now', include_docs: true }).on('change', onChange); + changes = local + .changes({ live: true, since: 'now', include_docs: true }) + .on('change', onChange); return Promise.resolve(); } function replicateToRemote() { ensureLocal(); if (!local || !remote) return; - try { if (repPush && repPush.cancel) repPush.cancel(); } catch (e) {} - try { if (repPull && repPull.cancel) repPull.cancel(); } catch (e) {} + try { + if (repPush && repPush.cancel) repPush.cancel(); + } catch (e) {} + try { + if (repPull && repPull.cancel) repPull.cancel(); + } catch (e) {} - repPush = PouchDB.replicate(local, remote, { live: true, retry: true }) - .on('error', function (err) { console.warn('Replication push error', err); }); - repPull = PouchDB.replicate(remote, local, { live: true, retry: true }) - .on('error', function (err) { console.warn('Replication pull error', err); }); + repPush = PouchDB.replicate(local, remote, { live: true, retry: true }).on( + 'error', + function (err) { + console.warn('Replication push error', err); + } + ); + repPull = PouchDB.replicate(remote, local, { live: true, retry: true }).on( + 'error', + function (err) { + console.warn('Replication pull error', err); + } + ); } if (typeof window !== 'undefined' && window.addEventListener) { window.addEventListener('online', function () { - try { setTimeout(replicateToRemote, 1000); } catch (e) {} + try { + setTimeout(replicateToRemote, 1000); + } catch (e) {} }); } @@ -86,7 +108,7 @@ var DB = (function () { // derive a stable color from the last record's data hash let payload = ''; try { - payload = (typeof doc.data === 'string') ? doc.data : JSON.stringify(doc.data || {}); + payload = typeof doc.data === 'string' ? doc.data : JSON.stringify(doc.data || {}); } catch (e) { payload = String(doc._id || ''); } @@ -104,8 +126,12 @@ var DB = (function () { if (change.deleted || doc._deleted) { delete docCache[doc._id]; if (callbacks[table]) { - callbacks[table].forEach(cb => { - try { cb(null, id); } catch (e) { console.error(e); } + callbacks[table].forEach((cb) => { + try { + cb(null, id); + } catch (e) { + console.error(e); + } }); } return; @@ -117,11 +143,17 @@ var DB = (function () { const prev = docCache[doc._id]; if (prev === now) return; // no meaningful change docCache[doc._id] = now; - } catch (e) { /* ignore cache errors */ } + } catch (e) { + /* ignore cache errors */ + } if (callbacks[table]) { - callbacks[table].forEach(cb => { - try { cb(doc.data, id); } catch (e) { console.error(e); } + callbacks[table].forEach((cb) => { + try { + cb(doc.data, id); + } catch (e) { + console.error(e); + } }); } } @@ -138,13 +170,25 @@ var DB = (function () { const doc = existing || { _id: _id }; var toStore = data; try { - var isEncryptedString = (typeof data === 'string' && data.startsWith('RSA{') && data.endsWith('}')); - if (!isEncryptedString && typeof TS_encrypt === 'function' && typeof SECRET !== 'undefined' && SECRET) { - toStore = await new Promise(resolve => { - try { TS_encrypt(data, SECRET, enc => resolve(enc)); } catch (e) { resolve(data); } + var isEncryptedString = + typeof data === 'string' && data.startsWith('RSA{') && data.endsWith('}'); + if ( + !isEncryptedString && + typeof TS_encrypt === 'function' && + typeof SECRET !== 'undefined' && + SECRET + ) { + toStore = await new Promise((resolve) => { + try { + TS_encrypt(data, SECRET, (enc) => resolve(enc)); + } catch (e) { + resolve(data); + } }); } - } catch (e) { toStore = data; } + } catch (e) { + toStore = data; + } doc.data = toStore; doc.table = table; doc.ts = new Date().toISOString(); @@ -154,7 +198,6 @@ var DB = (function () { // FIX: manually trigger map() callbacks for local update // onChange will update docCache and notify all subscribers onChange({ doc: doc }); - } catch (e) { console.error('DB.put error', e); } @@ -166,21 +209,33 @@ var DB = (function () { try { const doc = await local.get(_id); return doc.data; - } catch (e) { return null; } + } catch (e) { + return null; + } } - async function del(table, id) { return put(table, id, null); } + async function del(table, id) { + return put(table, id, null); + } async function list(table) { ensureLocal(); try { - const res = await local.allDocs({ include_docs: true, startkey: table + ':', endkey: table + ':\uffff' }); - return res.rows.map(r => { + const res = await local.allDocs({ + include_docs: true, + startkey: table + ':', + endkey: table + ':\uffff', + }); + return res.rows.map((r) => { const id = r.id.split(':')[1]; - try { docCache[r.id] = typeof r.doc.data === 'string' ? r.doc.data : JSON.stringify(r.doc.data); } catch (e) {} + try { + docCache[r.id] = typeof r.doc.data === 'string' ? r.doc.data : JSON.stringify(r.doc.data); + } catch (e) {} return { id: id, data: r.doc.data }; }); - } catch (e) { return []; } + } catch (e) { + return []; + } } function dataURLtoBlob(dataurl) { @@ -203,11 +258,15 @@ var DB = (function () { doc = await local.get(_id); } let blob = dataUrlOrBlob; - if (typeof dataUrlOrBlob === 'string' && dataUrlOrBlob.indexOf('data:') === 0) blob = dataURLtoBlob(dataUrlOrBlob); + if (typeof dataUrlOrBlob === 'string' && dataUrlOrBlob.indexOf('data:') === 0) + blob = dataURLtoBlob(dataUrlOrBlob); const type = contentType || (blob && blob.type) || 'application/octet-stream'; await local.putAttachment(_id, name, doc._rev, blob, type); return true; - } catch (e) { console.error('putAttachment error', e); return false; } + } catch (e) { + console.error('putAttachment error', e); + return false; + } } async function getAttachment(table, id, name) { @@ -218,11 +277,13 @@ var DB = (function () { if (!blob) return null; return await new Promise((resolve, reject) => { const reader = new FileReader(); - reader.onload = e => resolve(e.target.result); - reader.onerror = e => reject(e); + reader.onload = (e) => resolve(e.target.result); + reader.onerror = (e) => reject(e); reader.readAsDataURL(blob); }); - } catch (e) { return null; } + } catch (e) { + return null; + } } async function listAttachments(table, id) { @@ -259,10 +320,14 @@ var DB = (function () { try { const durl = await getAttachment(table, id, name); out.push({ name: name, dataUrl: durl, content_type: null }); - } catch (e) { out.push({ name: name, dataUrl: null, content_type: null }); } + } catch (e) { + out.push({ name: name, dataUrl: null, content_type: null }); + } } return out; - } catch (e2) { return []; } + } catch (e2) { + return []; + } } } @@ -275,15 +340,20 @@ var DB = (function () { delete doc._attachments[name]; await local.put(doc); return true; - } catch (e) { console.error('deleteAttachment error', e); return false; } + } catch (e) { + console.error('deleteAttachment error', e); + return false; + } } function map(table, cb) { ensureLocal(); callbacks[table] = callbacks[table] || []; callbacks[table].push(cb); - list(table).then(rows => rows.forEach(r => cb(r.data, r.id))); - return () => { callbacks[table] = callbacks[table].filter(x => x !== cb); } + list(table).then((rows) => rows.forEach((r) => cb(r.data, r.id))); + return () => { + callbacks[table] = callbacks[table].filter((x) => x !== cb); + }; } return { @@ -298,7 +368,7 @@ var DB = (function () { deleteAttachment, putAttachment, getAttachment, - _internal: { local } + _internal: { local }, }; })(); @@ -311,7 +381,15 @@ window.DB = DB; const username = localStorage.getItem('TELESEC_COUCH_USER') || ''; const password = localStorage.getItem('TELESEC_COUCH_PASS') || ''; const dbname = localStorage.getItem('TELESEC_COUCH_DBNAME') || undefined; - try { SECRET = localStorage.getItem('TELESEC_SECRET') || ''; } catch (e) { SECRET = ''; } - DB.init({ remoteServer, username, password, dbname }).catch(e => console.warn('DB.autoInit error', e)); - } catch (e) { console.warn('DB.autoInit unexpected error', e); } + try { + SECRET = localStorage.getItem('TELESEC_SECRET') || ''; + } catch (e) { + SECRET = ''; + } + DB.init({ remoteServer, username, password, dbname }).catch((e) => + console.warn('DB.autoInit error', e) + ); + } catch (e) { + console.warn('DB.autoInit unexpected error', e); + } })(); diff --git a/src/gun_init.js b/src/gun_init.js index 499bc92..8234a43 100644 --- a/src/gun_init.js +++ b/src/gun_init.js @@ -1,4 +1,4 @@ // gun_init.js - Deprecated // Gun/GunDB has been replaced by PouchDB/CouchDB in this project. // This file is kept for reference only and should not be used. -console.warn('gun_init.js is deprecated; using PouchDB/DB module instead.'); \ No newline at end of file +console.warn('gun_init.js is deprecated; using PouchDB/DB module instead.'); diff --git a/src/index.html b/src/index.html index c67992e..913b5f8 100644 --- a/src/index.html +++ b/src/index.html @@ -68,11 +68,8 @@ - - - diff --git a/src/page/aulas.js b/src/page/aulas.js index 7084300..ca2e53c 100644 --- a/src/page/aulas.js +++ b/src/page/aulas.js @@ -1,63 +1,105 @@ -PERMS["aulas"] = "Aulas (Solo docentes!)"; +PERMS['aulas'] = 'Aulas (Solo docentes!)'; PAGES.aulas = { //navcss: "btn1", - Title: "Gest-Aula", - icon: "static/appico/components.png", + Title: 'Gest-Aula', + icon: 'static/appico/components.png', AccessControl: true, index: function () { - if (!checkRole("aulas")) { - setUrlHash("index"); + if (!checkRole('aulas')) { + setUrlHash('index'); return; } var data_Comedor = safeuuid(); var data_Tareas = safeuuid(); var data_Diario = safeuuid(); var data_Weather = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

    Gestión del Aula

    - Notas esenciales - Como iniciar el día - Como realizar el café - Como acabar el día - Horario - Tareas + Notas esenciales + Como iniciar el día + Como realizar el café + Como acabar el día + Horario + Tareas
    - Acciones - Solicitudes de material - Diario de hoy - Ver Alertas - Informes y diarios - Ver comandas + Acciones + Solicitudes de material + Diario de hoy + Ver Alertas + Informes y diarios + Ver comandas
    - Datos de hoy - - Menú Comedor:
    Cargando...
    - Tareas:
    Cargando...
    - Diario:
    Cargando...
    - Clima:
    + Datos de hoy + + Menú Comedor:
    Cargando...
    + Tareas:
    +
    +Cargando...
    +
    + Diario:
    +
    +Cargando...
    +
    + Clima:
    - `; - + `; //#region Cargar Clima // Get location from DB settings.weather_location; if missing ask user and save it // url format: https://wttr.in/?F0m - DB.get('settings','weather_location').then((loc) => { + DB.get('settings', 'weather_location').then((loc) => { if (!loc) { - loc = prompt("Introduce tu ubicación para el clima (ciudad, país):", "Madrid, Spain"); + loc = prompt('Introduce tu ubicación para el clima (ciudad, país):', 'Madrid, Spain'); if (loc) { - DB.put('settings','weather_location', loc); + DB.put('settings', 'weather_location', loc); } } if (loc) { - document.getElementById(data_Weather).src = "https://wttr.in/" + encodeURIComponent(loc) + "_IF0m_background=FFFFFF.png"; + document.getElementById(data_Weather).src = + 'https://wttr.in/' + encodeURIComponent(loc) + '_IF0m_background=FFFFFF.png'; } else { - document.getElementById(data_Weather).src = "https://wttr.in/_IF0m_background=FFFFFF.png"; + document.getElementById(data_Weather).src = 'https://wttr.in/_IF0m_background=FFFFFF.png'; } }); //#endregion Cargar Clima @@ -65,17 +107,20 @@ PAGES.aulas = { DB.get('comedor', CurrentISODate()).then((data) => { function add_row(data) { // Fix newlines - data.Platos = data.Platos || "No hay platos registrados para hoy."; + data.Platos = data.Platos || 'No hay platos registrados para hoy.'; // Display platos - document.getElementById(data_Comedor).innerHTML = data.Platos.replace( - /\n/g, - "
    " - ); + document.getElementById(data_Comedor).innerHTML = data.Platos.replace(/\n/g, '
    '); } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - add_row(data || {}); - }, 'comedor', CurrentISODate()); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + add_row(data || {}); + }, + 'comedor', + CurrentISODate() + ); } else { add_row(data || {}); } @@ -85,17 +130,20 @@ PAGES.aulas = { DB.get('notas', 'tareas').then((data) => { function add_row(data) { // Fix newlines - data.Contenido = data.Contenido || "No hay tareas."; + data.Contenido = data.Contenido || 'No hay tareas.'; // Display platos - document.getElementById(data_Tareas).innerHTML = data.Contenido.replace( - /\n/g, - "
    " - ); + document.getElementById(data_Tareas).innerHTML = data.Contenido.replace(/\n/g, '
    '); } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - add_row(data || {}); - }, 'notas', 'tareas'); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + add_row(data || {}); + }, + 'notas', + 'tareas' + ); } else { add_row(data || {}); } @@ -105,17 +153,20 @@ PAGES.aulas = { DB.get('aulas_informes', 'diario-' + CurrentISODate()).then((data) => { function add_row(data) { // Fix newlines - data.Contenido = data.Contenido || "No hay un diario."; + data.Contenido = data.Contenido || 'No hay un diario.'; // Display platos - document.getElementById(data_Diario).innerHTML = data.Contenido.replace( - /\n/g, - "
    " - ); + document.getElementById(data_Diario).innerHTML = data.Contenido.replace(/\n/g, '
    '); } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - add_row(data || {}); - }, 'aulas_informes', 'diario-' + CurrentISODate()); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + add_row(data || {}); + }, + 'aulas_informes', + 'diario-' + CurrentISODate() + ); } else { add_row(data || {}); } @@ -125,33 +176,33 @@ PAGES.aulas = { _solicitudes: function () { const tablebody = safeuuid(); var btn_new = safeuuid(); - container.innerHTML = ` + container.innerHTML = html` ← Volver a Gestión de Aulas

    Solicitudes de Material

    - `; + `; TS_IndexElement( - "aulas,solicitudes", + 'aulas,solicitudes', [ { - key: "Solicitante", - type: "persona", - default: "", - label: "Solicitante", + key: 'Solicitante', + type: 'persona', + default: '', + label: 'Solicitante', }, { - key: "Asunto", - type: "raw", - default: "", - label: "Asunto", + key: 'Asunto', + type: 'raw', + default: '', + label: 'Asunto', }, ], - "aulas_solicitudes", - document.querySelector("#cont") + 'aulas_solicitudes', + document.querySelector('#cont') ); document.getElementById(btn_new).onclick = () => { - setUrlHash("aulas,solicitudes," + safeuuid("")); + setUrlHash('aulas,solicitudes,' + safeuuid('')); }; }, _solicitudes__edit: function (mid) { @@ -161,39 +212,46 @@ PAGES.aulas = { var field_autor = safeuuid(); var btn_guardar = safeuuid(); var btn_borrar = safeuuid(); - container.innerHTML = ` - ← Volver a solicitudes -

    Solicitud

    -
    - Valores -
    - - -
    - -
    - - -
    - `; + container.innerHTML = html` + ← Volver a solicitudes +

    Solicitud

    +
    + Valores +
    + + +
    + +
    + + +
    + `; (async () => { const data = await DB.get('aulas_solicitudes', mid); - function load_data(data, ENC = "") { + function load_data(data, ENC = '') { document.getElementById(nameh1).innerText = mid; - document.getElementById(field_asunto).value = data["Asunto"] || ""; - document.getElementById(field_contenido).value = data["Contenido"] || ""; - document.getElementById(field_autor).value = data["Solicitante"] || SUB_LOGGED_IN_ID || ""; + document.getElementById(field_asunto).value = data['Asunto'] || ''; + document.getElementById(field_contenido).value = data['Contenido'] || ''; + document.getElementById(field_autor).value = data['Solicitante'] || SUB_LOGGED_IN_ID || ''; } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - load_data(data, "%E"); - }, 'aulas_solicitudes', mid); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + load_data(data, '%E'); + }, + 'aulas_solicitudes', + mid + ); } else { load_data(data || {}); } @@ -202,36 +260,38 @@ PAGES.aulas = { // Disable button to prevent double-clicking var guardarBtn = document.getElementById(btn_guardar); if (guardarBtn.disabled) return; - + guardarBtn.disabled = true; - guardarBtn.style.opacity = "0.5"; - + guardarBtn.style.opacity = '0.5'; + var data = { Solicitante: document.getElementById(field_autor).value, Contenido: document.getElementById(field_contenido).value, Asunto: document.getElementById(field_asunto).value, }; - document.getElementById("actionStatus").style.display = "block"; - DB.put('aulas_solicitudes', mid, data).then(() => { - toastr.success("Guardado!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("aulas,solicitudes"); - }, SAVE_WAIT); - }).catch((e) => { - console.warn('DB.put error', e); - guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - document.getElementById("actionStatus").style.display = "none"; - toastr.error("Error al guardar la solicitud"); - }); + document.getElementById('actionStatus').style.display = 'block'; + DB.put('aulas_solicitudes', mid, data) + .then(() => { + toastr.success('Guardado!'); + setTimeout(() => { + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('aulas,solicitudes'); + }, SAVE_WAIT); + }) + .catch((e) => { + console.warn('DB.put error', e); + guardarBtn.disabled = false; + guardarBtn.style.opacity = '1'; + document.getElementById('actionStatus').style.display = 'none'; + toastr.error('Error al guardar la solicitud'); + }); }; document.getElementById(btn_borrar).onclick = () => { - if (confirm("¿Quieres borrar esta solicitud?") == true) { + if (confirm('¿Quieres borrar esta solicitud?') == true) { DB.del('aulas_solicitudes', mid).then(() => { - toastr.error("Borrado!"); + toastr.error('Borrado!'); setTimeout(() => { - setUrlHash("aulas,solicitudes"); + setUrlHash('aulas,solicitudes'); }, SAVE_WAIT); }); } @@ -242,53 +302,56 @@ PAGES.aulas = { var btn_new = safeuuid(); var field_new_byday = safeuuid(); var btn_new_byday = safeuuid(); - container.innerHTML = ` + container.innerHTML = html` ← Volver a Gestión de Aulas

    Informes

    -
    - Diario:
    - - -

    +
    + Diario:
    + + +
    +
    - `; + `; TS_IndexElement( - "aulas,informes", + 'aulas,informes', [ { - key: "Autor", - type: "persona", - default: "", - label: "Autor", + key: 'Autor', + type: 'persona', + default: '', + label: 'Autor', }, { - key: "Fecha", - type: "fecha", - default: "", - label: "Fecha", + key: 'Fecha', + type: 'fecha', + default: '', + label: 'Fecha', }, { - key: "Asunto", - type: "raw", - default: "", - label: "Asunto", + key: 'Asunto', + type: 'raw', + default: '', + label: 'Asunto', }, ], - "aulas_informes", - document.querySelector("#cont") + 'aulas_informes', + document.querySelector('#cont') ); document.getElementById(btn_new).onclick = () => { - setUrlHash("aulas,informes," + safeuuid("")); + setUrlHash('aulas,informes,' + safeuuid('')); }; document.getElementById(btn_new_byday).onclick = () => { const day = document.getElementById(field_new_byday).value; if (day) { - setUrlHash("aulas,informes,diario-" + day); + setUrlHash('aulas,informes,diario-' + day); } else { - toastr.error("Selecciona un día válido"); + toastr.error('Selecciona un día válido'); } - } + }; }, _informes__edit: function (mid) { var nameh1 = safeuuid(); @@ -298,45 +361,49 @@ PAGES.aulas = { var field_fecha = safeuuid(); var btn_guardar = safeuuid(); var btn_borrar = safeuuid(); - var title = ""; - if (mid.startsWith("diario-")) { - var date = mid.replace("diario-", "").split("-"); - title = "Diario " + date[2] + "/" + date[1] + "/" + date[0]; + var title = ''; + if (mid.startsWith('diario-')) { + var date = mid.replace('diario-', '').split('-'); + title = 'Diario ' + date[2] + '/' + date[1] + '/' + date[0]; } - container.innerHTML = ` + container.innerHTML = html` ← Volver a informes

    Informe

    - Valores -
    - - - -
    + Valores +
    -
    - - + + +
    + +
    + +
    - `; + `; (async () => { const data = await DB.get('aulas_informes', mid); - function load_data(data, ENC = "") { + function load_data(data, ENC = '') { document.getElementById(nameh1).innerText = mid; - document.getElementById(field_asunto).value = data["Asunto"] || title || ""; - document.getElementById(field_contenido).value = data["Contenido"] || ""; - document.getElementById(field_autor).value = data["Autor"] || SUB_LOGGED_IN_ID || ""; - document.getElementById(field_fecha).value = data["Fecha"] || mid.startsWith("diario-") ? mid.replace("diario-", "") : CurrentISODate(); + document.getElementById(field_asunto).value = data['Asunto'] || title || ''; + document.getElementById(field_contenido).value = data['Contenido'] || ''; + document.getElementById(field_autor).value = data['Autor'] || SUB_LOGGED_IN_ID || ''; + document.getElementById(field_fecha).value = + data['Fecha'] || mid.startsWith('diario-') + ? mid.replace('diario-', '') + : CurrentISODate(); } - if (typeof data == "string") { + if (typeof data == 'string') { TS_decrypt(data, SECRET, (data) => { - load_data(data, "%E"); + load_data(data, '%E'); }); } else { load_data(data || {}); @@ -346,55 +413,57 @@ PAGES.aulas = { // Disable button to prevent double-clicking var guardarBtn = document.getElementById(btn_guardar); if (guardarBtn.disabled) return; - + guardarBtn.disabled = true; - guardarBtn.style.opacity = "0.5"; - + guardarBtn.style.opacity = '0.5'; + var data = { Autor: document.getElementById(field_autor).value, Contenido: document.getElementById(field_contenido).value, Asunto: document.getElementById(field_asunto).value, Fecha: document.getElementById(field_fecha).value || CurrentISODate(), }; - document.getElementById("actionStatus").style.display = "block"; - DB.put('aulas_informes', mid, data).then(() => { - toastr.success("Guardado!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("aulas,informes"); - }, SAVE_WAIT); - }).catch((e) => { - console.warn('DB.put error', e); - guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - document.getElementById("actionStatus").style.display = "none"; - toastr.error("Error al guardar el informe"); - }); + document.getElementById('actionStatus').style.display = 'block'; + DB.put('aulas_informes', mid, data) + .then(() => { + toastr.success('Guardado!'); + setTimeout(() => { + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('aulas,informes'); + }, SAVE_WAIT); + }) + .catch((e) => { + console.warn('DB.put error', e); + guardarBtn.disabled = false; + guardarBtn.style.opacity = '1'; + document.getElementById('actionStatus').style.display = 'none'; + toastr.error('Error al guardar el informe'); + }); }; document.getElementById(btn_borrar).onclick = () => { - if (confirm("¿Quieres borrar este informe?") == true) { + if (confirm('¿Quieres borrar este informe?') == true) { DB.del('aulas_informes', mid).then(() => { - toastr.error("Borrado!"); + toastr.error('Borrado!'); setTimeout(() => { - setUrlHash("aulas,informes"); + setUrlHash('aulas,informes'); }, SAVE_WAIT); }); } }; }, edit: function (section) { - if (!checkRole("aulas")) { - setUrlHash("index"); + if (!checkRole('aulas')) { + setUrlHash('index'); return; } - var item = location.hash.replace("#", "").split(",")[2]; + var item = location.hash.replace('#', '').split(',')[2]; if (!item) { // No item, show section switch (section) { - case "solicitudes": + case 'solicitudes': this._solicitudes(); break; - case "informes": + case 'informes': this._informes(); break; default: @@ -404,10 +473,10 @@ PAGES.aulas = { } else { // Show section__edit switch (section) { - case "solicitudes": + case 'solicitudes': this._solicitudes__edit(item); break; - case "informes": + case 'informes': this._informes__edit(item); break; } diff --git a/src/page/avisos.js b/src/page/avisos.js index 290bce9..f18008b 100644 --- a/src/page/avisos.js +++ b/src/page/avisos.js @@ -1,234 +1,247 @@ -PERMS["avisos"] = "Avisos" -PERMS["avisos:edit"] = "> Editar" +PERMS['avisos'] = 'Avisos'; +PERMS['avisos:edit'] = '> Editar'; PAGES.avisos = { - navcss: "btn5", - icon: "static/appico/File_Plugin.svg", - AccessControl: true, - Title: "Avisos", - edit: function (mid) { - if (!checkRole("avisos:edit")) {setUrlHash("avisos");return} - var nameh1 = safeuuid(); - var field_fecha = safeuuid(); - var field_asunto = safeuuid(); - var field_origen = safeuuid(); - var field_destino = safeuuid(); - var field_estado = safeuuid(); - var field_mensaje = safeuuid(); - var field_respuesta = safeuuid(); - var btn_leer = safeuuid(); - var btn_desleer = safeuuid(); - var btn_guardar = safeuuid(); - var btn_borrar = safeuuid(); - var div_actions = safeuuid(); - container.innerHTML = ` -

    Aviso

    -
    - Valores - - - - -
    - - -
    - - -
    - `; - document.getElementById(btn_leer).onclick = () => { - document.getElementById(field_estado).value = "leido"; - }; - document.getElementById(btn_desleer).onclick = () => { - document.getElementById(field_estado).value = "por_leer"; - }; - var divact = document.getElementById(div_actions); - addCategory_Personas( - divact, - SC_Personas, - "", - (value) => { - document.getElementById(field_origen).value = value; - }, - "Origen" - ); - addCategory_Personas( - divact, - SC_Personas, - "", - (value) => { - document.getElementById(field_destino).value = value; - }, - "Destino" - ); - (async () => { - const data = await DB.get('notificaciones', mid); - function load_data(data, ENC = "") { - document.getElementById(nameh1).innerText = mid; - document.getElementById(field_fecha).value = data["Fecha"] || CurrentISODate() || ""; - document.getElementById(field_asunto).value = data["Asunto"] || ""; - document.getElementById(field_mensaje).value = data["Mensaje"] || ""; - document.getElementById(field_origen).value = data["Origen"] || SUB_LOGGED_IN_ID || ""; - document.getElementById(field_destino).value = data["Destino"] || ""; - document.getElementById(field_estado).value = data["Estado"] || "%%" || ""; - document.getElementById(field_respuesta).value = data["Respuesta"] || ""; + navcss: 'btn5', + icon: 'static/appico/File_Plugin.svg', + AccessControl: true, + Title: 'Avisos', + edit: function (mid) { + if (!checkRole('avisos:edit')) { + setUrlHash('avisos'); + return; + } + var nameh1 = safeuuid(); + var field_fecha = safeuuid(); + var field_asunto = safeuuid(); + var field_origen = safeuuid(); + var field_destino = safeuuid(); + var field_estado = safeuuid(); + var field_mensaje = safeuuid(); + var field_respuesta = safeuuid(); + var btn_leer = safeuuid(); + var btn_desleer = safeuuid(); + var btn_guardar = safeuuid(); + var btn_borrar = safeuuid(); + var div_actions = safeuuid(); + container.innerHTML = html` +

    Aviso

    +
    + Valores + + + + +
    + + + +
    + + +
    + `; + document.getElementById(btn_leer).onclick = () => { + document.getElementById(field_estado).value = 'leido'; + }; + document.getElementById(btn_desleer).onclick = () => { + document.getElementById(field_estado).value = 'por_leer'; + }; + var divact = document.getElementById(div_actions); + addCategory_Personas( + divact, + SC_Personas, + '', + (value) => { + document.getElementById(field_origen).value = value; + }, + 'Origen' + ); + addCategory_Personas( + divact, + SC_Personas, + '', + (value) => { + document.getElementById(field_destino).value = value; + }, + 'Destino' + ); + (async () => { + const data = await DB.get('notificaciones', mid); + function load_data(data, ENC = '') { + document.getElementById(nameh1).innerText = mid; + document.getElementById(field_fecha).value = data['Fecha'] || CurrentISODate() || ''; + document.getElementById(field_asunto).value = data['Asunto'] || ''; + document.getElementById(field_mensaje).value = data['Mensaje'] || ''; + document.getElementById(field_origen).value = data['Origen'] || SUB_LOGGED_IN_ID || ''; + document.getElementById(field_destino).value = data['Destino'] || ''; + document.getElementById(field_estado).value = data['Estado'] || '%%' || ''; + document.getElementById(field_respuesta).value = data['Respuesta'] || ''; - // Persona select - divact.innerHTML = ""; - addCategory_Personas( - divact, - SC_Personas, - data["Origen"] || "", - (value) => { - document.getElementById(field_origen).value = value; - }, - "Origen" - ); - addCategory_Personas( - divact, - SC_Personas, - data["Destino"] || "", - (value) => { - document.getElementById(field_destino).value = value; - }, - "Destino" - ); - } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - load_data(data, "%E"); - }, 'notificaciones', mid); - } else { - load_data(data || {}); - } - })(); - document.getElementById(btn_guardar).onclick = () => { - // Check if button is already disabled to prevent double-clicking - var guardarBtn = document.getElementById(btn_guardar); - if (guardarBtn.disabled) return; - - // Validate before disabling button - if (document.getElementById(field_origen).value == "") { - alert("¡Hay que elegir una persona de origen!"); - return; - } - if (document.getElementById(field_destino).value == "") { - alert("¡Hay que elegir una persona de origen!"); - return; - } - - // Disable button after validation passes - guardarBtn.disabled = true; - guardarBtn.style.opacity = "0.5"; - - var data = { - Fecha: document.getElementById(field_fecha).value, - Origen: document.getElementById(field_origen).value, - Destino: document.getElementById(field_destino).value, - Mensaje: document.getElementById(field_mensaje).value, - Respuesta: document.getElementById(field_respuesta).value, - Asunto: document.getElementById(field_asunto).value, - Estado: document - .getElementById(field_estado) - .value.replace("%%", "por_leer"), - }; - document.getElementById("actionStatus").style.display = "block"; - DB.put('notificaciones', mid, data).then(() => { - toastr.success("Guardado!"); + // Persona select + divact.innerHTML = ''; + addCategory_Personas( + divact, + SC_Personas, + data['Origen'] || '', + (value) => { + document.getElementById(field_origen).value = value; + }, + 'Origen' + ); + addCategory_Personas( + divact, + SC_Personas, + data['Destino'] || '', + (value) => { + document.getElementById(field_destino).value = value; + }, + 'Destino' + ); + } + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + load_data(data, '%E'); + }, + 'notificaciones', + mid + ); + } else { + load_data(data || {}); + } + })(); + document.getElementById(btn_guardar).onclick = () => { + // Check if button is already disabled to prevent double-clicking + var guardarBtn = document.getElementById(btn_guardar); + if (guardarBtn.disabled) return; + + // Validate before disabling button + if (document.getElementById(field_origen).value == '') { + alert('¡Hay que elegir una persona de origen!'); + return; + } + if (document.getElementById(field_destino).value == '') { + alert('¡Hay que elegir una persona de origen!'); + return; + } + + // Disable button after validation passes + guardarBtn.disabled = true; + guardarBtn.style.opacity = '0.5'; + + var data = { + Fecha: document.getElementById(field_fecha).value, + Origen: document.getElementById(field_origen).value, + Destino: document.getElementById(field_destino).value, + Mensaje: document.getElementById(field_mensaje).value, + Respuesta: document.getElementById(field_respuesta).value, + Asunto: document.getElementById(field_asunto).value, + Estado: document.getElementById(field_estado).value.replace('%%', 'por_leer'), + }; + document.getElementById('actionStatus').style.display = 'block'; + DB.put('notificaciones', mid, data) + .then(() => { + toastr.success('Guardado!'); setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("avisos"); + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('avisos'); }, SAVE_WAIT); - }).catch((e) => { + }) + .catch((e) => { console.warn('DB.put error', e); guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - document.getElementById("actionStatus").style.display = "none"; - toastr.error("Error al guardar la notificación"); + guardarBtn.style.opacity = '1'; + document.getElementById('actionStatus').style.display = 'none'; + toastr.error('Error al guardar la notificación'); + }); + }; + document.getElementById(btn_borrar).onclick = () => { + if (confirm('¿Quieres borrar esta notificación?') == true) { + DB.del('notificaciones', mid).then(() => { + toastr.error('Borrado!'); + setTimeout(() => { + setUrlHash('avisos'); + }, SAVE_WAIT); }); - }; - document.getElementById(btn_borrar).onclick = () => { - if (confirm("¿Quieres borrar esta notificación?") == true) { - DB.del('notificaciones', mid).then(() => { - toastr.error("Borrado!"); - setTimeout(() => { - setUrlHash("avisos"); - }, SAVE_WAIT); - }); - } - }; - }, - index: function () { - if (!checkRole("avisos")) {setUrlHash("index");return} - const tablebody = safeuuid(); - var btn_new = safeuuid(); - container.innerHTML = ` -

    Avisos

    - -
    - `; - TS_IndexElement( - "avisos", - [ - { - key: "Origen", - type: "persona", - default: "", - label: "Origen", - }, - { - key: "Destino", - type: "persona", - default: "", - label: "Destino", - }, - { - key: "Asunto", - type: "raw", - default: "", - label: "Asunto", - }, - { - key: "Estado", - type: "raw", - default: "", - label: "Estado", - }, - ], - "notificaciones", - document.querySelector("#cont"), - (data, new_tr) => { - new_tr.style.backgroundColor = "#FFCCCB"; - if (data.Estado == "leido") { - new_tr.style.backgroundColor = "lightgreen"; - } - } - ); - if (!checkRole("avisos:edit")) { - document.getElementById(btn_new).style.display = "none" - } else { - document.getElementById(btn_new).onclick = () => { - setUrlHash("avisos," + safeuuid("")); - }; } - }, - } \ No newline at end of file + }; + }, + index: function () { + if (!checkRole('avisos')) { + setUrlHash('index'); + return; + } + const tablebody = safeuuid(); + var btn_new = safeuuid(); + container.innerHTML = html` +

    Avisos

    + +
    + `; + TS_IndexElement( + 'avisos', + [ + { + key: 'Origen', + type: 'persona', + default: '', + label: 'Origen', + }, + { + key: 'Destino', + type: 'persona', + default: '', + label: 'Destino', + }, + { + key: 'Asunto', + type: 'raw', + default: '', + label: 'Asunto', + }, + { + key: 'Estado', + type: 'raw', + default: '', + label: 'Estado', + }, + ], + 'notificaciones', + document.querySelector('#cont'), + (data, new_tr) => { + new_tr.style.backgroundColor = '#FFCCCB'; + if (data.Estado == 'leido') { + new_tr.style.backgroundColor = 'lightgreen'; + } + } + ); + if (!checkRole('avisos:edit')) { + document.getElementById(btn_new).style.display = 'none'; + } else { + document.getElementById(btn_new).onclick = () => { + setUrlHash('avisos,' + safeuuid('')); + }; + } + }, +}; diff --git a/src/page/buscar.js b/src/page/buscar.js index 1466356..b13fc93 100644 --- a/src/page/buscar.js +++ b/src/page/buscar.js @@ -1,7 +1,7 @@ PAGES.buscar = { - navcss: "btn1", - icon: "static/appico/view.svg", - Title: "Buscar", + navcss: 'btn1', + icon: 'static/appico/view.svg', + Title: 'Buscar', AccessControl: true, Esconder: true, @@ -12,32 +12,33 @@ PAGES.buscar = { const recentSearches = safeuuid(); const moduleFilter = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

    🔍 Búsqueda Global

    Busca en todos los módulos: personas, materiales, café, comedor, notas y avisos

    - +
    Opciones de búsqueda - + - +
    - +
    - +
    Resultados
    🔍 Introduce un término de búsqueda para comenzar

    Puedes buscar por nombres, referencias, fechas, ubicaciones...

    - +
    💡 Consejos de búsqueda
      @@ -72,7 +73,7 @@ PAGES.buscar = { // Add only accessible modules accessibleModules.forEach((module) => { - const option = document.createElement("option"); + const option = document.createElement('option'); option.value = module.key; option.textContent = `${getModuleIcon(module.key)} ${module.title}`; moduleFilterEl.appendChild(option); @@ -82,24 +83,22 @@ PAGES.buscar = { // Helper function to get module icons (fallback for older module mappings) function getModuleIcon(moduleKey) { const iconMap = { - personas: "👤", - materiales: "📦", - supercafe: "☕", - comedor: "🍽️", - avisos: "🔔", - aulas: "🏫", - resumen_diario: "📊", + personas: '👤', + materiales: '📦', + supercafe: '☕', + comedor: '🍽️', + avisos: '🔔', + aulas: '🏫', + resumen_diario: '📊', }; - return iconMap[moduleKey] || "📋"; + return iconMap[moduleKey] || '📋'; } // Load recent searches from localStorage function loadRecentSearches() { - const recent = JSON.parse( - localStorage.getItem("telesec_recent_searches") || "[]" - ); + const recent = JSON.parse(localStorage.getItem('telesec_recent_searches') || '[]'); if (recent.length > 0) { - recentSearchesEl.innerHTML = ` + recentSearchesEl.innerHTML = html`
      Búsquedas recientes ${recent @@ -110,8 +109,11 @@ PAGES.buscar = { ` ) - .join("")} -
      @@ -126,14 +128,12 @@ PAGES.buscar = { function saveToRecent(term) { if (!term || term.length < 2) return; - let recent = JSON.parse( - localStorage.getItem("telesec_recent_searches") || "[]" - ); + let recent = JSON.parse(localStorage.getItem('telesec_recent_searches') || '[]'); recent = recent.filter((t) => t !== term); // Remove if exists recent.unshift(term); // Add to beginning recent = recent.slice(0, 5); // Keep only 5 most recent - localStorage.setItem("telesec_recent_searches", JSON.stringify(recent)); + localStorage.setItem('telesec_recent_searches', JSON.stringify(recent)); loadRecentSearches(); } @@ -143,7 +143,7 @@ PAGES.buscar = { const selectedModule = moduleFilterEl.value; if (searchTerm.length < 2) { - resultsEl.innerHTML = ` + resultsEl.innerHTML = html`
      Error
      ⚠️ Por favor, introduce al menos 2 caracteres para buscar
      @@ -153,7 +153,7 @@ PAGES.buscar = { } // Show loading - resultsEl.innerHTML = ` + resultsEl.innerHTML = html`
      Buscando...
      ⏳ Procesando búsqueda...
      @@ -166,9 +166,7 @@ PAGES.buscar = { // Filter by module if selected if (selectedModule) { - results = results.filter( - (result) => result._module === selectedModule - ); + results = results.filter((result) => result._module === selectedModule); } globalSearch.renderResults(results, resultsEl); @@ -176,16 +174,17 @@ PAGES.buscar = { // Add stats if (results.length > 0) { - const statsDiv = document.createElement("fieldset"); - const legend = document.createElement("legend"); - legend.textContent = "Estadísticas"; + const statsDiv = document.createElement('fieldset'); + const legend = document.createElement('legend'); + legend.textContent = 'Estadísticas'; statsDiv.appendChild(legend); let filterText = selectedModule ? ` en ${moduleFilterEl.options[moduleFilterEl.selectedIndex].text}` - : ""; - const content = document.createElement("div"); - content.innerHTML = `📊 Se encontraron ${results.length} resultados para "${searchTerm}"${filterText}`; + : ''; + const content = document.createElement('div'); + content.innerHTML = html`📊 Se encontraron ${results.length} resultados + para "${searchTerm}"${filterText}`; statsDiv.appendChild(content); resultsEl.insertBefore(statsDiv, resultsEl.firstChild); @@ -218,24 +217,24 @@ PAGES.buscar = { searchInputEl.focus(); // Add keyboard shortcuts - document.addEventListener("keydown", function (e) { + document.addEventListener('keydown', function (e) { // Ctrl+F or Cmd+F to focus search - if ((e.ctrlKey || e.metaKey) && e.key === "f") { + if ((e.ctrlKey || e.metaKey) && e.key === 'f') { e.preventDefault(); searchInputEl.focus(); searchInputEl.select(); } // Escape to clear search - if (e.key === "Escape") { - searchInputEl.value = ""; + if (e.key === 'Escape') { + searchInputEl.value = ''; searchInputEl.focus(); - resultsEl.innerHTML = ` + resultsEl.innerHTML = html`
      Resultados
      🔍 Introduce un término de búsqueda para comenzar

      Puedes buscar por nombres, referencias, fechas, ubicaciones...

      - +
      💡 Consejos de búsqueda
        @@ -252,10 +251,10 @@ PAGES.buscar = { }); // Check for quick search term from header - const quickSearchTerm = sessionStorage.getItem("telesec_quick_search"); + const quickSearchTerm = sessionStorage.getItem('telesec_quick_search'); if (quickSearchTerm) { searchInputEl.value = quickSearchTerm; - sessionStorage.removeItem("telesec_quick_search"); + sessionStorage.removeItem('telesec_quick_search'); // Perform search automatically setTimeout(performSearch, 100); } diff --git a/src/page/comedor.js b/src/page/comedor.js index f1529da..59e2fc6 100644 --- a/src/page/comedor.js +++ b/src/page/comedor.js @@ -1,44 +1,52 @@ -PERMS["comedor"] = "Comedor" -PERMS["comedor:edit"] = "> Editar" +PERMS['comedor'] = 'Comedor'; +PERMS['comedor:edit'] = '> Editar'; PAGES.comedor = { - navcss: "btn6", - icon: "static/appico/apple.png", + navcss: 'btn6', + icon: 'static/appico/apple.png', AccessControl: true, - Title: "Comedor", + Title: 'Comedor', edit: function (mid) { - if (!checkRole("comedor:edit")) {setUrlHash("comedor");return} + if (!checkRole('comedor:edit')) { + setUrlHash('comedor'); + return; + } var nameh1 = safeuuid(); var field_fecha = safeuuid(); var field_platos = safeuuid(); var btn_guardar = safeuuid(); var btn_borrar = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

        Entrada del menú

        Valores
        - `; + `; DB.get('comedor', mid).then((data) => { - function load_data(data, ENC = "") { + function load_data(data, ENC = '') { document.getElementById(nameh1).innerText = mid; - document.getElementById(field_fecha).value = data["Fecha"] || mid || CurrentISODate(); - document.getElementById(field_platos).value = - data["Platos"] || ""; + document.getElementById(field_fecha).value = data['Fecha'] || mid || CurrentISODate(); + document.getElementById(field_platos).value = data['Platos'] || ''; } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - load_data(data, "%E"); - }, 'comedor', mid); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + load_data(data, '%E'); + }, + 'comedor', + mid + ); } else { load_data(data || {}); } @@ -47,88 +55,93 @@ PAGES.comedor = { // Disable button to prevent double-clicking var guardarBtn = document.getElementById(btn_guardar); if (guardarBtn.disabled) return; - + guardarBtn.disabled = true; - guardarBtn.style.opacity = "0.5"; - + guardarBtn.style.opacity = '0.5'; + const newDate = document.getElementById(field_fecha).value; var data = { Fecha: newDate, Platos: document.getElementById(field_platos).value, }; - + // If the date has changed, we need to delete the old entry - if (mid !== newDate && mid !== "") { + if (mid !== newDate && mid !== '') { DB.del('comedor', mid); } - - document.getElementById("actionStatus").style.display = "block"; - DB.put('comedor', newDate, data).then(() => { - toastr.success("Guardado!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("comedor"); - }, SAVE_WAIT); - }).catch((e) => { - console.warn('DB.put error', e); - guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - document.getElementById("actionStatus").style.display = "none"; - toastr.error("Error al guardar el menú"); - }); + + document.getElementById('actionStatus').style.display = 'block'; + DB.put('comedor', newDate, data) + .then(() => { + toastr.success('Guardado!'); + setTimeout(() => { + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('comedor'); + }, SAVE_WAIT); + }) + .catch((e) => { + console.warn('DB.put error', e); + guardarBtn.disabled = false; + guardarBtn.style.opacity = '1'; + document.getElementById('actionStatus').style.display = 'none'; + toastr.error('Error al guardar el menú'); + }); }; document.getElementById(btn_borrar).onclick = () => { - if (confirm("¿Quieres borrar esta entrada?") == true) { + if (confirm('¿Quieres borrar esta entrada?') == true) { DB.del('comedor', mid).then(() => { - toastr.error("Borrado!"); + toastr.error('Borrado!'); setTimeout(() => { - setUrlHash("comedor"); + setUrlHash('comedor'); }, SAVE_WAIT); }); } }; }, index: function () { - if (!checkRole("comedor")) {setUrlHash("index");return} + if (!checkRole('comedor')) { + setUrlHash('index'); + return; + } const cont = safeuuid(); var btn_new = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

        Menú del comedor

        - `; + `; TS_IndexElement( - "comedor", + 'comedor', [ { - key: "Fecha", - type: "raw", - default: "", - label: "Fecha", + key: 'Fecha', + type: 'raw', + default: '', + label: 'Fecha', }, { - key: "Platos", - type: "raw", - default: "", - label: "Platos", - } + key: 'Platos', + type: 'raw', + default: '', + label: 'Platos', + }, ], - "comedor", + 'comedor', document.getElementById(cont), (data, new_tr) => { // new_tr.style.backgroundColor = "#FFCCCB"; if (data.Fecha == CurrentISODate()) { - new_tr.style.backgroundColor = "lightgreen"; + new_tr.style.backgroundColor = 'lightgreen'; } } ); - - if (!checkRole("comedor:edit")) { - document.getElementById(btn_new).style.display = "none" - } else { - document.getElementById(btn_new).onclick = () => { - setUrlHash("comedor," + safeuuid("")); - }; - } + + if (!checkRole('comedor:edit')) { + document.getElementById(btn_new).style.display = 'none'; + } else { + document.getElementById(btn_new).onclick = () => { + setUrlHash('comedor,' + safeuuid('')); + }; + } }, }; diff --git a/src/page/dataman.js b/src/page/dataman.js index 8dc165a..f6cf968 100644 --- a/src/page/dataman.js +++ b/src/page/dataman.js @@ -1,20 +1,20 @@ PAGES.dataman = { - navcss: "btn1", - icon: "static/appico/gear_edit.png", + navcss: 'btn1', + icon: 'static/appico/gear_edit.png', AccessControl: true, - Title: "Ajustes", + Title: 'Ajustes', edit: function (mid) { switch (mid) { - case "export": + case 'export': PAGES.dataman.__export(); break; - case "import": + case 'import': PAGES.dataman.__import(); break; - case "config": + case 'config': PAGES.dataman.__config(); break; - case "labels": + case 'labels': PAGES.dataman.__labels(); break; default: @@ -23,22 +23,22 @@ PAGES.dataman = { }, __config: function () { var form = safeuuid(); - container.innerHTML = ` -

        Ajustes

        -

        No disponible

        -
        - - -
        + container.innerHTML = html` +

        Ajustes

        +

        No disponible

        +
        + + +
        `; document.getElementById(form).onsubmit = (ev) => { ev.preventDefault(); var ford = new FormData(document.getElementById(form)); - if (ford.get("block_add_account") == "yes") { - config["block_add_account"] = true; + if (ford.get('block_add_account') == 'yes') { + config['block_add_account'] = true; } }; }, @@ -49,19 +49,21 @@ PAGES.dataman = { var button_export_safe = safeuuid(); var button_export_safe_cloud = safeuuid(); var button_clear = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

        Exportar Datos

        Exportar datos Al pulsar, Espera hasta que salga una notificacion verde. -
        -
        +
        +
        - +
        - `; + `; document.getElementById(button_export_local).onclick = () => { var data_export = {}; var output = { @@ -70,52 +72,62 @@ PAGES.dataman = { }; (async () => { const materiales = await DB.list('materiales'); - materiales.forEach(entry => { + materiales.forEach((entry) => { const key = entry.id; const value = entry.data; if (value != null) { if (typeof value == 'string') { - TS_decrypt(value, SECRET, (data, wasEncrypted) => { - output.materiales[key] = data; - }, 'materiales', key); + TS_decrypt( + value, + SECRET, + (data, wasEncrypted) => { + output.materiales[key] = data; + }, + 'materiales', + key + ); } else { output.materiales[key] = value; } } }); const personas = await DB.list('personas'); - personas.forEach(entry => { + personas.forEach((entry) => { const key = entry.id; const value = entry.data; if (value != null) { if (typeof value == 'string') { - TS_decrypt(value, SECRET, (data, wasEncrypted) => { - output.personas[key] = data; - }, 'personas', key); + TS_decrypt( + value, + SECRET, + (data, wasEncrypted) => { + output.personas[key] = data; + }, + 'personas', + key + ); } else { output.personas[key] = value; } } }); - toastr.success("Exportado todo, descargando!"); - download( - `Export %%TITLE%% ${getDBName()}.json.txt`, - JSON.stringify(output) - ); + toastr.success('Exportado todo, descargando!'); + download(`Export %%TITLE%% ${getDBName()}.json.txt`, JSON.stringify(output)); })(); }; document.getElementById(button_export_safe).onclick = () => { (async () => { const result = { materiales: {}, personas: {} }; const materiales = await DB.list('materiales'); - materiales.forEach(entry => { result.materiales[entry.id] = entry.data; }); + materiales.forEach((entry) => { + result.materiales[entry.id] = entry.data; + }); const personas = await DB.list('personas'); - personas.forEach(entry => { result.personas[entry.id] = entry.data; }); - toastr.success("Exportado todo, descargado!"); - download( - `Export %%TITLE%% Encriptado ${getDBName()}.json.txt`, - JSON.stringify(result) - ); + personas.forEach((entry) => { + result.personas[entry.id] = entry.data; + }); + toastr.success('Exportado todo, descargado!'); + download(`Export %%TITLE%% Encriptado ${getDBName()}.json.txt`, JSON.stringify(result)); })(); }; // document.getElementById(button_export_safe_cloud).onclick = () => { @@ -143,30 +155,34 @@ PAGES.dataman = { var textarea_content = safeuuid(); var button_import = safeuuid(); var button_clear = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

        Importar Datos

        - Importar datos - Espera hasta que se vacien todas las notificaciones. - - - - + Importar datos + Espera hasta que se vacien todas las notificaciones. + + + +
        - `; + `; document.getElementById(button_import).onclick = () => { - toastr.info("Importando datos..."); + toastr.info('Importando datos...'); var val = document.getElementById(textarea_content).value; var sel = document.getElementById(select_type).value; - if (sel == "%telesec") { + if (sel == '%telesec') { // legacy import, store entire payload as-is // for each top-level key, store their items in DB var parsed = JSON.parse(val); @@ -174,19 +190,23 @@ PAGES.dataman = { const sectionName = section[0]; const sectionData = section[1]; Object.entries(sectionData).forEach((entry) => { - DB.put(sectionName, entry[0], entry[1]).catch((e) => { console.warn('DB.put error', e); }); + DB.put(sectionName, entry[0], entry[1]).catch((e) => { + console.warn('DB.put error', e); + }); }); }); } else { - Object.entries(JSON.parse(val)["data"]).forEach((entry) => { - DB.put(sel, entry[0], entry[1]).catch((e) => { console.warn('DB.put error', e); }); + Object.entries(JSON.parse(val)['data']).forEach((entry) => { + DB.put(sel, entry[0], entry[1]).catch((e) => { + console.warn('DB.put error', e); + }); }); } setTimeout(() => { - toastr.info("Importado todo!"); + toastr.info('Importado todo!'); - if (sel == "%telesec") { - setUrlHash("inicio"); + if (sel == '%telesec') { + setUrlHash('inicio'); } else { setUrlHash(sel); } @@ -195,38 +215,34 @@ PAGES.dataman = { }, __labels: function (mid) { var div_materiales = safeuuid(); - container.innerHTML = ` -

        Imprimir Etiquetas QR

        + container.innerHTML = html`

        Imprimir Etiquetas QR

        Materiales

        -

        `; +

        `; div_materiales = document.getElementById(div_materiales); DB.map('materiales', (data, key) => { function add_row(data, key) { if (data != null) { - div_materiales.innerHTML += BuildQR( - "materiales," + key, - data["Nombre"] || key - ); + div_materiales.innerHTML += BuildQR('materiales,' + key, data['Nombre'] || key); } } - if (typeof data == "string") { + if (typeof data == 'string') { TS_decrypt(data, SECRET, (data) => { - add_row(data, key); - }); - } else { add_row(data, key); - } - }); + }); + } else { + add_row(data, key); + } + }); }, index: function () { - container.innerHTML = ` -

        Administración de datos

        - Importar datos - Exportar datos - Imprimir etiquetas - Ajustes + container.innerHTML = html` +

        Administración de datos

        + Importar datos + Exportar datos + Imprimir etiquetas + Ajustes `; }, }; diff --git a/src/page/index.js b/src/page/index.js index d0643e0..3cd5052 100644 --- a/src/page/index.js +++ b/src/page/index.js @@ -1,44 +1,44 @@ PAGES.index = { //navcss: "btn1", - Title: "Inicio", - icon: "static/appico/house.png", - index: function() { - container.innerHTML = ` -

        ¡Hola, ${SUB_LOGGED_IN_DETAILS.Nombre}!
        Bienvenidx a %%TITLE%%

        -

        Tienes ${parseFloat(SUB_LOGGED_IN_DETAILS.Monedero_Balance).toPrecision(2)} € en el monedero.

        + Title: 'Inicio', + icon: 'static/appico/house.png', + index: function () { + container.innerHTML = html` +

        ¡Hola, ${SUB_LOGGED_IN_DETAILS.Nombre}!
        Bienvenidx a %%TITLE%%

        +

        + Tienes ${parseFloat(SUB_LOGGED_IN_DETAILS.Monedero_Balance).toPrecision(2)} € en el + monedero. +

        Utiliza el menú superior para abrir un modulo -

        +

        `; }, - edit: function(mid) { + edit: function (mid) { switch (mid) { case 'qr': - PAGES.index.__scan() + PAGES.index.__scan(); break; } }, - __scan: function(mid) { - var qrscan = safeuuid() - container.innerHTML = ` -

        Escanear Codigo QR

        + __scan: function (mid) { + var qrscan = safeuuid(); + container.innerHTML = html`

        Escanear Codigo QR

        -

        `; - var html5QrcodeScanner = new Html5QrcodeScanner( - qrscan, { fps: 10, qrbox: 250 }); - +

        `; + var html5QrcodeScanner = new Html5QrcodeScanner(qrscan, { fps: 10, qrbox: 250 }); + function onScanSuccess(decodedText, decodedResult) { html5QrcodeScanner.clear(); // Handle on success condition with the decoded text or result. // alert(`Scan result: ${decodedText}`, decodedResult); - setUrlHash(decodedText) + setUrlHash(decodedText); // ... - + // ^ this will stop the scanner (video feed) and clear the scan area. - } - + html5QrcodeScanner.render(onScanSuccess); - EventListeners.QRScanner.push(html5QrcodeScanner) - } -} \ No newline at end of file + EventListeners.QRScanner.push(html5QrcodeScanner); + }, +}; diff --git a/src/page/login.js b/src/page/login.js index bea88f0..dee068f 100644 --- a/src/page/login.js +++ b/src/page/login.js @@ -1,505 +1,461 @@ +function makeCouchURLDisplay(host, user, pass, dbname) { + if (!host) return ''; + var display = user + ':' + pass + '@' + host.replace(/^https?:\/\//, '') + '/' + dbname; + return display; +} PAGES.login = { - Esconder: true, - Title: "Login", - onboarding: function (step) { - // Multi-step onboarding flow - step = step || 'config'; - - if (step === 'config') { - // Step 1: "Configuración de datos" - var field_couch = safeuuid(); - var field_couch_dbname = safeuuid(); - var field_couch_user = safeuuid(); - var field_couch_pass = safeuuid(); - var field_secret = safeuuid(); - var field_server_preset = safeuuid(); - var btn_existing_server = safeuuid(); - var btn_new_server = safeuuid(); - var btn_skip = safeuuid(); - var div_server_config = safeuuid(); - - container.innerHTML = ` -

        ¡Bienvenido a TeleSec! 🎉

        -

        Paso 1: Configuración de datos

        -

        Para comenzar, elige cómo quieres configurar tu base de datos:

        + Esconder: true, + Title: 'Login', + onboarding: function (step) { + // Multi-step onboarding flow + step = step || 'config'; + + if (step === 'config') { + // Step 1: "Configuración de datos" + var field_couch = safeuuid(); + var field_secret = safeuuid(); + var btn_existing_server = safeuuid(); + var btn_new_server = safeuuid(); + var btn_skip = safeuuid(); + var div_server_config = safeuuid(); + + container.innerHTML = html` +

        ¡Bienvenido a TeleSec! 🎉

        +

        Paso 1: Configuración de datos

        +

        Para comenzar, elige cómo quieres configurar tu base de datos:

        +
        + + + +
        + + `; + + document.getElementById(btn_existing_server).onclick = () => { + document.getElementById(div_server_config).style.display = 'block'; + }; + + document.getElementById(btn_new_server).onclick = () => { + window.open('https://tech.eus/telesec-signup.php', '_blank'); + toastr.info( + 'Una vez creado el servidor, vuelve aquí y conéctate usando el botón "Conectar a un servidor existente"' + ); + }; + + document.getElementById(btn_skip).onclick = () => { + // Continue to persona creation without server config + // Check if personas already exist (shouldn't happen but safety check) var hasPersonas = Object.keys(SC_Personas).length > 0; if (hasPersonas) { - toastr.info('Se detectaron personas existentes. Redirigiendo al login.'); + toastr.info('Ya existen personas. Saltando creación de cuenta.'); localStorage.setItem('TELESEC_ONBOARDING_COMPLETE', 'true'); open_page('login'); setUrlHash('login'); + } else { + open_page('login,onboarding-persona'); + setUrlHash('login,onboarding-persona'); + } + }; + + document.getElementById(btn_skip + '-save').onclick = () => { + var url = document.getElementById(field_couch).value.trim(); + var secret = document.getElementById(field_secret).value.trim(); + + if (!url) { + toastr.error('Por favor ingresa un servidor CouchDB'); return; } - - container.innerHTML = ` -

        ¡Bienvenido a TeleSec! 🎉

        -

        Paso 2: Crea tu cuenta de administrador

        -

        Para continuar, necesitas crear una cuenta personal con permisos de administrador.

        -
        - -

        ℹ️ Esta cuenta tendrá todos los permisos de administrador y podrás gestionar la aplicación completamente.

        - -
        - `; - - document.getElementById(btn_crear).onclick = () => { - var nombre = document.getElementById(field_nombre).value.trim(); - if (!nombre) { - toastr.error('Por favor ingresa tu nombre'); - return; + + if (!secret) { + toastr.error('La clave de encriptación es obligatoria'); + return; + } + + // Normalize URL: add https:// if no protocol specified + var normalizedUrl = url; + if (!/^https?:\/\//i.test(url)) { + normalizedUrl = 'https://' + url; + } + var URL_PARSED = parseURL(normalizedUrl); + var user = URL_PARSED.username || ''; + var pass = URL_PARSED.password || ''; + var dbname = URL_PARSED.pathname ? URL_PARSED.pathname.replace(/^\//, '') : ''; + var host = URL_PARSED.hostname || normalizedUrl; + localStorage.setItem('TELESEC_COUCH_URL', 'https://' + host); + localStorage.setItem('TELESEC_COUCH_DBNAME', dbname); + localStorage.setItem('TELESEC_COUCH_USER', user); + localStorage.setItem('TELESEC_COUCH_PASS', pass); + localStorage.setItem('TELESEC_SECRET', secret.toUpperCase()); + SECRET = secret.toUpperCase(); + + try { + DB.init({ + secret: SECRET, + remoteServer: 'https://' + host, + username: user, + password: pass, + dbname: dbname || undefined, + }); + toastr.success('Servidor configurado correctamente'); + document.getElementById('loading').style.display = 'block'; + function waitForReplicationIdle(maxWaitMs, idleMs) { + var startTime = Date.now(); + var lastSeenSync = window.TELESEC_LAST_SYNC || 0; + return new Promise((resolve) => { + var interval = setInterval(() => { + var now = Date.now(); + var currentSync = window.TELESEC_LAST_SYNC || 0; + if (currentSync > lastSeenSync) { + lastSeenSync = currentSync; + } + var lastActivity = Math.max(lastSeenSync, startTime); + var idleLongEnough = now - lastActivity >= idleMs; + var timedOut = now - startTime >= maxWaitMs; + if (idleLongEnough || timedOut) { + clearInterval(interval); + resolve(); + } + }, 250); + }); } - - // Disable button to prevent duplicate creation - var btnElement = document.getElementById(btn_crear); - btnElement.disabled = true; - btnElement.style.opacity = '0.5'; - btnElement.innerText = 'Creando...'; - - // Create persona with all admin permissions from PERMS object - var allPerms = Object.keys(PERMS).join(',') + ','; - var personaId = safeuuid('admin-'); - var persona = { - Nombre: nombre, - Roles: allPerms, - Region: '', - Monedero_Balance: 0, - markdown: 'Cuenta de administrador creada durante el onboarding' - }; - - DB.put('personas', personaId, persona).then(() => { + + // Wait until replication goes idle or timeout + waitForReplicationIdle(10000, 2500).then(() => { + // Check if personas were replicated from server + var hasPersonas = Object.keys(SC_Personas).length > 0; + document.getElementById('loading').style.display = 'none'; + if (hasPersonas) { + // Personas found from server, skip persona creation step + toastr.info('Se encontraron personas en el servidor. Saltando creación de cuenta.'); + localStorage.setItem('TELESEC_ONBOARDING_COMPLETE', 'true'); + open_page('login'); + setUrlHash('login'); + } else { + // No personas found, continue to persona creation + open_page('login,onboarding-persona'); + setUrlHash('login,onboarding-persona'); + } + }); + } catch (e) { + document.getElementById('loading').style.display = 'none'; + toastr.error('Error al configurar el servidor: ' + (e.message || e)); + } + }; + } else if (step === 'persona') { + // Step 2: "Crea una persona" + var field_nombre = safeuuid(); + var btn_crear = safeuuid(); + + // Check if personas already exist + var hasPersonas = Object.keys(SC_Personas).length > 0; + if (hasPersonas) { + toastr.info('Se detectaron personas existentes. Redirigiendo al login.'); + localStorage.setItem('TELESEC_ONBOARDING_COMPLETE', 'true'); + open_page('login'); + setUrlHash('login'); + return; + } + + container.innerHTML = html` +

        ¡Bienvenido a TeleSec! 🎉

        +

        Paso 2: Crea tu cuenta de administrador

        +

        Para continuar, necesitas crear una cuenta personal con permisos de administrador.

        +
        + +

        + ℹ️ Esta cuenta tendrá todos los permisos de administrador y podrás gestionar la + aplicación completamente. +

        + +
        + `; + + document.getElementById(btn_crear).onclick = () => { + var nombre = document.getElementById(field_nombre).value.trim(); + if (!nombre) { + toastr.error('Por favor ingresa tu nombre'); + return; + } + + // Disable button to prevent duplicate creation + var btnElement = document.getElementById(btn_crear); + btnElement.disabled = true; + btnElement.style.opacity = '0.5'; + btnElement.innerText = 'Creando...'; + + // Create persona with all admin permissions from PERMS object + var allPerms = Object.keys(PERMS).join(',') + ','; + var personaId = safeuuid('admin-'); + var persona = { + Nombre: nombre, + Roles: allPerms, + Region: '', + Monedero_Balance: 0, + markdown: 'Cuenta de administrador creada durante el onboarding', + }; + + DB.put('personas', personaId, persona) + .then(() => { toastr.success('¡Cuenta creada exitosamente! 🎉'); localStorage.setItem('TELESEC_ONBOARDING_COMPLETE', 'true'); localStorage.setItem('TELESEC_ADMIN_ID', personaId); - + // Auto-login SUB_LOGGED_IN_ID = personaId; SUB_LOGGED_IN_DETAILS = persona; SUB_LOGGED_IN = true; SetPages(); - + setTimeout(() => { open_page('index'); setUrlHash('index'); }, 500); - }).catch((e) => { + }) + .catch((e) => { toastr.error('Error creando cuenta: ' + (e.message || e)); // Re-enable button on error btnElement.disabled = false; btnElement.style.opacity = '1'; btnElement.innerText = 'Crear cuenta y empezar'; }); - }; - } - }, - edit: function (mid) { - // Handle onboarding routes - if (mid === 'onboarding-config') { - PAGES.login.onboarding('config'); - return; - } - if (mid === 'onboarding-persona') { - PAGES.login.onboarding('persona'); - return; - } - - // Setup form to configure CouchDB remote and initial group/secret - var field_couch = safeuuid(); - var field_couch_dbname = safeuuid(); - var field_couch_user = safeuuid(); - var field_couch_pass = safeuuid(); - var field_secret = safeuuid(); - var btn_import_json = safeuuid(); - var div_import_area = safeuuid(); - var field_json = safeuuid(); - var field_file = safeuuid(); - var btn_parse_json = safeuuid(); - var btn_start_scan = safeuuid(); - var div_scan = safeuuid(); - var btn_save = safeuuid(); - container.innerHTML = ` -

        Configuración del servidor CouchDB

        - Aviso: Después de guardar, la aplicación intentará sincronizar con el servidor CouchDB en segundo plano. Puede que falten registros hasta que se termine. Tenga paciencia. -
        - - - - - -
        - -
        - - -
        -

        Después de guardar, el navegador intentará sincronizar en segundo plano con el servidor.

        - `; - // Helper: normalize and apply config object - function applyConfig(cfg) { - try { - if (!cfg) throw new Error('JSON vacío'); - var url = cfg.server || cfg.couch || cfg.url || cfg.host || cfg.hostname || cfg.server_url; - var dbname = cfg.dbname || cfg.database || cfg.db || cfg.name; - var user = cfg.username || cfg.user || cfg.u; - var pass = cfg.password || cfg.pass || cfg.p; - var secret = (cfg.secret || cfg.key || cfg.secretKey || cfg.SECRET || '').toString(); - if (!url) throw new Error('Falta campo "server" en JSON'); - localStorage.setItem('TELESEC_COUCH_URL', 'https://' + url.replace(/^https?:\/\//, '')); - if (dbname) localStorage.setItem('TELESEC_COUCH_DBNAME', dbname); - if (user) localStorage.setItem('TELESEC_COUCH_USER', user); - if (pass) localStorage.setItem('TELESEC_COUCH_PASS', pass); - if (secret) { - localStorage.setItem('TELESEC_SECRET', secret.toUpperCase()); - SECRET = secret.toUpperCase(); - } - DB.init({ secret: SECRET, remoteServer: 'https://' + url.replace(/^https?:\/\//, ''), username: user, password: pass, dbname: dbname || undefined }); - toastr.success('Configuración aplicada e iniciando sincronización'); - location.hash = '#login'; - setTimeout(function(){ location.reload(); }, 400); - } catch (e) { - toastr.error('Error aplicando configuración: ' + (e && e.message ? e.message : e)); - } + }; + } + }, + edit: function (mid) { + // Handle onboarding routes + if (mid === 'onboarding-config') { + PAGES.login.onboarding('config'); + return; + } + if (mid === 'onboarding-persona') { + PAGES.login.onboarding('persona'); + return; + } + // Setup form to configure CouchDB remote and initial group/secret + var field_couch = safeuuid(); + var field_secret = safeuuid(); + var btn_save = safeuuid(); + container.innerHTML = html` +

        Configuración del servidor CouchDB

        + Aviso: Después de guardar, la aplicación intentará sincronizar con el servidor CouchDB en + segundo plano. Puede que falten registros hasta que se termine. Tenga paciencia. +
        + + + + +
        +

        + Después de guardar, el navegador intentará sincronizar en segundo plano con el servidor. +

        + `; + // Helper: normalize and apply config object + function applyConfig(cfg) { + try { + if (!cfg) throw new Error('JSON vacío'); + var url = cfg.server || cfg.couch || cfg.url || cfg.host || cfg.hostname || cfg.server_url; + var dbname = cfg.dbname || cfg.database || cfg.db || cfg.name; + var user = cfg.username || cfg.user || cfg.u; + var pass = cfg.password || cfg.pass || cfg.p; + var secret = (cfg.secret || cfg.key || cfg.secretKey || cfg.SECRET || '').toString(); + if (!url) throw new Error('Falta campo "server" en JSON'); + var URL_PARSED = parseURL(url); + var host = URL_PARSED.hostname || url; + localStorage.setItem('TELESEC_COUCH_URL', 'https://' + host); + if (dbname) localStorage.setItem('TELESEC_COUCH_DBNAME', dbname); + if (user) localStorage.setItem('TELESEC_COUCH_USER', user); + if (pass) localStorage.setItem('TELESEC_COUCH_PASS', pass); + if (secret) { + localStorage.setItem('TELESEC_SECRET', secret.toUpperCase()); + SECRET = secret.toUpperCase(); } - - // Toggle import area - document.getElementById(btn_import_json).onclick = function () { - var el = document.getElementById(div_import_area); - el.style.display = (el.style.display === 'none') ? 'block' : 'none'; - }; - - // Parse textarea JSON - document.getElementById(btn_parse_json).onclick = function () { - var txt = document.getElementById(field_json).value.trim(); - if (!txt) { toastr.error('JSON vacío'); return; } - try { - var obj = JSON.parse(txt); - applyConfig(obj); - } catch (e) { - toastr.error('JSON inválido: ' + e.message); - } - }; - - // File input: read JSON file and apply - document.getElementById(field_file).addEventListener('change', function (ev) { - var f = ev.target.files && ev.target.files[0]; - if (!f) return; - var r = new FileReader(); - r.onload = function (e) { - try { - var txt = e.target.result; - document.getElementById(field_json).value = txt; - var obj = JSON.parse(txt); - applyConfig(obj); - } catch (err) { - toastr.error('Error leyendo archivo JSON: ' + (err && err.message ? err.message : err)); - } - }; - r.readAsText(f); + DB.init({ + secret: SECRET, + remoteServer: 'https://' + url.replace(/^https?:\/\//, ''), + username: user, + password: pass, + dbname: dbname || undefined, }); - - // QR scanning (if html5-qrcode available) - document.getElementById(btn_start_scan).onclick = function () { - var scanDiv = document.getElementById(div_scan); - scanDiv.innerHTML = ''; - if (window.Html5QrcodeScanner || window.Html5Qrcode) { - try { - var targetId = div_scan + '-cam'; - scanDiv.innerHTML = '
        '; - var html5Qr; - if (window.Html5Qrcode) { - html5Qr = new Html5Qrcode(targetId); - Html5Qrcode.getCameras().then(function(cameras){ - var camId = (cameras && cameras[0] && cameras[0].id) ? cameras[0].id : undefined; - html5Qr.start({ facingMode: 'environment' }, { fps: 10, qrbox: 250 }, function(decodedText){ - try { - var obj = JSON.parse(decodedText); - html5Qr.stop(); - applyConfig(obj); - } catch (e) { - toastr.error('QR no contiene JSON válido'); - } - }, function(err){ /* ignore scan errors */ }).catch(function(err){ toastr.error('Error iniciando cámara: ' + err); }); - }).catch(function(){ - // fallback: start without camera list - html5Qr.start({ facingMode: 'environment' }, { fps: 10, qrbox: 250 }, function(decodedText){ - try { applyConfig(JSON.parse(decodedText)); } catch(e){ toastr.error('QR no contiene JSON válido'); } - }, function(){}).catch(function(err){ - toastr.error('Error iniciando cámara: ' + (err && err.message ? err.message : err)); - }); - }); - } else { - // Html5QrcodeScanner fallback - var scanner = new Html5QrcodeScanner(targetId, { fps: 10, qrbox: 250 }); - scanner.render(function(decodedText){ - try { applyConfig(JSON.parse(decodedText)); scanner.clear(); } catch(e){ toastr.error('QR no contiene JSON válido'); } - }); - } - // stop button - document.getElementById(targetId + '-stop').onclick = function () { - if (html5Qr && html5Qr.getState && html5Qr.getState() === Html5Qrcode.ScanStatus.SCANNING) { - html5Qr.stop().catch(function(){}); - } - scanDiv.innerHTML = ''; - }; - } catch (e) { - toastr.error('Error al iniciar escáner: ' + (e && e.message ? e.message : e)); - } - } else { - scanDiv.innerHTML = '

        Escáner no disponible. Copia/pega el JSON o sube un archivo.

        '; - } - }; - document.getElementById(btn_save).onclick = () => { - var url = document.getElementById(field_couch).value.trim(); - var dbname = document.getElementById(field_couch_dbname).value.trim(); - var user = document.getElementById(field_couch_user).value.trim(); - var pass = document.getElementById(field_couch_pass).value; - var secret = document.getElementById(field_secret).value || ''; - localStorage.setItem('TELESEC_COUCH_URL', "https://" + url); - localStorage.setItem('TELESEC_COUCH_DBNAME', dbname); - localStorage.setItem('TELESEC_COUCH_USER', user); - localStorage.setItem('TELESEC_COUCH_PASS', pass); - localStorage.setItem('TELESEC_SECRET', secret.toUpperCase()); - SECRET = secret.toUpperCase(); - try { - DB.init({ secret: SECRET, remoteServer: "https://" + url, username: user, password: pass, dbname: dbname || undefined }); - toastr.success('Iniciando sincronización con CouchDB'); - location.hash = "#login"; + toastr.success('Configuración aplicada e iniciando sincronización'); + location.hash = '#login'; + setTimeout(function () { location.reload(); - } catch (e) { - toastr.error('Error al iniciar sincronización: ' + e.message); - } - }; - }, - index: function (mid) { - // Check if onboarding is needed - var onboardingComplete = localStorage.getItem('TELESEC_ONBOARDING_COMPLETE'); - var hasPersonas = Object.keys(SC_Personas).length > 0; - - // If no personas exist and onboarding not complete, redirect to onboarding - if (!hasPersonas && !onboardingComplete && !AC_BYPASS) { - open_page('login,onboarding-config'); - setUrlHash('login,onboarding-config'); - return; - } - - var field_persona = safeuuid(); - var btn_guardar = safeuuid(); - var btn_reload = safeuuid(); - var div_actions = safeuuid(); - container.innerHTML = ` -

        Iniciar sesión

        -
        - Valores - -
        - - - Configurar base de datos -
        - `; - var divact = document.getElementById(div_actions); - addCategory_Personas( - divact, - SC_Personas, - "", - (value) => { - document.getElementById(field_persona).value = value; - }, - "¿Quién eres?", - true, - "- Pulsa recargar o rellena los credenciales abajo, si quieres crear un nuevo grupo, pulsa el boton 'Desde cero' -" - ); - document.getElementById(btn_guardar).onclick = () => { - if (document.getElementById(field_persona).value == "") { - alert("Tienes que elegir tu cuenta!"); - return; - } - SUB_LOGGED_IN_ID = document.getElementById(field_persona).value - SUB_LOGGED_IN_DETAILS = SC_Personas[SUB_LOGGED_IN_ID] - SUB_LOGGED_IN = true - SetPages() - if (location.hash.replace("#", "").startsWith("login")) { - open_page("index"); - setUrlHash("index") - } else{ - open_page(location.hash.replace("#", "")); - } - }; - - document.getElementById(btn_reload).onclick = () => { - open_page("login") - }; - - // AC_BYPASS: allow creating a local persona from the login screen - if (AC_BYPASS) { - var btn_bypass_create = safeuuid(); - divact.innerHTML += ``; - document.getElementById(btn_bypass_create).onclick = () => { - var name = prompt("Nombre de la persona (ej: Admin):"); - if (!name) return; - var id = 'bypass-' + Date.now(); - var persona = { Nombre: name, Roles: 'ADMIN,' }; - DB.put('personas', id, persona).then(() => { - toastr.success('Persona creada: ' + id); - localStorage.setItem('TELESEC_BYPASS_ID', id); - SUB_LOGGED_IN_ID = id; - SUB_LOGGED_IN_DETAILS = persona; - SUB_LOGGED_IN = true; - SetPages(); - open_page('index'); - }).catch((e) => { - toastr.error('Error creando persona: ' + (e && e.message ? e.message : e)); - }); - }; + }, 400); + } catch (e) { + toastr.error('Error aplicando configuración: ' + (e && e.message ? e.message : e)); } } - } \ No newline at end of file + document.getElementById(btn_save).onclick = () => { + var url = document.getElementById(field_couch).value.trim(); + var secret = document.getElementById(field_secret).value.trim(); + var URL_PARSED = parseURL(url); + var host = URL_PARSED.hostname || url; + var user = URL_PARSED.username || ''; + var pass = URL_PARSED.password || ''; + var dbname = URL_PARSED.pathname ? URL_PARSED.pathname.replace(/^\//, '') : ''; + localStorage.setItem('TELESEC_COUCH_URL', 'https://' + host); + localStorage.setItem('TELESEC_COUCH_DBNAME', dbname); + localStorage.setItem('TELESEC_COUCH_USER', user); + localStorage.setItem('TELESEC_COUCH_PASS', pass); + localStorage.setItem('TELESEC_SECRET', secret.toUpperCase()); + SECRET = secret.toUpperCase(); + try { + DB.init({ + secret: SECRET, + remoteServer: 'https://' + host, + username: user, + password: pass, + dbname: dbname || undefined, + }); + toastr.success('Iniciando sincronización con CouchDB'); + location.hash = '#login'; + //location.reload(); + } catch (e) { + toastr.error('Error al iniciar sincronización: ' + e.message); + } + }; + }, + index: function (mid) { + // Check if onboarding is needed + var onboardingComplete = localStorage.getItem('TELESEC_ONBOARDING_COMPLETE'); + var hasPersonas = Object.keys(SC_Personas).length > 0; + + // If no personas exist and onboarding not complete, redirect to onboarding + if (!hasPersonas && !onboardingComplete && !AC_BYPASS) { + open_page('login,onboarding-config'); + setUrlHash('login,onboarding-config'); + return; + } + + var field_persona = safeuuid(); + var btn_guardar = safeuuid(); + var btn_reload = safeuuid(); + var div_actions = safeuuid(); + container.innerHTML = html` +

        Iniciar sesión

        +
        + Valores + +
        + + + Configurar base de datos +
        + `; + var divact = document.getElementById(div_actions); + addCategory_Personas( + divact, + SC_Personas, + '', + (value) => { + document.getElementById(field_persona).value = value; + }, + '¿Quién eres?', + true, + "- Pulsa recargar o rellena los credenciales abajo, si quieres crear un nuevo grupo, pulsa el boton 'Desde cero' -" + ); + document.getElementById(btn_guardar).onclick = () => { + if (document.getElementById(field_persona).value == '') { + alert('Tienes que elegir tu cuenta!'); + return; + } + SUB_LOGGED_IN_ID = document.getElementById(field_persona).value; + SUB_LOGGED_IN_DETAILS = SC_Personas[SUB_LOGGED_IN_ID]; + SUB_LOGGED_IN = true; + SetPages(); + if (location.hash.replace('#', '').startsWith('login')) { + open_page('index'); + setUrlHash('index'); + } else { + open_page(location.hash.replace('#', '')); + } + }; + + document.getElementById(btn_reload).onclick = () => { + open_page('login'); + }; + + // AC_BYPASS: allow creating a local persona from the login screen + if (AC_BYPASS) { + var btn_bypass_create = safeuuid(); + divact.innerHTML += ``; + document.getElementById(btn_bypass_create).onclick = () => { + var name = prompt('Nombre de la persona (ej: Admin):'); + if (!name) return; + var id = 'bypass-' + Date.now(); + var persona = { Nombre: name, Roles: 'ADMIN,' }; + DB.put('personas', id, persona) + .then(() => { + toastr.success('Persona creada: ' + id); + localStorage.setItem('TELESEC_BYPASS_ID', id); + SUB_LOGGED_IN_ID = id; + SUB_LOGGED_IN_DETAILS = persona; + SUB_LOGGED_IN = true; + SetPages(); + open_page('index'); + }) + .catch((e) => { + toastr.error('Error creando persona: ' + (e && e.message ? e.message : e)); + }); + }; + } + }, +}; diff --git a/src/page/materiales.js b/src/page/materiales.js index a940852..1489179 100644 --- a/src/page/materiales.js +++ b/src/page/materiales.js @@ -1,13 +1,13 @@ -PERMS["materiales"] = "Almacén"; -PERMS["materiales:edit"] = "> Editar"; +PERMS['materiales'] = 'Almacén'; +PERMS['materiales:edit'] = '> Editar'; PAGES.materiales = { - navcss: "btn2", - icon: "static/appico/shelf.png", + navcss: 'btn2', + icon: 'static/appico/shelf.png', AccessControl: true, - Title: "Almacén", + Title: 'Almacén', edit: function (mid) { - if (!checkRole("materiales:edit")) { - setUrlHash("materiales"); + if (!checkRole('materiales:edit')) { + setUrlHash('materiales'); return; } var nameh1 = safeuuid(); @@ -20,63 +20,68 @@ PAGES.materiales = { var field_notas = safeuuid(); var btn_guardar = safeuuid(); var btn_borrar = safeuuid(); - var FECHA_ISO = new Date().toISOString().split("T")[0]; - container.innerHTML = ` + var FECHA_ISO = new Date().toISOString().split('T')[0]; + container.innerHTML = html`

        Material

        - ${BuildQR("materiales," + mid, "Este Material")} + ${BuildQR('materiales,' + mid, 'Este Material')}

        + Notas
        +

        + +
        `; DB.get('materiales', mid).then((data) => { - function load_data(data, ENC = "") { + function load_data(data, ENC = '') { document.getElementById(nameh1).innerText = mid; - document.getElementById(field_nombre).value = data["Nombre"] || ""; - document.getElementById(field_unidad).value = - data["Unidad"] || "unidad(es)"; - document.getElementById(field_cantidad).value = - data["Cantidad"] || ""; - document.getElementById(field_cantidad_min).value = - data["Cantidad_Minima"] || ""; - document.getElementById(field_ubicacion).value = - data["Ubicacion"] || "-"; - document.getElementById(field_revision).value = - data["Revision"] || "-"; - document.getElementById(field_notas).value = data["Notas"] || ""; + document.getElementById(field_nombre).value = data['Nombre'] || ''; + document.getElementById(field_unidad).value = data['Unidad'] || 'unidad(es)'; + document.getElementById(field_cantidad).value = data['Cantidad'] || ''; + document.getElementById(field_cantidad_min).value = data['Cantidad_Minima'] || ''; + document.getElementById(field_ubicacion).value = data['Ubicacion'] || '-'; + document.getElementById(field_revision).value = data['Revision'] || '-'; + document.getElementById(field_notas).value = data['Notas'] || ''; } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - load_data(data, "%E"); - }, 'materiales', mid); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + load_data(data, '%E'); + }, + 'materiales', + mid + ); } else { load_data(data || {}); } @@ -85,10 +90,10 @@ PAGES.materiales = { // Disable button to prevent double-clicking var guardarBtn = document.getElementById(btn_guardar); if (guardarBtn.disabled) return; - + guardarBtn.disabled = true; - guardarBtn.style.opacity = "0.5"; - + guardarBtn.style.opacity = '0.5'; + var data = { Nombre: document.getElementById(field_nombre).value, Unidad: document.getElementById(field_unidad).value, @@ -98,48 +103,51 @@ PAGES.materiales = { Revision: document.getElementById(field_revision).value, Notas: document.getElementById(field_notas).value, }; - document.getElementById("actionStatus").style.display = "block"; - DB.put('materiales', mid, data).then(() => { - toastr.success("Guardado!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("materiales"); - }, SAVE_WAIT); - }).catch((e) => { - console.warn('DB.put error', e); - guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - document.getElementById("actionStatus").style.display = "none"; - toastr.error("Error al guardar el material"); - }); + document.getElementById('actionStatus').style.display = 'block'; + DB.put('materiales', mid, data) + .then(() => { + toastr.success('Guardado!'); + setTimeout(() => { + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('materiales'); + }, SAVE_WAIT); + }) + .catch((e) => { + console.warn('DB.put error', e); + guardarBtn.disabled = false; + guardarBtn.style.opacity = '1'; + document.getElementById('actionStatus').style.display = 'none'; + toastr.error('Error al guardar el material'); + }); }; document.getElementById(btn_borrar).onclick = () => { - if (confirm("¿Quieres borrar este material?") == true) { + if (confirm('¿Quieres borrar este material?') == true) { DB.del('materiales', mid).then(() => { - toastr.error("Borrado!"); + toastr.error('Borrado!'); setTimeout(() => { - setUrlHash("materiales"); + setUrlHash('materiales'); }, SAVE_WAIT); }); } }; }, index: function () { - if (!checkRole("materiales")) { - setUrlHash("index"); + if (!checkRole('materiales')) { + setUrlHash('index'); return; } var btn_new = safeuuid(); var select_ubicacion = safeuuid(); var check_lowstock = safeuuid(); var tableContainer = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

        Materiales del Almacén


        -
        + - -
        -
    - + navcss: 'btn5', + icon: 'static/appico/edit.png', + AccessControl: true, + Title: 'Notas', + edit: function (mid) { + if (!checkRole('notas:edit')) { + setUrlHash('notas'); + return; + } + var nameh1 = safeuuid(); + var field_asunto = safeuuid(); + var field_contenido = safeuuid(); + var field_autor = safeuuid(); + var field_files = safeuuid(); + var attachments_list = safeuuid(); + var btn_guardar = safeuuid(); + var btn_borrar = safeuuid(); + var div_actions = safeuuid(); + container.innerHTML = html` +

    Nota

    +
    + Valores +
    -
    - - -
    - `; - var divact = document.getElementById(div_actions); - addCategory_Personas( - divact, - SC_Personas, - SUB_LOGGED_IN_ID, - (value) => { - document.getElementById(field_autor).value = value; - }, - "Autor" - ); - DB.get('notas', mid).then((data) => { - function load_data(data, ENC = "") { - document.getElementById(nameh1).innerText = mid; - document.getElementById(field_asunto).value = data["Asunto"] || ""; - document.getElementById(field_contenido).value = - data["Contenido"] || ""; - document.getElementById(field_autor).value = data["Autor"] || SUB_LOGGED_IN_ID || ""; + +
    + + + +
    + + + + `; + var divact = document.getElementById(div_actions); + addCategory_Personas( + divact, + SC_Personas, + SUB_LOGGED_IN_ID, + (value) => { + document.getElementById(field_autor).value = value; + }, + 'Autor' + ); + DB.get('notas', mid).then((data) => { + function load_data(data, ENC = '') { + document.getElementById(nameh1).innerText = mid; + document.getElementById(field_asunto).value = data['Asunto'] || ''; + document.getElementById(field_contenido).value = data['Contenido'] || ''; + document.getElementById(field_autor).value = data['Autor'] || SUB_LOGGED_IN_ID || ''; - // Persona select - divact.innerHTML = ""; - addCategory_Personas( - divact, - SC_Personas, - data["Autor"] || SUB_LOGGED_IN_ID || "", - (value) => { - document.getElementById(field_autor).value = value; - }, - "Autor" - ); - // Mostrar adjuntos existentes (si los hay). - // No confiar en `data._attachments` porque `DB.get` devuelve solo `doc.data`. - const attachContainer = document.getElementById(attachments_list); - attachContainer.innerHTML = ""; - // Usar API de DB para listar attachments (no acceder a internals desde la UI) - DB.listAttachments('notas', mid).then((list) => { + // Persona select + divact.innerHTML = ''; + addCategory_Personas( + divact, + SC_Personas, + data['Autor'] || SUB_LOGGED_IN_ID || '', + (value) => { + document.getElementById(field_autor).value = value; + }, + 'Autor' + ); + // Mostrar adjuntos existentes (si los hay). + // No confiar en `data._attachments` porque `DB.get` devuelve solo `doc.data`. + const attachContainer = document.getElementById(attachments_list); + attachContainer.innerHTML = ''; + // Usar API de DB para listar attachments (no acceder a internals desde la UI) + DB.listAttachments('notas', mid) + .then((list) => { if (!list || !Array.isArray(list)) return; list.forEach((att) => { addAttachmentRow(att.name, att.dataUrl); }); - }).catch((e) => { console.warn('listAttachments error', e); }); - } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data) => { - load_data(data, "%E"); + }) + .catch((e) => { + console.warn('listAttachments error', e); }); - } else { - load_data(data || {}); - } - }); - // gestión de archivos seleccionados antes de guardar - const attachmentsToUpload = []; - function addAttachmentRow(name, url) { - const attachContainer = document.getElementById(attachments_list); - const idRow = safeuuid(); - const isImage = url && url.indexOf('data:image') === 0; - const preview = isImage ? `` : `${name}`; - const html = ` + } + if (typeof data == 'string') { + TS_decrypt(data, SECRET, (data) => { + load_data(data, '%E'); + }); + } else { + load_data(data || {}); + } + }); + // gestión de archivos seleccionados antes de guardar + const attachmentsToUpload = []; + function addAttachmentRow(name, url) { + const attachContainer = document.getElementById(attachments_list); + const idRow = safeuuid(); + const isImage = url && url.indexOf('data:image') === 0; + const preview = isImage + ? `` + : `${name}`; + const html = `
    ${preview}${name}
    `; - attachContainer.insertAdjacentHTML('beforeend', html); - attachContainer.querySelectorAll(`button[data-name="${name}"]`).forEach((btn) => { - btn.onclick = () => { - if (!confirm('¿Borrar este adjunto?')) return; - // Usar API pública en DB para borrar metadata del attachment - DB.deleteAttachment('notas', mid, name).then((ok) => { + attachContainer.insertAdjacentHTML('beforeend', html); + attachContainer.querySelectorAll(`button[data-name="${name}"]`).forEach((btn) => { + btn.onclick = () => { + if (!confirm('¿Borrar este adjunto?')) return; + // Usar API pública en DB para borrar metadata del attachment + DB.deleteAttachment('notas', mid, name) + .then((ok) => { if (ok) { document.getElementById(idRow).remove(); toastr.error('Adjunto borrado'); } else { toastr.error('No se pudo borrar el adjunto'); } - }).catch((e) => { console.warn('deleteAttachment error', e); toastr.error('Error borrando adjunto'); }); - }; - }); - } - - document.getElementById(field_files).addEventListener('change', function (e) { - const files = Array.from(e.target.files || []); - files.forEach((file) => { - const reader = new FileReader(); - reader.onload = function (ev) { - const dataUrl = ev.target.result; - attachmentsToUpload.push({ name: file.name, data: dataUrl, type: file.type || 'application/octet-stream' }); - // mostrar preview temporal - addAttachmentRow(file.name, dataUrl); - }; - reader.readAsDataURL(file); - }); - // limpiar input para permitir re-subidas del mismo archivo - e.target.value = ''; - }); - document.getElementById(btn_guardar).onclick = () => { - // Disable button to prevent double-clicking - var guardarBtn = document.getElementById(btn_guardar); - if (guardarBtn.disabled) return; - - guardarBtn.disabled = true; - guardarBtn.style.opacity = "0.5"; - - var data = { - Autor: document.getElementById(field_autor).value, - Contenido: document.getElementById(field_contenido).value, - Asunto: document.getElementById(field_asunto).value, + }) + .catch((e) => { + console.warn('deleteAttachment error', e); + toastr.error('Error borrando adjunto'); + }); }; - document.getElementById("actionStatus").style.display = "block"; - DB.put('notas', mid, data).then(() => { + }); + } + + document.getElementById(field_files).addEventListener('change', function (e) { + const files = Array.from(e.target.files || []); + files.forEach((file) => { + const reader = new FileReader(); + reader.onload = function (ev) { + const dataUrl = ev.target.result; + attachmentsToUpload.push({ + name: file.name, + data: dataUrl, + type: file.type || 'application/octet-stream', + }); + // mostrar preview temporal + addAttachmentRow(file.name, dataUrl); + }; + reader.readAsDataURL(file); + }); + // limpiar input para permitir re-subidas del mismo archivo + e.target.value = ''; + }); + document.getElementById(btn_guardar).onclick = () => { + // Disable button to prevent double-clicking + var guardarBtn = document.getElementById(btn_guardar); + if (guardarBtn.disabled) return; + + guardarBtn.disabled = true; + guardarBtn.style.opacity = '0.5'; + + var data = { + Autor: document.getElementById(field_autor).value, + Contenido: document.getElementById(field_contenido).value, + Asunto: document.getElementById(field_asunto).value, + }; + document.getElementById('actionStatus').style.display = 'block'; + DB.put('notas', mid, data) + .then(() => { // subir attachments si los hay const uploadPromises = []; attachmentsToUpload.forEach((att) => { if (DB.putAttachment) { - uploadPromises.push(DB.putAttachment('notas', mid, att.name, att.data, att.type).catch((e) => { console.warn('putAttachment error', e); })); + uploadPromises.push( + DB.putAttachment('notas', mid, att.name, att.data, att.type).catch((e) => { + console.warn('putAttachment error', e); + }) + ); } }); - Promise.all(uploadPromises).then(() => { - // limpiar lista temporal y recargar attachments - attachmentsToUpload.length = 0; - try { // recargar lista actual sin salir - const pouchId = 'notas:' + mid; - if (DB && DB._internal && DB._internal.local) { - DB._internal.local.get(pouchId, { attachments: true }).then((doc) => { - const attachContainer = document.getElementById(attachments_list); - attachContainer.innerHTML = ''; - if (doc && doc._attachments) { - Object.keys(doc._attachments).forEach((name) => { - try { - const att = doc._attachments[name]; - if (att && att.data) { - const durl = 'data:' + (att.content_type || 'application/octet-stream') + ';base64,' + att.data; - addAttachmentRow(name, durl); - return; - } - } catch (e) {} - DB.getAttachment('notas', mid, name).then((durl) => { addAttachmentRow(name, durl); }).catch(() => {}); + Promise.all(uploadPromises) + .then(() => { + // limpiar lista temporal y recargar attachments + attachmentsToUpload.length = 0; + try { + // recargar lista actual sin salir + const pouchId = 'notas:' + mid; + if (DB && DB._internal && DB._internal.local) { + DB._internal.local + .get(pouchId, { attachments: true }) + .then((doc) => { + const attachContainer = document.getElementById(attachments_list); + attachContainer.innerHTML = ''; + if (doc && doc._attachments) { + Object.keys(doc._attachments).forEach((name) => { + try { + const att = doc._attachments[name]; + if (att && att.data) { + const durl = + 'data:' + + (att.content_type || 'application/octet-stream') + + ';base64,' + + att.data; + addAttachmentRow(name, durl); + return; + } + } catch (e) {} + DB.getAttachment('notas', mid, name) + .then((durl) => { + addAttachmentRow(name, durl); + }) + .catch(() => {}); + }); + } + }) + .catch(() => { + /* ignore reload errors */ }); - } - }).catch(() => { /* ignore reload errors */ }); - } - } catch (e) {} - toastr.success("Guardado!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("notas"); - }, SAVE_WAIT); - }).catch((e) => { - console.warn('Attachment upload error', e); - document.getElementById("actionStatus").style.display = "none"; - guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - toastr.error("Error al guardar los adjuntos"); - }); - }).catch((e) => { - console.warn('DB.put error', e); - document.getElementById("actionStatus").style.display = "none"; + } + } catch (e) {} + toastr.success('Guardado!'); + setTimeout(() => { + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('notas'); + }, SAVE_WAIT); + }) + .catch((e) => { + console.warn('Attachment upload error', e); + document.getElementById('actionStatus').style.display = 'none'; + guardarBtn.disabled = false; + guardarBtn.style.opacity = '1'; + toastr.error('Error al guardar los adjuntos'); + }); + }) + .catch((e) => { + console.warn('DB.put error', e); + document.getElementById('actionStatus').style.display = 'none'; guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - toastr.error("Error al guardar la nota"); + guardarBtn.style.opacity = '1'; + toastr.error('Error al guardar la nota'); + }); + }; + document.getElementById(btn_borrar).onclick = () => { + if (confirm('¿Quieres borrar esta nota?') == true) { + DB.del('notas', mid).then(() => { + toastr.error('Borrado!'); + setTimeout(() => { + setUrlHash('notas'); + }, SAVE_WAIT); }); - }; - document.getElementById(btn_borrar).onclick = () => { - if (confirm("¿Quieres borrar esta nota?") == true) { - DB.del('notas', mid).then(() => { - toastr.error("Borrado!"); - setTimeout(() => { - setUrlHash("notas"); - }, SAVE_WAIT); - }); - } - }; - }, - index: function () { - if (!checkRole("notas")) {setUrlHash("index");return} - const tablebody = safeuuid(); - var btn_new = safeuuid(); - container.innerHTML = ` -

    Notas

    - -
    - `; - TS_IndexElement( - "notas", - [ - { - key: "Autor", - type: "persona-nombre", - default: "", - label: "Autor", - }, - { - key: "Asunto", - type: "raw", - default: "", - label: "Asunto", - }, - ], - "notas", - document.querySelector("#cont"), - ); - if (!checkRole("notas:edit")) { - document.getElementById(btn_new).style.display = "none" - } else { - document.getElementById(btn_new).onclick = () => { - setUrlHash("notas," + safeuuid("")); - }; } - }, - } \ No newline at end of file + }; + }, + index: function () { + if (!checkRole('notas')) { + setUrlHash('index'); + return; + } + const tablebody = safeuuid(); + var btn_new = safeuuid(); + container.innerHTML = html` +

    Notas

    + +
    + `; + TS_IndexElement( + 'notas', + [ + { + key: 'Autor', + type: 'persona-nombre', + default: '', + label: 'Autor', + }, + { + key: 'Asunto', + type: 'raw', + default: '', + label: 'Asunto', + }, + ], + 'notas', + document.querySelector('#cont') + ); + if (!checkRole('notas:edit')) { + document.getElementById(btn_new).style.display = 'none'; + } else { + document.getElementById(btn_new).onclick = () => { + setUrlHash('notas,' + safeuuid('')); + }; + } + }, +}; diff --git a/src/page/pagos.js b/src/page/pagos.js index ec0af79..bab40b0 100644 --- a/src/page/pagos.js +++ b/src/page/pagos.js @@ -1,37 +1,37 @@ -PERMS["pagos"] = "Pagos"; -PERMS["pagos:edit"] = "> Editar"; +PERMS['pagos'] = 'Pagos'; +PERMS['pagos:edit'] = '> Editar'; PAGES.pagos = { - navcss: "btn7", - icon: "static/appico/credit_cards.png", + navcss: 'btn7', + icon: 'static/appico/credit_cards.png', AccessControl: true, - Title: "Pagos", - + Title: 'Pagos', + // Datafono view for creating/processing transactions - datafono: function(prefilledData = {}) { - if (!checkRole("pagos:edit")) { - setUrlHash("pagos"); + datafono: function (prefilledData = {}) { + if (!checkRole('pagos:edit')) { + setUrlHash('pagos'); return; } - + // Check for prefilled data from SuperCafé - var prefilledStr = sessionStorage.getItem("pagos_prefill"); + var prefilledStr = sessionStorage.getItem('pagos_prefill'); if (prefilledStr) { try { var prefilled = JSON.parse(prefilledStr); prefilledData = { ...prefilled, ...prefilledData }; - sessionStorage.removeItem("pagos_prefill"); + sessionStorage.removeItem('pagos_prefill'); } catch (e) { - console.error("Error parsing prefilled data:", e); + console.error('Error parsing prefilled data:', e); } } - + // Check for scanned persona from QR scanner - var scannedPersona = sessionStorage.getItem("pagos_scanned_persona"); + var scannedPersona = sessionStorage.getItem('pagos_scanned_persona'); if (scannedPersona) { prefilledData.persona = scannedPersona; - sessionStorage.removeItem("pagos_scanned_persona"); + sessionStorage.removeItem('pagos_scanned_persona'); } - + var field_tipo = safeuuid(); var field_monto = safeuuid(); var field_persona = safeuuid(); @@ -45,103 +45,140 @@ PAGES.pagos = { var btn_cancel = safeuuid(); var scan_qr_btn = safeuuid(); var currentStep = 1; - var selectedPersona = prefilledData.persona || ""; - var selectedPersonaDestino = ""; - - container.innerHTML = ` -
    + var selectedPersona = prefilledData.persona || ''; + var selectedPersonaDestino = ''; + + container.innerHTML = html` +

    Terminal de pago TeleSec

    - -
    -

    Paso 1 de 3

    - + +
    +

    + Paso 1 de 3 +

    +
    - 1. Información de Transacción - + + 1. Información de Transacción + + - + - + - +
    - + - +
    - + -
    - -
    -
    `; - + // Tipo change handler - document.getElementById(field_tipo).addEventListener("change", function() { + document.getElementById(field_tipo).addEventListener('change', function () { var tipo = this.value; var divDestino = document.getElementById(div_persona_destino); var metodoSelect = document.getElementById(field_metodo); - - if (tipo === "Transferencia") { - divDestino.style.display = "block"; + + if (tipo === 'Transferencia') { + divDestino.style.display = 'block'; } else { - divDestino.style.display = "none"; + divDestino.style.display = 'none'; } - + // Restrict Ingreso to Efectivo only - if (tipo === "Ingreso") { - metodoSelect.value = "Efectivo"; + if (tipo === 'Ingreso') { + metodoSelect.value = 'Efectivo'; metodoSelect.disabled = true; } else { metodoSelect.disabled = false; } }); - + // Confirm/Next button document.getElementById(btn_confirm).onclick = () => { if (currentStep === 1) { @@ -217,65 +274,64 @@ PAGES.pagos = { var tipo = document.getElementById(field_tipo).value; var metodo = document.getElementById(field_metodo).value; var monto = parseFloat(document.getElementById(numpad_display).value); - + if (!tipo) { - alert("Por favor selecciona el tipo de transacción"); + alert('Por favor selecciona el tipo de transacción'); return; } if (!metodo) { - alert("Por favor selecciona el método de pago"); + alert('Por favor selecciona el método de pago'); return; } - if (tipo === "Ingreso" && metodo !== "Efectivo") { - alert("Los ingresos solo pueden ser en Efectivo"); + if (tipo === 'Ingreso' && metodo !== 'Efectivo') { + alert('Los ingresos solo pueden ser en Efectivo'); return; } if (isNaN(monto) || monto <= 0) { - alert("Por favor ingresa un monto válido"); + alert('Por favor ingresa un monto válido'); return; } - + // Move to step 2 - document.getElementById("step1").style.display = "none"; - document.getElementById("step2").style.display = "block"; - document.getElementById("stepIndicator").innerText = "2"; - document.getElementById("step2Amount").innerText = - monto.toFixed(2) + "€"; + document.getElementById('step1').style.display = 'none'; + document.getElementById('step2').style.display = 'block'; + document.getElementById('stepIndicator').innerText = '2'; + document.getElementById('step2Amount').innerText = monto.toFixed(2) + '€'; currentStep = 2; - + // Load personas for selection loadPersonaSelector(); - if (tipo === "Transferencia") { + if (tipo === 'Transferencia') { loadPersonaDestinoSelector(); } } else if (currentStep === 2) { var personaId = document.getElementById(field_persona).value; if (!personaId) { - alert("Por favor selecciona un monedero"); + alert('Por favor selecciona un monedero'); return; } - + var tipo = document.getElementById(field_tipo).value; var metodo = document.getElementById(field_metodo).value; var monto = parseFloat(document.getElementById(numpad_display).value); - - if (tipo === "Transferencia") { + + if (tipo === 'Transferencia') { var personaDestinoId = document.getElementById(field_persona_destino).value; if (!personaDestinoId) { - alert("Por favor selecciona el monedero destino"); + alert('Por favor selecciona el monedero destino'); return; } if (personaId === personaDestinoId) { - alert("No puedes transferir al mismo monedero"); + alert('No puedes transferir al mismo monedero'); return; } } - + // 🔒 VALIDACIÓN CORREGIDA DE SALDO INSUFICIENTE (ANTES DEL PASO 3) - if ((tipo === "Gasto" || tipo === "Transferencia") && metodo === "Tarjeta") { + if ((tipo === 'Gasto' || tipo === 'Transferencia') && metodo === 'Tarjeta') { var persona = SC_Personas[personaId]; var currentBalance = parseFloat(persona.Monedero_Balance || 0); - + if (currentBalance < monto) { alert( `❌ Saldo insuficiente\n\nSaldo actual: ${currentBalance.toFixed(2)}€\nMonto requerido: ${monto.toFixed(2)}€` @@ -283,79 +339,79 @@ PAGES.pagos = { return; // ⛔ NO pasar al paso 3 } } - + // Move to step 3 - confirmation document.getElementById('step2').style.display = 'none'; document.getElementById('step3').style.display = 'block'; document.getElementById('stepIndicator').innerText = '3'; - + var monto = parseFloat(document.getElementById(numpad_display).value); document.getElementById('confirmAmount').innerText = monto.toFixed(2) + '€'; - + // Populate confirmation data var tipoText = document.getElementById(field_tipo).selectedOptions[0].text; var metodoText = document.getElementById(field_metodo).selectedOptions[0].text; var personaName = SC_Personas[personaId]?.Nombre || personaId; - var notas = document.getElementById(field_notas).value || "(sin notas)"; - + var notas = document.getElementById(field_notas).value || '(sin notas)'; + document.getElementById('confirmTipo').innerText = tipoText; document.getElementById('confirmMetodo').innerText = metodoText; document.getElementById('confirmPersona').innerText = personaName; document.getElementById('confirmNotas').innerText = notas; - + if (tipo === 'Transferencia') { var personaDestinoId = document.getElementById(field_persona_destino).value; var personaDestinoName = SC_Personas[personaDestinoId]?.Nombre || personaDestinoId; document.getElementById('confirmPersonaDestinoName').innerText = personaDestinoName; document.getElementById('confirmPersonaDestino').style.display = 'block'; } - + // Switch to final button layout document.getElementById('buttonContainer').style.display = 'none'; document.getElementById('buttonContainerFinal').style.display = 'grid'; - + currentStep = 3; } }; - + // Confirm final transaction button - document.getElementById(btn_confirm + "2").onclick = () => { + document.getElementById(btn_confirm + '2').onclick = () => { // Disable button to prevent double-clicking - var confirmBtn = document.getElementById(btn_confirm + "2"); + var confirmBtn = document.getElementById(btn_confirm + '2'); if (confirmBtn.disabled) return; confirmBtn.disabled = true; - confirmBtn.style.opacity = "0.5"; + confirmBtn.style.opacity = '0.5'; processTransaction(); }; - + // Back button document.getElementById(btn_back).onclick = () => { if (currentStep === 3) { // Go back to step 2 - document.getElementById("step3").style.display = "none"; - document.getElementById("step2").style.display = "block"; - document.getElementById("stepIndicator").innerText = "2"; - document.getElementById("buttonContainer").style.display = "grid"; - document.getElementById("buttonContainerFinal").style.display = "none"; + document.getElementById('step3').style.display = 'none'; + document.getElementById('step2').style.display = 'block'; + document.getElementById('stepIndicator').innerText = '2'; + document.getElementById('buttonContainer').style.display = 'grid'; + document.getElementById('buttonContainerFinal').style.display = 'none'; currentStep = 2; } }; - + // Cancel button document.getElementById(btn_cancel).onclick = () => { - if (confirm("¿Seguro que quieres cancelar esta transacción?")) { - setUrlHash("pagos"); + if (confirm('¿Seguro que quieres cancelar esta transacción?')) { + setUrlHash('pagos'); } }; - + // QR Scanner button document.getElementById(scan_qr_btn).onclick = () => { - setUrlHash("pagos,scan_qr"); + setUrlHash('pagos,scan_qr'); }; - + function loadPersonaSelector() { - var container = document.querySelector("#personaSelector"); - container.innerHTML = ""; + var container = document.querySelector('#personaSelector'); + container.innerHTML = ''; document.getElementById(field_persona).value = selectedPersona; addCategory_Personas( container, @@ -365,17 +421,16 @@ PAGES.pagos = { document.getElementById(field_persona).value = personaId; selectedPersona = personaId; }, - "Monedero", + 'Monedero', false, - "- No hay personas registradas -" + '- No hay personas registradas -' ); } - + function loadPersonaDestinoSelector() { - var container = document.querySelector("#personaDestinoSelector"); - container.innerHTML = ""; - document.getElementById(field_persona_destino).value = - selectedPersonaDestino; + var container = document.querySelector('#personaDestinoSelector'); + container.innerHTML = ''; + document.getElementById(field_persona_destino).value = selectedPersonaDestino; addCategory_Personas( container, SC_Personas, @@ -384,40 +439,38 @@ PAGES.pagos = { document.getElementById(field_persona_destino).value = personaId; selectedPersonaDestino = personaId; }, - "Monedero Destino", + 'Monedero Destino', false, - "- No hay personas registradas -" + '- No hay personas registradas -' ); } - + function processTransaction() { var tipo = document.getElementById(field_tipo).value; var monto = parseFloat(document.getElementById(numpad_display).value); var personaId = document.getElementById(field_persona).value; var metodo = document.getElementById(field_metodo).value; var notas = document.getElementById(field_notas).value; - + if (!personaId) { - alert("Por favor selecciona un monedero"); + alert('Por favor selecciona un monedero'); return; } - - if (tipo === "Transferencia") { - var personaDestinoId = document.getElementById( - field_persona_destino - ).value; + + if (tipo === 'Transferencia') { + var personaDestinoId = document.getElementById(field_persona_destino).value; if (!personaDestinoId) { - alert("Por favor selecciona el monedero destino"); + alert('Por favor selecciona el monedero destino'); return; } if (personaId === personaDestinoId) { - alert("No puedes transferir al mismo monedero"); + alert('No puedes transferir al mismo monedero'); return; } } - + // Create transaction - var ticketId = safeuuid(""); + var ticketId = safeuuid(''); var transactionData = { Ticket: ticketId, Fecha: CurrentISOTime(), @@ -426,15 +479,13 @@ PAGES.pagos = { Persona: personaId, Metodo: metodo, Notas: notas, - Estado: "Completado", + Estado: 'Completado', }; - - if (tipo === "Transferencia") { - transactionData.PersonaDestino = document.getElementById( - field_persona_destino - ).value; + + if (tipo === 'Transferencia') { + transactionData.PersonaDestino = document.getElementById(field_persona_destino).value; } - + // Add prefilled data if exists if (prefilledData.origen) { transactionData.Origen = prefilledData.origen; @@ -442,16 +493,16 @@ PAGES.pagos = { if (prefilledData.origen_id) { transactionData.OrigenID = prefilledData.origen_id; } - + // Update wallet balances // Don't update balance for Efectivo Gastos (paying with cash) - var shouldUpdateBalance = !(tipo === "Gasto" && metodo === "Efectivo"); - + var shouldUpdateBalance = !(tipo === 'Gasto' && metodo === 'Efectivo'); + if (shouldUpdateBalance) { updateWalletBalance(personaId, tipo, monto, () => { - if (tipo === "Transferencia") { + if (tipo === 'Transferencia') { var destinoId = transactionData.PersonaDestino; - updateWalletBalance(destinoId, "Ingreso", monto, () => { + updateWalletBalance(destinoId, 'Ingreso', monto, () => { saveTransaction(ticketId, transactionData); }); } else { @@ -463,66 +514,73 @@ PAGES.pagos = { saveTransaction(ticketId, transactionData); } } - + function updateWalletBalance(personaId, tipo, monto, callback) { var persona = SC_Personas[personaId]; if (!persona) { - alert("Error: Persona no encontrada"); + alert('Error: Persona no encontrada'); return; } - + var currentBalance = parseFloat(persona.Monedero_Balance || 0); var newBalance = currentBalance; - - if (tipo === "Ingreso") { + + if (tipo === 'Ingreso') { newBalance = currentBalance + monto; - } else if (tipo === "Gasto" || tipo === "Transferencia") { + } else if (tipo === 'Gasto' || tipo === 'Transferencia') { newBalance = currentBalance - monto; } - + persona.Monedero_Balance = fixfloat(newBalance); - DB.put('personas', personaId, persona).then(() => { - if (callback) callback(); - }).catch((e) => { console.warn('DB.put error', e); if (callback) callback(); }); + DB.put('personas', personaId, persona) + .then(() => { + if (callback) callback(); + }) + .catch((e) => { + console.warn('DB.put error', e); + if (callback) callback(); + }); } - + function saveTransaction(ticketId, data) { - document.getElementById("actionStatus").style.display = "block"; - DB.put('pagos', ticketId, data).then(() => { - // If this is from SuperCafé, update the order - if (data.Origen === "SuperCafé" && data.OrigenID) { - handleSuperCafePayment(data); - } - - // Check for promotional bonus on Ingreso transactions (Efectivo only) - if (data.Tipo === "Ingreso" && data.Metodo === "Efectivo") { - var bonusAmount = calculatePromoBonus(data.Monto); - if (bonusAmount > 0) { - createPromoBonusTransaction(data.Persona, bonusAmount, data.Monto); + document.getElementById('actionStatus').style.display = 'block'; + DB.put('pagos', ticketId, data) + .then(() => { + // If this is from SuperCafé, update the order + if (data.Origen === 'SuperCafé' && data.OrigenID) { + handleSuperCafePayment(data); } - } - toastr.success("¡Transacción completada!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("pagos," + ticketId); - }, SAVE_WAIT); - }).catch((e) => { - console.warn('DB.put error', e); - // Re-enable confirm button on error - var confirmBtn = document.getElementById(btn_confirm + "2"); - if (confirmBtn) { - confirmBtn.disabled = false; - confirmBtn.style.opacity = "1"; - } - document.getElementById("actionStatus").style.display = "none"; - toastr.error("Error al guardar la transacción"); - }); + // Check for promotional bonus on Ingreso transactions (Efectivo only) + if (data.Tipo === 'Ingreso' && data.Metodo === 'Efectivo') { + var bonusAmount = calculatePromoBonus(data.Monto); + if (bonusAmount > 0) { + createPromoBonusTransaction(data.Persona, bonusAmount, data.Monto); + } + } + + toastr.success('¡Transacción completada!'); + setTimeout(() => { + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('pagos,' + ticketId); + }, SAVE_WAIT); + }) + .catch((e) => { + console.warn('DB.put error', e); + // Re-enable confirm button on error + var confirmBtn = document.getElementById(btn_confirm + '2'); + if (confirmBtn) { + confirmBtn.disabled = false; + confirmBtn.style.opacity = '1'; + } + document.getElementById('actionStatus').style.display = 'none'; + toastr.error('Error al guardar la transacción'); + }); } - + function calculatePromoBonus(monto) { var amount = parseFloat(monto); - + if (amount >= 5) { return 0.2; // 20% bonus } else if (amount >= 4) { @@ -532,62 +590,62 @@ PAGES.pagos = { } else if (amount >= 2) { return 0.05; // 5% bonus } - + return 0; // No bonus for amounts under 2€ } - - function createPromoBonusTransaction( - personaId, - bonusAmount, - originalAmount - ) { + + function createPromoBonusTransaction(personaId, bonusAmount, originalAmount) { return; - var bonusTicketId = safeuuid(""); + var bonusTicketId = safeuuid(''); var bonusData = { Ticket: bonusTicketId, Fecha: CurrentISOTime(), - Tipo: "Ingreso", + Tipo: 'Ingreso', Monto: bonusAmount, Persona: personaId, - Metodo: "Efectivo", - Notas: "Promo Bono - " + + Metodo: 'Efectivo', + Notas: + 'Promo Bono - ' + bonusAmount.toFixed(2) + - "€ extra por recarga de " + + '€ extra por recarga de ' + originalAmount.toFixed(2) + - "€", - Estado: "Completado", - Origen: "Promo Bono", + '€', + Estado: 'Completado', + Origen: 'Promo Bono', }; - + // Update wallet balance with bonus var persona = SC_Personas[personaId]; if (persona) { var currentBalance = parseFloat(persona.Monedero_Balance || 0); var newBalance = currentBalance + bonusAmount; persona.Monedero_Balance = fixfloat(newBalance); - DB.put('personas', personaId, persona).catch((e) => { console.warn('DB.put error', e); }); + DB.put('personas', personaId, persona).catch((e) => { + console.warn('DB.put error', e); + }); } - + // Save bonus transaction - DB.put('pagos', bonusTicketId, bonusData).catch((e) => { console.warn('DB.put error', e); }); - - toastr.success( - "🎉 ¡Promo Bono aplicado! +" + bonusAmount.toFixed(2) + "€ extra" - ); + DB.put('pagos', bonusTicketId, bonusData).catch((e) => { + console.warn('DB.put error', e); + }); + + toastr.success('🎉 ¡Promo Bono aplicado! +' + bonusAmount.toFixed(2) + '€ extra'); } - + function handleSuperCafePayment(transactionData) { // Mark the SuperCafé order as paid and delete it DB.del('supercafe', transactionData.OrigenID).then(() => {}); - // Update persona points var persona = SC_Personas[transactionData.Persona]; if (!persona) return; - - DB.put('personas', transactionData.Persona, persona).catch((e) => { console.warn('DB.put error', e); }); + + DB.put('personas', transactionData.Persona, persona).catch((e) => { + console.warn('DB.put error', e); + }); } - + // Pre-fill if data provided if (prefilledData.monto) { displayValue = prefilledData.monto; @@ -600,31 +658,31 @@ PAGES.pagos = { document.getElementById(field_notas).value = prefilledData.notas; } }, - + // Edit/view transaction - edit: function(tid) { - if (!checkRole("pagos")) { - setUrlHash("pagos"); + edit: function (tid) { + if (!checkRole('pagos')) { + setUrlHash('pagos'); return; } - var tid2 = location.hash.split(","); - if (tid == "datafono") { + var tid2 = location.hash.split(','); + if (tid == 'datafono') { PAGES.pagos.datafono(); return; } - if (tid == "datafono_prefill") { + if (tid == 'datafono_prefill') { PAGES.pagos.datafono(JSON.parse(atob(tid2[2]))); return; } - if (tid == "scan_qr") { + if (tid == 'scan_qr') { PAGES.pagos.__scanQR(); return; } - if (tid == "edit_transaction") { + if (tid == 'edit_transaction') { PAGES.pagos.__editTransaction(tid2[2]); return; } - + var nameh1 = safeuuid(); var field_ticket = safeuuid(); var field_fecha = safeuuid(); @@ -643,388 +701,463 @@ PAGES.pagos = { var btn_edit = safeuuid(); var btn_delete = safeuuid(); var btn_revert = safeuuid(); - - container.innerHTML = ` + + container.innerHTML = html`

    Transacción

    - ${BuildQR("pagos," + tid, "Esta Transacción")} + ${BuildQR('pagos,' + tid, 'Esta Transacción')}
    Detalles de la Transacción - + - + - + - + - + - + - + - + - + - +
    - +
    Acciones - - -
    `; - + document.getElementById(btn_volver).onclick = () => { - setUrlHash("pagos"); + setUrlHash('pagos'); }; document.getElementById(btn_volver2).onclick = () => { - setUrlHash("supercafe"); + setUrlHash('supercafe'); }; - + (async () => { const data = await DB.get('pagos', tid); function load_data(data) { - console.log("Transaction data:", data); + console.log('Transaction data:', data); document.getElementById(nameh1).innerText = tid; document.getElementById(field_ticket).value = data.Ticket || tid; - - var fecha = data.Fecha || ""; + + var fecha = data.Fecha || ''; if (fecha) { var d = new Date(fecha); - document.getElementById(field_fecha).value = - d.toLocaleString("es-ES"); + document.getElementById(field_fecha).value = d.toLocaleString('es-ES'); } - - document.getElementById(field_tipo).value = data.Tipo || ""; - document.getElementById(field_monto).value = - (data.Monto || 0).toFixed(2) + "€"; - + + document.getElementById(field_tipo).value = data.Tipo || ''; + document.getElementById(field_monto).value = (data.Monto || 0).toFixed(2) + '€'; + var persona = SC_Personas[data.Persona] || {}; - document.getElementById(field_persona).value = - persona.Nombre || data.Persona || ""; - + document.getElementById(field_persona).value = persona.Nombre || data.Persona || ''; + if (data.PersonaDestino) { var personaDestino = SC_Personas[data.PersonaDestino] || {}; document.getElementById(field_persona_destino).value = - personaDestino.Nombre || data.PersonaDestino || ""; - document.getElementById(div_persona_destino).style.display = - "block"; + personaDestino.Nombre || data.PersonaDestino || ''; + document.getElementById(div_persona_destino).style.display = 'block'; } - - document.getElementById(field_metodo).value = data.Metodo || ""; - document.getElementById(field_estado).value = data.Estado || ""; - document.getElementById(field_notas).value = data.Notas || ""; - - if (data.Origen) { - document.getElementById(field_origen).value = - data.Origen + (data.OrigenID ? " (" + data.OrigenID + ")" : ""); - document.getElementById(div_origen).style.display = "block"; + + document.getElementById(field_metodo).value = data.Metodo || ''; + document.getElementById(field_estado).value = data.Estado || ''; + document.getElementById(field_notas).value = data.Notas || ''; + + if (data.Origen) { + document.getElementById(field_origen).value = + data.Origen + (data.OrigenID ? ' (' + data.OrigenID + ')' : ''); + document.getElementById(div_origen).style.display = 'block'; + } + + // Edit button - navigate to edit mode + document.getElementById(btn_edit).onclick = () => { + setUrlHash('pagos,edit_transaction,' + tid); + }; + + // Delete button + document.getElementById(btn_delete).onclick = () => { + if (!checkRole('pagos:edit')) { + alert('No tienes permisos para eliminar transacciones'); + return; } - - // Edit button - navigate to edit mode - document.getElementById(btn_edit).onclick = () => { - setUrlHash("pagos,edit_transaction," + tid); - }; - - // Delete button - document.getElementById(btn_delete).onclick = () => { - if (!checkRole("pagos:edit")) { - alert("No tienes permisos para eliminar transacciones"); - return; - } - - if ( - confirm( - "¿Estás seguro de que quieres ELIMINAR esta transacción?\n\nEsta acción NO se puede deshacer y los cambios en los monederos NO se revertirán automáticamente.\n\nPara revertir los cambios en los monederos, usa el botón 'Revertir Transacción' en su lugar." - ) - ) { - DB.del('pagos', tid).then(() => { - toastr.success("Transacción eliminada"); - setTimeout(() => { - setUrlHash("pagos"); - }, 1000); - }); - } - }; - - // Revert button - reverses wallet balance changes and deletes transaction - document.getElementById(btn_revert).onclick = () => { - if (!checkRole("pagos:edit")) { - alert("No tienes permisos para revertir transacciones"); - return; - } - - if ( - confirm( - "¿Estás seguro de que quieres REVERTIR esta transacción?\n\nEsto revertirá los cambios en los monederos y eliminará la transacción." - ) - ) { - // Reverse the wallet balance changes - var tipo = data.Tipo; - var monto = parseFloat(data.Monto || 0); - var personaId = data.Persona; - - // For Ingreso, subtract from balance (reverse) - // For Gasto, add to balance (reverse) - // For Transferencia, reverse both sides - - if (tipo === "Ingreso") { - revertWalletBalance(personaId, "Gasto", monto, () => { - deleteTransaction(tid); - }); - } else if (tipo === "Gasto") { - revertWalletBalance(personaId, "Ingreso", monto, () => { - deleteTransaction(tid); - }); - } else if (tipo === "Transferencia") { - var destinoId = data.PersonaDestino; - revertWalletBalance(personaId, "Ingreso", monto, () => { - revertWalletBalance(destinoId, "Gasto", monto, () => { - deleteTransaction(tid); - }); - }); - } - } - }; - - function revertWalletBalance(personaId, tipo, monto, callback) { - var persona = SC_Personas[personaId]; - if (!persona) { - toastr.error("Error: Persona no encontrada"); - return; - } - - var currentBalance = parseFloat(persona.Monedero_Balance || 0); - var newBalance = currentBalance; - - if (tipo === "Ingreso") { - newBalance = currentBalance + monto; - } else if (tipo === "Gasto") { - newBalance = currentBalance - monto; - } - - persona.Monedero_Balance = fixfloat(newBalance); - DB.put('personas', personaId, persona).then(() => { - if (callback) callback(); - }).catch((e) => { console.warn('DB.put error', e); if (callback) callback(); }); - } - - function deleteTransaction(transactionKey) { - DB.del('pagos', transactionKey).then(() => { - toastr.success("Transacción revertida y eliminada"); + + if ( + confirm( + "¿Estás seguro de que quieres ELIMINAR esta transacción?\n\nEsta acción NO se puede deshacer y los cambios en los monederos NO se revertirán automáticamente.\n\nPara revertir los cambios en los monederos, usa el botón 'Revertir Transacción' en su lugar." + ) + ) { + DB.del('pagos', tid).then(() => { + toastr.success('Transacción eliminada'); setTimeout(() => { - setUrlHash("pagos"); + setUrlHash('pagos'); }, 1000); }); } + }; + + // Revert button - reverses wallet balance changes and deletes transaction + document.getElementById(btn_revert).onclick = () => { + if (!checkRole('pagos:edit')) { + alert('No tienes permisos para revertir transacciones'); + return; + } + + if ( + confirm( + '¿Estás seguro de que quieres REVERTIR esta transacción?\n\nEsto revertirá los cambios en los monederos y eliminará la transacción.' + ) + ) { + // Reverse the wallet balance changes + var tipo = data.Tipo; + var monto = parseFloat(data.Monto || 0); + var personaId = data.Persona; + + // For Ingreso, subtract from balance (reverse) + // For Gasto, add to balance (reverse) + // For Transferencia, reverse both sides + + if (tipo === 'Ingreso') { + revertWalletBalance(personaId, 'Gasto', monto, () => { + deleteTransaction(tid); + }); + } else if (tipo === 'Gasto') { + revertWalletBalance(personaId, 'Ingreso', monto, () => { + deleteTransaction(tid); + }); + } else if (tipo === 'Transferencia') { + var destinoId = data.PersonaDestino; + revertWalletBalance(personaId, 'Ingreso', monto, () => { + revertWalletBalance(destinoId, 'Gasto', monto, () => { + deleteTransaction(tid); + }); + }); + } + } + }; + + function revertWalletBalance(personaId, tipo, monto, callback) { + var persona = SC_Personas[personaId]; + if (!persona) { + toastr.error('Error: Persona no encontrada'); + return; + } + + var currentBalance = parseFloat(persona.Monedero_Balance || 0); + var newBalance = currentBalance; + + if (tipo === 'Ingreso') { + newBalance = currentBalance + monto; + } else if (tipo === 'Gasto') { + newBalance = currentBalance - monto; + } + + persona.Monedero_Balance = fixfloat(newBalance); + DB.put('personas', personaId, persona) + .then(() => { + if (callback) callback(); + }) + .catch((e) => { + console.warn('DB.put error', e); + if (callback) callback(); + }); } - - if (typeof data == "string") { - TS_decrypt(data, SECRET, load_data); - } else { - load_data(data || {}); + + function deleteTransaction(transactionKey) { + DB.del('pagos', transactionKey).then(() => { + toastr.success('Transacción revertida y eliminada'); + setTimeout(() => { + setUrlHash('pagos'); + }, 1000); + }); } - })(); + } + + if (typeof data == 'string') { + TS_decrypt(data, SECRET, load_data); + } else { + load_data(data || {}); + } + })(); }, - + // Main index view with transaction log - index: function() { - if (!checkRole("pagos")) { - setUrlHash("index"); + index: function () { + if (!checkRole('pagos')) { + setUrlHash('index'); return; } - + var btn_datafono = safeuuid(); var total_ingresos = safeuuid(); var total_gastos = safeuuid(); var balance_total = safeuuid(); - - container.innerHTML = ` + + container.innerHTML = html`

    💳 Pagos y Transacciones

    - -
    -
    + +
    +

    Total Ingresos

    -
    0.00€
    +
    + 0.00€ +
    -
    +

    Total Gastos

    -
    0.00€
    +
    + 0.00€ +
    -
    +

    Balance Total

    -
    0.00€
    +
    + 0.00€ +
    - - - + + +

    Registro de Transacciones

    `; - + var totals = { ingresos: 0, gastos: 0 }; - + const config = [ - { - key: "Fecha", - label: "Fecha/Hora", - type: "template", - template: (data, element) => { - if (data.Fecha) { - var d = new Date(data.Fecha); - element.innerText = d.toLocaleString("es-ES"); - } + { + key: 'Fecha', + label: 'Fecha/Hora', + type: 'template', + template: (data, element) => { + if (data.Fecha) { + var d = new Date(data.Fecha); + element.innerText = d.toLocaleString('es-ES'); + } + }, + default: '', }, - default: "", - }, - { - key: "Tipo", - label: "Tipo", - type: "template", - template: (data, element) => { - var tipo = data.Tipo || ""; - var icon = ""; - var color = ""; - - if (tipo === "Ingreso") { - icon = "💵"; - color = "#2ed573"; - } else if (tipo === "Gasto") { - icon = "💸"; - color = "#ff4757"; - } else if (tipo === "Transferencia") { - icon = "🔄"; - color = "#667eea"; - } - - element.innerHTML = `${icon} ${tipo}`; + { + key: 'Tipo', + label: 'Tipo', + type: 'template', + template: (data, element) => { + var tipo = data.Tipo || ''; + var icon = ''; + var color = ''; + + if (tipo === 'Ingreso') { + icon = '💵'; + color = '#2ed573'; + } else if (tipo === 'Gasto') { + icon = '💸'; + color = '#ff4757'; + } else if (tipo === 'Transferencia') { + icon = '🔄'; + color = '#667eea'; + } + + element.innerHTML = html`${icon} ${tipo}`; + }, + default: '', }, - default: "", - }, - { - key: "Monto", - label: "Monto", - type: "template", - template: (data, element) => { - var monto = parseFloat(data.Monto || 0); - var color = data.Tipo === "Ingreso" ? "#2ed573" : "#ff4757"; - element.innerHTML = `${monto.toFixed( - 2 - )}€`; + { + key: 'Monto', + label: 'Monto', + type: 'template', + template: (data, element) => { + var monto = parseFloat(data.Monto || 0); + var color = data.Tipo === 'Ingreso' ? '#2ed573' : '#ff4757'; + element.innerHTML = html`${monto.toFixed(2)}€`; + }, + default: '0.00€', }, - default: "0.00€", - }, - { - key: "Persona", - label: "Monedero", - type: "persona-nombre", - default: "", - }, - { - key: "Metodo", - label: "Método", - type: "text", - default: "", - }, - { - key: "Estado", - label: "Estado", - type: "template", - template: (data, element) => { - var estado = data.Estado || "Pendiente"; - var color = estado === "Completado" ? "#2ed573" : "#ffa502"; - element.innerHTML = `${estado}`; + { + key: 'Persona', + label: 'Monedero', + type: 'persona-nombre', + default: '', }, - default: "Pendiente", - }, ]; - + { + key: 'Metodo', + label: 'Método', + type: 'text', + default: '', + }, + { + key: 'Estado', + label: 'Estado', + type: 'template', + template: (data, element) => { + var estado = data.Estado || 'Pendiente'; + var color = estado === 'Completado' ? '#2ed573' : '#ffa502'; + element.innerHTML = html`${estado}`; + }, + default: 'Pendiente', + }, + ]; + // Persistent totals object by ID let totalData = { ingresos: {}, // { id: monto } gastos: {}, // { id: monto } }; var balance_real = 0; - EventListeners.Interval.push(setInterval(() => { - balance_real = 0; - Object.values(SC_Personas).forEach((persona) => { - balance_real += parseFloat(persona.Monedero_Balance || 0); - }); - document.getElementById(balance_total).innerText = - balance_real.toFixed(2) + "€"; - document.getElementById(balance_total).style.color = - balance_real >= 0 ? "white" : "#ffcccc"; - }, 1000)); + EventListeners.Interval.push( + setInterval(() => { + balance_real = 0; + Object.values(SC_Personas).forEach((persona) => { + balance_real += parseFloat(persona.Monedero_Balance || 0); + }); + document.getElementById(balance_total).innerText = balance_real.toFixed(2) + '€'; + document.getElementById(balance_total).style.color = + balance_real >= 0 ? 'white' : '#ffcccc'; + }, 1000) + ); TS_IndexElement( - "pagos", + 'pagos', config, 'pagos', - document.getElementById("tableContainer"), + document.getElementById('tableContainer'), (data, new_tr) => { var id = data._key; - + const monto = parseFloat(data.Monto || 0) || 0; const tipo = data.Tipo; - const metodo = data.Metodo || ""; - + const metodo = data.Metodo || ''; + // Count all Ingresos and Gastos in totals (excluding Transferencias) // Reset entries on every call for this ID - if (tipo === "Ingreso") { - if (data.Origen != "Promo Bono") { + if (tipo === 'Ingreso') { + if (data.Origen != 'Promo Bono') { totalData.gastos[id] = 0; totalData.ingresos[id] = monto; } - } else if (tipo === "Gasto") { - if (metodo != "Tarjeta") { + } else if (tipo === 'Gasto') { + if (metodo != 'Tarjeta') { totalData.ingresos[id] = 0; totalData.gastos[id] = monto; } @@ -1033,122 +1166,117 @@ PAGES.pagos = { totalData.ingresos[id] = 0; totalData.gastos[id] = 0; } - + // Compute totals by summing all objects - const totalIngresos = Object.values(totalData.ingresos).reduce( - (a, b) => a + b, - 0 - ); - const totalGastos = Object.values(totalData.gastos).reduce( - (a, b) => a + b, - 0 - ); + const totalIngresos = Object.values(totalData.ingresos).reduce((a, b) => a + b, 0); + const totalGastos = Object.values(totalData.gastos).reduce((a, b) => a + b, 0); const balance = totalIngresos - totalGastos; - + // Update UI - document.getElementById(total_ingresos).innerText = - totalIngresos.toFixed(2) + "€"; - document.getElementById(total_gastos).innerText = - totalGastos.toFixed(2) + "€"; + document.getElementById(total_ingresos).innerText = totalIngresos.toFixed(2) + '€'; + document.getElementById(total_gastos).innerText = totalGastos.toFixed(2) + '€'; } ); - + document.getElementById(btn_datafono).onclick = () => { - setUrlHash("pagos,datafono"); + setUrlHash('pagos,datafono'); }; - - if (!checkRole("pagos:edit")) { - document.getElementById(btn_datafono).style.display = "none"; + + if (!checkRole('pagos:edit')) { + document.getElementById(btn_datafono).style.display = 'none'; } }, - + // QR Scanner for selecting wallet/persona - __scanQR: function() { - if (!checkRole("pagos:edit")) { - setUrlHash("pagos"); + __scanQR: function () { + if (!checkRole('pagos:edit')) { + setUrlHash('pagos'); return; } - + var qrscan = safeuuid(); var btn_cancel = safeuuid(); - - container.innerHTML = ` -
    + + container.innerHTML = html` +

    📷 Escanear QR de Monedero

    - +

    Escanea el código QR del monedero de la persona para seleccionarlo automáticamente

    - -
    `; - + // Initialize QR scanner var html5QrcodeScanner = new Html5QrcodeScanner(qrscan, { fps: 10, qrbox: 250, }); - + function onScanSuccess(decodedText, decodedResult) { html5QrcodeScanner.clear(); - + // Parse the QR code result // Expected format: "personas,{personaId}" or just "{personaId}" var personaId = decodedText; - + // If it's a full URL hash, extract the persona ID - if (decodedText.includes("personas,")) { - var parts = decodedText.split(","); + if (decodedText.includes('personas,')) { + var parts = decodedText.split(','); if (parts.length > 1) { personaId = parts[1]; } } - + // Verify the persona exists if (SC_Personas[personaId]) { - toastr.success( - "✅ Monedero escaneado: " + SC_Personas[personaId].Nombre - ); - + toastr.success('✅ Monedero escaneado: ' + SC_Personas[personaId].Nombre); + // Store the selected persona in sessionStorage and return to datafono - sessionStorage.setItem("pagos_scanned_persona", personaId); - + sessionStorage.setItem('pagos_scanned_persona', personaId); + // Navigate back to datafono - setUrlHash("pagos,datafono"); + setUrlHash('pagos,datafono'); } else { - toastr.error("❌ Código QR no reconocido como un monedero válido"); + toastr.error('❌ Código QR no reconocido como un monedero válido'); setTimeout(() => { - setUrlHash("pagos,datafono"); + setUrlHash('pagos,datafono'); }, 2000); } } - + html5QrcodeScanner.render(onScanSuccess); EventListeners.QRScanner.push(html5QrcodeScanner); - + // Cancel button document.getElementById(btn_cancel).onclick = () => { html5QrcodeScanner.clear(); - setUrlHash("pagos,datafono"); + setUrlHash('pagos,datafono'); }; }, - + // Edit existing transaction - __editTransaction: function(transactionId) { - if (!checkRole("pagos:edit")) { - setUrlHash("pagos"); + __editTransaction: function (transactionId) { + if (!checkRole('pagos:edit')) { + setUrlHash('pagos'); return; } - + var field_tipo = safeuuid(); var field_monto = safeuuid(); var field_persona = safeuuid(); @@ -1159,137 +1287,159 @@ PAGES.pagos = { var div_persona_destino = safeuuid(); var btn_save = safeuuid(); var btn_cancel = safeuuid(); - - var selectedPersona = ""; - var selectedPersonaDestino = ""; + + var selectedPersona = ''; + var selectedPersonaDestino = ''; var originalData = null; - - container.innerHTML = ` -
    + + container.innerHTML = html` +

    ✏️ Editar Transacción

    - +
    Información de Transacción - + - + - + - + - + - + - +
    - +
    - -
    `; - + // Load transaction data (async () => { const data = await DB.get('pagos', transactionId); function loadTransactionData(data) { originalData = data; - - document.getElementById(field_tipo).value = data.Tipo || "Ingreso"; - document.getElementById(field_metodo).value = - data.Metodo || "Efectivo"; + + document.getElementById(field_tipo).value = data.Tipo || 'Ingreso'; + document.getElementById(field_metodo).value = data.Metodo || 'Efectivo'; document.getElementById(field_monto).value = data.Monto || 0; - document.getElementById(field_estado).value = - data.Estado || "Completado"; - document.getElementById(field_notas).value = data.Notas || ""; - - selectedPersona = data.Persona || ""; - selectedPersonaDestino = data.PersonaDestino || ""; - + document.getElementById(field_estado).value = data.Estado || 'Completado'; + document.getElementById(field_notas).value = data.Notas || ''; + + selectedPersona = data.Persona || ''; + selectedPersonaDestino = data.PersonaDestino || ''; + loadPersonaSelector(); - - if (data.Tipo === "Transferencia") { - document.getElementById(div_persona_destino).style.display = - "block"; + + if (data.Tipo === 'Transferencia') { + document.getElementById(div_persona_destino).style.display = 'block'; loadPersonaDestinoSelector(); } - } - - if (typeof data == "string") { - TS_decrypt(data, SECRET, loadTransactionData); - } else { - loadTransactionData(data || {}); - } - })(); - + } + + if (typeof data == 'string') { + TS_decrypt(data, SECRET, loadTransactionData); + } else { + loadTransactionData(data || {}); + } + })(); + // Tipo change handler - document.getElementById(field_tipo).addEventListener("change", function() { + document.getElementById(field_tipo).addEventListener('change', function () { var tipo = this.value; var divDestino = document.getElementById(div_persona_destino); - if (tipo === "Transferencia") { - divDestino.style.display = "block"; + if (tipo === 'Transferencia') { + divDestino.style.display = 'block'; loadPersonaDestinoSelector(); } else { - divDestino.style.display = "none"; + divDestino.style.display = 'none'; } }); - + function loadPersonaSelector() { - var container = document.querySelector("#personaSelector"); - container.innerHTML = ""; + var container = document.querySelector('#personaSelector'); + container.innerHTML = ''; document.getElementById(field_persona).value = selectedPersona; addCategory_Personas( container, @@ -1299,17 +1449,16 @@ PAGES.pagos = { document.getElementById(field_persona).value = personaId; selectedPersona = personaId; }, - "Monedero", + 'Monedero', false, - "- No hay personas registradas -" + '- No hay personas registradas -' ); } - + function loadPersonaDestinoSelector() { - var container = document.querySelector("#personaDestinoSelector"); - container.innerHTML = ""; - document.getElementById(field_persona_destino).value = - selectedPersonaDestino; + var container = document.querySelector('#personaDestinoSelector'); + container.innerHTML = ''; + document.getElementById(field_persona_destino).value = selectedPersonaDestino; addCategory_Personas( container, SC_Personas, @@ -1318,60 +1467,58 @@ PAGES.pagos = { document.getElementById(field_persona_destino).value = personaId; selectedPersonaDestino = personaId; }, - "Monedero Destino", + 'Monedero Destino', false, - "- No hay personas registradas -" + '- No hay personas registradas -' ); } - + // Save button document.getElementById(btn_save).onclick = () => { // Disable button to prevent double-clicking var saveBtn = document.getElementById(btn_save); if (saveBtn.disabled) return; - + var tipo = document.getElementById(field_tipo).value; var monto = parseFloat(document.getElementById(field_monto).value); var personaId = document.getElementById(field_persona).value; var metodo = document.getElementById(field_metodo).value; var notas = document.getElementById(field_notas).value; var estado = document.getElementById(field_estado).value; - + if (!personaId) { - alert("Por favor selecciona un monedero"); + alert('Por favor selecciona un monedero'); return; } - + if (isNaN(monto) || monto < 0) { - alert("Por favor ingresa un monto válido"); + alert('Por favor ingresa un monto válido'); return; } - - if (tipo === "Transferencia") { - var personaDestinoId = document.getElementById( - field_persona_destino - ).value; + + if (tipo === 'Transferencia') { + var personaDestinoId = document.getElementById(field_persona_destino).value; if (!personaDestinoId) { - alert("Por favor selecciona el monedero destino"); + alert('Por favor selecciona el monedero destino'); return; } if (personaId === personaDestinoId) { - alert("No puedes transferir al mismo monedero"); + alert('No puedes transferir al mismo monedero'); return; } } - + if ( !confirm( - "¿Estás seguro de que quieres guardar los cambios?\n\nNOTA: Los cambios en los monederos NO se ajustarán automáticamente. Si cambiaste el monto, tipo o persona, deberías revertir la transacción original y crear una nueva." + '¿Estás seguro de que quieres guardar los cambios?\n\nNOTA: Los cambios en los monederos NO se ajustarán automáticamente. Si cambiaste el monto, tipo o persona, deberías revertir la transacción original y crear una nueva.' ) ) { return; } - + saveBtn.disabled = true; - saveBtn.style.opacity = "0.5"; - + saveBtn.style.opacity = '0.5'; + // Update transaction data var updatedData = { ...originalData, @@ -1382,38 +1529,36 @@ PAGES.pagos = { Notas: notas, Estado: estado, }; - - if (tipo === "Transferencia") { - updatedData.PersonaDestino = document.getElementById( - field_persona_destino - ).value; + + if (tipo === 'Transferencia') { + updatedData.PersonaDestino = document.getElementById(field_persona_destino).value; } else { delete updatedData.PersonaDestino; } - - document.getElementById("actionStatus").style.display = "block"; - DB.put('pagos', transactionId, updatedData).then(() => { - toastr.success("¡Transacción actualizada!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("pagos," + transactionId); - }, SAVE_WAIT); - }).catch((e) => { - console.warn('DB.put error', e); - // Re-enable save button on error - saveBtn.disabled = false; - saveBtn.style.opacity = "1"; - document.getElementById("actionStatus").style.display = "none"; - toastr.error("Error al actualizar la transacción"); - }); + + document.getElementById('actionStatus').style.display = 'block'; + DB.put('pagos', transactionId, updatedData) + .then(() => { + toastr.success('¡Transacción actualizada!'); + setTimeout(() => { + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('pagos,' + transactionId); + }, SAVE_WAIT); + }) + .catch((e) => { + console.warn('DB.put error', e); + // Re-enable save button on error + saveBtn.disabled = false; + saveBtn.style.opacity = '1'; + document.getElementById('actionStatus').style.display = 'none'; + toastr.error('Error al actualizar la transacción'); + }); }; - + // Cancel button document.getElementById(btn_cancel).onclick = () => { - if ( - confirm("¿Seguro que quieres cancelar? Los cambios no se guardarán.") - ) { - setUrlHash("pagos," + transactionId); + if (confirm('¿Seguro que quieres cancelar? Los cambios no se guardarán.')) { + setUrlHash('pagos,' + transactionId); } }; }, diff --git a/src/page/personas.js b/src/page/personas.js index 99060a8..bd17fe0 100644 --- a/src/page/personas.js +++ b/src/page/personas.js @@ -1,13 +1,13 @@ -PERMS["personas"] = "Personas"; -PERMS["personas:edit"] = "> Editar"; +PERMS['personas'] = 'Personas'; +PERMS['personas:edit'] = '> Editar'; PAGES.personas = { - navcss: "btn3", - icon: "static/appico/users.png", + navcss: 'btn3', + icon: 'static/appico/users.png', AccessControl: true, - Title: "Personas", + Title: 'Personas', edit: function (mid) { - if (!checkRole("personas:edit")) { - setUrlHash("personas"); + if (!checkRole('personas:edit')) { + setUrlHash('personas'); return; } var nameh1 = safeuuid(); @@ -23,9 +23,9 @@ PAGES.personas = { var btn_guardar = safeuuid(); var btn_borrar = safeuuid(); var btn_ver_monedero = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

    Persona

    - ${BuildQR("personas," + mid, "Esta Persona")} + ${BuildQR('personas,' + mid, 'Esta Persona')}
    `; - var resized = ""; + var resized = ''; var pdel = document.getElementById(permisosdet); DB.get('personas', mid).then((data) => { - function load_data(data, ENC = "") { + function load_data(data, ENC = '') { document.getElementById(nameh1).innerText = mid; - var pot = "
      "; + var pot = '
        '; Object.entries(PERMS).forEach((page) => { - var c = ""; - if ((data["Roles"] || ",").split(",").includes(page[0])) { - c = "checked"; + var c = ''; + if ((data['Roles'] || ',').split(',').includes(page[0])) { + c = 'checked'; } pot += `
      • `; }); - pdel.innerHTML = pot + "
      "; - document.getElementById(field_nombre).value = data["Nombre"] || ""; - document.getElementById(field_zona).value = data["Region"] || ""; - document.getElementById(field_anilla).value = data["SC_Anilla"] || ""; + pdel.innerHTML = pot + '
    '; + document.getElementById(field_nombre).value = data['Nombre'] || ''; + document.getElementById(field_zona).value = data['Region'] || ''; + document.getElementById(field_anilla).value = data['SC_Anilla'] || ''; // set fallback image immediately - document.getElementById(render_foto).src = data["Foto"] || "static/ico/user_generic.png"; - resized = data["Foto"] || "static/ico/user_generic.png"; + document.getElementById(render_foto).src = data['Foto'] || 'static/ico/user_generic.png'; + resized = data['Foto'] || 'static/ico/user_generic.png'; // try to load attachment 'foto' if present (preferred storage) - DB.getAttachment('personas', mid, 'foto').then((durl) => { - if (durl) { - document.getElementById(render_foto).src = durl; - resized = durl; - } - }).catch(() => {}); - document.getElementById(field_notas).value = data["markdown"] || ""; - document.getElementById(field_monedero_balance).value = - data["Monedero_Balance"] || 0; - document.getElementById(field_monedero_notas).value = - data["Monedero_Notas"] || ""; + DB.getAttachment('personas', mid, 'foto') + .then((durl) => { + if (durl) { + document.getElementById(render_foto).src = durl; + resized = durl; + } + }) + .catch(() => {}); + document.getElementById(field_notas).value = data['markdown'] || ''; + document.getElementById(field_monedero_balance).value = data['Monedero_Balance'] || 0; + document.getElementById(field_monedero_notas).value = data['Monedero_Notas'] || ''; } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - load_data(data, "%E"); - }, 'personas', mid); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + load_data(data, '%E'); + }, + 'personas', + mid + ); } else { load_data(data || {}); } }); - document.getElementById(field_foto).addEventListener("change", function (e) { + document.getElementById(field_foto).addEventListener('change', function (e) { const file = e.target.files[0]; if (!file) return; // Do NOT resize — keep original uploaded image @@ -150,76 +156,78 @@ PAGES.personas = { // Disable button to prevent double-clicking var guardarBtn = document.getElementById(btn_guardar); if (guardarBtn.disabled) return; - + guardarBtn.disabled = true; - guardarBtn.style.opacity = "0.5"; - + guardarBtn.style.opacity = '0.5'; + var dt = new FormData(pdel); var data = { Nombre: document.getElementById(field_nombre).value, Region: document.getElementById(field_zona).value, - Roles: dt.getAll("perm").join(",") + ",", + Roles: dt.getAll('perm').join(',') + ',', SC_Anilla: document.getElementById(field_anilla).value, // Foto moved to PouchDB attachment named 'foto' markdown: document.getElementById(field_notas).value, - Monedero_Balance: - parseFloat(document.getElementById(field_monedero_balance).value) || - 0, + Monedero_Balance: parseFloat(document.getElementById(field_monedero_balance).value) || 0, Monedero_Notas: document.getElementById(field_monedero_notas).value, }; - document.getElementById("actionStatus").style.display = "block"; - DB.put('personas', mid, data).then(() => { - // if resized is a data URL (new/updated image), save as attachment - var attachPromise = Promise.resolve(true); - if (typeof resized === 'string' && resized.indexOf('data:') === 0) { - attachPromise = DB.putAttachment('personas', mid, 'foto', resized, 'image/png'); - } - attachPromise.then(() => { - toastr.success("Guardado!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("personas"); - }, SAVE_WAIT); - }).catch((e) => { - console.warn('putAttachment error', e); - document.getElementById("actionStatus").style.display = "none"; + document.getElementById('actionStatus').style.display = 'block'; + DB.put('personas', mid, data) + .then(() => { + // if resized is a data URL (new/updated image), save as attachment + var attachPromise = Promise.resolve(true); + if (typeof resized === 'string' && resized.indexOf('data:') === 0) { + attachPromise = DB.putAttachment('personas', mid, 'foto', resized, 'image/png'); + } + attachPromise + .then(() => { + toastr.success('Guardado!'); + setTimeout(() => { + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('personas'); + }, SAVE_WAIT); + }) + .catch((e) => { + console.warn('putAttachment error', e); + document.getElementById('actionStatus').style.display = 'none'; + guardarBtn.disabled = false; + guardarBtn.style.opacity = '1'; + toastr.error('Error al guardar la foto'); + }); + }) + .catch((e) => { + console.warn('DB.put error', e); + document.getElementById('actionStatus').style.display = 'none'; guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - toastr.error("Error al guardar la foto"); + guardarBtn.style.opacity = '1'; + toastr.error('Error al guardar la persona'); }); - }).catch((e) => { - console.warn('DB.put error', e); - document.getElementById("actionStatus").style.display = "none"; - guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - toastr.error("Error al guardar la persona"); - }); }; document.getElementById(btn_ver_monedero).onclick = () => { - setUrlHash("pagos"); // Navigate to pagos and show transactions for this person + setUrlHash('pagos'); // Navigate to pagos and show transactions for this person }; document.getElementById(btn_borrar).onclick = () => { - if (confirm("¿Quieres borrar esta persona?") == true) { + if (confirm('¿Quieres borrar esta persona?') == true) { DB.del('personas', mid).then(() => { - toastr.error("Borrado!"); + toastr.error('Borrado!'); setTimeout(() => { - setUrlHash("personas"); + setUrlHash('personas'); }, SAVE_WAIT); }); } }; }, index: function () { - if (!checkRole("personas")) { - setUrlHash("index"); + if (!checkRole('personas')) { + setUrlHash('index'); return; } var btn_new = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

    Personas

    - `; + `; const config = [ // { @@ -227,28 +235,28 @@ PAGES.personas = { // type: "persona", // self: true, // }, - { key: "Foto", label: "Foto", type: "attachment-persona", default: "", self: true }, - { key: "Nombre", label: "Nombre", type: "text", default: "" }, - { key: "Region", label: "Zona", type: "text", default: "" }, - { key: "Monedero_Balance", label: "Saldo Monedero", type: "moneda" }, + { key: 'Foto', label: 'Foto', type: 'attachment-persona', default: '', self: true }, + { key: 'Nombre', label: 'Nombre', type: 'text', default: '' }, + { key: 'Region', label: 'Zona', type: 'text', default: '' }, + { key: 'Monedero_Balance', label: 'Saldo Monedero', type: 'moneda' }, //{ key: "markdown", label: "Notas", type: "markdown", default: "" }, //{ key: "Roles", label: "Permisos", type: "text", default: "" } ]; TS_IndexElement( - "personas", + 'personas', config, - "personas", - document.getElementById("tableContainer"), + 'personas', + document.getElementById('tableContainer'), undefined, undefined, true // Enable global search bar ); - if (!checkRole("personas:edit")) { - document.getElementById(btn_new).style.display = "none"; + if (!checkRole('personas:edit')) { + document.getElementById(btn_new).style.display = 'none'; } else { document.getElementById(btn_new).onclick = () => { - setUrlHash("personas," + safeuuid("")); + setUrlHash('personas,' + safeuuid('')); }; } }, diff --git a/src/page/resumen_diario.js b/src/page/resumen_diario.js index 5959dc9..703200a 100644 --- a/src/page/resumen_diario.js +++ b/src/page/resumen_diario.js @@ -1,41 +1,67 @@ -PERMS["resumen_diario"] = "Resumen diario (Solo docentes!)"; +PERMS['resumen_diario'] = 'Resumen diario (Solo docentes!)'; PAGES.resumen_diario = { - icon: "static/appico/calendar.png", - navcss: "btn3", + icon: 'static/appico/calendar.png', + navcss: 'btn3', AccessControl: true, - Title: "Resumen Diario", + Title: 'Resumen Diario', index: function () { var data_Comedor = safeuuid(); var data_Tareas = safeuuid(); var data_Diario = safeuuid(); var data_Weather = safeuuid(); - if (!checkRole("resumen_diario")) { - setUrlHash("index"); + if (!checkRole('resumen_diario')) { + setUrlHash('index'); return; } - container.innerHTML = ` + container.innerHTML = html`

    Resumen Diario ${CurrentISODate()}

    -
    Menú Comedor:
    Cargando...
    -
    Tareas:
    Cargando...
    -
    Diario:
    Cargando...
    -
    Clima:
    +
    Menú Comedor:
    Cargando...
    +
    Tareas:
    +
    +Cargando...
    +
    +
    Diario:
    +
    +Cargando...
    +
    +
    Clima:
    `; //#region Cargar Clima // Get location from DB settings.weather_location; if missing ask user and save it // url format: https://wttr.in/?F0m - DB.get('settings','weather_location').then((loc) => { + DB.get('settings', 'weather_location').then((loc) => { if (!loc) { - loc = prompt("Introduce tu ubicación para el clima (ciudad, país):", "Madrid, Spain"); + loc = prompt('Introduce tu ubicación para el clima (ciudad, país):', 'Madrid, Spain'); if (loc) { - DB.put('settings','weather_location', loc); + DB.put('settings', 'weather_location', loc); } } if (loc) { - document.getElementById(data_Weather).src = "https://wttr.in/" + encodeURIComponent(loc) + "_IF0m_background=FFFFFF.png"; + document.getElementById(data_Weather).src = + 'https://wttr.in/' + encodeURIComponent(loc) + '_IF0m_background=FFFFFF.png'; } else { - document.getElementById(data_Weather).src = "https://wttr.in/_IF0m_background=FFFFFF.png"; + document.getElementById(data_Weather).src = 'https://wttr.in/_IF0m_background=FFFFFF.png'; } }); //#endregion Cargar Clima @@ -43,17 +69,20 @@ PAGES.resumen_diario = { DB.get('comedor', CurrentISODate()).then((data) => { function add_row(data) { // Fix newlines - data.Platos = data.Platos || "No hay platos registrados para hoy."; + data.Platos = data.Platos || 'No hay platos registrados para hoy.'; // Display platos - document.getElementById(data_Comedor).innerHTML = data.Platos.replace( - /\n/g, - "
    " - ); + document.getElementById(data_Comedor).innerHTML = data.Platos.replace(/\n/g, '
    '); } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - add_row(data || {}); - }, 'comedor', CurrentISODate()); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + add_row(data || {}); + }, + 'comedor', + CurrentISODate() + ); } else { add_row(data || {}); } @@ -63,17 +92,20 @@ PAGES.resumen_diario = { DB.get('notas', 'tareas').then((data) => { function add_row(data) { // Fix newlines - data.Contenido = data.Contenido || "No hay tareas."; + data.Contenido = data.Contenido || 'No hay tareas.'; // Display platos - document.getElementById(data_Tareas).innerHTML = data.Contenido.replace( - /\n/g, - "
    " - ); + document.getElementById(data_Tareas).innerHTML = data.Contenido.replace(/\n/g, '
    '); } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - add_row(data || {}); - }, 'notas', 'tareas'); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + add_row(data || {}); + }, + 'notas', + 'tareas' + ); } else { add_row(data || {}); } @@ -83,17 +115,20 @@ PAGES.resumen_diario = { DB.get('aulas_informes', 'diario-' + CurrentISODate()).then((data) => { function add_row(data) { // Fix newlines - data.Contenido = data.Contenido || "No hay un diario."; + data.Contenido = data.Contenido || 'No hay un diario.'; // Display platos - document.getElementById(data_Diario).innerHTML = data.Contenido.replace( - /\n/g, - "
    " - ); + document.getElementById(data_Diario).innerHTML = data.Contenido.replace(/\n/g, '
    '); } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - add_row(data || {}); - }, 'aulas_informes', 'diario-' + CurrentISODate()); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + add_row(data || {}); + }, + 'aulas_informes', + 'diario-' + CurrentISODate() + ); } else { add_row(data || {}); } diff --git a/src/page/supercafe.js b/src/page/supercafe.js index 5d51daf..ac5476c 100644 --- a/src/page/supercafe.js +++ b/src/page/supercafe.js @@ -1,13 +1,13 @@ -PERMS["supercafe"] = "Cafetería"; -PERMS["supercafe:edit"] = "> Editar"; +PERMS['supercafe'] = 'Cafetería'; +PERMS['supercafe:edit'] = '> Editar'; PAGES.supercafe = { - navcss: "btn4", - icon: "static/appico/cup.png", + navcss: 'btn4', + icon: 'static/appico/cup.png', AccessControl: true, - Title: "Cafetería", + Title: 'Cafetería', edit: function (mid) { - if (!checkRole("supercafe:edit")) { - setUrlHash("supercafe"); + if (!checkRole('supercafe:edit')) { + setUrlHash('supercafe'); return; } var nameh1 = safeuuid(); @@ -19,46 +19,46 @@ PAGES.supercafe = { var div_actions = safeuuid(); var btn_guardar = safeuuid(); var btn_borrar = safeuuid(); - container.innerHTML = ` + container.innerHTML = html`

    Comanda

    - Rellenar comanda - - - -
    - -
    - - - - + Rellenar comanda + + + +
    + +
    + + + +
    - `; + `; var currentData = {}; - var currentPersonaID = ""; + var currentPersonaID = ''; var divact = document.getElementById(div_actions); function loadActions() { - divact.innerHTML = ""; + divact.innerHTML = ''; addCategory_Personas(divact, SC_Personas, currentPersonaID, (value) => { document.getElementById(field_persona).value = value; }); @@ -77,23 +77,29 @@ PAGES.supercafe = { } loadActions(); DB.get('supercafe', mid).then((data) => { - function load_data(data, ENC = "") { + function load_data(data, ENC = '') { document.getElementById(nameh1).innerText = mid; - document.getElementById(field_fecha).value = data["Fecha"] || CurrentISODate(); - document.getElementById(field_persona).value = data["Persona"] || ""; - currentPersonaID = data["Persona"] || ""; + document.getElementById(field_fecha).value = data['Fecha'] || CurrentISODate(); + document.getElementById(field_persona).value = data['Persona'] || ''; + currentPersonaID = data['Persona'] || ''; document.getElementById(field_comanda).value = - SC_parse(JSON.parse(data["Comanda"] || "{}")) || ""; - document.getElementById(field_notas).value = data["Notas"] || ""; - document.getElementById(field_estado).value = data["Estado"] || "%%"; - currentData = JSON.parse(data["Comanda"] || "{}"); + SC_parse(JSON.parse(data['Comanda'] || '{}')) || ''; + document.getElementById(field_notas).value = data['Notas'] || ''; + document.getElementById(field_estado).value = data['Estado'] || '%%'; + currentData = JSON.parse(data['Comanda'] || '{}'); loadActions(); } - if (typeof data == "string") { - TS_decrypt(data, SECRET, (data, wasEncrypted) => { - load_data(data, "%E"); - }, 'supercafe', mid); + if (typeof data == 'string') { + TS_decrypt( + data, + SECRET, + (data, wasEncrypted) => { + load_data(data, '%E'); + }, + 'supercafe', + mid + ); } else { load_data(data || {}); } @@ -102,69 +108,69 @@ PAGES.supercafe = { // Check if button is already disabled to prevent double-clicking var guardarBtn = document.getElementById(btn_guardar); if (guardarBtn.disabled) return; - + // Validate before disabling button - if (document.getElementById(field_persona).value == "") { - alert("¡Hay que elegir una persona!"); + if (document.getElementById(field_persona).value == '') { + alert('¡Hay que elegir una persona!'); return; } - + // Disable button after validation passes guardarBtn.disabled = true; - guardarBtn.style.opacity = "0.5"; - + guardarBtn.style.opacity = '0.5'; + var data = { Fecha: document.getElementById(field_fecha).value, Persona: document.getElementById(field_persona).value, Comanda: JSON.stringify(currentData), Notas: document.getElementById(field_notas).value, - Estado: document - .getElementById(field_estado) - .value.replace("%%", "Pedido"), + Estado: document.getElementById(field_estado).value.replace('%%', 'Pedido'), }; - document.getElementById("actionStatus").style.display = "block"; - DB.put('supercafe', mid, data).then(() => { - toastr.success("Guardado!"); - setTimeout(() => { - document.getElementById("actionStatus").style.display = "none"; - setUrlHash("supercafe"); - }, SAVE_WAIT); - }).catch((e) => { - console.warn('DB.put error', e); - guardarBtn.disabled = false; - guardarBtn.style.opacity = "1"; - document.getElementById("actionStatus").style.display = "none"; - toastr.error("Error al guardar la comanda"); - }); + document.getElementById('actionStatus').style.display = 'block'; + DB.put('supercafe', mid, data) + .then(() => { + toastr.success('Guardado!'); + setTimeout(() => { + document.getElementById('actionStatus').style.display = 'none'; + setUrlHash('supercafe'); + }, SAVE_WAIT); + }) + .catch((e) => { + console.warn('DB.put error', e); + guardarBtn.disabled = false; + guardarBtn.style.opacity = '1'; + document.getElementById('actionStatus').style.display = 'none'; + toastr.error('Error al guardar la comanda'); + }); }; document.getElementById(btn_borrar).onclick = () => { if ( confirm( - "¿Quieres borrar esta comanda? - NO se actualizará el monedero de la persona asignada." + '¿Quieres borrar esta comanda? - NO se actualizará el monedero de la persona asignada.' ) == true ) { DB.del('supercafe', mid).then(() => { setTimeout(() => { - setUrlHash("supercafe"); + setUrlHash('supercafe'); }, SAVE_WAIT); }); } }; }, index: function () { - if (!checkRole("supercafe")) { - setUrlHash("index"); + if (!checkRole('supercafe')) { + setUrlHash('index'); return; } var tts = false; - var sc_nobtn = ""; - if (urlParams.get("sc_nobtn") == "yes") { - sc_nobtn = "pointer-events: none; opacity: 0.5"; + var sc_nobtn = ''; + if (urlParams.get('sc_nobtn') == 'yes') { + sc_nobtn = 'pointer-events: none; opacity: 0.5'; } var ev = setTimeout(() => { tts = true; - console.log("TTS Enabled"); - toastr.info("Texto a voz disponible"); + console.log('TTS Enabled'); + toastr.info('Texto a voz disponible'); }, 6500); EventListeners.Timeout.push(ev); const tablebody = safeuuid(); @@ -173,58 +179,64 @@ PAGES.supercafe = { var totalprecio = safeuuid(); var tts_check = safeuuid(); var old = {}; - container.innerHTML = ` + container.innerHTML = html`

    Cafetería - Total: 0c

    -
    +
    -
    +
    Todas las comandas
    -
    -
    +
    +
    Deudas
    - `; + `; var config = [ { - key: "Persona", - type: "persona", - default: "", - label: "Persona", + key: 'Persona', + type: 'persona', + default: '', + label: 'Persona', }, { - key: "Estado", - type: "comanda-status", - default: "", - label: "Estado", + key: 'Estado', + type: 'comanda-status', + default: '', + label: 'Estado', }, { - key: "Comanda", - type: "comanda", - default: "", - label: "Comanda", + key: 'Comanda', + type: 'comanda', + default: '', + label: 'Comanda', }, ]; - if (!checkRole("supercafe:edit")) { + if (!checkRole('supercafe:edit')) { config = [ { - key: "Persona", - type: "persona", - default: "", - label: "Persona", + key: 'Persona', + type: 'persona', + default: '', + label: 'Persona', }, { - key: "Comanda", - type: "comanda", - default: "", - label: "Comanda", + key: 'Comanda', + type: 'comanda', + default: '', + label: 'Comanda', }, ]; } @@ -239,42 +251,42 @@ PAGES.supercafe = { return tot; } TS_IndexElement( - "supercafe", + 'supercafe', config, - "supercafe", - document.querySelector("#cont1"), + 'supercafe', + document.querySelector('#cont1'), (data, new_tr) => { // new_tr.style.backgroundColor = "#FFCCCB"; comandasTot[data._key] = SC_priceCalc(JSON.parse(data.Comanda))[0]; calcPrecio(); - if (data.Estado == "Pedido") { - new_tr.style.backgroundColor = "#FFFFFF"; + if (data.Estado == 'Pedido') { + new_tr.style.backgroundColor = '#FFFFFF'; } - if (data.Estado == "En preparación") { - new_tr.style.backgroundColor = "#FFCCCB"; + if (data.Estado == 'En preparación') { + new_tr.style.backgroundColor = '#FFCCCB'; } - if (data.Estado == "Listo") { - new_tr.style.backgroundColor = "gold"; + if (data.Estado == 'Listo') { + new_tr.style.backgroundColor = 'gold'; } - if (data.Estado == "Entregado") { - new_tr.style.backgroundColor = "lightgreen"; + if (data.Estado == 'Entregado') { + new_tr.style.backgroundColor = 'lightgreen'; } - if (data.Estado == "Deuda") { - new_tr.style.backgroundColor = "#f5d3ff"; + if (data.Estado == 'Deuda') { + new_tr.style.backgroundColor = '#f5d3ff'; } }, (data) => { - if (data.Estado == "Deuda") { + if (data.Estado == 'Deuda') { return true; } var key = data._key; if (old[key] == undefined) { - old[key] = ""; + old[key] = ''; } if (old[key] != data.Estado) { if (tts && document.getElementById(tts_check).checked) { var msg = `Comanda de ${SC_Personas[data.Persona].Region}. - ${ - JSON.parse(data.Comanda)["Selección"] + JSON.parse(data.Comanda)['Selección'] }. - ${SC_Personas[data.Persona].Nombre}. - ${data.Estado}`; let utterance = new SpeechSynthesisUtterance(msg); utterance.rate = 0.9; @@ -288,43 +300,43 @@ PAGES.supercafe = { //Deudas TS_IndexElement( - "supercafe", + 'supercafe', config, - "supercafe", - document.querySelector("#cont2"), + 'supercafe', + document.querySelector('#cont2'), (data, new_tr) => { // new_tr.style.backgroundColor = "#FFCCCB"; comandasTot[data._key] = 0; // No mostrar comandas en deuda. calcPrecio(); - if (data.Estado == "Pedido") { - new_tr.style.backgroundColor = "#FFFFFF"; + if (data.Estado == 'Pedido') { + new_tr.style.backgroundColor = '#FFFFFF'; } - if (data.Estado == "En preparación") { - new_tr.style.backgroundColor = "#FFCCCB"; + if (data.Estado == 'En preparación') { + new_tr.style.backgroundColor = '#FFCCCB'; } - if (data.Estado == "Listo") { - new_tr.style.backgroundColor = "gold"; + if (data.Estado == 'Listo') { + new_tr.style.backgroundColor = 'gold'; } - if (data.Estado == "Entregado") { - new_tr.style.backgroundColor = "lightgreen"; + if (data.Estado == 'Entregado') { + new_tr.style.backgroundColor = 'lightgreen'; } - if (data.Estado == "Deuda") { - new_tr.style.backgroundColor = "#f5d3ff"; + if (data.Estado == 'Deuda') { + new_tr.style.backgroundColor = '#f5d3ff'; } }, (data) => { - if (data.Estado != "Deuda") { + if (data.Estado != 'Deuda') { return true; } var key = data._key; if (old[key] == undefined) { - old[key] = ""; + old[key] = ''; } if (old[key] != data.Estado) { if (tts && document.getElementById(tts_check).checked) { var msg = `Comanda de ${SC_Personas[data.Persona].Region}. - ${ - JSON.parse(data.Comanda)["Selección"] + JSON.parse(data.Comanda)['Selección'] }. - ${SC_Personas[data.Persona].Nombre}. - ${data.Estado}`; let utterance = new SpeechSynthesisUtterance(msg); utterance.rate = 0.9; @@ -335,11 +347,11 @@ PAGES.supercafe = { old[key] = data.Estado; } ); - if (!checkRole("supercafe:edit")) { - document.getElementById(btn_new).style.display = "none"; + if (!checkRole('supercafe:edit')) { + document.getElementById(btn_new).style.display = 'none'; } else { document.getElementById(btn_new).onclick = () => { - setUrlHash("supercafe," + safeuuid("")); + setUrlHash('supercafe,' + safeuuid('')); }; } }, diff --git a/src/pwa.js b/src/pwa.js index 7eb0210..639464d 100644 --- a/src/pwa.js +++ b/src/pwa.js @@ -1,28 +1,28 @@ let newWorker; function showUpdateBar() { - let snackbar = document.getElementById("snackbar"); - snackbar.className = "show"; + let snackbar = document.getElementById('snackbar'); + snackbar.className = 'show'; } // The click event on the pop up notification -document.getElementById("reload").addEventListener("click", function () { +document.getElementById('reload').addEventListener('click', function () { setTimeout(() => { removeCache(); }, 1000); - newWorker.postMessage({ action: "skipWaiting" }); + newWorker.postMessage({ action: 'skipWaiting' }); }); -if ("serviceWorker" in navigator) { - navigator.serviceWorker.register("sw.js").then((reg) => { - reg.addEventListener("updatefound", () => { +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('sw.js').then((reg) => { + reg.addEventListener('updatefound', () => { // A wild service worker has appeared in reg.installing! newWorker = reg.installing; - newWorker.addEventListener("statechange", () => { + newWorker.addEventListener('statechange', () => { // Has network.state changed? switch (newWorker.state) { - case "installed": + case 'installed': if (navigator.serviceWorker.controller) { // new update available showUpdateBar(); @@ -35,7 +35,7 @@ if ("serviceWorker" in navigator) { }); let refreshing; - navigator.serviceWorker.addEventListener("controllerchange", function () { + navigator.serviceWorker.addEventListener('controllerchange', function () { if (refreshing) return; window.location.reload(); refreshing = true; diff --git a/src/sw.js b/src/sw.js index 1b1fe99..3bdc5b2 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,10 +1,8 @@ -var CACHE = "telesec_%%VERSIONCO%%"; -importScripts( - "https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js" -); +var CACHE = 'telesec_%%VERSIONCO%%'; +importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js'); -self.addEventListener("message", (event) => { - if (event.data && event.data.type === "SKIP_WAITING") { +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); } }); @@ -18,9 +16,8 @@ self.addEventListener("message", (event) => { // All but couchdb workbox.routing.registerRoute( - ({ url }) => - !url.pathname.startsWith("/_couchdb/") && url.origin === self.location.origin, + ({ url }) => !url.pathname.startsWith('/_couchdb/') && url.origin === self.location.origin, new workbox.strategies.NetworkFirst({ cacheName: CACHE, }) -); \ No newline at end of file +);