diff options
Diffstat (limited to 'htdocs/js')
-rw-r--r-- | htdocs/js/admin.js | 193 | ||||
-rw-r--r-- | htdocs/js/common.js | 392 |
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'); + } +})(); |