2017-05-18 20:08:54 +00:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const $ = require("jquery");
|
2017-11-01 09:16:19 +00:00
|
|
|
const escape = require("css.escape");
|
2018-03-08 13:46:05 +00:00
|
|
|
const viewport = $("#viewport");
|
2018-07-06 18:15:15 +00:00
|
|
|
const {vueApp} = require("./vue");
|
2017-05-18 20:08:54 +00:00
|
|
|
|
2018-01-11 11:33:36 +00:00
|
|
|
var serverHash = -1; // eslint-disable-line no-var
|
2017-08-28 09:18:31 +00:00
|
|
|
|
2017-05-18 20:08:54 +00:00
|
|
|
module.exports = {
|
2018-03-15 13:11:15 +00:00
|
|
|
// Same value as media query in CSS that forces sidebars to become overlays
|
|
|
|
mobileViewportPixels: 768,
|
2017-09-02 16:28:36 +00:00
|
|
|
findCurrentNetworkChan,
|
2017-08-28 09:18:31 +00:00
|
|
|
serverHash,
|
2017-05-18 20:08:54 +00:00
|
|
|
confirmExit,
|
2018-03-08 16:27:20 +00:00
|
|
|
scrollIntoViewNicely,
|
2017-12-20 09:45:12 +00:00
|
|
|
hasRoleInChannel,
|
2017-05-18 20:08:54 +00:00
|
|
|
move,
|
2018-04-19 06:31:44 +00:00
|
|
|
closeChan,
|
2018-09-05 19:44:19 +00:00
|
|
|
synchronizeNotifiedState,
|
2018-05-08 21:40:39 +00:00
|
|
|
togglePasswordField,
|
2017-09-06 19:03:56 +00:00
|
|
|
requestIdleCallback,
|
2017-05-18 20:08:54 +00:00
|
|
|
};
|
|
|
|
|
2017-09-02 16:28:36 +00:00
|
|
|
function findCurrentNetworkChan(name) {
|
|
|
|
name = name.toLowerCase();
|
|
|
|
|
2018-07-09 17:31:48 +00:00
|
|
|
return vueApp.activeChannel.network.channels.find((c) => c.name.toLowerCase() === name);
|
2017-09-02 16:28:36 +00:00
|
|
|
}
|
|
|
|
|
2018-04-13 15:53:41 +00:00
|
|
|
// Given a channel element will determine if the lounge user or a given nick is one of the supplied roles.
|
|
|
|
function hasRoleInChannel(channel, roles, nick) {
|
2017-12-20 09:45:12 +00:00
|
|
|
if (!channel || !roles) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-07-19 10:46:30 +00:00
|
|
|
const channelID = channel.attr("data-id");
|
2017-11-01 09:16:19 +00:00
|
|
|
const network = $("#sidebar .network").has(`.chan[data-id="${channelID}"]`);
|
2018-04-28 08:19:49 +00:00
|
|
|
const target = nick || network.attr("data-nick");
|
2019-01-22 14:33:20 +00:00
|
|
|
const user = channel.find(`.names .user[data-name="${escape(target)}"]`).first();
|
2017-12-20 09:45:12 +00:00
|
|
|
return user.parent().is("." + roles.join(", ."));
|
2017-11-01 09:16:19 +00:00
|
|
|
}
|
|
|
|
|
2018-03-08 16:27:20 +00:00
|
|
|
// Reusable scrollIntoView parameters for channel list / user list
|
|
|
|
function scrollIntoViewNicely(el) {
|
|
|
|
// Ideally this would use behavior: "smooth", but that does not consistently work in e.g. Chrome
|
|
|
|
// https://github.com/iamdustan/smoothscroll/issues/28#issuecomment-364061459
|
2018-07-24 04:50:19 +00:00
|
|
|
el.scrollIntoView({block: "center", inline: "nearest"});
|
2018-03-08 16:27:20 +00:00
|
|
|
}
|
|
|
|
|
2017-05-18 20:08:54 +00:00
|
|
|
const favicon = $("#favicon");
|
|
|
|
|
2018-09-05 19:44:19 +00:00
|
|
|
function synchronizeNotifiedState() {
|
|
|
|
updateTitle();
|
|
|
|
|
|
|
|
let hasAnyHighlights = false;
|
|
|
|
|
|
|
|
for (const network of vueApp.networks) {
|
|
|
|
for (const chan of network.channels) {
|
|
|
|
if (chan.highlight > 0) {
|
|
|
|
hasAnyHighlights = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
toggleNotificationMarkers(hasAnyHighlights);
|
|
|
|
}
|
|
|
|
|
2017-05-18 20:08:54 +00:00
|
|
|
function toggleNotificationMarkers(newState) {
|
|
|
|
// Toggles the favicon to red when there are unread notifications
|
2018-09-05 19:44:19 +00:00
|
|
|
if (vueApp.isNotified !== newState) {
|
|
|
|
vueApp.isNotified = newState;
|
|
|
|
|
2018-01-11 11:33:36 +00:00
|
|
|
const old = favicon.prop("href");
|
2018-01-30 09:38:33 +00:00
|
|
|
favicon.prop("href", favicon.data("other"));
|
2017-05-18 20:08:54 +00:00
|
|
|
favicon.data("other", old);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Toggles a dot on the menu icon when there are unread notifications
|
2018-03-08 13:46:05 +00:00
|
|
|
viewport.toggleClass("notified", newState);
|
2017-05-18 20:08:54 +00:00
|
|
|
}
|
|
|
|
|
2018-06-01 17:32:30 +00:00
|
|
|
function updateTitle() {
|
2018-07-06 18:15:15 +00:00
|
|
|
let title = vueApp.appName;
|
2018-06-01 17:32:30 +00:00
|
|
|
|
2018-07-06 18:15:15 +00:00
|
|
|
if (vueApp.activeChannel) {
|
2019-02-18 05:35:04 +00:00
|
|
|
title = `${vueApp.activeChannel.channel.name} — ${title}`;
|
2018-06-01 17:32:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// add highlight count to title
|
|
|
|
let alertEventCount = 0;
|
2018-07-06 18:15:15 +00:00
|
|
|
|
|
|
|
for (const network of vueApp.networks) {
|
|
|
|
for (const channel of network.channels) {
|
|
|
|
alertEventCount += channel.highlight;
|
|
|
|
}
|
|
|
|
}
|
2018-06-01 17:32:30 +00:00
|
|
|
|
|
|
|
if (alertEventCount > 0) {
|
|
|
|
title = `(${alertEventCount}) ${title}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
document.title = title;
|
|
|
|
}
|
|
|
|
|
2018-05-09 23:25:17 +00:00
|
|
|
function togglePasswordField(elem) {
|
|
|
|
$(elem).on("click", function() {
|
|
|
|
const $this = $(this);
|
|
|
|
const input = $this.closest("div").find("input");
|
2018-05-08 21:40:39 +00:00
|
|
|
|
2018-05-23 22:12:37 +00:00
|
|
|
input.attr("type", input.attr("type") === "password" ? "text" : "password");
|
2018-05-09 23:25:17 +00:00
|
|
|
|
|
|
|
swapLabel($this);
|
2018-05-24 19:20:23 +00:00
|
|
|
swapLabel($this.find("span"));
|
2018-05-09 23:25:17 +00:00
|
|
|
$this.toggleClass("visible");
|
|
|
|
});
|
|
|
|
}
|
2018-05-08 21:40:39 +00:00
|
|
|
|
2018-05-23 22:12:37 +00:00
|
|
|
// Given a element, swap its aria-label with the content of `data-alt-label`
|
|
|
|
function swapLabel(element) {
|
|
|
|
const altText = element.data("alt-label");
|
|
|
|
element.data("alt-label", element.attr("aria-label")).attr("aria-label", altText);
|
2018-05-08 21:40:39 +00:00
|
|
|
}
|
|
|
|
|
2017-05-18 20:08:54 +00:00
|
|
|
function confirmExit() {
|
2018-02-23 19:22:05 +00:00
|
|
|
if ($(document.body).hasClass("public")) {
|
2017-05-18 20:08:54 +00:00
|
|
|
window.onbeforeunload = function() {
|
|
|
|
return "Are you sure you want to navigate away from this page?";
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function move(array, old_index, new_index) {
|
|
|
|
if (new_index >= array.length) {
|
|
|
|
let k = new_index - array.length;
|
2018-02-20 07:28:04 +00:00
|
|
|
|
2019-07-17 09:33:59 +00:00
|
|
|
while (k-- + 1) {
|
2017-05-18 20:08:54 +00:00
|
|
|
this.push(undefined);
|
|
|
|
}
|
|
|
|
}
|
2018-02-20 07:28:04 +00:00
|
|
|
|
2017-05-18 20:08:54 +00:00
|
|
|
array.splice(new_index, 0, array.splice(old_index, 1)[0]);
|
|
|
|
return array;
|
|
|
|
}
|
2017-09-06 19:03:56 +00:00
|
|
|
|
2018-04-19 06:31:44 +00:00
|
|
|
function closeChan(chan) {
|
|
|
|
const socket = require("./socket");
|
|
|
|
let cmd = "/close";
|
|
|
|
|
|
|
|
if (chan.hasClass("lobby")) {
|
|
|
|
cmd = "/quit";
|
|
|
|
const server = chan.find(".name").html();
|
|
|
|
|
2019-07-17 15:53:34 +00:00
|
|
|
// eslint-disable-next-line no-alert
|
2019-07-17 09:33:59 +00:00
|
|
|
if (!confirm(`Are you sure you want to remove ${server}?`)) {
|
2018-04-19 06:31:44 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
socket.emit("input", {
|
2018-07-19 10:46:30 +00:00
|
|
|
target: Number(chan.attr("data-id")),
|
2018-04-19 06:31:44 +00:00
|
|
|
text: cmd,
|
|
|
|
});
|
|
|
|
chan.css({
|
|
|
|
transition: "none",
|
|
|
|
opacity: 0.4,
|
|
|
|
});
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-09-06 19:03:56 +00:00
|
|
|
function requestIdleCallback(callback, timeout) {
|
|
|
|
if (window.requestIdleCallback) {
|
|
|
|
// During an idle period the user agent will run idle callbacks in FIFO order
|
|
|
|
// until either the idle period ends or there are no more idle callbacks eligible to be run.
|
2018-03-05 00:59:16 +00:00
|
|
|
window.requestIdleCallback(callback, {timeout});
|
2017-09-06 19:03:56 +00:00
|
|
|
} else {
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
}
|