|  | //- polyfills | 
        
          |  | 'use strict'; | 
        
          |  |  | 
        
          |  | (function (p) { | 
        
          |  | if (!p.matches) p.matches = p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector; | 
        
          |  | })(Element.prototype); | 
        
          |  |  | 
        
          |  | if (typeof window.CustomEvent !== 'function') { | 
        
          |  | window.CustomEvent = function (name, p) { | 
        
          |  | p = p || {}; | 
        
          |  | var e = document.createEvent('CustomEvent'); | 
        
          |  | e.initCustomEvent(name, p.bubbles || false, p.cancelable || false, p.detail); | 
        
          |  | return e; | 
        
          |  | }; | 
        
          |  | window.CustomEvent.prototype = window.Event.prototype; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | //- Eddy The Editor by //github.com/Fedia | 
        
          |  | // License: MIT | 
        
          |  | (function (ui_html) { | 
        
          |  |  | 
        
          |  | var api = window._ed || {}; | 
        
          |  | if (!api.client_id) { | 
        
          |  | console.error('client_id is missing'); | 
        
          |  | return; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | if (!/^\?v\d+/.test(location.search)) { | 
        
          |  | refresh(); | 
        
          |  | return; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | document.body.insertAdjacentHTML('beforeend', ui_html); | 
        
          |  |  | 
        
          |  | if (api.plugins) { | 
        
          |  | api.plugins.forEach(load); | 
        
          |  | } | 
        
          |  | load('//apis.google.com/js/client:platform.js', init); | 
        
          |  |  | 
        
          |  | function load(url, cb) { | 
        
          |  | var s = document.createElement('script'); | 
        
          |  | s.src = url; | 
        
          |  | s.onload = typeof cb === 'function' ? cb : null; | 
        
          |  | document.head.appendChild(s); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function init() { | 
        
          |  | auth(function () { | 
        
          |  | gapi.client.load('storage').then(editPage); | 
        
          |  | document.querySelector('#ed-gauth').style.display = 'none'; | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function auth(done) { | 
        
          |  | gapi.load('auth2', function () { | 
        
          |  | gapi.auth2.init({ | 
        
          |  | client_id: api.client_id | 
        
          |  | }).then(function (auth) { | 
        
          |  | if (auth.isSignedIn.get()) { | 
        
          |  | done(); | 
        
          |  | } else { | 
        
          |  | gapi.signin2.render('ed-gauth', { | 
        
          |  | 'scope': 'https://www.googleapis.com/auth/devstorage.read_write', | 
        
          |  | 'width': 220, | 
        
          |  | 'height': 48, | 
        
          |  | 'longtitle': true, | 
        
          |  | 'theme': 'dark', | 
        
          |  | 'onsuccess': done | 
        
          |  | }); | 
        
          |  | } | 
        
          |  | }); | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function refresh() { | 
        
          |  | location.search = '?v' + Date.now(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function getContainer(root) { | 
        
          |  | if (!api.selector) { | 
        
          |  | api.selector = '.ed-container'; | 
        
          |  | } | 
        
          |  | return (root || document).querySelector(api.selector); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function Eddy(el, opts) { | 
        
          |  | this.el = el; | 
        
          |  | var conf = this.conf = { | 
        
          |  | schema: el.tagName + ' > p, p > br, a, b, i', | 
        
          |  | paragraph: 'p' | 
        
          |  | }; | 
        
          |  | if (opts) for (var k in opts) conf[k] = opts[k]; | 
        
          |  | var each = Function.prototype.call.bind(Array.prototype.forEach); | 
        
          |  | var sanitize = this.applySchema.bind(this); | 
        
          |  | this.onMutation(el, function (mutations) { | 
        
          |  | if (el.isContentEditable) { | 
        
          |  | each(mutations, function (m) { | 
        
          |  | each(m.addedNodes, sanitize); | 
        
          |  | }); | 
        
          |  | el.dispatchEvent(new CustomEvent('change', { bubbles: true })); | 
        
          |  | } | 
        
          |  | }); | 
        
          |  | el.addEventListener('focus', function () { | 
        
          |  | setTimeout(function () { | 
        
          |  | // walkaround for Chrome warnings | 
        
          |  | var doc = el.ownerDocument; | 
        
          |  | doc.execCommand('enableObjectResizing', false, false); | 
        
          |  | doc.execCommand('defaultParagraphSeparator', false, conf.paragraph); | 
        
          |  | }, 1); | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | Eddy.prototype.onMutation = function (el, cb) { | 
        
          |  | var o = new MutationObserver(cb); | 
        
          |  | o.observe(el, { subtree: true, childList: true, characterData: true }); | 
        
          |  | return o; | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | Eddy.prototype.applySchema = function (el) { | 
        
          |  | if (el.nodeType === el.ELEMENT_NODE && el.parentNode) { | 
        
          |  | var schema = this.conf.schema; | 
        
          |  | var doc = el.ownerDocument; | 
        
          |  | var iter = doc.createNodeIterator(el, NodeFilter.SHOW_ELEMENT, function (n) { | 
        
          |  | return n.matches(schema) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT; | 
        
          |  | }, false); | 
        
          |  | var inv, parent, next; | 
        
          |  | while (inv = iter.nextNode()) { | 
        
          |  | parent = inv.parentNode; | 
        
          |  | next = inv.nextSibling; | 
        
          |  | while (inv.firstChild) { | 
        
          |  | parent.insertBefore(inv.firstChild, next); | 
        
          |  | } | 
        
          |  | parent.removeChild(inv); | 
        
          |  | } | 
        
          |  | } | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | function editPage() { | 
        
          |  | var toolbar = document.querySelector('.ed-panel'); | 
        
          |  | toolbar.style.display = ''; | 
        
          |  |  | 
        
          |  | var editable = getContainer(); | 
        
          |  | api.editor = new Eddy(editable); | 
        
          |  |  | 
        
          |  | editable.addEventListener('change', function () { | 
        
          |  | clearAttribute(editable, 'style'); | 
        
          |  | clearAttribute(editable, 'class'); | 
        
          |  | clearAttribute(editable, 'id'); | 
        
          |  | if (editable.children.length === 0) { | 
        
          |  | document.execCommand('formatBlock', false, 'p'); | 
        
          |  | } | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | editable.addEventListener('click', function (e) { | 
        
          |  | var el = e.target; | 
        
          |  | if (editable.isContentEditable && el.matches('a[href]')) { | 
        
          |  | e.preventDefault(); | 
        
          |  | var url = prompt('Link', el.getAttribute('href')); | 
        
          |  | if (url !== null) { | 
        
          |  | el.setAttribute('href', url); | 
        
          |  | } | 
        
          |  | } | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | editable.dispatchEvent(new CustomEvent('beforeedit', { bubbles: true })); | 
        
          |  | editable.contentEditable = true; | 
        
          |  | editable.dispatchEvent(new CustomEvent('edit', { bubbles: true })); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function clearAttribute(root, attr) { | 
        
          |  | var nodes = root.querySelectorAll('[' + attr + ']'); | 
        
          |  | for (var i = 0; i < nodes.length; i++) { | 
        
          |  | nodes[i].removeAttribute(attr); | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function getBucketName() { | 
        
          |  | return location.hostname; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function getObjectName() { | 
        
          |  | return location.pathname.replace(/^\/|\/$/g, '') || 'index.html'; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function getPageHTML(name, cb) { | 
        
          |  | return gapi.client.storage.objects.get({ | 
        
          |  | bucket: getBucketName(), | 
        
          |  | object: name || getObjectName(), | 
        
          |  | alt: 'media' | 
        
          |  | }).then(function (res) { | 
        
          |  | if (cb) cb(res.body); | 
        
          |  | return res.body; | 
        
          |  | }, function (e) { | 
        
          |  | alert(e.result.error.message); | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | api.uploadFile = uploadFile; | 
        
          |  | function uploadFile(name, data, headers) { | 
        
          |  | return gapi.client.request({ | 
        
          |  | path: '/upload/storage/v1/b/' + getBucketName() + '/o', | 
        
          |  | method: 'POST', | 
        
          |  | params: { | 
        
          |  | name: name, | 
        
          |  | uploadType: 'media' | 
        
          |  | }, | 
        
          |  | headers: headers || {}, | 
        
          |  | body: data | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | api.deleteFile = deleteFile; | 
        
          |  | function deleteFile(name) { | 
        
          |  | return gapi.client.storage.objects['delete']({ | 
        
          |  | bucket: getBucketName(), | 
        
          |  | object: name | 
        
          |  | }).then(function (res) { | 
        
          |  | return res; | 
        
          |  | }, function (e) { | 
        
          |  | alert(e.result.error.message); | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function parseHTML(html) { | 
        
          |  | // return (new DOMParser()).parseFromString(html, 'text/html'); | 
        
          |  | var dom = document.implementation.createHTMLDocument(); | 
        
          |  | dom.documentElement.innerHTML = html; | 
        
          |  | return dom; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function toHTML(dom) { | 
        
          |  | var garbage = ' xmlns="http://www.w3.org/1999/xhtml"'; | 
        
          |  | return new XMLSerializer().serializeToString(dom).replace(garbage, ''); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | api.save = savePage; | 
        
          |  | function savePage() { | 
        
          |  | var saveAs = ''; | 
        
          |  | while (!saveAs.length || /[^\w\d\.\-]/.test(saveAs)) { | 
        
          |  | saveAs = window.prompt('URL', saveAs || getObjectName()); | 
        
          |  | if (saveAs === null) return; | 
        
          |  | } | 
        
          |  | if (saveAs.substr(-5) !== '.html') { | 
        
          |  | saveAs += '.html'; | 
        
          |  | } | 
        
          |  | var container = getContainer(); //.cloneNode(true); | 
        
          |  | container.dispatchEvent(new CustomEvent('beforesave', { bubbles: true })); | 
        
          |  |  | 
        
          |  | var template = api.template || null; | 
        
          |  | return getPageHTML(template).then(function (html) { | 
        
          |  | var doc = parseHTML(html); | 
        
          |  | getContainer(doc).innerHTML = container.innerHTML; | 
        
          |  | container.dispatchEvent(new CustomEvent('save', { | 
        
          |  | bubbles: true, | 
        
          |  | detail: { document: doc } | 
        
          |  | })); | 
        
          |  | return toHTML(doc); | 
        
          |  | }).then(function (html) { | 
        
          |  | return uploadFile(saveAs, html, { | 
        
          |  | 'Content-Type': 'text/html' | 
        
          |  | }); | 
        
          |  | }).then(function () { | 
        
          |  | location.href = '/' + saveAs; | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | if ('index.html' === getObjectName()) { | 
        
          |  | //document.querySelector('.ed-btn-deletepage').style.display = 'none'; | 
        
          |  | document.querySelector('.ed-actions .ed-btn:last-of-type').style.display = 'none'; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | api['delete'] = deletePage; | 
        
          |  | function deletePage() { | 
        
          |  | var page = getObjectName(); | 
        
          |  | if (page === 'index.html') { | 
        
          |  | return; | 
        
          |  | } | 
        
          |  | if (confirm('Delete ' + page + '?')) { | 
        
          |  | return deleteFile(page); | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | document.addEventListener('save', function (e) { | 
        
          |  | var doc = e.detail.document; | 
        
          |  | var page_title = getContainer(doc).querySelector('h1,h2'); | 
        
          |  | if (page_title) { | 
        
          |  | var title = doc.querySelector('title'); | 
        
          |  | title.textContent = page_title.textContent; | 
        
          |  | } | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | api.cmd_format = function (cmd) { | 
        
          |  | document.execCommand(cmd, false, null); | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | document.addEventListener('beforeedit', function () { | 
        
          |  | api.editor.conf.schema += ',  ' + api.selector + '> h1, ' + api.selector + '> h2'; | 
        
          |  | }); | 
        
          |  | var cmd_block_edgevalues = {}; | 
        
          |  | api.cmd_block = function (tag) { | 
        
          |  | var cmd = 'formatBlock'; | 
        
          |  | var val = document.queryCommandValue(cmd); | 
        
          |  | if (val === tag || val === cmd_block_edgevalues[tag]) { | 
        
          |  | tag = 'p'; | 
        
          |  | } | 
        
          |  | document.execCommand(cmd, false, tag); | 
        
          |  | cmd_block_edgevalues[tag] = document.queryCommandValue(cmd); | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | api.cmd_clear = function () { | 
        
          |  | document.execCommand('unlink', false, null); | 
        
          |  | document.execCommand('removeFormat', false, null); | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | api.cmd_link = function (val) { | 
        
          |  | var url = prompt('Link', val || 'https://'); | 
        
          |  | if (url) { | 
        
          |  | document.execCommand('createLink', false, url); | 
        
          |  | } | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | document.addEventListener('click', function (e) { | 
        
          |  | var cl = 'ed-menu__show'; | 
        
          |  | var sel = '.' + cl; | 
        
          |  | if (!e.target.matches(sel)) { | 
        
          |  | var btn = document.querySelector(sel); | 
        
          |  | if (btn) btn.classList.remove(cl); | 
        
          |  | } | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | api.menu = function (btn) { | 
        
          |  | btn.classList.add('ed-menu__show'); | 
        
          |  | }; | 
        
          |  | })('<style>@import "//gistcdn.githack.com/Fedia/d48bde020c15c4e5f1cf497d2dc1fcca/raw/styles.css"</style>\n<div class="ed-panel" style="display:none">\n    <span>π</span>\n    <span class="ed-tools">\n        <button class="ed-btn" onclick="_ed.cmd_block(\'h1\')"><b>H</b></button>\n        <button class="ed-btn" onclick="_ed.cmd_block(\'h2\')"><b><small>H</small></b></button>\n        <button class="ed-btn" onclick="_ed.cmd_format(\'bold\')"><b><small>b</small></b></button>\n        <button class="ed-btn" onclick="_ed.cmd_format(\'italic\')"><i><small>i</small></i></button>\n        <button class="ed-btn" onclick="_ed.cmd_clear()"><i><b>T</b></i><small>x</small></button>\n        <button class="ed-btn" onclick="_ed.menu(this)">+</button>\n        <div class="ed-menu">\n            <button class="ed-btn" onclick="_ed.cmd_link()">π Link</button>\n        </div>\n    </span>\n    <span class="ed-actions">\n        <button class="ed-btn" onclick="_ed.save()">πΎ</button>\n        <button class="ed-btn" onclick="_ed.menu(this)">β </button>\n        <div class="ed-menu">\n          <button class="ed-btn ed-btn-deletepage" onclick="_ed.delete()">ποΈ Delete</button>\n        </div>\n    </span>\n</div>\n<div id="ed-gauth"></div>'); | 
        
          |  |  | 
        
          |  | //- Image upload plugin | 
        
          |  | (function (api) { | 
        
          |  |  | 
        
          |  | var btn_html = '<button class="ed-btn" onclick="_ed.cmd_img()">π· Picture</button>'; | 
        
          |  | var placeholder_img = 'data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHhtbG5zOnhsaW5rPSdodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rJyB2aWV3Qm94PScwIDAge3t3fX0ge3tofX0nPjxkZWZzPjxzeW1ib2wgaWQ9J2EnIHZpZXdCb3g9JzAgMCA5MCA2Nicgb3BhY2l0eT0nMC4zJz48cGF0aCBkPSdNODUgNXY1Nkg1VjVoODBtNS01SDB2NjZoOTBWMHonLz48Y2lyY2xlIGN4PScxOCcgY3k9JzIwJyByPSc2Jy8+PHBhdGggZD0nTTU2IDE0TDM3IDM5bC04LTYtMTcgMjNoNjd6Jy8+PC9zeW1ib2w+PC9kZWZzPjx1c2UgeGxpbms6aHJlZj0nI2EnIHdpZHRoPScyMCUnIHg9JzQwJScvPjwvc3ZnPg=='; | 
        
          |  |  | 
        
          |  | document.addEventListener('beforeedit', function (e) { | 
        
          |  | init(e.target); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | function init(editable) { | 
        
          |  | document.querySelector('.ed-tools .ed-menu').insertAdjacentHTML('beforeend', btn_html); | 
        
          |  | api.editor.conf.schema += ', p > img'; | 
        
          |  | editable.addEventListener('click', function (e) { | 
        
          |  | var el = e.target; | 
        
          |  | if (editable.isContentEditable && el.matches('img')) { | 
        
          |  | e.preventDefault(); | 
        
          |  | img_upload(el); | 
        
          |  | } | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | api.cmd_img = function () { | 
        
          |  | var sel = window.getSelection(); | 
        
          |  | if (sel.rangeCount) { | 
        
          |  | var range = sel.getRangeAt(0); | 
        
          |  | var img = new Image(); | 
        
          |  | img.src = placeholder_img; | 
        
          |  | range.insertNode(img); | 
        
          |  | } | 
        
          |  | }; | 
        
          |  |  | 
        
          |  | function readFile(file, cb) { | 
        
          |  | var fr = new FileReader(); | 
        
          |  | fr.onloadend = function (e) { | 
        
          |  | var uri = fr.result; | 
        
          |  | cb(null, uri.substr(uri.indexOf(',') + 1), fr); | 
        
          |  | }; | 
        
          |  | fr.onerror = function (e) { | 
        
          |  | cb(e, null); | 
        
          |  | }; | 
        
          |  | fr.readAsDataURL(file); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | function img_upload(dest) { | 
        
          |  | var input = document.createElement('input'); | 
        
          |  | input.setAttribute('type', 'file'); | 
        
          |  | input.setAttribute('accept', 'image/*'); | 
        
          |  | input.onchange = function () { | 
        
          |  | var f = input.files[0]; | 
        
          |  | if (!f || f.type.substr(0, 6) !== 'image/') return; | 
        
          |  | var path = 'img/' + f.name; | 
        
          |  | readFile(f, function (err, data, reader) { | 
        
          |  | if (err) return alert(err); | 
        
          |  | dest.src = reader.result; | 
        
          |  | api.uploadFile(path, data, { | 
        
          |  | 'Content-Type': f.type, | 
        
          |  | 'Content-Encoding': 'base64' | 
        
          |  | }).then(function () { | 
        
          |  | dest.src = '/' + path + '?v' + Date.now(); | 
        
          |  | }); | 
        
          |  | }); | 
        
          |  | }; | 
        
          |  | input.click(); | 
        
          |  | } | 
        
          |  | })(_ed); |