aboutsummaryrefslogtreecommitdiff
path: root/htdocs/js
diff options
context:
space:
mode:
Diffstat (limited to 'htdocs/js')
-rw-r--r--htdocs/js/admin.js193
-rw-r--r--htdocs/js/common.js392
2 files changed, 585 insertions, 0 deletions
diff --git a/htdocs/js/admin.js b/htdocs/js/admin.js
new file mode 100644
index 0000000..a717d5c
--- /dev/null
+++ b/htdocs/js/admin.js
@@ -0,0 +1,193 @@
+var LS = window.localStorage;
+
+var Draft = {
+ get: function() {
+ if (!LS) return null;
+
+ var title = LS.getItem('draft_title') || null;
+ var text = LS.getItem('draft_text') || null;
+
+ return {
+ title: title,
+ text: text
+ };
+ },
+
+ setTitle: function(text) {
+ if (!LS) return null;
+ LS.setItem('draft_title', text);
+ },
+
+ setText: function(text) {
+ if (!LS) return null;
+ LS.setItem('draft_text', text);
+ },
+
+ reset: function() {
+ if (!LS) return;
+ LS.removeItem('draft_title');
+ LS.removeItem('draft_text');
+ }
+};
+
+var AdminWriteForm = {
+ form: null,
+ previewTimeout: null,
+ previewRequest: null,
+
+ init: function(opts) {
+ opts = opts || {};
+
+ this.opts = opts;
+ this.form = document.forms[opts.pages ? 'pageForm' : 'postForm'];
+ this.isFixed = false;
+
+ addEvent(this.form, 'submit', this.onSubmit);
+ if (!opts.pages)
+ addEvent(this.form.title, 'input', this.onInput);
+
+ addEvent(this.form.text, 'input', this.onInput);
+ addEvent(ge('toggle_wrap'), 'click', this.onToggleWrapClick);
+
+ if (this.form.text.value !== '')
+ this.showPreview();
+
+ // TODO make it more clever and context-aware
+ /*var draft = Draft.get();
+ if (draft.title)
+ this.form.title.value = draft.title;
+ if (draft.text)
+ this.form.text.value = draft.text;*/
+
+ addEvent(window, 'scroll', this.onScroll);
+ addEvent(window, 'resize', this.onResize);
+ },
+
+ showPreview: function() {
+ if (this.previewRequest !== null) {
+ this.previewRequest.abort();
+ }
+ this.previewRequest = ajax.post('/admin/markdown-preview.ajax', {
+ title: this.form.elements.title.value,
+ md: this.form.elements.text.value,
+ use_image_previews: this.opts.pages ? 1 : 0
+ }, function(err, response) {
+ if (err)
+ return console.error(err);
+ ge('preview_html').innerHTML = response.html;
+ });
+ },
+
+ onSubmit: function(event) {
+ try {
+ var fields = ['title', 'text'];
+ if (!this.opts.pages)
+ fields.push('tags');
+ if (this.opts.edit) {
+ fields.push('new_short_name');
+ } else {
+ fields.push('short_name');
+ }
+ for (var i = 0; i < fields.length; i++) {
+ var field = fields[i];
+ if (event.target.elements[field].value.trim() === '')
+ throw 'no_'+field
+ }
+
+ // Draft.reset();
+ } catch (e) {
+ var error = typeof e == 'string' ? lang((this.opts.pages ? 'err_pages_' : 'err_blog_')+e) : e.message;
+ alert(error);
+ console.error(e);
+ return cancelEvent(event);
+ }
+ },
+
+ onToggleWrapClick: function(e) {
+ var textarea = this.form.elements.text;
+ if (!hasClass(textarea, 'nowrap')) {
+ addClass(textarea, 'nowrap');
+ } else {
+ removeClass(textarea, 'nowrap');
+ }
+ return cancelEvent(e);
+ },
+
+ onInput: function(e) {
+ if (this.previewTimeout !== null) {
+ clearTimeout(this.previewTimeout);
+ }
+ this.previewTimeout = setTimeout(function() {
+ this.previewTimeout = null;
+ this.showPreview();
+
+ // Draft[e.target.name === 'title' ? 'setTitle' : 'setText'](e.target.value);
+ }.bind(this), 300);
+ },
+
+ onScroll: function() {
+ var ANCHOR_TOP = 10;
+
+ var y = window.pageYOffset;
+ var form = this.form;
+ var td = ge('form_first_cell');
+ var ph = ge('form_placeholder');
+
+ var rect = td.getBoundingClientRect();
+
+ if (rect.top <= ANCHOR_TOP && !this.isFixed) {
+ ph.style.height = form.getBoundingClientRect().height+'px';
+
+ var w = (rect.width - (parseInt(getComputedStyle(td).paddingRight, 10) || 0));
+ form.style.display = 'block';
+ form.style.width = w+'px';
+ form.style.position = 'fixed';
+ form.style.top = ANCHOR_TOP+'px';
+
+ this.isFixed = true;
+ } else if (rect.top > ANCHOR_TOP && this.isFixed) {
+ form.style.display = '';
+ form.style.width = '';
+ form.style.position = '';
+ form.style.position = '';
+ ph.style.height = '';
+
+ this.isFixed = false;
+ }
+ },
+
+ onResize: function() {
+ if (this.isFixed) {
+ var form = this.form;
+ var td = ge('form_first_cell');
+ var ph = ge('form_placeholder');
+
+ var rect = td.getBoundingClientRect();
+ var pr = parseInt(getComputedStyle(td).paddingRight, 10) || 0;
+
+ ph.style.height = form.getBoundingClientRect().height+'px';
+ form.style.width = (rect.width - pr) + 'px';
+ }
+ }
+};
+bindEventHandlers(AdminWriteForm);
+
+var BlogUploadList = {
+ submitNoteEdit: function(action, note) {
+ if (note === null)
+ return;
+
+ var form = document.createElement('form');
+ form.setAttribute('method', 'post');
+ form.setAttribute('action', action);
+
+ var input = document.createElement('input');
+ input.setAttribute('type', 'hidden');
+ input.setAttribute('name', 'note');
+ input.setAttribute('value', note);
+
+ form.appendChild(input);
+ document.body.appendChild(form);
+ form.submit();
+ }
+};
diff --git a/htdocs/js/common.js b/htdocs/js/common.js
new file mode 100644
index 0000000..4e4199c
--- /dev/null
+++ b/htdocs/js/common.js
@@ -0,0 +1,392 @@
+if (!String.prototype.startsWith) {
+ String.prototype.startsWith = function(search, pos) {
+ pos = !pos || pos < 0 ? 0 : +pos;
+ return this.substring(pos, pos + search.length) === search;
+ };
+}
+
+if (!String.prototype.endsWith) {
+ String.prototype.endsWith = function(search, this_len) {
+ if (this_len === undefined || this_len > this.length) {
+ this_len = this.length;
+ }
+ return this.substring(this_len - search.length, this_len) === search;
+ };
+}
+
+//
+// AJAX
+//
+(function() {
+
+ var defaultOpts = {
+ json: true
+ };
+
+ function createXMLHttpRequest() {
+ if (window.XMLHttpRequest) {
+ return new XMLHttpRequest();
+ }
+
+ var xhr;
+ try {
+ xhr = new ActiveXObject('Msxml2.XMLHTTP');
+ } catch (e) {
+ try {
+ xhr = new ActiveXObject('Microsoft.XMLHTTP');
+ } catch (e) {}
+ }
+ if (!xhr) {
+ console.error('Your browser doesn\'t support XMLHttpRequest.');
+ }
+ return xhr;
+ }
+
+ function request(method, url, data, optarg1, optarg2) {
+ data = data || null;
+
+ var opts, callback;
+ if (optarg2 !== undefined) {
+ opts = optarg1;
+ callback = optarg2;
+ } else {
+ callback = optarg1;
+ }
+
+ opts = opts || {};
+
+ if (typeof callback != 'function') {
+ throw new Error('callback must be a function');
+ }
+
+ if (!url) {
+ throw new Error('no url specified');
+ }
+
+ switch (method) {
+ case 'GET':
+ if (isObject(data)) {
+ for (var k in data) {
+ if (data.hasOwnProperty(k)) {
+ url += (url.indexOf('?') == -1 ? '?' : '&')+encodeURIComponent(k)+'='+encodeURIComponent(data[k])
+ }
+ }
+ }
+ break;
+
+ case 'POST':
+ if (isObject(data)) {
+ var sdata = [];
+ for (var k in data) {
+ if (data.hasOwnProperty(k)) {
+ sdata.push(encodeURIComponent(k)+'='+encodeURIComponent(data[k]));
+ }
+ }
+ data = sdata.join('&');
+ }
+ break;
+ }
+
+ opts = extend({}, defaultOpts, opts);
+
+ var xhr = createXMLHttpRequest();
+ xhr.open(method, url);
+
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ if (method == 'POST') {
+ xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+ }
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ if ('status' in xhr && !/^2|1223/.test(xhr.status)) {
+ throw new Error('http code '+xhr.status)
+ }
+ if (opts.json) {
+ var resp = JSON.parse(xhr.responseText)
+ if (!isObject(resp)) {
+ throw new Error('ajax: object expected')
+ }
+ if (resp.error) {
+ throw new Error(resp.error)
+ }
+ callback(null, resp.response);
+ } else {
+ callback(null, xhr.responseText);
+ }
+ }
+ };
+
+ xhr.onerror = function(e) {
+ callback(e);
+ };
+
+ xhr.send(method == 'GET' ? null : data);
+
+ return xhr;
+ }
+
+ window.ajax = {
+ get: request.bind(request, 'GET'),
+ post: request.bind(request, 'POST')
+ }
+
+})();
+
+function bindEventHandlers(obj) {
+ for (var k in obj) {
+ if (obj.hasOwnProperty(k)
+ && typeof obj[k] == 'function'
+ && k.length > 2
+ && k.startsWith('on')
+ && k[2].charCodeAt(0) >= 65
+ && k[2].charCodeAt(0) <= 90) {
+ obj[k] = obj[k].bind(obj)
+ }
+ }
+}
+
+//
+// DOM helpers
+//
+function ge(id) {
+ return document.getElementById(id)
+}
+
+function hasClass(el, name) {
+ return el && el.nodeType === 1 && (" " + el.className + " ").replace(/[\t\r\n\f]/g, " ").indexOf(" " + name + " ") >= 0
+}
+
+function addClass(el, name) {
+ if (!el) {
+ return console.warn('addClass: el is', el)
+ }
+ if (!hasClass(el, name)) {
+ el.className = (el.className ? el.className + ' ' : '') + name
+ }
+}
+
+function removeClass(el, name) {
+ if (!el) {
+ return console.warn('removeClass: el is', el)
+ }
+ if (isArray(name)) {
+ for (var i = 0; i < name.length; i++) {
+ removeClass(el, name[i]);
+ }
+ return;
+ }
+ el.className = ((el.className || '').replace((new RegExp('(\\s|^)' + name + '(\\s|$)')), ' ')).trim()
+}
+
+function addEvent(el, type, f, useCapture) {
+ if (!el) {
+ return console.warn('addEvent: el is', el, stackTrace())
+ }
+
+ if (isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ addEvent(el, type[i], f, useCapture);
+ }
+ return;
+ }
+
+ if (el.addEventListener) {
+ el.addEventListener(type, f, useCapture || false);
+ return true;
+ } else if (el.attachEvent) {
+ return el.attachEvent('on' + type, f);
+ }
+
+ return false;
+}
+
+function removeEvent(el, type, f, useCapture) {
+ if (isArray(type)) {
+ for (var i = 0; i < type.length; i++) {
+ var t = type[i];
+ removeEvent(el, type[i], f, useCapture);
+ }
+ return;
+ }
+
+ if (el.removeEventListener) {
+ el.removeEventListener(type, f, useCapture || false);
+ } else if (el.detachEvent) {
+ return el.detachEvent('on' + type, f);
+ }
+
+ return false;
+}
+
+function cancelEvent(evt) {
+ if (!evt) {
+ return console.warn('cancelEvent: event is', evt)
+ }
+
+ if (evt.preventDefault) evt.preventDefault();
+ if (evt.stopPropagation) evt.stopPropagation();
+
+ evt.cancelBubble = true;
+ evt.returnValue = false;
+
+ return false;
+}
+
+
+//
+// Cookies
+//
+function setCookie(name, value, days) {
+ var expires = "";
+ if (days) {
+ var date = new Date();
+ date.setTime(date.getTime() + (days*24*60*60*1000));
+ expires = "; expires=" + date.toUTCString();
+ }
+ document.cookie = name + "=" + (value || "") + expires + "; path=/";
+}
+
+function unsetCookie(name) {
+ document.cookie = name+'=; Max-Age=-99999999;';
+}
+
+function getCookie(name) {
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) === ' ')
+ c = c.substring(1, c.length);
+ if (c.indexOf(nameEQ) === 0)
+ return c.substring(nameEQ.length, c.length);
+ }
+ return null;
+}
+
+//
+// Misc
+//
+function isObject(o) {
+ return Object.prototype.toString.call(o) === '[object Object]';
+}
+
+function isArray(a) {
+ return Object.prototype.toString.call(a) === '[object Array]';
+}
+
+function extend(dst, src) {
+ if (!isObject(dst)) {
+ return console.error('extend: dst is not an object');
+ }
+ if (!isObject(src)) {
+ return console.error('extend: src is not an object');
+ }
+ for (var key in src) {
+ dst[key] = src[key];
+ }
+ return dst;
+}
+
+function stackTrace(split) {
+ if (split === undefined) {
+ split = true;
+ }
+ try {
+ o.lo.lo += 0;
+ } catch(e) {
+ if (e.stack) {
+ var stack = split ? e.stack.split('\n') : e.stack;
+ stack.shift();
+ stack.shift();
+ return stack.join('\n');
+ }
+ }
+ return null;
+}
+
+function escape(str) {
+ var pre = document.createElement('pre');
+ var text = document.createTextNode(str);
+ pre.appendChild(text);
+ return pre.innerHTML;
+}
+
+//
+//
+
+function lang(key) {
+ return __lang[key] !== undefined ? __lang[key] : '{'+key+'}';
+}
+
+var DynamicLogo = {
+ dynLink: null,
+ afr: null,
+ afrUrl: null,
+
+ init: function() {
+ this.dynLink = ge('head_dyn_link');
+ this.cdText = ge('head_cd_text');
+
+ if (!this.dynLink) {
+ return console.warn('DynamicLogo.init: !this.dynLink');
+ }
+
+ var spans = this.dynLink.querySelectorAll('span.head-logo-path');
+ for (var i = 0; i < spans.length; i++) {
+ var span = spans[i];
+ addEvent(span, 'mouseover', this.onSpanOver);
+ addEvent(span, 'mouseout', this.onSpanOut);
+ }
+ },
+
+ setUrl: function(url) {
+ if (this.afr !== null) {
+ cancelAnimationFrame(this.afr);
+ }
+ this.afrUrl = url;
+ this.afr = requestAnimationFrame(this.onAnimationFrame);
+ },
+
+ onAnimationFrame: function() {
+ var url = this.afrUrl;
+
+ // update link
+ this.dynLink.setAttribute('href', url);
+
+ // update console text
+ if (this.afrUrl === '/') {
+ url = '~';
+ } else {
+ url = '~'+url.replace(/\/$/, '');
+ }
+ this.cdText.innerHTML = escape(url);
+
+ this.afr = null;
+ },
+
+ onSpanOver: function() {
+ var span = event.target;
+ this.setUrl(span.getAttribute('data-url'));
+ cancelEvent(event);
+ },
+
+ onSpanOut: function() {
+ var span = event.target;
+ this.setUrl('/');
+ cancelEvent(event);
+ }
+};
+bindEventHandlers(DynamicLogo);
+
+window.__lang = {};
+
+// set/remove retina cookie
+(function() {
+ var isRetina = window.devicePixelRatio >= 1.5;
+ if (isRetina) {
+ setCookie('is_retina', 1, 365);
+ } else {
+ unsetCookie('is_retina');
+ }
+})();