Move unnecessary templates in headless package to main package
This commit is contained in:
parent
18024b8bd2
commit
7590a030b0
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
define([
|
define([
|
||||||
|
"./utils/html",
|
||||||
"utils/emoji",
|
"utils/emoji",
|
||||||
"@converse/headless/converse-core",
|
"@converse/headless/converse-core",
|
||||||
"xss",
|
"xss",
|
||||||
@ -17,6 +18,7 @@
|
|||||||
"templates/message_versions_modal.html",
|
"templates/message_versions_modal.html",
|
||||||
], factory);
|
], factory);
|
||||||
}(this, function (
|
}(this, function (
|
||||||
|
html,
|
||||||
u,
|
u,
|
||||||
converse,
|
converse,
|
||||||
xss,
|
xss,
|
||||||
|
@ -12,12 +12,12 @@
|
|||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
define(["utils/form",
|
define(["utils/form",
|
||||||
"@converse/headless/converse-core",
|
"@converse/headless/converse-core",
|
||||||
"@converse/headless/templates/form_username.html",
|
"templates/form_username.html",
|
||||||
"templates/register_link.html",
|
"templates/register_link.html",
|
||||||
"templates/register_panel.html",
|
"templates/register_panel.html",
|
||||||
"templates/registration_form.html",
|
"templates/registration_form.html",
|
||||||
"templates/registration_request.html",
|
"templates/registration_request.html",
|
||||||
"@converse/headless/templates/form_input.html",
|
"templates/form_input.html",
|
||||||
"templates/spinner.html",
|
"templates/spinner.html",
|
||||||
"converse-controlbox"
|
"converse-controlbox"
|
||||||
], factory);
|
], factory);
|
||||||
|
@ -12,15 +12,9 @@
|
|||||||
define([
|
define([
|
||||||
"sizzle",
|
"sizzle",
|
||||||
"es6-promise/dist/es6-promise.auto",
|
"es6-promise/dist/es6-promise.auto",
|
||||||
"fast-text-encoding/text",
|
|
||||||
"../lodash.noconflict",
|
"../lodash.noconflict",
|
||||||
"backbone",
|
"backbone",
|
||||||
"strophe.js",
|
"strophe.js",
|
||||||
"urijs",
|
|
||||||
"../templates/audio.html",
|
|
||||||
"../templates/file.html",
|
|
||||||
"../templates/image.html",
|
|
||||||
"../templates/video.html"
|
|
||||||
], factory);
|
], factory);
|
||||||
} else {
|
} else {
|
||||||
// Used by the mockups
|
// Used by the mockups
|
||||||
@ -49,57 +43,14 @@
|
|||||||
}(this, function (
|
}(this, function (
|
||||||
sizzle,
|
sizzle,
|
||||||
Promise,
|
Promise,
|
||||||
FastTextEncoding,
|
|
||||||
_,
|
_,
|
||||||
Backbone,
|
Backbone,
|
||||||
Strophe,
|
Strophe
|
||||||
URI,
|
|
||||||
tpl_audio,
|
|
||||||
tpl_file,
|
|
||||||
tpl_image,
|
|
||||||
tpl_video
|
|
||||||
) {
|
) {
|
||||||
"use strict";
|
"use strict";
|
||||||
Strophe = Strophe.Strophe;
|
Strophe = Strophe.Strophe;
|
||||||
|
|
||||||
const URL_REGEX = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g;
|
const u = {};
|
||||||
|
|
||||||
const logger = _.assign({
|
|
||||||
'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop,
|
|
||||||
'error': _.get(console, 'log') ? console.log.bind(console) : _.noop,
|
|
||||||
'info': _.get(console, 'log') ? console.log.bind(console) : _.noop,
|
|
||||||
'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop
|
|
||||||
}, console);
|
|
||||||
|
|
||||||
const isImage = function (url) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
var img = new Image();
|
|
||||||
var timer = window.setTimeout(function () {
|
|
||||||
reject(new Error("Could not determine whether it's an image"));
|
|
||||||
img = null;
|
|
||||||
}, 3000);
|
|
||||||
img.onerror = img.onabort = function () {
|
|
||||||
clearTimeout(timer);
|
|
||||||
reject(new Error("Could not determine whether it's an image"));
|
|
||||||
};
|
|
||||||
img.onload = function () {
|
|
||||||
clearTimeout(timer);
|
|
||||||
resolve(img);
|
|
||||||
};
|
|
||||||
img.src = url;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function slideOutWrapup (el) {
|
|
||||||
/* Wrapup function for slideOut. */
|
|
||||||
el.removeAttribute('data-slider-marker');
|
|
||||||
el.classList.remove('collapsed');
|
|
||||||
el.style.overflow = "";
|
|
||||||
el.style.height = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var u = {};
|
|
||||||
|
|
||||||
u.getLongestSubstring = function (string, candidates) {
|
u.getLongestSubstring = function (string, candidates) {
|
||||||
function reducer (accumulator, current_value) {
|
function reducer (accumulator, current_value) {
|
||||||
@ -116,118 +67,6 @@
|
|||||||
return candidates.reduce(reducer, '');
|
return candidates.reduce(reducer, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
u.getNextElement = function (el, selector='*') {
|
|
||||||
let next_el = el.nextElementSibling;
|
|
||||||
while (!_.isNull(next_el) && !sizzle.matchesSelector(next_el, selector)) {
|
|
||||||
next_el = next_el.nextElementSibling;
|
|
||||||
}
|
|
||||||
return next_el;
|
|
||||||
}
|
|
||||||
|
|
||||||
u.getPreviousElement = function (el, selector='*') {
|
|
||||||
let prev_el = el.previousSibling;
|
|
||||||
while (!_.isNull(prev_el) && !sizzle.matchesSelector(prev_el, selector)) {
|
|
||||||
prev_el = prev_el.previousSibling
|
|
||||||
}
|
|
||||||
return prev_el;
|
|
||||||
}
|
|
||||||
|
|
||||||
u.getFirstChildElement = function (el, selector='*') {
|
|
||||||
let first_el = el.firstElementChild;
|
|
||||||
while (!_.isNull(first_el) && !sizzle.matchesSelector(first_el, selector)) {
|
|
||||||
first_el = first_el.nextSibling
|
|
||||||
}
|
|
||||||
return first_el;
|
|
||||||
}
|
|
||||||
|
|
||||||
u.getLastChildElement = function (el, selector='*') {
|
|
||||||
let last_el = el.lastElementChild;
|
|
||||||
while (!_.isNull(last_el) && !sizzle.matchesSelector(last_el, selector)) {
|
|
||||||
last_el = last_el.previousSibling
|
|
||||||
}
|
|
||||||
return last_el;
|
|
||||||
}
|
|
||||||
|
|
||||||
u.calculateElementHeight = function (el) {
|
|
||||||
/* Return the height of the passed in DOM element,
|
|
||||||
* based on the heights of its children.
|
|
||||||
*/
|
|
||||||
return _.reduce(
|
|
||||||
el.children,
|
|
||||||
(result, child) => result + child.offsetHeight, 0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
u.addClass = function (className, el) {
|
|
||||||
if (el instanceof Element) {
|
|
||||||
el.classList.add(className);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u.removeClass = function (className, el) {
|
|
||||||
if (el instanceof Element) {
|
|
||||||
el.classList.remove(className);
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
u.removeElement = function (el) {
|
|
||||||
if (!_.isNil(el) && !_.isNil(el.parentNode)) {
|
|
||||||
el.parentNode.removeChild(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u.showElement = _.flow(
|
|
||||||
_.partial(u.removeClass, 'collapsed'),
|
|
||||||
_.partial(u.removeClass, 'hidden')
|
|
||||||
)
|
|
||||||
|
|
||||||
u.hideElement = function (el) {
|
|
||||||
if (!_.isNil(el)) {
|
|
||||||
el.classList.add('hidden');
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
u.ancestor = function (el, selector) {
|
|
||||||
let parent = el;
|
|
||||||
while (!_.isNil(parent) && !sizzle.matchesSelector(parent, selector)) {
|
|
||||||
parent = parent.parentElement;
|
|
||||||
}
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
u.nextUntil = function (el, selector, include_self=false) {
|
|
||||||
/* Return the element's siblings until one matches the selector. */
|
|
||||||
const matches = [];
|
|
||||||
let sibling_el = el.nextElementSibling;
|
|
||||||
while (!_.isNil(sibling_el) && !sibling_el.matches(selector)) {
|
|
||||||
matches.push(sibling_el);
|
|
||||||
sibling_el = sibling_el.nextElementSibling;
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
u.unescapeHTML = function (string) {
|
|
||||||
/* Helper method that replace HTML-escaped symbols with equivalent characters
|
|
||||||
* (e.g. transform occurrences of '&' to '&')
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (String) string: a String containing the HTML-escaped symbols.
|
|
||||||
*/
|
|
||||||
var div = document.createElement('div');
|
|
||||||
div.innerHTML = string;
|
|
||||||
return div.innerText;
|
|
||||||
};
|
|
||||||
|
|
||||||
u.escapeHTML = function (string) {
|
|
||||||
return string
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """);
|
|
||||||
};
|
|
||||||
|
|
||||||
u.prefixMentions = function (message) {
|
u.prefixMentions = function (message) {
|
||||||
/* Given a message object, return its text with @ chars
|
/* Given a message object, return its text with @ chars
|
||||||
* inserted before the mentioned nicknames.
|
* inserted before the mentioned nicknames.
|
||||||
@ -241,328 +80,6 @@
|
|||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
u.addMentionsMarkup = function (text, references, chatbox) {
|
|
||||||
if (chatbox.get('message_type') !== 'groupchat') {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
const nick = chatbox.get('nick');
|
|
||||||
references
|
|
||||||
.sort((a, b) => b.begin - a.begin)
|
|
||||||
.forEach(ref => {
|
|
||||||
const mention = text.slice(ref.begin, ref.end)
|
|
||||||
chatbox;
|
|
||||||
if (mention === nick) {
|
|
||||||
text = text.slice(0, ref.begin) + `<span class="mention mention--self badge badge-info">${mention}</span>` + text.slice(ref.end);
|
|
||||||
} else {
|
|
||||||
text = text.slice(0, ref.begin) + `<span class="mention">${mention}</span>` + text.slice(ref.end);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
u.addHyperlinks = function (text) {
|
|
||||||
return URI.withinString(text, url => {
|
|
||||||
const uri = new URI(url);
|
|
||||||
url = uri.normalize()._string;
|
|
||||||
const pretty_url = uri._parts.urn ? url : uri.readable();
|
|
||||||
if (!uri._parts.protocol && !url.startsWith('http://') && !url.startsWith('https://')) {
|
|
||||||
url = 'http://' + url;
|
|
||||||
}
|
|
||||||
if (uri._parts.protocol === 'xmpp' && uri._parts.query === 'join') {
|
|
||||||
return `<a target="_blank" rel="noopener" class="open-chatroom" href="${url}">${u.escapeHTML(pretty_url)}</a>`;
|
|
||||||
}
|
|
||||||
return `<a target="_blank" rel="noopener" href="${url}">${u.escapeHTML(pretty_url)}</a>`;
|
|
||||||
}, {
|
|
||||||
'start': /\b(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
u.renderNewLines = function (text) {
|
|
||||||
return text.replace(/\n\n+/g, '<br/><br/>').replace(/\n/g, '<br/>');
|
|
||||||
};
|
|
||||||
|
|
||||||
u.renderImageURLs = function (_converse, el) {
|
|
||||||
/* Returns a Promise which resolves once all images have been loaded.
|
|
||||||
*/
|
|
||||||
if (!_converse.show_images_inline) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
const { __ } = _converse;
|
|
||||||
const list = el.textContent.match(URL_REGEX) || [];
|
|
||||||
return Promise.all(
|
|
||||||
_.map(list, url =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
if (u.isImageURL(url)) {
|
|
||||||
return isImage(url).then(img => {
|
|
||||||
const i = new Image();
|
|
||||||
i.src = img.src;
|
|
||||||
i.addEventListener('load', resolve);
|
|
||||||
// We also resolve for non-images, otherwise the
|
|
||||||
// Promise.all resolves prematurely.
|
|
||||||
i.addEventListener('error', resolve);
|
|
||||||
|
|
||||||
const { __ } = _converse;
|
|
||||||
_.each(sizzle(`a[href="${url}"]`, el), (a) => {
|
|
||||||
a.outerHTML= tpl_image({
|
|
||||||
'url': url,
|
|
||||||
'label_download': __('Download')
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}).catch(resolve)
|
|
||||||
} else {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
u.renderFileURL = function (_converse, url) {
|
|
||||||
const uri = new URI(url);
|
|
||||||
if (u.isImageURL(uri) || u.isVideoURL(uri) || u.isAudioURL(uri)) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
const { __ } = _converse,
|
|
||||||
filename = uri.filename();
|
|
||||||
return tpl_file({
|
|
||||||
'url': url,
|
|
||||||
'label_download': __('Download file "%1$s"', decodeURI(filename))
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
u.isAudioURL = function (url) {
|
|
||||||
if (!(url instanceof URI)) {
|
|
||||||
url = new URI(url);
|
|
||||||
}
|
|
||||||
const filename = url.filename().toLowerCase();
|
|
||||||
if (!_.includes(["https", "http"], url.protocol().toLowerCase())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return filename.endsWith('.ogg') || filename.endsWith('.mp3') || filename.endsWith('.m4a');
|
|
||||||
}
|
|
||||||
|
|
||||||
u.isVideoURL = function (url) {
|
|
||||||
if (!(url instanceof URI)) {
|
|
||||||
url = new URI(url);
|
|
||||||
}
|
|
||||||
const filename = url.filename().toLowerCase();
|
|
||||||
if (!_.includes(["https", "http"], url.protocol().toLowerCase())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return filename.endsWith('.mp4') || filename.endsWith('.webm');
|
|
||||||
}
|
|
||||||
|
|
||||||
u.isImageURL = function (url) {
|
|
||||||
if (!(url instanceof URI)) {
|
|
||||||
url = new URI(url);
|
|
||||||
}
|
|
||||||
const filename = url.filename().toLowerCase();
|
|
||||||
if (!_.includes(["https", "http"], url.protocol().toLowerCase())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return filename.endsWith('.jpg') || filename.endsWith('.jpeg') ||
|
|
||||||
filename.endsWith('.png') || filename.endsWith('.gif') ||
|
|
||||||
filename.endsWith('.bmp') || filename.endsWith('.tiff') ||
|
|
||||||
filename.endsWith('.svg');
|
|
||||||
};
|
|
||||||
|
|
||||||
u.renderImageURL = function (_converse, url) {
|
|
||||||
if (!_converse.show_images_inline) {
|
|
||||||
return u.addHyperlinks(url);
|
|
||||||
}
|
|
||||||
const uri = new URI(url);
|
|
||||||
if (u.isImageURL(uri)) {
|
|
||||||
const { __ } = _converse;
|
|
||||||
return tpl_image({
|
|
||||||
'url': url,
|
|
||||||
'label_download': __('Download image "%1$s"', decodeURI(uri.filename()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
u.renderMovieURL = function (_converse, url) {
|
|
||||||
const uri = new URI(url);
|
|
||||||
if (u.isVideoURL(uri)) {
|
|
||||||
const { __ } = _converse;
|
|
||||||
return tpl_video({
|
|
||||||
'url': url,
|
|
||||||
'label_download': __('Download video file "%1$s"', decodeURI(uri.filename()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
u.renderAudioURL = function (_converse, url) {
|
|
||||||
const uri = new URI(url);
|
|
||||||
if (u.isAudioURL(uri)) {
|
|
||||||
const { __ } = _converse;
|
|
||||||
return tpl_audio({
|
|
||||||
'url': url,
|
|
||||||
'label_download': __('Download audio file "%1$s"', decodeURI(uri.filename()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
u.slideInAllElements = function (elements, duration=300) {
|
|
||||||
return Promise.all(
|
|
||||||
_.map(
|
|
||||||
elements,
|
|
||||||
_.partial(u.slideIn, _, duration)
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
u.slideToggleElement = function (el, duration) {
|
|
||||||
if (_.includes(el.classList, 'collapsed') ||
|
|
||||||
_.includes(el.classList, 'hidden')) {
|
|
||||||
return u.slideOut(el, duration);
|
|
||||||
} else {
|
|
||||||
return u.slideIn(el, duration);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
u.hasClass = function (className, el) {
|
|
||||||
return _.includes(el.classList, className);
|
|
||||||
};
|
|
||||||
|
|
||||||
u.slideOut = function (el, duration=200) {
|
|
||||||
/* Shows/expands an element by sliding it out of itself
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (HTMLElement) el - The HTML string
|
|
||||||
* (Number) duration - The duration amount in milliseconds
|
|
||||||
*/
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (_.isNil(el)) {
|
|
||||||
const err = "Undefined or null element passed into slideOut"
|
|
||||||
logger.warn(err);
|
|
||||||
reject(new Error(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const marker = el.getAttribute('data-slider-marker');
|
|
||||||
if (marker) {
|
|
||||||
el.removeAttribute('data-slider-marker');
|
|
||||||
window.cancelAnimationFrame(marker);
|
|
||||||
}
|
|
||||||
const end_height = u.calculateElementHeight(el);
|
|
||||||
if (window.converse_disable_effects) { // Effects are disabled (for tests)
|
|
||||||
el.style.height = end_height + 'px';
|
|
||||||
slideOutWrapup(el);
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!u.hasClass('collapsed', el) && !u.hasClass('hidden', el)) {
|
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const steps = duration/17; // We assume 17ms per animation which is ~60FPS
|
|
||||||
let height = 0;
|
|
||||||
|
|
||||||
function draw () {
|
|
||||||
height += end_height/steps;
|
|
||||||
if (height < end_height) {
|
|
||||||
el.style.height = height + 'px';
|
|
||||||
el.setAttribute(
|
|
||||||
'data-slider-marker',
|
|
||||||
window.requestAnimationFrame(draw)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// We recalculate the height to work around an apparent
|
|
||||||
// browser bug where browsers don't know the correct
|
|
||||||
// offsetHeight beforehand.
|
|
||||||
el.removeAttribute('data-slider-marker');
|
|
||||||
el.style.height = u.calculateElementHeight(el) + 'px';
|
|
||||||
el.style.overflow = "";
|
|
||||||
el.style.height = "";
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
el.style.height = '0';
|
|
||||||
el.style.overflow = 'hidden';
|
|
||||||
el.classList.remove('hidden');
|
|
||||||
el.classList.remove('collapsed');
|
|
||||||
el.setAttribute(
|
|
||||||
'data-slider-marker',
|
|
||||||
window.requestAnimationFrame(draw)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
u.slideIn = function (el, duration=200) {
|
|
||||||
/* Hides/collapses an element by sliding it into itself. */
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (_.isNil(el)) {
|
|
||||||
const err = "Undefined or null element passed into slideIn";
|
|
||||||
logger.warn(err);
|
|
||||||
return reject(new Error(err));
|
|
||||||
} else if (_.includes(el.classList, 'collapsed')) {
|
|
||||||
return resolve(el);
|
|
||||||
} else if (window.converse_disable_effects) { // Effects are disabled (for tests)
|
|
||||||
el.classList.add('collapsed');
|
|
||||||
el.style.height = "";
|
|
||||||
return resolve(el);
|
|
||||||
}
|
|
||||||
const marker = el.getAttribute('data-slider-marker');
|
|
||||||
if (marker) {
|
|
||||||
el.removeAttribute('data-slider-marker');
|
|
||||||
window.cancelAnimationFrame(marker);
|
|
||||||
}
|
|
||||||
const original_height = el.offsetHeight,
|
|
||||||
steps = duration/17; // We assume 17ms per animation which is ~60FPS
|
|
||||||
let height = original_height;
|
|
||||||
|
|
||||||
el.style.overflow = 'hidden';
|
|
||||||
|
|
||||||
function draw () {
|
|
||||||
height -= original_height/steps;
|
|
||||||
if (height > 0) {
|
|
||||||
el.style.height = height + 'px';
|
|
||||||
el.setAttribute(
|
|
||||||
'data-slider-marker',
|
|
||||||
window.requestAnimationFrame(draw)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
el.removeAttribute('data-slider-marker');
|
|
||||||
el.classList.add('collapsed');
|
|
||||||
el.style.height = "";
|
|
||||||
resolve(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
el.setAttribute(
|
|
||||||
'data-slider-marker',
|
|
||||||
window.requestAnimationFrame(draw)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function afterAnimationEnds (el, callback) {
|
|
||||||
el.classList.remove('visible');
|
|
||||||
if (_.isFunction(callback)) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
u.fadeIn = function (el, callback) {
|
|
||||||
if (_.isNil(el)) {
|
|
||||||
logger.warn("Undefined or null element passed into fadeIn");
|
|
||||||
}
|
|
||||||
if (window.converse_disable_effects) {
|
|
||||||
el.classList.remove('hidden');
|
|
||||||
return afterAnimationEnds(el, callback);
|
|
||||||
}
|
|
||||||
if (_.includes(el.classList, 'hidden')) {
|
|
||||||
el.classList.add('visible');
|
|
||||||
el.classList.remove('hidden');
|
|
||||||
el.addEventListener("webkitAnimationEnd", _.partial(afterAnimationEnds, el, callback));
|
|
||||||
el.addEventListener("animationend", _.partial(afterAnimationEnds, el, callback));
|
|
||||||
el.addEventListener("oanimationend", _.partial(afterAnimationEnds, el, callback));
|
|
||||||
} else {
|
|
||||||
afterAnimationEnds(el, callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
u.isValidJID = function (jid) {
|
u.isValidJID = function (jid) {
|
||||||
return _.compact(jid.split('@')).length === 2 && !jid.startsWith('@') && !jid.endsWith('@');
|
return _.compact(jid.split('@')).length === 2 && !jid.startsWith('@') && !jid.endsWith('@');
|
||||||
};
|
};
|
||||||
|
@ -6,49 +6,16 @@
|
|||||||
// Copyright (c) 2013-2018, Jan-Carel Brand <jc@opkode.com>
|
// Copyright (c) 2013-2018, Jan-Carel Brand <jc@opkode.com>
|
||||||
// Licensed under the Mozilla Public License (MPLv2)
|
// Licensed under the Mozilla Public License (MPLv2)
|
||||||
//
|
//
|
||||||
/*global define, escape, Jed */
|
/*global define */
|
||||||
(function (root, factory) {
|
(function (root, factory) {
|
||||||
define([
|
define([
|
||||||
"sizzle",
|
|
||||||
"../lodash.noconflict",
|
"../lodash.noconflict",
|
||||||
"./core",
|
"./core",
|
||||||
"../templates/field.html",
|
"../templates/field.html"
|
||||||
"../templates/select_option.html",
|
|
||||||
"../templates/form_select.html",
|
|
||||||
"../templates/form_textarea.html",
|
|
||||||
"../templates/form_checkbox.html",
|
|
||||||
"../templates/form_username.html",
|
|
||||||
"../templates/form_input.html",
|
|
||||||
"../templates/form_captcha.html",
|
|
||||||
"../templates/form_url.html",
|
|
||||||
], factory);
|
], factory);
|
||||||
}(this, function (
|
}(this, function (_, u, tpl_field) {
|
||||||
sizzle,
|
|
||||||
_,
|
|
||||||
u,
|
|
||||||
tpl_field,
|
|
||||||
tpl_select_option,
|
|
||||||
tpl_form_select,
|
|
||||||
tpl_form_textarea,
|
|
||||||
tpl_form_checkbox,
|
|
||||||
tpl_form_username,
|
|
||||||
tpl_form_input,
|
|
||||||
tpl_form_captcha,
|
|
||||||
tpl_form_url
|
|
||||||
) {
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var XFORM_TYPE_MAP = {
|
|
||||||
'text-private': 'password',
|
|
||||||
'text-single': 'text',
|
|
||||||
'fixed': 'label',
|
|
||||||
'boolean': 'checkbox',
|
|
||||||
'hidden': 'hidden',
|
|
||||||
'jid-multi': 'textarea',
|
|
||||||
'list-single': 'dropdown',
|
|
||||||
'list-multi': 'dropdown'
|
|
||||||
};
|
|
||||||
|
|
||||||
u.webForm2xForm = function (field) {
|
u.webForm2xForm = function (field) {
|
||||||
/* Takes an HTML DOM and turns it into an XForm field.
|
/* Takes an HTML DOM and turns it into an XForm field.
|
||||||
*
|
*
|
||||||
@ -72,101 +39,5 @@
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
u.xForm2webForm = function (field, stanza, domain) {
|
|
||||||
/* Takes a field in XMPP XForm (XEP-004: Data Forms) format
|
|
||||||
* and turns it into an HTML field.
|
|
||||||
*
|
|
||||||
* Returns either text or a DOM element (which is not ideal, but fine
|
|
||||||
* for now).
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
* (XMLElement) field - the field to convert
|
|
||||||
*/
|
|
||||||
if (field.getAttribute('type')) {
|
|
||||||
if (field.getAttribute('type') === 'list-single' ||
|
|
||||||
field.getAttribute('type') === 'list-multi') {
|
|
||||||
|
|
||||||
const values = _.map(
|
|
||||||
u.queryChildren(field, 'value'),
|
|
||||||
_.partial(_.get, _, 'textContent')
|
|
||||||
);
|
|
||||||
const options = _.map(
|
|
||||||
u.queryChildren(field, 'option'),
|
|
||||||
function (option) {
|
|
||||||
const value = _.get(option.querySelector('value'), 'textContent');
|
|
||||||
return tpl_select_option({
|
|
||||||
'value': value,
|
|
||||||
'label': option.getAttribute('label'),
|
|
||||||
'selected': _.includes(values, value),
|
|
||||||
'required': !_.isNil(field.querySelector('required'))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return tpl_form_select({
|
|
||||||
'id': u.getUniqueId(),
|
|
||||||
'name': field.getAttribute('var'),
|
|
||||||
'label': field.getAttribute('label'),
|
|
||||||
'options': options.join(''),
|
|
||||||
'multiple': (field.getAttribute('type') === 'list-multi'),
|
|
||||||
'required': !_.isNil(field.querySelector('required'))
|
|
||||||
});
|
|
||||||
} else if (field.getAttribute('type') === 'fixed') {
|
|
||||||
const text = _.get(field.querySelector('value'), 'textContent');
|
|
||||||
return '<p class="form-help">'+text+'</p>';
|
|
||||||
} else if (field.getAttribute('type') === 'jid-multi') {
|
|
||||||
return tpl_form_textarea({
|
|
||||||
'name': field.getAttribute('var'),
|
|
||||||
'label': field.getAttribute('label') || '',
|
|
||||||
'value': _.get(field.querySelector('value'), 'textContent'),
|
|
||||||
'required': !_.isNil(field.querySelector('required'))
|
|
||||||
});
|
|
||||||
} else if (field.getAttribute('type') === 'boolean') {
|
|
||||||
return tpl_form_checkbox({
|
|
||||||
'id': u.getUniqueId(),
|
|
||||||
'name': field.getAttribute('var'),
|
|
||||||
'label': field.getAttribute('label') || '',
|
|
||||||
'checked': _.get(field.querySelector('value'), 'textContent') === "1" && 'checked="1"' || '',
|
|
||||||
'required': !_.isNil(field.querySelector('required'))
|
|
||||||
});
|
|
||||||
} else if (field.getAttribute('var') === 'url') {
|
|
||||||
return tpl_form_url({
|
|
||||||
'label': field.getAttribute('label') || '',
|
|
||||||
'value': _.get(field.querySelector('value'), 'textContent')
|
|
||||||
});
|
|
||||||
} else if (field.getAttribute('var') === 'username') {
|
|
||||||
return tpl_form_username({
|
|
||||||
'domain': ' @'+domain,
|
|
||||||
'name': field.getAttribute('var'),
|
|
||||||
'type': XFORM_TYPE_MAP[field.getAttribute('type')],
|
|
||||||
'label': field.getAttribute('label') || '',
|
|
||||||
'value': _.get(field.querySelector('value'), 'textContent'),
|
|
||||||
'required': !_.isNil(field.querySelector('required'))
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return tpl_form_input({
|
|
||||||
'id': u.getUniqueId(),
|
|
||||||
'label': field.getAttribute('label') || '',
|
|
||||||
'name': field.getAttribute('var'),
|
|
||||||
'placeholder': null,
|
|
||||||
'required': !_.isNil(field.querySelector('required')),
|
|
||||||
'type': XFORM_TYPE_MAP[field.getAttribute('type')],
|
|
||||||
'value': _.get(field.querySelector('value'), 'textContent')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (field.getAttribute('var') === 'ocr') { // Captcha
|
|
||||||
const uri = field.querySelector('uri');
|
|
||||||
const el = sizzle('data[cid="'+uri.textContent.replace(/^cid:/, '')+'"]', stanza)[0];
|
|
||||||
return tpl_form_captcha({
|
|
||||||
'label': field.getAttribute('label'),
|
|
||||||
'name': field.getAttribute('var'),
|
|
||||||
'data': _.get(el, 'textContent'),
|
|
||||||
'type': uri.getAttribute('type'),
|
|
||||||
'required': !_.isNil(field.querySelector('required'))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return u;
|
return u;
|
||||||
}));
|
}));
|
||||||
|
643
src/utils/html.js
Normal file
643
src/utils/html.js
Normal file
@ -0,0 +1,643 @@
|
|||||||
|
// Converse.js (A browser based XMPP chat client)
|
||||||
|
// http://conversejs.org
|
||||||
|
//
|
||||||
|
// This is a form utilities module.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2013-2018, Jan-Carel Brand <jc@opkode.com>
|
||||||
|
// Licensed under the Mozilla Public License (MPLv2)
|
||||||
|
//
|
||||||
|
/*global define */
|
||||||
|
(function (root, factory) {
|
||||||
|
define([
|
||||||
|
"sizzle",
|
||||||
|
"../headless/lodash.noconflict",
|
||||||
|
"../headless/utils/core",
|
||||||
|
"urijs",
|
||||||
|
"../templates/audio.html",
|
||||||
|
"../headless/templates/field.html",
|
||||||
|
"../templates/file.html",
|
||||||
|
"../templates/form_captcha.html",
|
||||||
|
"../templates/form_checkbox.html",
|
||||||
|
"../templates/form_input.html",
|
||||||
|
"../templates/form_select.html",
|
||||||
|
"../templates/form_textarea.html",
|
||||||
|
"../templates/form_url.html",
|
||||||
|
"../templates/form_username.html",
|
||||||
|
"../templates/image.html",
|
||||||
|
"../templates/select_option.html",
|
||||||
|
"../templates/video.html"
|
||||||
|
], factory);
|
||||||
|
}(this, function (
|
||||||
|
sizzle,
|
||||||
|
_,
|
||||||
|
u,
|
||||||
|
URI,
|
||||||
|
tpl_audio,
|
||||||
|
tpl_field,
|
||||||
|
tpl_file,
|
||||||
|
tpl_form_captcha,
|
||||||
|
tpl_form_checkbox,
|
||||||
|
tpl_form_input,
|
||||||
|
tpl_form_select,
|
||||||
|
tpl_form_textarea,
|
||||||
|
tpl_form_url,
|
||||||
|
tpl_form_username,
|
||||||
|
tpl_image,
|
||||||
|
tpl_select_option,
|
||||||
|
tpl_video
|
||||||
|
) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const URL_REGEX = /\b(https?:\/\/|www\.|https?:\/\/www\.)[^\s<>]{2,200}\b\/?/g;
|
||||||
|
|
||||||
|
const logger = _.assign({
|
||||||
|
'debug': _.get(console, 'log') ? console.log.bind(console) : _.noop,
|
||||||
|
'error': _.get(console, 'log') ? console.log.bind(console) : _.noop,
|
||||||
|
'info': _.get(console, 'log') ? console.log.bind(console) : _.noop,
|
||||||
|
'warn': _.get(console, 'log') ? console.log.bind(console) : _.noop
|
||||||
|
}, console);
|
||||||
|
|
||||||
|
const XFORM_TYPE_MAP = {
|
||||||
|
'text-private': 'password',
|
||||||
|
'text-single': 'text',
|
||||||
|
'fixed': 'label',
|
||||||
|
'boolean': 'checkbox',
|
||||||
|
'hidden': 'hidden',
|
||||||
|
'jid-multi': 'textarea',
|
||||||
|
'list-single': 'dropdown',
|
||||||
|
'list-multi': 'dropdown'
|
||||||
|
};
|
||||||
|
|
||||||
|
function slideOutWrapup (el) {
|
||||||
|
/* Wrapup function for slideOut. */
|
||||||
|
el.removeAttribute('data-slider-marker');
|
||||||
|
el.classList.remove('collapsed');
|
||||||
|
el.style.overflow = "";
|
||||||
|
el.style.height = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const isImage = function (url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var img = new Image();
|
||||||
|
var timer = window.setTimeout(function () {
|
||||||
|
reject(new Error("Could not determine whether it's an image"));
|
||||||
|
img = null;
|
||||||
|
}, 3000);
|
||||||
|
img.onerror = img.onabort = function () {
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(new Error("Could not determine whether it's an image"));
|
||||||
|
};
|
||||||
|
img.onload = function () {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve(img);
|
||||||
|
};
|
||||||
|
img.src = url;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.isAudioURL = function (url) {
|
||||||
|
if (!(url instanceof URI)) {
|
||||||
|
url = new URI(url);
|
||||||
|
}
|
||||||
|
const filename = url.filename().toLowerCase();
|
||||||
|
if (!_.includes(["https", "http"], url.protocol().toLowerCase())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return filename.endsWith('.ogg') || filename.endsWith('.mp3') || filename.endsWith('.m4a');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
u.isImageURL = function (url) {
|
||||||
|
if (!(url instanceof URI)) {
|
||||||
|
url = new URI(url);
|
||||||
|
}
|
||||||
|
const filename = url.filename().toLowerCase();
|
||||||
|
if (!_.includes(["https", "http"], url.protocol().toLowerCase())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return filename.endsWith('.jpg') || filename.endsWith('.jpeg') ||
|
||||||
|
filename.endsWith('.png') || filename.endsWith('.gif') ||
|
||||||
|
filename.endsWith('.bmp') || filename.endsWith('.tiff') ||
|
||||||
|
filename.endsWith('.svg');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.isVideoURL = function (url) {
|
||||||
|
if (!(url instanceof URI)) {
|
||||||
|
url = new URI(url);
|
||||||
|
}
|
||||||
|
const filename = url.filename().toLowerCase();
|
||||||
|
if (!_.includes(["https", "http"], url.protocol().toLowerCase())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return filename.endsWith('.mp4') || filename.endsWith('.webm');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
u.renderAudioURL = function (_converse, url) {
|
||||||
|
const uri = new URI(url);
|
||||||
|
if (u.isAudioURL(uri)) {
|
||||||
|
const { __ } = _converse;
|
||||||
|
return tpl_audio({
|
||||||
|
'url': url,
|
||||||
|
'label_download': __('Download audio file "%1$s"', decodeURI(uri.filename()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.renderFileURL = function (_converse, url) {
|
||||||
|
const uri = new URI(url);
|
||||||
|
if (u.isImageURL(uri) || u.isVideoURL(uri) || u.isAudioURL(uri)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
const { __ } = _converse,
|
||||||
|
filename = uri.filename();
|
||||||
|
return tpl_file({
|
||||||
|
'url': url,
|
||||||
|
'label_download': __('Download file "%1$s"', decodeURI(filename))
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.renderImageURL = function (_converse, url) {
|
||||||
|
if (!_converse.show_images_inline) {
|
||||||
|
return u.addHyperlinks(url);
|
||||||
|
}
|
||||||
|
const uri = new URI(url);
|
||||||
|
if (u.isImageURL(uri)) {
|
||||||
|
const { __ } = _converse;
|
||||||
|
return tpl_image({
|
||||||
|
'url': url,
|
||||||
|
'label_download': __('Download image "%1$s"', decodeURI(uri.filename()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.renderImageURLs = function (_converse, el) {
|
||||||
|
/* Returns a Promise which resolves once all images have been loaded.
|
||||||
|
*/
|
||||||
|
if (!_converse.show_images_inline) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const { __ } = _converse;
|
||||||
|
const list = el.textContent.match(URL_REGEX) || [];
|
||||||
|
return Promise.all(
|
||||||
|
_.map(list, url =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
if (u.isImageURL(url)) {
|
||||||
|
return isImage(url).then(img => {
|
||||||
|
const i = new Image();
|
||||||
|
i.src = img.src;
|
||||||
|
i.addEventListener('load', resolve);
|
||||||
|
// We also resolve for non-images, otherwise the
|
||||||
|
// Promise.all resolves prematurely.
|
||||||
|
i.addEventListener('error', resolve);
|
||||||
|
|
||||||
|
const { __ } = _converse;
|
||||||
|
_.each(sizzle(`a[href="${url}"]`, el), (a) => {
|
||||||
|
a.outerHTML= tpl_image({
|
||||||
|
'url': url,
|
||||||
|
'label_download': __('Download')
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}).catch(resolve)
|
||||||
|
} else {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.renderMovieURL = function (_converse, url) {
|
||||||
|
const uri = new URI(url);
|
||||||
|
if (u.isVideoURL(uri)) {
|
||||||
|
const { __ } = _converse;
|
||||||
|
return tpl_video({
|
||||||
|
'url': url,
|
||||||
|
'label_download': __('Download video file "%1$s"', decodeURI(uri.filename()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.renderNewLines = function (text) {
|
||||||
|
return text.replace(/\n\n+/g, '<br/><br/>').replace(/\n/g, '<br/>');
|
||||||
|
};
|
||||||
|
|
||||||
|
u.calculateElementHeight = function (el) {
|
||||||
|
/* Return the height of the passed in DOM element,
|
||||||
|
* based on the heights of its children.
|
||||||
|
*/
|
||||||
|
return _.reduce(
|
||||||
|
el.children,
|
||||||
|
(result, child) => result + child.offsetHeight, 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
u.getNextElement = function (el, selector='*') {
|
||||||
|
let next_el = el.nextElementSibling;
|
||||||
|
while (!_.isNull(next_el) && !sizzle.matchesSelector(next_el, selector)) {
|
||||||
|
next_el = next_el.nextElementSibling;
|
||||||
|
}
|
||||||
|
return next_el;
|
||||||
|
}
|
||||||
|
|
||||||
|
u.getPreviousElement = function (el, selector='*') {
|
||||||
|
let prev_el = el.previousSibling;
|
||||||
|
while (!_.isNull(prev_el) && !sizzle.matchesSelector(prev_el, selector)) {
|
||||||
|
prev_el = prev_el.previousSibling
|
||||||
|
}
|
||||||
|
return prev_el;
|
||||||
|
}
|
||||||
|
|
||||||
|
u.getFirstChildElement = function (el, selector='*') {
|
||||||
|
let first_el = el.firstElementChild;
|
||||||
|
while (!_.isNull(first_el) && !sizzle.matchesSelector(first_el, selector)) {
|
||||||
|
first_el = first_el.nextSibling
|
||||||
|
}
|
||||||
|
return first_el;
|
||||||
|
}
|
||||||
|
|
||||||
|
u.getLastChildElement = function (el, selector='*') {
|
||||||
|
let last_el = el.lastElementChild;
|
||||||
|
while (!_.isNull(last_el) && !sizzle.matchesSelector(last_el, selector)) {
|
||||||
|
last_el = last_el.previousSibling
|
||||||
|
}
|
||||||
|
return last_el;
|
||||||
|
}
|
||||||
|
|
||||||
|
u.hasClass = function (className, el) {
|
||||||
|
return _.includes(el.classList, className);
|
||||||
|
};
|
||||||
|
|
||||||
|
u.addClass = function (className, el) {
|
||||||
|
if (el instanceof Element) {
|
||||||
|
el.classList.add(className);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.removeClass = function (className, el) {
|
||||||
|
if (el instanceof Element) {
|
||||||
|
el.classList.remove(className);
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
u.removeElement = function (el) {
|
||||||
|
if (!_.isNil(el) && !_.isNil(el.parentNode)) {
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.showElement = _.flow(
|
||||||
|
_.partial(u.removeClass, 'collapsed'),
|
||||||
|
_.partial(u.removeClass, 'hidden')
|
||||||
|
)
|
||||||
|
|
||||||
|
u.hideElement = function (el) {
|
||||||
|
if (!_.isNil(el)) {
|
||||||
|
el.classList.add('hidden');
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
u.ancestor = function (el, selector) {
|
||||||
|
let parent = el;
|
||||||
|
while (!_.isNil(parent) && !sizzle.matchesSelector(parent, selector)) {
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
u.nextUntil = function (el, selector, include_self=false) {
|
||||||
|
/* Return the element's siblings until one matches the selector. */
|
||||||
|
const matches = [];
|
||||||
|
let sibling_el = el.nextElementSibling;
|
||||||
|
while (!_.isNil(sibling_el) && !sibling_el.matches(selector)) {
|
||||||
|
matches.push(sibling_el);
|
||||||
|
sibling_el = sibling_el.nextElementSibling;
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
u.unescapeHTML = function (string) {
|
||||||
|
/* Helper method that replace HTML-escaped symbols with equivalent characters
|
||||||
|
* (e.g. transform occurrences of '&' to '&')
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (String) string: a String containing the HTML-escaped symbols.
|
||||||
|
*/
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = string;
|
||||||
|
return div.innerText;
|
||||||
|
};
|
||||||
|
|
||||||
|
u.escapeHTML = function (string) {
|
||||||
|
return string
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.addMentionsMarkup = function (text, references, chatbox) {
|
||||||
|
if (chatbox.get('message_type') !== 'groupchat') {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
const nick = chatbox.get('nick');
|
||||||
|
references
|
||||||
|
.sort((a, b) => b.begin - a.begin)
|
||||||
|
.forEach(ref => {
|
||||||
|
const mention = text.slice(ref.begin, ref.end)
|
||||||
|
chatbox;
|
||||||
|
if (mention === nick) {
|
||||||
|
text = text.slice(0, ref.begin) + `<span class="mention mention--self badge badge-info">${mention}</span>` + text.slice(ref.end);
|
||||||
|
} else {
|
||||||
|
text = text.slice(0, ref.begin) + `<span class="mention">${mention}</span>` + text.slice(ref.end);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.addHyperlinks = function (text) {
|
||||||
|
return URI.withinString(text, url => {
|
||||||
|
const uri = new URI(url);
|
||||||
|
url = uri.normalize()._string;
|
||||||
|
const pretty_url = uri._parts.urn ? url : uri.readable();
|
||||||
|
if (!uri._parts.protocol && !url.startsWith('http://') && !url.startsWith('https://')) {
|
||||||
|
url = 'http://' + url;
|
||||||
|
}
|
||||||
|
if (uri._parts.protocol === 'xmpp' && uri._parts.query === 'join') {
|
||||||
|
return `<a target="_blank" rel="noopener" class="open-chatroom" href="${url}">${u.escapeHTML(pretty_url)}</a>`;
|
||||||
|
}
|
||||||
|
return `<a target="_blank" rel="noopener" href="${url}">${u.escapeHTML(pretty_url)}</a>`;
|
||||||
|
}, {
|
||||||
|
'start': /\b(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.slideInAllElements = function (elements, duration=300) {
|
||||||
|
return Promise.all(
|
||||||
|
_.map(
|
||||||
|
elements,
|
||||||
|
_.partial(u.slideIn, _, duration)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
u.slideToggleElement = function (el, duration) {
|
||||||
|
if (_.includes(el.classList, 'collapsed') ||
|
||||||
|
_.includes(el.classList, 'hidden')) {
|
||||||
|
return u.slideOut(el, duration);
|
||||||
|
} else {
|
||||||
|
return u.slideIn(el, duration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.slideOut = function (el, duration=200) {
|
||||||
|
/* Shows/expands an element by sliding it out of itself
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (HTMLElement) el - The HTML string
|
||||||
|
* (Number) duration - The duration amount in milliseconds
|
||||||
|
*/
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (_.isNil(el)) {
|
||||||
|
const err = "Undefined or null element passed into slideOut"
|
||||||
|
logger.warn(err);
|
||||||
|
reject(new Error(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const marker = el.getAttribute('data-slider-marker');
|
||||||
|
if (marker) {
|
||||||
|
el.removeAttribute('data-slider-marker');
|
||||||
|
window.cancelAnimationFrame(marker);
|
||||||
|
}
|
||||||
|
const end_height = u.calculateElementHeight(el);
|
||||||
|
if (window.converse_disable_effects) { // Effects are disabled (for tests)
|
||||||
|
el.style.height = end_height + 'px';
|
||||||
|
slideOutWrapup(el);
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!u.hasClass('collapsed', el) && !u.hasClass('hidden', el)) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps = duration/17; // We assume 17ms per animation which is ~60FPS
|
||||||
|
let height = 0;
|
||||||
|
|
||||||
|
function draw () {
|
||||||
|
height += end_height/steps;
|
||||||
|
if (height < end_height) {
|
||||||
|
el.style.height = height + 'px';
|
||||||
|
el.setAttribute(
|
||||||
|
'data-slider-marker',
|
||||||
|
window.requestAnimationFrame(draw)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// We recalculate the height to work around an apparent
|
||||||
|
// browser bug where browsers don't know the correct
|
||||||
|
// offsetHeight beforehand.
|
||||||
|
el.removeAttribute('data-slider-marker');
|
||||||
|
el.style.height = u.calculateElementHeight(el) + 'px';
|
||||||
|
el.style.overflow = "";
|
||||||
|
el.style.height = "";
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
el.style.height = '0';
|
||||||
|
el.style.overflow = 'hidden';
|
||||||
|
el.classList.remove('hidden');
|
||||||
|
el.classList.remove('collapsed');
|
||||||
|
el.setAttribute(
|
||||||
|
'data-slider-marker',
|
||||||
|
window.requestAnimationFrame(draw)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
u.slideIn = function (el, duration=200) {
|
||||||
|
/* Hides/collapses an element by sliding it into itself. */
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (_.isNil(el)) {
|
||||||
|
const err = "Undefined or null element passed into slideIn";
|
||||||
|
logger.warn(err);
|
||||||
|
return reject(new Error(err));
|
||||||
|
} else if (_.includes(el.classList, 'collapsed')) {
|
||||||
|
return resolve(el);
|
||||||
|
} else if (window.converse_disable_effects) { // Effects are disabled (for tests)
|
||||||
|
el.classList.add('collapsed');
|
||||||
|
el.style.height = "";
|
||||||
|
return resolve(el);
|
||||||
|
}
|
||||||
|
const marker = el.getAttribute('data-slider-marker');
|
||||||
|
if (marker) {
|
||||||
|
el.removeAttribute('data-slider-marker');
|
||||||
|
window.cancelAnimationFrame(marker);
|
||||||
|
}
|
||||||
|
const original_height = el.offsetHeight,
|
||||||
|
steps = duration/17; // We assume 17ms per animation which is ~60FPS
|
||||||
|
let height = original_height;
|
||||||
|
|
||||||
|
el.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
function draw () {
|
||||||
|
height -= original_height/steps;
|
||||||
|
if (height > 0) {
|
||||||
|
el.style.height = height + 'px';
|
||||||
|
el.setAttribute(
|
||||||
|
'data-slider-marker',
|
||||||
|
window.requestAnimationFrame(draw)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
el.removeAttribute('data-slider-marker');
|
||||||
|
el.classList.add('collapsed');
|
||||||
|
el.style.height = "";
|
||||||
|
resolve(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
el.setAttribute(
|
||||||
|
'data-slider-marker',
|
||||||
|
window.requestAnimationFrame(draw)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function afterAnimationEnds (el, callback) {
|
||||||
|
el.classList.remove('visible');
|
||||||
|
if (_.isFunction(callback)) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.fadeIn = function (el, callback) {
|
||||||
|
if (_.isNil(el)) {
|
||||||
|
logger.warn("Undefined or null element passed into fadeIn");
|
||||||
|
}
|
||||||
|
if (window.converse_disable_effects) {
|
||||||
|
el.classList.remove('hidden');
|
||||||
|
return afterAnimationEnds(el, callback);
|
||||||
|
}
|
||||||
|
if (_.includes(el.classList, 'hidden')) {
|
||||||
|
el.classList.add('visible');
|
||||||
|
el.classList.remove('hidden');
|
||||||
|
el.addEventListener("webkitAnimationEnd", _.partial(afterAnimationEnds, el, callback));
|
||||||
|
el.addEventListener("animationend", _.partial(afterAnimationEnds, el, callback));
|
||||||
|
el.addEventListener("oanimationend", _.partial(afterAnimationEnds, el, callback));
|
||||||
|
} else {
|
||||||
|
afterAnimationEnds(el, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
u.xForm2webForm = function (field, stanza, domain) {
|
||||||
|
/* Takes a field in XMPP XForm (XEP-004: Data Forms) format
|
||||||
|
* and turns it into an HTML field.
|
||||||
|
*
|
||||||
|
* Returns either text or a DOM element (which is not ideal, but fine
|
||||||
|
* for now).
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* (XMLElement) field - the field to convert
|
||||||
|
*/
|
||||||
|
if (field.getAttribute('type')) {
|
||||||
|
if (field.getAttribute('type') === 'list-single' ||
|
||||||
|
field.getAttribute('type') === 'list-multi') {
|
||||||
|
|
||||||
|
const values = _.map(
|
||||||
|
u.queryChildren(field, 'value'),
|
||||||
|
_.partial(_.get, _, 'textContent')
|
||||||
|
);
|
||||||
|
const options = _.map(
|
||||||
|
u.queryChildren(field, 'option'),
|
||||||
|
function (option) {
|
||||||
|
const value = _.get(option.querySelector('value'), 'textContent');
|
||||||
|
return tpl_select_option({
|
||||||
|
'value': value,
|
||||||
|
'label': option.getAttribute('label'),
|
||||||
|
'selected': _.includes(values, value),
|
||||||
|
'required': !_.isNil(field.querySelector('required'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return tpl_form_select({
|
||||||
|
'id': u.getUniqueId(),
|
||||||
|
'name': field.getAttribute('var'),
|
||||||
|
'label': field.getAttribute('label'),
|
||||||
|
'options': options.join(''),
|
||||||
|
'multiple': (field.getAttribute('type') === 'list-multi'),
|
||||||
|
'required': !_.isNil(field.querySelector('required'))
|
||||||
|
});
|
||||||
|
} else if (field.getAttribute('type') === 'fixed') {
|
||||||
|
const text = _.get(field.querySelector('value'), 'textContent');
|
||||||
|
return '<p class="form-help">'+text+'</p>';
|
||||||
|
} else if (field.getAttribute('type') === 'jid-multi') {
|
||||||
|
return tpl_form_textarea({
|
||||||
|
'name': field.getAttribute('var'),
|
||||||
|
'label': field.getAttribute('label') || '',
|
||||||
|
'value': _.get(field.querySelector('value'), 'textContent'),
|
||||||
|
'required': !_.isNil(field.querySelector('required'))
|
||||||
|
});
|
||||||
|
} else if (field.getAttribute('type') === 'boolean') {
|
||||||
|
return tpl_form_checkbox({
|
||||||
|
'id': u.getUniqueId(),
|
||||||
|
'name': field.getAttribute('var'),
|
||||||
|
'label': field.getAttribute('label') || '',
|
||||||
|
'checked': _.get(field.querySelector('value'), 'textContent') === "1" && 'checked="1"' || '',
|
||||||
|
'required': !_.isNil(field.querySelector('required'))
|
||||||
|
});
|
||||||
|
} else if (field.getAttribute('var') === 'url') {
|
||||||
|
return tpl_form_url({
|
||||||
|
'label': field.getAttribute('label') || '',
|
||||||
|
'value': _.get(field.querySelector('value'), 'textContent')
|
||||||
|
});
|
||||||
|
} else if (field.getAttribute('var') === 'username') {
|
||||||
|
return tpl_form_username({
|
||||||
|
'domain': ' @'+domain,
|
||||||
|
'name': field.getAttribute('var'),
|
||||||
|
'type': XFORM_TYPE_MAP[field.getAttribute('type')],
|
||||||
|
'label': field.getAttribute('label') || '',
|
||||||
|
'value': _.get(field.querySelector('value'), 'textContent'),
|
||||||
|
'required': !_.isNil(field.querySelector('required'))
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return tpl_form_input({
|
||||||
|
'id': u.getUniqueId(),
|
||||||
|
'label': field.getAttribute('label') || '',
|
||||||
|
'name': field.getAttribute('var'),
|
||||||
|
'placeholder': null,
|
||||||
|
'required': !_.isNil(field.querySelector('required')),
|
||||||
|
'type': XFORM_TYPE_MAP[field.getAttribute('type')],
|
||||||
|
'value': _.get(field.querySelector('value'), 'textContent')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (field.getAttribute('var') === 'ocr') { // Captcha
|
||||||
|
const uri = field.querySelector('uri');
|
||||||
|
const el = sizzle('data[cid="'+uri.textContent.replace(/^cid:/, '')+'"]', stanza)[0];
|
||||||
|
return tpl_form_captcha({
|
||||||
|
'label': field.getAttribute('label'),
|
||||||
|
'name': field.getAttribute('var'),
|
||||||
|
'data': _.get(el, 'textContent'),
|
||||||
|
'type': uri.getAttribute('type'),
|
||||||
|
'required': !_.isNil(field.querySelector('required'))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}));
|
Loading…
Reference in New Issue
Block a user