|
//- 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); |