2016-04-09 18:54:23 +02:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
require('../util/polyfill.js');
|
2016-06-11 09:59:29 +02:00
|
|
|
const api = require('../api.js');
|
2016-05-21 09:46:41 +02:00
|
|
|
const templates = require('../templates.js');
|
2016-04-09 18:54:23 +02:00
|
|
|
const domParser = new DOMParser();
|
2016-05-09 20:07:54 +02:00
|
|
|
const misc = require('./misc.js');
|
2017-01-20 21:51:04 +01:00
|
|
|
const uri = require('./uri.js');
|
2016-05-09 20:07:54 +02:00
|
|
|
|
2016-05-11 23:46:56 +02:00
|
|
|
function _imbueId(options) {
|
|
|
|
if (!options.id) {
|
|
|
|
options.id = 'gen-' + Math.random().toString(36).substring(7);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-09 20:07:54 +02:00
|
|
|
function _makeLabel(options, attrs) {
|
|
|
|
if (!options.text) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
if (!attrs) {
|
|
|
|
attrs = {};
|
|
|
|
}
|
|
|
|
attrs.for = options.id;
|
2016-11-11 23:08:50 +01:00
|
|
|
return makeElement('label', attrs, options.text);
|
2016-05-09 20:07:54 +02:00
|
|
|
}
|
|
|
|
|
2016-05-22 22:39:31 +02:00
|
|
|
function makeFileSize(fileSize) {
|
|
|
|
return misc.formatFileSize(fileSize);
|
|
|
|
}
|
|
|
|
|
2016-06-11 17:41:28 +02:00
|
|
|
function makeMarkdown(text) {
|
|
|
|
return misc.formatMarkdown(text);
|
|
|
|
}
|
|
|
|
|
2016-05-09 20:07:54 +02:00
|
|
|
function makeRelativeTime(time) {
|
2016-11-11 23:08:50 +01:00
|
|
|
return makeElement(
|
|
|
|
'time', {datetime: time, title: time}, misc.formatRelativeTime(time));
|
2016-05-09 20:07:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeThumbnail(url) {
|
2016-11-11 23:08:50 +01:00
|
|
|
return makeElement(
|
2016-05-09 20:07:54 +02:00
|
|
|
'span',
|
2016-08-23 22:31:56 +02:00
|
|
|
url ?
|
2016-11-11 23:08:50 +01:00
|
|
|
{class: 'thumbnail', style: `background-image: url(\'${url}\')`} :
|
2016-08-23 22:31:56 +02:00
|
|
|
{class: 'thumbnail empty'},
|
2016-11-11 23:08:50 +01:00
|
|
|
makeElement('img', {alt: 'thumbnail', src: url}));
|
2016-05-09 20:07:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeRadio(options) {
|
2016-05-11 23:46:56 +02:00
|
|
|
_imbueId(options);
|
2016-11-11 23:08:50 +01:00
|
|
|
return makeElement(
|
2016-10-22 12:28:09 +02:00
|
|
|
'label',
|
|
|
|
{for: options.id},
|
2016-11-11 23:08:50 +01:00
|
|
|
makeElement(
|
2016-10-22 12:28:09 +02:00
|
|
|
'input',
|
|
|
|
{
|
|
|
|
id: options.id,
|
|
|
|
name: options.name,
|
|
|
|
value: options.value,
|
|
|
|
type: 'radio',
|
|
|
|
checked: options.selectedValue === options.value,
|
|
|
|
disabled: options.readonly,
|
|
|
|
required: options.required,
|
2016-11-11 23:08:50 +01:00
|
|
|
}),
|
|
|
|
makeElement('span', {class: 'radio'}, options.text));
|
2016-05-09 20:07:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeCheckbox(options) {
|
2016-05-11 23:46:56 +02:00
|
|
|
_imbueId(options);
|
2016-11-11 23:08:50 +01:00
|
|
|
return makeElement(
|
2016-10-22 12:28:09 +02:00
|
|
|
'label',
|
|
|
|
{for: options.id},
|
2016-11-11 23:08:50 +01:00
|
|
|
makeElement(
|
2016-10-22 12:28:09 +02:00
|
|
|
'input',
|
|
|
|
{
|
|
|
|
id: options.id,
|
|
|
|
name: options.name,
|
|
|
|
value: options.value,
|
|
|
|
type: 'checkbox',
|
|
|
|
checked: options.checked !== undefined ?
|
|
|
|
options.checked : false,
|
|
|
|
disabled: options.readonly,
|
|
|
|
required: options.required,
|
2016-11-11 23:08:50 +01:00
|
|
|
}),
|
|
|
|
makeElement('span', {class: 'checkbox'}, options.text));
|
2016-05-09 20:07:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeSelect(options) {
|
|
|
|
return _makeLabel(options) +
|
2016-11-11 23:08:50 +01:00
|
|
|
makeElement(
|
2016-05-09 20:07:54 +02:00
|
|
|
'select',
|
2016-05-11 23:46:56 +02:00
|
|
|
{
|
|
|
|
id: options.id,
|
|
|
|
name: options.name,
|
|
|
|
disabled: options.readonly,
|
|
|
|
},
|
2016-11-11 23:08:50 +01:00
|
|
|
...Object.keys(options.keyValues).map(key =>
|
|
|
|
makeElement(
|
2016-05-09 20:07:54 +02:00
|
|
|
'option',
|
|
|
|
{value: key, selected: key === options.selectedKey},
|
2016-11-11 23:08:50 +01:00
|
|
|
options.keyValues[key])));
|
2016-05-09 20:07:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function makeInput(options) {
|
2016-06-08 22:51:15 +02:00
|
|
|
options.value = options.value || '';
|
2016-11-11 23:08:50 +01:00
|
|
|
return _makeLabel(options) + makeElement('input', options);
|
2016-05-09 20:07:54 +02:00
|
|
|
}
|
|
|
|
|
2016-06-01 21:31:15 +02:00
|
|
|
function makeButton(options) {
|
2016-06-08 22:51:15 +02:00
|
|
|
options.type = 'button';
|
2016-06-01 21:31:15 +02:00
|
|
|
return makeInput(options);
|
|
|
|
}
|
|
|
|
|
2016-05-09 20:07:54 +02:00
|
|
|
function makeTextInput(options) {
|
2016-06-08 22:51:15 +02:00
|
|
|
options.type = 'text';
|
2016-05-09 20:07:54 +02:00
|
|
|
return makeInput(options);
|
|
|
|
}
|
|
|
|
|
2016-06-21 18:16:27 +02:00
|
|
|
function makeTextarea(options) {
|
|
|
|
const value = options.value || '';
|
|
|
|
delete options.value;
|
2016-11-11 23:08:50 +01:00
|
|
|
return _makeLabel(options) + makeElement('textarea', options, value);
|
2016-06-21 18:16:27 +02:00
|
|
|
}
|
|
|
|
|
2016-05-09 20:07:54 +02:00
|
|
|
function makePasswordInput(options) {
|
2016-06-08 22:51:15 +02:00
|
|
|
options.type = 'password';
|
2016-05-09 20:07:54 +02:00
|
|
|
return makeInput(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
function makeEmailInput(options) {
|
2016-06-08 22:51:15 +02:00
|
|
|
options.type = 'email';
|
2016-05-09 20:07:54 +02:00
|
|
|
return makeInput(options);
|
|
|
|
}
|
|
|
|
|
2016-05-10 10:57:59 +02:00
|
|
|
function makeColorInput(options) {
|
2016-11-11 23:08:50 +01:00
|
|
|
const textInput = makeElement(
|
2016-05-10 10:57:59 +02:00
|
|
|
'input', {
|
|
|
|
type: 'text',
|
|
|
|
value: options.value || '',
|
|
|
|
required: options.required,
|
2017-02-07 21:33:44 +01:00
|
|
|
class: 'color',
|
2016-05-10 10:57:59 +02:00
|
|
|
});
|
2017-02-07 21:33:44 +01:00
|
|
|
const backgroundPreviewNode = makeElement(
|
|
|
|
'div',
|
|
|
|
{
|
|
|
|
class: 'preview background-preview',
|
|
|
|
style:
|
|
|
|
`border-color: ${options.value};
|
|
|
|
background-color: ${options.value}`,
|
|
|
|
});
|
|
|
|
const textPreviewNode = makeElement(
|
|
|
|
'div',
|
|
|
|
{
|
|
|
|
class: 'preview text-preview',
|
|
|
|
style:
|
|
|
|
`border-color: ${options.value};
|
|
|
|
color: ${options.value}`,
|
|
|
|
});
|
|
|
|
return makeElement(
|
|
|
|
'label', {class: 'color'},
|
|
|
|
textInput,
|
|
|
|
backgroundPreviewNode,
|
|
|
|
textPreviewNode);
|
2016-05-10 10:57:59 +02:00
|
|
|
}
|
|
|
|
|
2016-08-05 20:00:07 +02:00
|
|
|
function makeNumericInput(options) {
|
|
|
|
options.type = 'number';
|
|
|
|
return makeInput(options);
|
|
|
|
}
|
|
|
|
|
2018-02-25 11:44:02 +01:00
|
|
|
function makeDateInput(options) {
|
|
|
|
options.type = 'date';
|
|
|
|
return makeInput(options)
|
|
|
|
}
|
|
|
|
|
2016-07-07 21:18:35 +02:00
|
|
|
function getPostUrl(id, parameters) {
|
2017-01-20 21:51:04 +01:00
|
|
|
return uri.formatClientLink(
|
|
|
|
'post', id,
|
|
|
|
parameters ? {query: parameters.query} : {});
|
2016-07-07 21:18:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function getPostEditUrl(id, parameters) {
|
2017-01-20 21:51:04 +01:00
|
|
|
return uri.formatClientLink(
|
|
|
|
'post', id, 'edit',
|
|
|
|
parameters ? {query: parameters.query} : {});
|
2016-07-07 21:18:35 +02:00
|
|
|
}
|
|
|
|
|
2016-08-17 13:01:17 +02:00
|
|
|
function makePostLink(id, includeHash) {
|
|
|
|
let text = id;
|
|
|
|
if (includeHash) {
|
|
|
|
text = '@' + id;
|
|
|
|
}
|
2016-06-11 09:59:29 +02:00
|
|
|
return api.hasPrivilege('posts:view') ?
|
2016-11-11 23:08:50 +01:00
|
|
|
makeElement(
|
2016-10-02 20:17:08 +02:00
|
|
|
'a',
|
2017-01-20 21:51:04 +01:00
|
|
|
{href: uri.formatClientLink('post', id)},
|
2016-10-02 20:17:08 +02:00
|
|
|
misc.escapeHtml(text)) :
|
|
|
|
misc.escapeHtml(text);
|
2016-05-29 12:28:52 +02:00
|
|
|
}
|
|
|
|
|
2017-10-01 21:46:53 +02:00
|
|
|
function makeTagLink(name, includeHash, includeCount, tag) {
|
2016-06-11 09:59:29 +02:00
|
|
|
const category = tag ? tag.category : 'unknown';
|
2016-08-17 13:01:17 +02:00
|
|
|
let text = name;
|
|
|
|
if (includeHash === true) {
|
|
|
|
text = '#' + text;
|
|
|
|
}
|
2017-10-01 21:46:53 +02:00
|
|
|
if (includeCount === true) {
|
|
|
|
text += ' (' + (tag ? tag.postCount : 0) + ')';
|
|
|
|
}
|
2016-06-11 09:59:29 +02:00
|
|
|
return api.hasPrivilege('tags:view') ?
|
2016-11-11 23:08:50 +01:00
|
|
|
makeElement(
|
2016-08-17 13:01:17 +02:00
|
|
|
'a',
|
|
|
|
{
|
2017-01-20 21:51:04 +01:00
|
|
|
href: uri.formatClientLink('tag', name),
|
|
|
|
class: misc.makeCssName(category, 'tag'),
|
2016-06-11 09:59:29 +02:00
|
|
|
},
|
2016-10-02 20:17:08 +02:00
|
|
|
misc.escapeHtml(text)) :
|
2016-11-11 23:08:50 +01:00
|
|
|
makeElement(
|
2016-08-17 13:01:17 +02:00
|
|
|
'span',
|
2017-01-20 21:51:04 +01:00
|
|
|
{class: misc.makeCssName(category, 'tag')},
|
2016-10-02 20:17:08 +02:00
|
|
|
misc.escapeHtml(text));
|
2016-05-10 10:57:59 +02:00
|
|
|
}
|
|
|
|
|
2016-05-29 12:28:52 +02:00
|
|
|
function makeUserLink(user) {
|
2016-08-02 11:58:56 +02:00
|
|
|
let text = makeThumbnail(user ? user.avatarUrl : null);
|
2016-10-02 20:17:08 +02:00
|
|
|
text += user && user.name ? misc.escapeHtml(user.name) : 'Anonymous';
|
2016-08-02 11:58:56 +02:00
|
|
|
const link = user && api.hasPrivilege('users:view') ?
|
2016-11-11 23:08:50 +01:00
|
|
|
makeElement(
|
2017-01-20 21:51:04 +01:00
|
|
|
'a', {href: uri.formatClientLink('user', user.name)}, text) :
|
2016-06-11 11:00:52 +02:00
|
|
|
text;
|
2016-11-11 23:08:50 +01:00
|
|
|
return makeElement('span', {class: 'user'}, link);
|
2016-05-29 12:28:52 +02:00
|
|
|
}
|
|
|
|
|
2016-05-09 20:07:54 +02:00
|
|
|
function makeFlexboxAlign(options) {
|
2016-08-05 20:09:11 +02:00
|
|
|
return [...misc.range(20)]
|
2016-05-09 20:07:54 +02:00
|
|
|
.map(() => '<li class="flexbox-dummy"></li>').join('');
|
|
|
|
}
|
2016-04-09 18:54:23 +02:00
|
|
|
|
2016-05-29 12:24:48 +02:00
|
|
|
function makeAccessKey(html, key) {
|
|
|
|
const regex = new RegExp('(' + key + ')', 'i');
|
|
|
|
html = html.replace(
|
|
|
|
regex, '<span class="access-key" data-accesskey="$1">$1</span>');
|
|
|
|
return html;
|
|
|
|
}
|
|
|
|
|
2016-04-10 10:23:27 +02:00
|
|
|
function _serializeElement(name, attributes) {
|
|
|
|
return [name]
|
|
|
|
.concat(Object.keys(attributes).map(key => {
|
|
|
|
if (attributes[key] === true) {
|
|
|
|
return key;
|
|
|
|
} else if (attributes[key] === false ||
|
|
|
|
attributes[key] === undefined) {
|
|
|
|
return '';
|
|
|
|
}
|
2016-06-23 11:41:52 +02:00
|
|
|
const attribute = misc.escapeHtml(attributes[key] || '');
|
|
|
|
return `${key}="${attribute}"`;
|
2016-04-10 10:23:27 +02:00
|
|
|
}))
|
|
|
|
.join(' ');
|
|
|
|
}
|
|
|
|
|
2016-11-11 23:08:50 +01:00
|
|
|
function makeElement(name, attrs, ...content) {
|
|
|
|
return content.length !== undefined ?
|
|
|
|
`<${_serializeElement(name, attrs)}>${content.join('')}</${name}>` :
|
|
|
|
`<${_serializeElement(name, attrs)}/>`;
|
2016-04-10 10:23:27 +02:00
|
|
|
}
|
|
|
|
|
2016-08-28 18:53:06 +02:00
|
|
|
function emptyContent(target) {
|
|
|
|
while (target.lastChild) {
|
|
|
|
target.removeChild(target.lastChild);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function replaceContent(target, source) {
|
|
|
|
emptyContent(target);
|
|
|
|
if (source instanceof NodeList) {
|
|
|
|
for (let child of [...source]) {
|
|
|
|
target.appendChild(child);
|
|
|
|
}
|
|
|
|
} else if (source instanceof Node) {
|
|
|
|
target.appendChild(source);
|
|
|
|
} else if (source !== null) {
|
|
|
|
throw `Invalid view source: ${source}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-11 17:41:28 +02:00
|
|
|
function showMessage(target, message, className) {
|
2016-05-11 21:29:57 +02:00
|
|
|
if (!message) {
|
|
|
|
message = 'Unknown message';
|
|
|
|
}
|
2017-01-06 14:05:54 +01:00
|
|
|
const messagesHolderNode = target.querySelector('.messages');
|
|
|
|
if (!messagesHolderNode) {
|
2016-05-11 21:29:57 +02:00
|
|
|
return false;
|
|
|
|
}
|
2017-01-06 14:05:54 +01:00
|
|
|
const textNode = document.createElement('div');
|
|
|
|
textNode.innerHTML = message.replace(/\n/g, '<br/>');
|
|
|
|
textNode.classList.add('message');
|
|
|
|
textNode.classList.add(className);
|
|
|
|
const wrapperNode = document.createElement('div');
|
|
|
|
wrapperNode.classList.add('message-wrapper');
|
|
|
|
wrapperNode.appendChild(textNode);
|
|
|
|
messagesHolderNode.appendChild(wrapperNode);
|
2016-05-11 21:29:57 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-01-08 02:31:07 +01:00
|
|
|
function appendExclamationMark() {
|
2017-01-06 14:05:54 +01:00
|
|
|
if (!document.title.startsWith('!')) {
|
|
|
|
document.oldTitle = document.title;
|
|
|
|
document.title = `! ${document.title}`;
|
|
|
|
}
|
2017-01-08 02:31:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function showError(target, message) {
|
|
|
|
appendExclamationMark();
|
2016-09-10 11:11:07 +02:00
|
|
|
return showMessage(target, misc.formatInlineMarkdown(message), 'error');
|
2016-06-11 17:41:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function showSuccess(target, message) {
|
2016-09-10 11:11:07 +02:00
|
|
|
return showMessage(target, misc.formatInlineMarkdown(message), 'success');
|
2016-06-11 17:41:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function showInfo(target, message) {
|
2016-09-10 11:11:07 +02:00
|
|
|
return showMessage(target, misc.formatInlineMarkdown(message), 'info');
|
2016-06-11 17:41:28 +02:00
|
|
|
}
|
|
|
|
|
2016-04-09 18:54:23 +02:00
|
|
|
function clearMessages(target) {
|
2016-09-26 22:44:32 +02:00
|
|
|
if (document.oldTitle) {
|
|
|
|
document.title = document.oldTitle;
|
|
|
|
document.oldTitle = null;
|
|
|
|
}
|
2017-01-06 14:05:54 +01:00
|
|
|
for (let messagesHolderNode of target.querySelectorAll('.messages')) {
|
|
|
|
emptyContent(messagesHolderNode);
|
|
|
|
}
|
2016-04-09 18:54:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function htmlToDom(html) {
|
2016-06-20 21:43:07 +02:00
|
|
|
// code taken from jQuery + Krasimir Tsonev's blog
|
|
|
|
const wrapMap = {
|
|
|
|
_: [1, '<div>', '</div>'],
|
|
|
|
option: [1, '<select multiple>', '</select>'],
|
|
|
|
legend: [1, '<fieldset>', '</fieldset>'],
|
|
|
|
area: [1, '<map>', '</map>'],
|
|
|
|
param: [1, '<object>', '</object>'],
|
|
|
|
thead: [1, '<table>', '</table>'],
|
|
|
|
tr: [2, '<table><tbody>', '</tbody></table>'],
|
|
|
|
td: [3, '<table><tbody><tr>', '</tr></tbody></table>'],
|
|
|
|
col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
|
|
|
|
};
|
|
|
|
wrapMap.optgroup = wrapMap.option;
|
|
|
|
wrapMap.tbody =
|
|
|
|
wrapMap.tfoot =
|
|
|
|
wrapMap.colgroup =
|
|
|
|
wrapMap.caption =
|
|
|
|
wrapMap.thead;
|
|
|
|
wrapMap.th = wrapMap.td;
|
|
|
|
|
|
|
|
let element = document.createElement('div');
|
|
|
|
const match = /<\s*(\w+)[^>]*?>/g.exec(html);
|
|
|
|
|
|
|
|
if (match) {
|
|
|
|
const tag = match[1];
|
|
|
|
const [depthToChild, prefix, suffix] = wrapMap[tag] || wrapMap._;
|
|
|
|
element.innerHTML = prefix + html + suffix;
|
|
|
|
for (let i = 0; i < depthToChild; i++) {
|
|
|
|
element = element.lastChild;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
element.innerHTML = html;
|
|
|
|
}
|
|
|
|
return element.childNodes.length > 1 ?
|
|
|
|
element.childNodes :
|
|
|
|
element.firstChild;
|
2016-04-09 18:54:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function getTemplate(templatePath) {
|
2016-04-13 18:58:34 +02:00
|
|
|
if (!(templatePath in templates)) {
|
2016-06-11 09:30:22 +02:00
|
|
|
throw `Missing template: ${templatePath}`;
|
2016-04-09 18:54:23 +02:00
|
|
|
}
|
2016-05-21 09:46:41 +02:00
|
|
|
const templateFactory = templates[templatePath];
|
2016-05-09 20:07:54 +02:00
|
|
|
return ctx => {
|
|
|
|
if (!ctx) {
|
|
|
|
ctx = {};
|
|
|
|
}
|
2016-09-29 11:16:55 +02:00
|
|
|
Object.assign(ctx, {
|
|
|
|
getPostUrl: getPostUrl,
|
|
|
|
getPostEditUrl: getPostEditUrl,
|
|
|
|
makeRelativeTime: makeRelativeTime,
|
|
|
|
makeFileSize: makeFileSize,
|
|
|
|
makeMarkdown: makeMarkdown,
|
|
|
|
makeThumbnail: makeThumbnail,
|
|
|
|
makeRadio: makeRadio,
|
|
|
|
makeCheckbox: makeCheckbox,
|
|
|
|
makeSelect: makeSelect,
|
|
|
|
makeInput: makeInput,
|
|
|
|
makeButton: makeButton,
|
|
|
|
makeTextarea: makeTextarea,
|
|
|
|
makeTextInput: makeTextInput,
|
|
|
|
makePasswordInput: makePasswordInput,
|
|
|
|
makeEmailInput: makeEmailInput,
|
|
|
|
makeColorInput: makeColorInput,
|
2018-02-25 11:44:02 +01:00
|
|
|
makeDateInput: makeDateInput,
|
2016-09-29 11:16:55 +02:00
|
|
|
makePostLink: makePostLink,
|
|
|
|
makeTagLink: makeTagLink,
|
|
|
|
makeUserLink: makeUserLink,
|
|
|
|
makeFlexboxAlign: makeFlexboxAlign,
|
|
|
|
makeAccessKey: makeAccessKey,
|
2016-11-11 23:14:51 +01:00
|
|
|
makeElement: makeElement,
|
2016-09-29 11:16:55 +02:00
|
|
|
makeCssName: misc.makeCssName,
|
|
|
|
makeNumericInput: makeNumericInput,
|
2017-01-20 21:51:04 +01:00
|
|
|
formatClientLink: uri.formatClientLink
|
2016-09-29 11:16:55 +02:00
|
|
|
});
|
2016-05-09 20:07:54 +02:00
|
|
|
return htmlToDom(templateFactory(ctx));
|
2016-04-09 18:54:23 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function decorateValidator(form) {
|
|
|
|
// postpone showing form fields validity until user actually tries
|
|
|
|
// to submit it (seeing red/green form w/o doing anything breaks POLA)
|
2016-05-10 10:57:59 +02:00
|
|
|
let submitButton = form.querySelector('.buttons input');
|
|
|
|
if (!submitButton) {
|
|
|
|
submitButton = form.querySelector('input[type=submit]');
|
|
|
|
}
|
|
|
|
if (submitButton) {
|
|
|
|
submitButton.addEventListener('click', e => {
|
|
|
|
form.classList.add('show-validation');
|
|
|
|
});
|
|
|
|
}
|
2016-04-09 18:54:23 +02:00
|
|
|
form.addEventListener('submit', e => {
|
|
|
|
form.classList.remove('show-validation');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function disableForm(form) {
|
|
|
|
for (let input of form.querySelectorAll('input')) {
|
|
|
|
input.disabled = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function enableForm(form) {
|
|
|
|
for (let input of form.querySelectorAll('input')) {
|
|
|
|
input.disabled = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-13 17:18:28 +02:00
|
|
|
function syncScrollPosition() {
|
|
|
|
window.requestAnimationFrame(
|
|
|
|
() => {
|
2016-08-28 18:53:06 +02:00
|
|
|
if (history.state && history.state.hasOwnProperty('scrollX')) {
|
2016-07-13 17:18:28 +02:00
|
|
|
window.scrollTo(history.state.scrollX, history.state.scrollY);
|
|
|
|
} else {
|
|
|
|
window.scrollTo(0, 0);
|
|
|
|
}
|
|
|
|
});
|
2016-04-14 19:37:05 +02:00
|
|
|
}
|
|
|
|
|
2016-05-22 12:35:16 +02:00
|
|
|
function slideDown(element) {
|
|
|
|
const duration = 500;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const height = element.getBoundingClientRect().height;
|
|
|
|
element.style.maxHeight = '0';
|
|
|
|
element.style.overflow = 'hidden';
|
|
|
|
window.setTimeout(() => {
|
|
|
|
element.style.transition = `all ${duration}ms ease`;
|
|
|
|
element.style.maxHeight = `${height}px`;
|
|
|
|
}, 50);
|
|
|
|
window.setTimeout(() => {
|
|
|
|
resolve();
|
|
|
|
}, duration);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function slideUp(element) {
|
|
|
|
const duration = 500;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const height = element.getBoundingClientRect().height;
|
|
|
|
element.style.overflow = 'hidden';
|
|
|
|
element.style.maxHeight = `${height}px`;
|
|
|
|
element.style.transition = `all ${duration}ms ease`;
|
|
|
|
window.setTimeout(() => {
|
|
|
|
element.style.maxHeight = 0;
|
|
|
|
}, 10);
|
|
|
|
window.setTimeout(() => {
|
|
|
|
resolve();
|
|
|
|
}, duration);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-05-29 12:22:59 +02:00
|
|
|
function monitorNodeRemoval(monitoredNode, callback) {
|
|
|
|
const mutationObserver = new MutationObserver(
|
|
|
|
mutations => {
|
|
|
|
for (let mutation of mutations) {
|
|
|
|
for (let node of mutation.removedNodes) {
|
|
|
|
if (node.contains(monitoredNode)) {
|
|
|
|
mutationObserver.disconnect();
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
mutationObserver.observe(
|
|
|
|
document.body, {childList: true, subtree: true});
|
|
|
|
}
|
|
|
|
|
2016-05-10 10:57:59 +02:00
|
|
|
document.addEventListener('input', e => {
|
2017-02-07 21:33:44 +01:00
|
|
|
if (e.target.classList.contains('color')) {
|
|
|
|
let bkNode = e.target.parentNode.querySelector('.background-preview');
|
|
|
|
let textNode = e.target.parentNode.querySelector('.text-preview');
|
|
|
|
bkNode.style.backgroundColor = e.target.value;
|
|
|
|
bkNode.style.borderColor = e.target.value;
|
|
|
|
textNode.style.color = e.target.value;
|
|
|
|
textNode.style.borderColor = e.target.value;
|
2016-05-10 10:57:59 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-08-22 01:25:10 +02:00
|
|
|
// prevent opening buttons in new tabs
|
|
|
|
document.addEventListener('click', e => {
|
|
|
|
if (e.target.getAttribute('href') === '' && e.which === 2) {
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-09-29 11:16:55 +02:00
|
|
|
module.exports = {
|
2017-01-08 02:31:07 +01:00
|
|
|
htmlToDom: htmlToDom,
|
|
|
|
getTemplate: getTemplate,
|
|
|
|
emptyContent: emptyContent,
|
|
|
|
replaceContent: replaceContent,
|
|
|
|
enableForm: enableForm,
|
|
|
|
disableForm: disableForm,
|
|
|
|
decorateValidator: decorateValidator,
|
|
|
|
makeTagLink: makeTagLink,
|
|
|
|
makePostLink: makePostLink,
|
|
|
|
makeCheckbox: makeCheckbox,
|
|
|
|
makeRadio: makeRadio,
|
|
|
|
syncScrollPosition: syncScrollPosition,
|
|
|
|
slideDown: slideDown,
|
|
|
|
slideUp: slideUp,
|
|
|
|
monitorNodeRemoval: monitorNodeRemoval,
|
|
|
|
clearMessages: clearMessages,
|
|
|
|
appendExclamationMark: appendExclamationMark,
|
|
|
|
showError: showError,
|
|
|
|
showSuccess: showSuccess,
|
|
|
|
showInfo: showInfo,
|
2016-09-29 11:16:55 +02:00
|
|
|
};
|