s/var/let/

This commit is contained in:
Simon Ser 2021-06-10 18:11:11 +02:00
parent 4db845a4b1
commit 8972130252
20 changed files with 481 additions and 479 deletions

View File

@ -2,7 +2,7 @@ import * as irc from "./lib/irc.js";
import { SERVER_BUFFER, BufferType } from "./state.js"; import { SERVER_BUFFER, BufferType } from "./state.js";
function getActiveClient(app) { function getActiveClient(app) {
var buf = app.state.buffers.get(app.state.activeBuffer); let buf = app.state.buffers.get(app.state.activeBuffer);
if (!buf) { if (!buf) {
throw new Error("Not connected to server"); throw new Error("Not connected to server");
} }
@ -10,7 +10,7 @@ function getActiveClient(app) {
} }
function getActiveTarget(app) { function getActiveTarget(app) {
var activeBuffer = app.state.buffers.get(app.state.activeBuffer); let activeBuffer = app.state.buffers.get(app.state.activeBuffer);
if (!activeBuffer) { if (!activeBuffer) {
throw new Error("Not in a buffer"); throw new Error("Not in a buffer");
} }
@ -18,7 +18,7 @@ function getActiveTarget(app) {
} }
function getActiveChannel(app) { function getActiveChannel(app) {
var activeBuffer = app.state.buffers.get(app.state.activeBuffer); let activeBuffer = app.state.buffers.get(app.state.activeBuffer);
if (!activeBuffer || activeBuffer.type !== BufferType.CHANNEL) { if (!activeBuffer || activeBuffer.type !== BufferType.CHANNEL) {
throw new Error("Not in a channel"); throw new Error("Not in a channel");
} }
@ -26,12 +26,12 @@ function getActiveChannel(app) {
} }
function setUserHostMode(app, args, mode) { function setUserHostMode(app, args, mode) {
var nick = args[0]; let nick = args[0];
if (!nick) { if (!nick) {
throw new Error("Missing nick"); throw new Error("Missing nick");
} }
var activeChannel = getActiveChannel(app); let activeChannel = getActiveChannel(app);
var client = getActiveClient(app); let client = getActiveClient(app);
client.whois(nick).then((whois) => { client.whois(nick).then((whois) => {
const info = whois[irc.RPL_WHOISUSER].params; const info = whois[irc.RPL_WHOISUSER].params;
const user = info[2]; const user = info[2];
@ -47,7 +47,7 @@ const join = {
usage: "<name>", usage: "<name>",
description: "Join a channel", description: "Join a channel",
execute: (app, args) => { execute: (app, args) => {
var channel = args[0]; let channel = args[0];
if (!channel) { if (!channel) {
throw new Error("Missing channel name"); throw new Error("Missing channel name");
} }
@ -59,9 +59,9 @@ const kick = {
usage: "<nick>", usage: "<nick>",
description: "Remove a user from the channel", description: "Remove a user from the channel",
execute: (app, args) => { execute: (app, args) => {
var nick = args[0]; let nick = args[0];
var activeChannel = getActiveChannel(app); let activeChannel = getActiveChannel(app);
var params = [activeChannel, nick]; let params = [activeChannel, nick];
if (args.length > 1) { if (args.length > 1) {
params.push(args.slice(1).join(" ")); params.push(args.slice(1).join(" "));
} }
@ -71,11 +71,11 @@ const kick = {
function givemode(app, args, mode) { function givemode(app, args, mode) {
// TODO: Handle several users at once // TODO: Handle several users at once
var nick = args[0]; let nick = args[0];
if (!nick) { if (!nick) {
throw new Error("Missing nick"); throw new Error("Missing nick");
} }
var activeChannel = getActiveChannel(app); let activeChannel = getActiveChannel(app);
getActiveClient(app).send({ getActiveClient(app).send({
command: "MODE", command: "MODE",
params: [activeChannel, mode, nick], params: [activeChannel, mode, nick],
@ -88,7 +88,7 @@ export default {
description: "Ban a user from the channel, or display the current ban list", description: "Ban a user from the channel, or display the current ban list",
execute: (app, args) => { execute: (app, args) => {
if (args.length == 0) { if (args.length == 0) {
var activeChannel = getActiveChannel(app); let activeChannel = getActiveChannel(app);
getActiveClient(app).send({ getActiveClient(app).send({
command: "MODE", command: "MODE",
params: [activeChannel, "+b"], params: [activeChannel, "+b"],
@ -102,8 +102,8 @@ export default {
usage: "<name>", usage: "<name>",
description: "Switch to a buffer", description: "Switch to a buffer",
execute: (app, args) => { execute: (app, args) => {
var name = args[0]; let name = args[0];
for (var buf of app.state.buffers.values()) { for (let buf of app.state.buffers.values()) {
if (buf.name === name) { if (buf.name === name) {
app.switchBuffer(buf); app.switchBuffer(buf);
return; return;
@ -115,7 +115,7 @@ export default {
"close": { "close": {
description: "Close the current buffer", description: "Close the current buffer",
execute: (app, args) => { execute: (app, args) => {
var activeBuffer = app.state.buffers.get(app.state.activeBuffer); let activeBuffer = app.state.buffers.get(app.state.activeBuffer);
if (!activeBuffer || activeBuffer.type == BufferType.SERVER) { if (!activeBuffer || activeBuffer.type == BufferType.SERVER) {
throw new Error("Not in a user or channel buffer"); throw new Error("Not in a user or channel buffer");
} }
@ -148,11 +148,11 @@ export default {
usage: "<nick>", usage: "<nick>",
description: "Invite a user to the channel", description: "Invite a user to the channel",
execute: (app, args) => { execute: (app, args) => {
var nick = args[0]; let nick = args[0];
if (!nick) { if (!nick) {
throw new Error("Missing nick"); throw new Error("Missing nick");
} }
var activeChannel = getActiveChannel(app); let activeChannel = getActiveChannel(app);
getActiveClient(app).send({ command: "INVITE", params: [ getActiveClient(app).send({ command: "INVITE", params: [
nick, activeChannel, nick, activeChannel,
]}); ]});
@ -180,9 +180,9 @@ export default {
usage: "<action>", usage: "<action>",
description: "Send an action message to the current buffer", description: "Send an action message to the current buffer",
execute: (app, args) => { execute: (app, args) => {
var action = args.join(" "); let action = args.join(" ");
var target = getActiveTarget(app); let target = getActiveTarget(app);
var text = `\x01ACTION ${action}\x01`; let text = `\x01ACTION ${action}\x01`;
app.privmsg(target, text); app.privmsg(target, text);
}, },
}, },
@ -190,9 +190,9 @@ export default {
usage: "[target] [modes] [mode args...]", usage: "[target] [modes] [mode args...]",
description: "Query or change a channel or user mode", description: "Query or change a channel or user mode",
execute: (app, args) => { execute: (app, args) => {
var target = args[0]; let target = args[0];
if (!target || target.startsWith("+") || target.startsWith("-")) { if (!target || target.startsWith("+") || target.startsWith("-")) {
var activeChannel = getActiveChannel(app); let activeChannel = getActiveChannel(app);
args = [activeChannel, ...args]; args = [activeChannel, ...args];
} }
getActiveClient(app).send({ command: "MODE", params: args }); getActiveClient(app).send({ command: "MODE", params: args });
@ -209,8 +209,8 @@ export default {
usage: "<target> <message>", usage: "<target> <message>",
description: "Send a message to a nickname or a channel", description: "Send a message to a nickname or a channel",
execute: (app, args) => { execute: (app, args) => {
var target = args[0]; let target = args[0];
var text = args.slice(1).join(" "); let text = args.slice(1).join(" ");
getActiveClient(app).send({ command: "PRIVMSG", params: [target, text] }); getActiveClient(app).send({ command: "PRIVMSG", params: [target, text] });
}, },
}, },
@ -218,7 +218,7 @@ export default {
usage: "<nick>", usage: "<nick>",
description: "Change current nickname", description: "Change current nickname",
execute: (app, args) => { execute: (app, args) => {
var newNick = args[0]; let newNick = args[0];
getActiveClient(app).send({ command: "NICK", params: [newNick] }); getActiveClient(app).send({ command: "NICK", params: [newNick] });
}, },
}, },
@ -226,8 +226,8 @@ export default {
usage: "<target> <message>", usage: "<target> <message>",
description: "Send a notice to a nickname or a channel", description: "Send a notice to a nickname or a channel",
execute: (app, args) => { execute: (app, args) => {
var target = args[0]; let target = args[0];
var text = args.slice(1).join(" "); let text = args.slice(1).join(" ");
getActiveClient(app).send({ command: "NOTICE", params: [target, text] }); getActiveClient(app).send({ command: "NOTICE", params: [target, text] });
}, },
}, },
@ -240,9 +240,9 @@ export default {
usage: "[reason]", usage: "[reason]",
description: "Leave a channel", description: "Leave a channel",
execute: (app, args) => { execute: (app, args) => {
var reason = args.join(" "); let reason = args.join(" ");
var activeChannel = getActiveChannel(app); let activeChannel = getActiveChannel(app);
var params = [activeChannel]; let params = [activeChannel];
if (reason) { if (reason) {
params.push(reason); params.push(reason);
} }
@ -253,7 +253,7 @@ export default {
usage: "<nick>", usage: "<nick>",
description: "Open a buffer to send messages to a nickname", description: "Open a buffer to send messages to a nickname",
execute: (app, args) => { execute: (app, args) => {
var nick = args[0]; let nick = args[0];
if (!nick) { if (!nick) {
throw new Error("Missing nickname"); throw new Error("Missing nickname");
} }
@ -270,7 +270,7 @@ export default {
usage: "<command>", usage: "<command>",
description: "Send a raw IRC command to the server", description: "Send a raw IRC command to the server",
execute: (app, args) => { execute: (app, args) => {
var msg; let msg;
try { try {
msg = irc.parseMessage(args.join(" ")); msg = irc.parseMessage(args.join(" "));
} catch (err) { } catch (err) {
@ -289,8 +289,8 @@ export default {
usage: "<realname>", usage: "<realname>",
description: "Change current realname", description: "Change current realname",
execute: (app, args) => { execute: (app, args) => {
var newRealname = args.join(" "); let newRealname = args.join(" ");
var client = getActiveClient(app); let client = getActiveClient(app);
if (!client.enabledCaps["setname"]) { if (!client.enabledCaps["setname"]) {
throw new Error("Server doesn't support changing the realname"); throw new Error("Server doesn't support changing the realname");
} }
@ -301,11 +301,11 @@ export default {
usage: "<query> [server]", usage: "<query> [server]",
description: "Request server statistics", description: "Request server statistics",
execute: (app, args) => { execute: (app, args) => {
var query = args[0]; let query = args[0];
if (!query) { if (!query) {
throw new Error("Missing query"); throw new Error("Missing query");
} }
var params = [query]; let params = [query];
if (args.length > 1) { if (args.length > 1) {
params.push(args.slice(1).join(" ")); params.push(args.slice(1).join(" "));
} }
@ -316,8 +316,8 @@ export default {
usage: "<topic>", usage: "<topic>",
description: "Change the topic of the current channel", description: "Change the topic of the current channel",
execute: (app, args) => { execute: (app, args) => {
var activeChannel = getActiveChannel(app); let activeChannel = getActiveChannel(app);
var params = [activeChannel]; let params = [activeChannel];
if (args.length > 0) { if (args.length > 0) {
params.push(args.join(" ")); params.push(args.join(" "));
} }
@ -347,7 +347,7 @@ export default {
usage: "<nick>", usage: "<nick>",
description: "Retrieve information about a user", description: "Retrieve information about a user",
execute: (app, args) => { execute: (app, args) => {
var nick = args[0]; let nick = args[0];
if (!nick) { if (!nick) {
throw new Error("Missing nick"); throw new Error("Missing nick");
} }

View File

@ -35,28 +35,28 @@ const configPromise = fetch("./config.json")
const CHATHISTORY_MAX_SIZE = 4000; const CHATHISTORY_MAX_SIZE = 4000;
var messagesCount = 0; let messagesCount = 0;
function parseQueryString() { function parseQueryString() {
var query = window.location.search.substring(1); let query = window.location.search.substring(1);
var params = {}; let params = {};
query.split('&').forEach((s) => { query.split('&').forEach((s) => {
if (!s) { if (!s) {
return; return;
} }
var pair = s.split('='); let pair = s.split('=');
params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || ""); params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
}); });
return params; return params;
} }
function fillConnectParams(params) { function fillConnectParams(params) {
var host = window.location.host || "localhost:8080"; let host = window.location.host || "localhost:8080";
var proto = "wss:"; let proto = "wss:";
if (window.location.protocol != "https:") { if (window.location.protocol != "https:") {
proto = "ws:"; proto = "ws:";
} }
var path = window.location.pathname || "/"; let path = window.location.pathname || "/";
if (!window.location.host) { if (!window.location.host) {
path = "/"; path = "/";
} }
@ -78,7 +78,7 @@ function fillConnectParams(params) {
} }
function debounce(f, delay) { function debounce(f, delay) {
var timeout = null; let timeout = null;
return (...args) => { return (...args) => {
clearTimeout(timeout); clearTimeout(timeout);
timeout = setTimeout(() => { timeout = setTimeout(() => {
@ -177,7 +177,7 @@ export default class App extends Component {
handleConfig(config) { handleConfig(config) {
this.config = config; this.config = config;
var connectParams = {}; let connectParams = {};
if (config.server) { if (config.server) {
connectParams.url = config.server.url; connectParams.url = config.server.url;
@ -188,7 +188,7 @@ export default class App extends Component {
} }
} }
var autoconnect = store.autoconnect.load(); let autoconnect = store.autoconnect.load();
if (autoconnect) { if (autoconnect) {
connectParams = { connectParams = {
...connectParams, ...connectParams,
@ -197,7 +197,7 @@ export default class App extends Component {
}; };
} }
var queryParams = parseQueryString(); let queryParams = parseQueryString();
if (queryParams.server) { if (queryParams.server) {
connectParams.url = queryParams.server; connectParams.url = queryParams.server;
} }
@ -245,10 +245,10 @@ export default class App extends Component {
} }
createBuffer(serverID, name) { createBuffer(serverID, name) {
var id = null; let id = null;
this.setState((state) => { this.setState((state) => {
var client = this.clients.get(serverID); let client = this.clients.get(serverID);
var updated; let updated;
[id, updated] = State.createBuffer(state, name, serverID, client); [id, updated] = State.createBuffer(state, name, serverID, client);
return updated; return updated;
}); });
@ -256,7 +256,7 @@ export default class App extends Component {
} }
switchBuffer(id) { switchBuffer(id) {
var buf; let buf;
this.setState((state) => { this.setState((state) => {
buf = State.getBuffer(state, id); buf = State.getBuffer(state, id);
if (!buf) { if (!buf) {
@ -268,7 +268,7 @@ export default class App extends Component {
return; return;
} }
var lastReadReceipt = this.getReceipt(buf.name, ReceiptType.READ); let lastReadReceipt = this.getReceipt(buf.name, ReceiptType.READ);
// TODO: only mark as read if user scrolled at the bottom // TODO: only mark as read if user scrolled at the bottom
this.setBufferState(buf.id, { this.setBufferState(buf.id, {
unread: Unread.NONE, unread: Unread.NONE,
@ -282,7 +282,7 @@ export default class App extends Component {
if (buf.messages.length == 0) { if (buf.messages.length == 0) {
return; return;
} }
var lastMsg = buf.messages[buf.messages.length - 1]; let lastMsg = buf.messages[buf.messages.length - 1];
this.setReceipt(buf.name, ReceiptType.READ, lastMsg); this.setReceipt(buf.name, ReceiptType.READ, lastMsg);
}); });
} }
@ -292,7 +292,7 @@ export default class App extends Component {
} }
getReceipt(target, type) { getReceipt(target, type) {
var receipts = this.receipts.get(target); let receipts = this.receipts.get(target);
if (!receipts) { if (!receipts) {
return undefined; return undefined;
} }
@ -300,12 +300,12 @@ export default class App extends Component {
} }
hasReceipt(target, type, msg) { hasReceipt(target, type, msg) {
var receipt = this.getReceipt(target, type); let receipt = this.getReceipt(target, type);
return receipt && msg.tags.time <= receipt.time; return receipt && msg.tags.time <= receipt.time;
} }
setReceipt(target, type, msg) { setReceipt(target, type, msg) {
var receipt = this.getReceipt(target, type); let receipt = this.getReceipt(target, type);
if (this.hasReceipt(target, type, msg)) { if (this.hasReceipt(target, type, msg)) {
return; return;
} }
@ -318,9 +318,9 @@ export default class App extends Component {
} }
latestReceipt(type) { latestReceipt(type) {
var last = null; let last = null;
this.receipts.forEach((receipts, target) => { this.receipts.forEach((receipts, target) => {
var delivery = receipts[type]; let delivery = receipts[type];
if (target == "*" || !delivery || !delivery.time) { if (target == "*" || !delivery || !delivery.time) {
return; return;
} }
@ -332,7 +332,7 @@ export default class App extends Component {
} }
addMessage(serverID, bufName, msg) { addMessage(serverID, bufName, msg) {
var client = this.clients.get(serverID); let client = this.clients.get(serverID);
msg.key = messagesCount; msg.key = messagesCount;
messagesCount++; messagesCount++;
@ -346,15 +346,15 @@ export default class App extends Component {
msg.tags.time = irc.formatDate(new Date()); msg.tags.time = irc.formatDate(new Date());
} }
var isDelivered = this.hasReceipt(bufName, ReceiptType.DELIVERED, msg); let isDelivered = this.hasReceipt(bufName, ReceiptType.DELIVERED, msg);
var isRead = this.hasReceipt(bufName, ReceiptType.READ, msg); let isRead = this.hasReceipt(bufName, ReceiptType.READ, msg);
// TODO: messages coming from infinite scroll shouldn't trigger notifications // TODO: messages coming from infinite scroll shouldn't trigger notifications
var msgUnread = Unread.NONE; let msgUnread = Unread.NONE;
if ((msg.command == "PRIVMSG" || msg.command == "NOTICE") && !isRead) { if ((msg.command == "PRIVMSG" || msg.command == "NOTICE") && !isRead) {
var text = msg.params[1]; let text = msg.params[1];
var kind; let kind;
if (msg.isHighlight) { if (msg.isHighlight) {
msgUnread = Unread.HIGHLIGHT; msgUnread = Unread.HIGHLIGHT;
kind = "highlight"; kind = "highlight";
@ -366,11 +366,11 @@ export default class App extends Component {
} }
if (msgUnread == Unread.HIGHLIGHT && !isDelivered && !irc.parseCTCP(msg)) { if (msgUnread == Unread.HIGHLIGHT && !isDelivered && !irc.parseCTCP(msg)) {
var title = "New " + kind + " from " + msg.prefix.name; let title = "New " + kind + " from " + msg.prefix.name;
if (client.isChannel(bufName)) { if (client.isChannel(bufName)) {
title += " in " + bufName; title += " in " + bufName;
} }
var notif = showNotification(title, { let notif = showNotification(title, {
body: stripANSI(text), body: stripANSI(text),
requireInteraction: true, requireInteraction: true,
tag: "msg," + msg.prefix.name + "," + bufName, tag: "msg," + msg.prefix.name + "," + bufName,
@ -384,8 +384,8 @@ export default class App extends Component {
if (msg.command === "INVITE" && client.isMyNick(msg.params[0])) { if (msg.command === "INVITE" && client.isMyNick(msg.params[0])) {
msgUnread = Unread.HIGHLIGHT; msgUnread = Unread.HIGHLIGHT;
var channel = msg.params[1]; let channel = msg.params[1];
var notif = new Notification("Invitation to " + channel, { let notif = new Notification("Invitation to " + channel, {
body: msg.prefix.name + " has invited you to " + channel, body: msg.prefix.name + " has invited you to " + channel,
requireInteraction: true, requireInteraction: true,
tag: "invite," + msg.prefix.name + "," + channel, tag: "invite," + msg.prefix.name + "," + channel,
@ -411,12 +411,12 @@ export default class App extends Component {
this.setReceipt(bufName, ReceiptType.DELIVERED, msg); this.setReceipt(bufName, ReceiptType.DELIVERED, msg);
var bufID = { server: serverID, name: bufName }; let bufID = { server: serverID, name: bufName };
this.setState((state) => State.addMessage(state, msg, bufID)); this.setState((state) => State.addMessage(state, msg, bufID));
this.setBufferState(bufID, (buf) => { this.setBufferState(bufID, (buf) => {
// TODO: set unread if scrolled up // TODO: set unread if scrolled up
var unread = buf.unread; let unread = buf.unread;
var lastReadReceipt = buf.lastReadReceipt; let lastReadReceipt = buf.lastReadReceipt;
if (this.state.activeBuffer != buf.id) { if (this.state.activeBuffer != buf.id) {
unread = Unread.union(unread, msgUnread); unread = Unread.union(unread, msgUnread);
} else { } else {
@ -428,15 +428,15 @@ export default class App extends Component {
} }
connect(params) { connect(params) {
var serverID = null; let serverID = null;
this.setState((state) => { this.setState((state) => {
var update; let update;
[serverID, update] = State.createServer(state); [serverID, update] = State.createServer(state);
return update; return update;
}); });
this.setState({ connectParams: params }); this.setState({ connectParams: params });
var client = new Client(fillConnectParams(params)); let client = new Client(fillConnectParams(params));
this.clients.set(serverID, client); this.clients.set(serverID, client);
this.setServerState(serverID, { status: client.status }); this.setServerState(serverID, { status: client.status });
@ -474,7 +474,7 @@ export default class App extends Component {
serverID = State.getActiveServerID(this.state); serverID = State.getActiveServerID(this.state);
} }
var client = this.clients.get(serverID); let client = this.clients.get(serverID);
if (client) { if (client) {
this.clients.delete(serverID); this.clients.delete(serverID);
client.disconnect(); client.disconnect();
@ -486,14 +486,14 @@ export default class App extends Component {
serverID = State.getActiveServerID(this.state); serverID = State.getActiveServerID(this.state);
} }
var client = this.clients.get(serverID); let client = this.clients.get(serverID);
if (client) { if (client) {
client.reconnect(); client.reconnect();
} }
} }
serverFromBouncerNetwork(bouncerNetworkID) { serverFromBouncerNetwork(bouncerNetworkID) {
for (var [id, client] of this.clients) { for (let [id, client] of this.clients) {
if (client.params.bouncerNetwork === bouncerNetworkID) { if (client.params.bouncerNetwork === bouncerNetworkID) {
return id; return id;
} }
@ -502,11 +502,12 @@ export default class App extends Component {
} }
handleMessage(serverID, msg) { handleMessage(serverID, msg) {
var client = this.clients.get(serverID); let client = this.clients.get(serverID);
var chatHistoryBatch = irc.findBatchByType(msg, "chathistory"); let chatHistoryBatch = irc.findBatchByType(msg, "chathistory");
this.setState((state) => State.handleMessage(state, msg, serverID, client)); this.setState((state) => State.handleMessage(state, msg, serverID, client));
let target, channel, affectedBuffers;
switch (msg.command) { switch (msg.command) {
case irc.RPL_WELCOME: case irc.RPL_WELCOME:
if (this.state.connectParams.autojoin.length > 0) { if (this.state.connectParams.autojoin.length > 0) {
@ -516,23 +517,23 @@ export default class App extends Component {
}); });
} }
var lastReceipt = this.latestReceipt(ReceiptType.READ); let lastReceipt = this.latestReceipt(ReceiptType.READ);
if (lastReceipt && lastReceipt.time && client.enabledCaps["draft/chathistory"] && (!client.enabledCaps["soju.im/bouncer-networks"] || client.params.bouncerNetwork)) { if (lastReceipt && lastReceipt.time && client.enabledCaps["draft/chathistory"] && (!client.enabledCaps["soju.im/bouncer-networks"] || client.params.bouncerNetwork)) {
var now = irc.formatDate(new Date()); let now = irc.formatDate(new Date());
client.fetchHistoryTargets(now, lastReceipt.time).then((targets) => { client.fetchHistoryTargets(now, lastReceipt.time).then((targets) => {
targets.forEach((target) => { targets.forEach((target) => {
var from = this.getReceipt(target, ReceiptType.READ); let from = this.getReceipt(target, ReceiptType.READ);
if (!from) { if (!from) {
from = lastReceipt; from = lastReceipt;
} }
var to = { time: msg.tags.time || irc.formatDate(new Date()) }; let to = { time: msg.tags.time || irc.formatDate(new Date()) };
this.fetchBacklog(client, target.name, from, to); this.fetchBacklog(client, target.name, from, to);
}); });
}); });
} }
break; break;
case "MODE": case "MODE":
var target = msg.params[0]; target = msg.params[0];
if (client.isChannel(target)) { if (client.isChannel(target)) {
this.addMessage(serverID, target, msg); this.addMessage(serverID, target, msg);
} }
@ -542,7 +543,7 @@ export default class App extends Component {
break; break;
case "NOTICE": case "NOTICE":
case "PRIVMSG": case "PRIVMSG":
var target = msg.params[0]; target = msg.params[0];
if (client.isMyNick(target)) { if (client.isMyNick(target)) {
if (client.cm(msg.prefix.name) === client.cm(client.serverPrefix.name)) { if (client.cm(msg.prefix.name) === client.cm(client.serverPrefix.name)) {
target = SERVER_BUFFER; target = SERVER_BUFFER;
@ -555,9 +556,9 @@ export default class App extends Component {
target = SERVER_BUFFER; target = SERVER_BUFFER;
} }
var allowedPrefixes = client.isupport.get("STATUSMSG"); let allowedPrefixes = client.isupport.get("STATUSMSG");
if (allowedPrefixes) { if (allowedPrefixes) {
var parts = irc.parseTargetPrefix(target, allowedPrefixes); let parts = irc.parseTargetPrefix(target, allowedPrefixes);
if (client.isChannel(parts.name)) { if (client.isChannel(parts.name)) {
target = parts.name; target = parts.name;
} }
@ -566,7 +567,7 @@ export default class App extends Component {
this.addMessage(serverID, target, msg); this.addMessage(serverID, target, msg);
break; break;
case "JOIN": case "JOIN":
var channel = msg.params[0]; channel = msg.params[0];
if (!client.isMyNick(msg.prefix.name)) { if (!client.isMyNick(msg.prefix.name)) {
this.addMessage(serverID, channel, msg); this.addMessage(serverID, channel, msg);
@ -577,7 +578,7 @@ export default class App extends Component {
} }
break; break;
case "PART": case "PART":
var channel = msg.params[0]; channel = msg.params[0];
this.addMessage(serverID, channel, msg); this.addMessage(serverID, channel, msg);
@ -587,16 +588,16 @@ export default class App extends Component {
} }
break; break;
case "KICK": case "KICK":
var channel = msg.params[0]; channel = msg.params[0];
this.addMessage(serverID, channel, msg); this.addMessage(serverID, channel, msg);
break; break;
case "QUIT": case "QUIT":
var affectedBuffers = []; affectedBuffers = [];
if (chatHistoryBatch) { if (chatHistoryBatch) {
affectedBuffers.push(chatHistoryBatch.params[0]); affectedBuffers.push(chatHistoryBatch.params[0]);
} else { } else {
this.setState((state) => { this.setState((state) => {
var buffers = new Map(state.buffers); let buffers = new Map(state.buffers);
state.buffers.forEach((buf) => { state.buffers.forEach((buf) => {
if (buf.server != serverID) { if (buf.server != serverID) {
return; return;
@ -604,9 +605,9 @@ export default class App extends Component {
if (!buf.members.has(msg.prefix.name) && client.cm(buf.name) !== client.cm(msg.prefix.name)) { if (!buf.members.has(msg.prefix.name) && client.cm(buf.name) !== client.cm(msg.prefix.name)) {
return; return;
} }
var members = new irc.CaseMapMap(buf.members); let members = new irc.CaseMapMap(buf.members);
members.delete(msg.prefix.name); members.delete(msg.prefix.name);
var offline = client.cm(buf.name) === client.cm(msg.prefix.name); let offline = client.cm(buf.name) === client.cm(msg.prefix.name);
buffers.set(buf.id, { ...buf, members, offline }); buffers.set(buf.id, { ...buf, members, offline });
affectedBuffers.push(buf.name); affectedBuffers.push(buf.name);
}); });
@ -617,14 +618,14 @@ export default class App extends Component {
affectedBuffers.forEach((name) => this.addMessage(serverID, name, msg)); affectedBuffers.forEach((name) => this.addMessage(serverID, name, msg));
break; break;
case "NICK": case "NICK":
var newNick = msg.params[0]; let newNick = msg.params[0];
var affectedBuffers = []; affectedBuffers = [];
if (chatHistoryBatch) { if (chatHistoryBatch) {
affectedBuffers.push(chatHistoryBatch.params[0]); affectedBuffers.push(chatHistoryBatch.params[0]);
} else { } else {
this.setState((state) => { this.setState((state) => {
var buffers = new Map(state.buffers); let buffers = new Map(state.buffers);
state.buffers.forEach((buf) => { state.buffers.forEach((buf) => {
if (buf.server != serverID) { if (buf.server != serverID) {
return; return;
@ -632,7 +633,7 @@ export default class App extends Component {
if (!buf.members.has(msg.prefix.name)) { if (!buf.members.has(msg.prefix.name)) {
return; return;
} }
var members = new irc.CaseMapMap(buf.members); let members = new irc.CaseMapMap(buf.members);
members.set(newNick, members.get(msg.prefix.name)); members.set(newNick, members.get(msg.prefix.name));
members.delete(msg.prefix.name); members.delete(msg.prefix.name);
buffers.set(buf.id, { ...buf, members }); buffers.set(buf.id, { ...buf, members });
@ -645,14 +646,14 @@ export default class App extends Component {
affectedBuffers.forEach((name) => this.addMessage(serverID, name, msg)); affectedBuffers.forEach((name) => this.addMessage(serverID, name, msg));
break; break;
case "TOPIC": case "TOPIC":
var channel = msg.params[0]; channel = msg.params[0];
this.addMessage(serverID, channel, msg); this.addMessage(serverID, channel, msg);
break; break;
case "INVITE": case "INVITE":
var channel = msg.params[1]; channel = msg.params[1];
// TODO: find a more reliable way to do this // TODO: find a more reliable way to do this
var bufName = channel; let bufName = channel;
if (!State.getBuffer(this.state, { server: serverID, name: channel })) { if (!State.getBuffer(this.state, { server: serverID, name: channel })) {
bufName = SERVER_BUFFER; bufName = SERVER_BUFFER;
} }
@ -670,19 +671,19 @@ export default class App extends Component {
break; break;
} }
var id = msg.params[1]; let id = msg.params[1];
var attrs = null; let attrs = null;
if (msg.params[2] !== "*") { if (msg.params[2] !== "*") {
attrs = irc.parseTags(msg.params[2]); attrs = irc.parseTags(msg.params[2]);
} }
var isNew = false; let isNew = false;
this.setState((state) => { this.setState((state) => {
var bouncerNetworks = new Map(state.bouncerNetworks); let bouncerNetworks = new Map(state.bouncerNetworks);
if (!attrs) { if (!attrs) {
bouncerNetworks.delete(id); bouncerNetworks.delete(id);
} else { } else {
var prev = bouncerNetworks.get(id); let prev = bouncerNetworks.get(id);
isNew = prev === undefined; isNew = prev === undefined;
attrs = { ...prev, ...attrs }; attrs = { ...prev, ...attrs };
bouncerNetworks.set(id, attrs); bouncerNetworks.set(id, attrs);
@ -690,7 +691,7 @@ export default class App extends Component {
return { bouncerNetworks }; return { bouncerNetworks };
}, () => { }, () => {
if (!attrs) { if (!attrs) {
var serverID = this.serverFromBouncerNetwork(id); let serverID = this.serverFromBouncerNetwork(id);
if (serverID) { if (serverID) {
this.close({ server: serverID, name: SERVER_BUFFER }); this.close({ server: serverID, name: SERVER_BUFFER });
} }
@ -710,7 +711,7 @@ export default class App extends Component {
case irc.RPL_ENDOFEXCEPTLIST: case irc.RPL_ENDOFEXCEPTLIST:
case irc.RPL_BANLIST: case irc.RPL_BANLIST:
case irc.RPL_ENDOFBANLIST: case irc.RPL_ENDOFBANLIST:
var channel = msg.params[1]; channel = msg.params[1];
this.addMessage(serverID, channel, msg); this.addMessage(serverID, channel, msg);
break; break;
case irc.RPL_MYINFO: case irc.RPL_MYINFO:
@ -734,7 +735,7 @@ export default class App extends Component {
break; break;
default: default:
if (irc.isError(msg.command) && msg.command != irc.ERR_NOMOTD) { if (irc.isError(msg.command) && msg.command != irc.ERR_NOMOTD) {
var description = msg.params[msg.params.length - 1]; let description = msg.params[msg.params.length - 1];
this.setState({ error: description }); this.setState({ error: description });
} }
this.addMessage(serverID, SERVER_BUFFER, msg); this.addMessage(serverID, SERVER_BUFFER, msg);
@ -754,8 +755,8 @@ export default class App extends Component {
} }
handleChannelClick(channel) { handleChannelClick(channel) {
var serverID = State.getActiveServerID(this.state); let serverID = State.getActiveServerID(this.state);
var buf = State.getBuffer(this.state, { server: serverID, name: channel }); let buf = State.getBuffer(this.state, { server: serverID, name: channel });
if (buf) { if (buf) {
this.switchBuffer(buf.id); this.switchBuffer(buf.id);
} else { } else {
@ -780,7 +781,7 @@ export default class App extends Component {
serverID = State.getActiveServerID(this.state); serverID = State.getActiveServerID(this.state);
} }
var client = this.clients.get(serverID); let client = this.clients.get(serverID);
if (client.isServer(target)) { if (client.isServer(target)) {
this.switchBuffer({ server: serverID }); this.switchBuffer({ server: serverID });
} else if (client.isChannel(target)) { } else if (client.isChannel(target)) {
@ -794,22 +795,23 @@ export default class App extends Component {
} }
close(id) { close(id) {
var buf = State.getBuffer(this.state, id); let buf = State.getBuffer(this.state, id);
if (!buf) { if (!buf) {
return; return;
} }
let client = this.clients.get(buf.server);
switch (buf.type) { switch (buf.type) {
case BufferType.SERVER: case BufferType.SERVER:
this.setState((state) => { this.setState((state) => {
var buffers = new Map(state.buffers); let buffers = new Map(state.buffers);
for (var [id, b] of state.buffers) { for (let [id, b] of state.buffers) {
if (b.server === buf.server) { if (b.server === buf.server) {
buffers.delete(id); buffers.delete(id);
} }
} }
var activeBuffer = state.activeBuffer; let activeBuffer = state.activeBuffer;
if (activeBuffer && state.buffers.get(activeBuffer).server === buf.server) { if (activeBuffer && state.buffers.get(activeBuffer).server === buf.server) {
if (buffers.size > 0) { if (buffers.size > 0) {
activeBuffer = buffers.keys().next().value; activeBuffer = buffers.keys().next().value;
@ -821,16 +823,15 @@ export default class App extends Component {
return { buffers, activeBuffer }; return { buffers, activeBuffer };
}); });
var client = this.clients.get(buf.server); let disconnectAll = client && !client.params.bouncerNetwork && client.enabledCaps["soju.im/bouncer-networks"];
var disconnectAll = client && !client.params.bouncerNetwork && client.enabledCaps["soju.im/bouncer-networks"];
this.disconnect(buf.server); this.disconnect(buf.server);
this.setState((state) => { this.setState((state) => {
var servers = new Map(state.servers); let servers = new Map(state.servers);
servers.delete(buf.server); servers.delete(buf.server);
var connectForm = state.connectForm; let connectForm = state.connectForm;
if (servers.size == 0) { if (servers.size == 0) {
connectForm = true; connectForm = true;
} }
@ -839,7 +840,7 @@ export default class App extends Component {
}); });
if (disconnectAll) { if (disconnectAll) {
for (var serverID of this.clients.keys()) { for (let serverID of this.clients.keys()) {
this.close({ server: serverID, name: SERVER_BUFFER }); this.close({ server: serverID, name: SERVER_BUFFER });
} }
} }
@ -850,13 +851,12 @@ export default class App extends Component {
} }
break; break;
case BufferType.CHANNEL: case BufferType.CHANNEL:
var client = this.clients.get(buf.server);
client.send({ command: "PART", params: [buf.name] }); client.send({ command: "PART", params: [buf.name] });
// fallthrough // fallthrough
case BufferType.NICK: case BufferType.NICK:
this.switchBuffer({ name: SERVER_BUFFER }); this.switchBuffer({ name: SERVER_BUFFER });
this.setState((state) => { this.setState((state) => {
var buffers = new Map(state.buffers); let buffers = new Map(state.buffers);
buffers.delete(buf.id); buffers.delete(buf.id);
return { buffers }; return { buffers };
}); });
@ -868,11 +868,11 @@ export default class App extends Component {
} }
executeCommand(s) { executeCommand(s) {
var parts = s.split(" "); let parts = s.split(" ");
var name = parts[0].toLowerCase().slice(1); let name = parts[0].toLowerCase().slice(1);
var args = parts.slice(1); let args = parts.slice(1);
var cmd = commands[name]; let cmd = commands[name];
if (!cmd) { if (!cmd) {
this.setState({ error: `Unknown command "${name}" (run "/help" to get a command list)` }); this.setState({ error: `Unknown command "${name}" (run "/help" to get a command list)` });
return; return;
@ -892,10 +892,10 @@ export default class App extends Component {
return; return;
} }
var serverID = State.getActiveServerID(this.state); let serverID = State.getActiveServerID(this.state);
var client = this.clients.get(serverID); let client = this.clients.get(serverID);
var msg = { command: "PRIVMSG", params: [target, text] }; let msg = { command: "PRIVMSG", params: [target, text] };
client.send(msg); client.send(msg);
if (!client.enabledCaps["echo-message"]) { if (!client.enabledCaps["echo-message"]) {
@ -916,7 +916,7 @@ export default class App extends Component {
return; return;
} }
var buf = this.state.buffers.get(this.state.activeBuffer); let buf = this.state.buffers.get(this.state.activeBuffer);
if (!buf) { if (!buf) {
return; return;
} }
@ -931,7 +931,7 @@ export default class App extends Component {
toggleBufferList() { toggleBufferList() {
this.setState((state) => { this.setState((state) => {
var openPanels = { let openPanels = {
...state.openPanels, ...state.openPanels,
bufferList: !state.openPanels.bufferList, bufferList: !state.openPanels.bufferList,
}; };
@ -941,7 +941,7 @@ export default class App extends Component {
toggleMemberList() { toggleMemberList() {
this.setState((state) => { this.setState((state) => {
var openPanels = { let openPanels = {
...state.openPanels, ...state.openPanels,
memberList: !state.openPanels.memberList, memberList: !state.openPanels.memberList,
}; };
@ -951,7 +951,7 @@ export default class App extends Component {
closeBufferList() { closeBufferList() {
this.setState((state) => { this.setState((state) => {
var openPanels = { let openPanels = {
...state.openPanels, ...state.openPanels,
bufferList: false, bufferList: false,
}; };
@ -961,7 +961,7 @@ export default class App extends Component {
closeMemberList() { closeMemberList() {
this.setState((state) => { this.setState((state) => {
var openPanels = { let openPanels = {
...state.openPanels, ...state.openPanels,
memberList: false, memberList: false,
}; };
@ -974,7 +974,7 @@ export default class App extends Component {
} }
handleJoinSubmit(data) { handleJoinSubmit(data) {
var client = this.clients.get(this.state.joinDialog.server); let client = this.clients.get(this.state.joinDialog.server);
this.switchToChannel = data.channel; this.switchToChannel = data.channel;
client.send({ command: "JOIN", params: [data.channel] }); client.send({ command: "JOIN", params: [data.channel] });
@ -985,8 +985,8 @@ export default class App extends Component {
autocomplete(prefix) { autocomplete(prefix) {
function fromList(l, prefix) { function fromList(l, prefix) {
prefix = prefix.toLowerCase(); prefix = prefix.toLowerCase();
var repl = null; let repl = null;
for (var item of l) { for (let item of l) {
if (item.toLowerCase().startsWith(prefix)) { if (item.toLowerCase().startsWith(prefix)) {
if (repl) { if (repl) {
return null; return null;
@ -998,14 +998,14 @@ export default class App extends Component {
} }
if (prefix.startsWith("/")) { if (prefix.startsWith("/")) {
var repl = fromList(Object.keys(commands), prefix.slice(1)); let repl = fromList(Object.keys(commands), prefix.slice(1));
if (repl) { if (repl) {
repl = "/" + repl; repl = "/" + repl;
} }
return repl; return repl;
} }
var buf = this.state.buffers.get(this.state.activeBuffer); let buf = this.state.buffers.get(this.state.activeBuffer);
if (!buf || !buf.members) { if (!buf || !buf.members) {
return null; return null;
} }
@ -1017,12 +1017,12 @@ export default class App extends Component {
} }
handleBufferScrollTop() { handleBufferScrollTop() {
var buf = this.state.buffers.get(this.state.activeBuffer); let buf = this.state.buffers.get(this.state.activeBuffer);
if (!buf || buf.type == BufferType.SERVER) { if (!buf || buf.type == BufferType.SERVER) {
return; return;
} }
var client = this.clients.get(buf.server); let client = this.clients.get(buf.server);
if (!client || !client.enabledCaps["draft/chathistory"] || !client.enabledCaps["server-time"]) { if (!client || !client.enabledCaps["draft/chathistory"] || !client.enabledCaps["server-time"]) {
return; return;
@ -1031,7 +1031,7 @@ export default class App extends Component {
return; return;
} }
var before; let before;
if (buf.messages.length > 0) { if (buf.messages.length > 0) {
before = buf.messages[0].tags["time"]; before = buf.messages[0].tags["time"];
} else { } else {
@ -1055,9 +1055,9 @@ export default class App extends Component {
} }
handleManageNetworkClick(serverID) { handleManageNetworkClick(serverID) {
var server = this.state.servers.get(serverID); let server = this.state.servers.get(serverID);
var bouncerNetID = server.isupport.get("BOUNCER_NETID"); let bouncerNetID = server.isupport.get("BOUNCER_NETID");
var bouncerNetwork = this.state.bouncerNetworks.get(bouncerNetID); let bouncerNetwork = this.state.bouncerNetworks.get(bouncerNetID);
this.setState({ this.setState({
dialog: "network", dialog: "network",
networkDialog: { networkDialog: {
@ -1068,7 +1068,7 @@ export default class App extends Component {
} }
handleNetworkSubmit(attrs) { handleNetworkSubmit(attrs) {
var client = this.clients.values().next().value; let client = this.clients.values().next().value;
if (this.state.networkDialog && this.state.networkDialog.id) { if (this.state.networkDialog && this.state.networkDialog.id) {
if (Object.keys(attrs).length == 0) { if (Object.keys(attrs).length == 0) {
@ -1092,7 +1092,7 @@ export default class App extends Component {
} }
handleNetworkRemove() { handleNetworkRemove() {
var client = this.clients.values().next().value; let client = this.clients.values().next().value;
client.send({ client.send({
command: "BOUNCER", command: "BOUNCER",
@ -1103,29 +1103,29 @@ export default class App extends Component {
} }
handleMode(serverID, msg) { handleMode(serverID, msg) {
var client = this.clients.get(serverID); let client = this.clients.get(serverID);
var chanmodes = client.isupport.get("CHANMODES") || irc.STD_CHANMODES; let chanmodes = client.isupport.get("CHANMODES") || irc.STD_CHANMODES;
var prefix = client.isupport.get("PREFIX") || ""; let prefix = client.isupport.get("PREFIX") || "";
var prefixByMode = new Map(irc.parseMembershipModes(prefix).map((membership) => { let prefixByMode = new Map(irc.parseMembershipModes(prefix).map((membership) => {
return [membership.mode, membership.prefix]; return [membership.mode, membership.prefix];
})); }));
var typeByMode = new Map(); let typeByMode = new Map();
var [a, b, c, d] = chanmodes.split(","); let [a, b, c, d] = chanmodes.split(",");
Array.from(a).forEach((mode) => typeByMode.set(mode, "A")); Array.from(a).forEach((mode) => typeByMode.set(mode, "A"));
Array.from(b).forEach((mode) => typeByMode.set(mode, "B")); Array.from(b).forEach((mode) => typeByMode.set(mode, "B"));
Array.from(c).forEach((mode) => typeByMode.set(mode, "C")); Array.from(c).forEach((mode) => typeByMode.set(mode, "C"));
Array.from(d).forEach((mode) => typeByMode.set(mode, "D")); Array.from(d).forEach((mode) => typeByMode.set(mode, "D"));
prefixByMode.forEach((prefix, mode) => typeByMode.set(mode, "B")); prefixByMode.forEach((prefix, mode) => typeByMode.set(mode, "B"));
var channel = msg.params[0]; let channel = msg.params[0];
var change = msg.params[1]; let change = msg.params[1];
var args = msg.params.slice(2); let args = msg.params.slice(2);
var plusMinus = null; let plusMinus = null;
var j = 0; let j = 0;
for (var i = 0; i < change.length; i++) { for (let i = 0; i < change.length; i++) {
if (change[i] === "+" || change[i] === "-") { if (change[i] === "+" || change[i] === "-") {
plusMinus = change[i]; plusMinus = change[i];
continue; continue;
@ -1134,15 +1134,15 @@ export default class App extends Component {
throw new Error("malformed mode string: missing plus/minus"); throw new Error("malformed mode string: missing plus/minus");
} }
var mode = change[i]; let mode = change[i];
var add = plusMinus === "+"; let add = plusMinus === "+";
var modeType = typeByMode.get(mode); let modeType = typeByMode.get(mode);
if (!modeType) { if (!modeType) {
continue; continue;
} }
var arg = null; let arg = null;
if (modeType === "A" || modeType === "B" || (modeType === "C" && add)) { if (modeType === "A" || modeType === "B" || (modeType === "C" && add)) {
arg = args[j]; arg = args[j];
j++; j++;
@ -1159,18 +1159,18 @@ export default class App extends Component {
} }
handlePrefixChange(serverID, channel, nick, letter, add) { handlePrefixChange(serverID, channel, nick, letter, add) {
var client = this.clients.get(serverID); let client = this.clients.get(serverID);
var prefix = client.isupport.get("PREFIX") || ""; let prefix = client.isupport.get("PREFIX") || "";
var prefixPrivs = new Map(irc.parseMembershipModes(prefix).map((membership, i) => { let prefixPrivs = new Map(irc.parseMembershipModes(prefix).map((membership, i) => {
return [membership.prefix, i]; return [membership.prefix, i];
})); }));
this.setBufferState({ server: serverID, name: channel }, (buf) => { this.setBufferState({ server: serverID, name: channel }, (buf) => {
var members = new irc.CaseMapMap(buf.members); let members = new irc.CaseMapMap(buf.members);
var membership = members.get(nick); let membership = members.get(nick);
if (add) { if (add) {
var i = membership.indexOf(letter); let i = membership.indexOf(letter);
if (i < 0) { if (i < 0) {
membership += letter; membership += letter;
} }
@ -1190,24 +1190,24 @@ export default class App extends Component {
} }
render() { render() {
var activeBuffer = null, activeServer = null, activeBouncerNetwork = null; let activeBuffer = null, activeServer = null, activeBouncerNetwork = null;
var isBouncer = false; let isBouncer = false;
if (this.state.buffers.get(this.state.activeBuffer)) { if (this.state.buffers.get(this.state.activeBuffer)) {
activeBuffer = this.state.buffers.get(this.state.activeBuffer); activeBuffer = this.state.buffers.get(this.state.activeBuffer);
activeServer = this.state.servers.get(activeBuffer.server); activeServer = this.state.servers.get(activeBuffer.server);
var activeClient = this.clients.get(activeBuffer.server); let activeClient = this.clients.get(activeBuffer.server);
isBouncer = activeClient && activeClient.enabledCaps["soju.im/bouncer-networks"]; isBouncer = activeClient && activeClient.enabledCaps["soju.im/bouncer-networks"];
var bouncerNetID = activeServer.isupport.get("BOUNCER_NETID"); let bouncerNetID = activeServer.isupport.get("BOUNCER_NETID");
if (bouncerNetID) { if (bouncerNetID) {
activeBouncerNetwork = this.state.bouncerNetworks.get(bouncerNetID); activeBouncerNetwork = this.state.bouncerNetworks.get(bouncerNetID);
} }
} }
if (this.state.connectForm) { if (this.state.connectForm) {
var status = activeServer ? activeServer.status : ServerStatus.DISCONNECTED; let status = activeServer ? activeServer.status : ServerStatus.DISCONNECTED;
var connecting = status === ServerStatus.CONNECTING || status === ServerStatus.REGISTERING; let connecting = status === ServerStatus.CONNECTING || status === ServerStatus.REGISTERING;
// TODO: using key=connectParams trashes the ConnectForm state on update // TODO: using key=connectParams trashes the ConnectForm state on update
return html` return html`
<section id="connect"> <section id="connect">
@ -1223,7 +1223,7 @@ export default class App extends Component {
`; `;
} }
var bufferHeader = null; let bufferHeader = null;
if (activeBuffer) { if (activeBuffer) {
bufferHeader = html` bufferHeader = html`
<section id="buffer-header"> <section id="buffer-header">
@ -1242,7 +1242,7 @@ export default class App extends Component {
`; `;
} }
var memberList = null; let memberList = null;
if (activeBuffer && activeBuffer.type == BufferType.CHANNEL) { if (activeBuffer && activeBuffer.type == BufferType.CHANNEL) {
memberList = html` memberList = html`
<section <section
@ -1269,10 +1269,10 @@ export default class App extends Component {
`; `;
} }
var dialog = null; let dialog = null;
switch (this.state.dialog) { switch (this.state.dialog) {
case "network": case "network":
var title = this.state.networkDialog ? "Edit network" : "Add network"; let title = this.state.networkDialog ? "Edit network" : "Add network";
dialog = html` dialog = html`
<${Dialog} title=${title} onDismiss=${this.handleDialogDismiss}> <${Dialog} title=${title} onDismiss=${this.handleDialogDismiss}>
<${NetworkForm} <${NetworkForm}
@ -1299,7 +1299,7 @@ export default class App extends Component {
break; break;
} }
var error = null; let error = null;
if (this.state.error) { if (this.state.error) {
error = html` error = html`
<p id="error-msg"> <p id="error-msg">
@ -1310,7 +1310,7 @@ export default class App extends Component {
`; `;
} }
var composerReadOnly = false; let composerReadOnly = false;
if (activeBuffer && activeBuffer.type === BufferType.SERVER) { if (activeBuffer && activeBuffer.type === BufferType.SERVER) {
composerReadOnly = true; composerReadOnly = true;
} }

View File

@ -10,12 +10,12 @@ const UserStatus = {
}; };
function NickStatus(props) { function NickStatus(props) {
var textMap = { let textMap = {
[UserStatus.HERE]: "User is online", [UserStatus.HERE]: "User is online",
[UserStatus.GONE]: "User is away", [UserStatus.GONE]: "User is away",
[UserStatus.OFFLINE]: "User is offline", [UserStatus.OFFLINE]: "User is offline",
}; };
var text = textMap[props.status]; let text = textMap[props.status];
return html`<span class="status status-${props.status}" title=${text}>●</span>`; return html`<span class="status status-${props.status}" title=${text}>●</span>`;
} }
@ -37,7 +37,7 @@ export default function BufferHeader(props) {
props.onManageNetwork(); props.onManageNetwork();
} }
var description = null, actions = null; let description = null, actions = null;
switch (props.buffer.type) { switch (props.buffer.type) {
case BufferType.SERVER: case BufferType.SERVER:
switch (props.server.status) { switch (props.server.status) {
@ -65,7 +65,7 @@ export default function BufferHeader(props) {
break; break;
} }
} else if (props.buffer.serverInfo) { } else if (props.buffer.serverInfo) {
var serverInfo = props.buffer.serverInfo; let serverInfo = props.buffer.serverInfo;
description = `Connected to ${serverInfo.name}`; description = `Connected to ${serverInfo.name}`;
} else { } else {
description = "Connected"; description = "Connected";
@ -126,11 +126,11 @@ export default function BufferHeader(props) {
break; break;
case BufferType.NICK: case BufferType.NICK:
if (props.buffer.who) { if (props.buffer.who) {
var who = props.buffer.who; let who = props.buffer.who;
var realname = stripANSI(who.realname || ""); let realname = stripANSI(who.realname || "");
var status = UserStatus.HERE; let status = UserStatus.HERE;
if (who.away) { if (who.away) {
status = UserStatus.GONE; status = UserStatus.GONE;
} }
@ -154,7 +154,7 @@ export default function BufferHeader(props) {
break; break;
} }
var name = props.buffer.name; let name = props.buffer.name;
if (props.buffer.type == BufferType.SERVER) { if (props.buffer.type == BufferType.SERVER) {
name = getServerName(props.server, props.bouncerNetwork, props.isBouncer); name = getServerName(props.server, props.bouncerNetwork, props.isBouncer);
} }

View File

@ -8,12 +8,12 @@ function BufferItem(props) {
props.onClick(); props.onClick();
} }
var name = props.buffer.name; let name = props.buffer.name;
if (props.buffer.type == BufferType.SERVER) { if (props.buffer.type == BufferType.SERVER) {
name = getServerName(props.server, props.bouncerNetwork, props.isBouncer); name = getServerName(props.server, props.bouncerNetwork, props.isBouncer);
} }
var classes = ["type-" + props.buffer.type]; let classes = ["type-" + props.buffer.type];
if (props.active) { if (props.active) {
classes.push("active"); classes.push("active");
} }
@ -30,11 +30,11 @@ function BufferItem(props) {
export default function BufferList(props) { export default function BufferList(props) {
var items = Array.from(props.buffers.values()).map((buf) => { let items = Array.from(props.buffers.values()).map((buf) => {
var server = props.servers.get(buf.server); let server = props.servers.get(buf.server);
var bouncerNetwork = null; let bouncerNetwork = null;
var bouncerNetID = server.isupport.get("BOUNCER_NETID"); let bouncerNetID = server.isupport.get("BOUNCER_NETID");
if (bouncerNetID) { if (bouncerNetID) {
bouncerNetwork = props.bouncerNetworks.get(bouncerNetID); bouncerNetwork = props.bouncerNetworks.get(bouncerNetID);
} }

View File

@ -5,8 +5,8 @@ import { strip as stripANSI } from "../lib/ansi.js";
import { BufferType, getNickURL, getChannelURL, getMessageURL } from "../state.js"; import { BufferType, getNickURL, getChannelURL, getMessageURL } from "../state.js";
function djb2(s) { function djb2(s) {
var hash = 5381; let hash = 5381;
for (var i = 0; i < s.length; i++) { for (let i = 0; i < s.length; i++) {
hash = (hash << 5) + hash + s.charCodeAt(i); hash = (hash << 5) + hash + s.charCodeAt(i);
hash = hash >>> 0; // convert to uint32 hash = hash >>> 0; // convert to uint32
} }
@ -19,7 +19,7 @@ function Nick(props) {
props.onClick(); props.onClick();
} }
var colorIndex = djb2(props.nick) % 16 + 1; let colorIndex = djb2(props.nick) % 16 + 1;
return html` return html`
<a href=${getNickURL(props.nick)} class="nick nick-${colorIndex}" onClick=${handleClick}>${props.nick}</a> <a href=${getNickURL(props.nick)} class="nick nick-${colorIndex}" onClick=${handleClick}>${props.nick}</a>
`; `;
@ -30,10 +30,10 @@ function Timestamp({ date, url }) {
return html`<spam class="timestamp">--:--:--</span>`; return html`<spam class="timestamp">--:--:--</span>`;
} }
var hh = date.getHours().toString().padStart(2, "0"); let hh = date.getHours().toString().padStart(2, "0");
var mm = date.getMinutes().toString().padStart(2, "0"); let mm = date.getMinutes().toString().padStart(2, "0");
var ss = date.getSeconds().toString().padStart(2, "0"); let ss = date.getSeconds().toString().padStart(2, "0");
var timestamp = `${hh}:${mm}:${ss}`; let timestamp = `${hh}:${mm}:${ss}`;
return html` return html`
<a href=${url} class="timestamp" onClick=${(event) => event.preventDefault()}>${timestamp}</a> <a href=${url} class="timestamp" onClick=${(event) => event.preventDefault()}>${timestamp}</a>
`; `;
@ -62,10 +62,10 @@ class LogLine extends Component {
} }
render() { render() {
var msg = this.props.message; let msg = this.props.message;
var onNickClick = this.props.onNickClick; let onNickClick = this.props.onNickClick;
var onChannelClick = this.props.onChannelClick; let onChannelClick = this.props.onChannelClick;
function createNick(nick) { function createNick(nick) {
return html` return html`
<${Nick} nick=${nick} onClick=${() => onNickClick(nick)}/> <${Nick} nick=${nick} onClick=${() => onNickClick(nick)}/>
@ -83,14 +83,14 @@ class LogLine extends Component {
`; `;
} }
var lineClass = ""; let lineClass = "";
var content; let content;
switch (msg.command) { switch (msg.command) {
case "NOTICE": case "NOTICE":
case "PRIVMSG": case "PRIVMSG":
var text = msg.params[1]; let text = msg.params[1];
var ctcp = irc.parseCTCP(msg); let ctcp = irc.parseCTCP(msg);
if (ctcp) { if (ctcp) {
if (ctcp.command == "ACTION") { if (ctcp.command == "ACTION") {
lineClass = "me-tell"; lineClass = "me-tell";
@ -102,7 +102,7 @@ class LogLine extends Component {
} }
} else { } else {
lineClass = "talk"; lineClass = "talk";
var prefix = "<", suffix = ">"; let prefix = "<", suffix = ">";
if (msg.command == "NOTICE") { if (msg.command == "NOTICE") {
prefix = suffix = "-"; prefix = suffix = "-";
} }
@ -129,7 +129,7 @@ class LogLine extends Component {
`; `;
break; break;
case "NICK": case "NICK":
var newNick = msg.params[0]; let newNick = msg.params[0];
content = html` content = html`
${createNick(msg.prefix.name)} is now known as ${createNick(newNick)} ${createNick(msg.prefix.name)} is now known as ${createNick(newNick)}
`; `;
@ -145,14 +145,14 @@ class LogLine extends Component {
`; `;
break; break;
case "TOPIC": case "TOPIC":
var topic = msg.params[1]; let topic = msg.params[1];
content = html` content = html`
${createNick(msg.prefix.name)} changed the topic to: ${linkify(stripANSI(topic), onChannelClick)} ${createNick(msg.prefix.name)} changed the topic to: ${linkify(stripANSI(topic), onChannelClick)}
`; `;
break; break;
case "INVITE": case "INVITE":
var invitee = msg.params[0]; let invitee = msg.params[0];
var channel = msg.params[1]; let channel = msg.params[1];
// TODO: instead of checking buffer type, check if invitee is our nick // TODO: instead of checking buffer type, check if invitee is our nick
if (this.props.buffer.type === BufferType.SERVER) { if (this.props.buffer.type === BufferType.SERVER) {
lineClass = "talk"; lineClass = "talk";
@ -193,7 +193,7 @@ function createNickList(nicks, createNick) {
return createNick(nicks[0]); return createNick(nicks[0]);
} }
var l = nicks.slice(0, nicks.length - 1).map((nick, i) => { let l = nicks.slice(0, nicks.length - 1).map((nick, i) => {
if (i === 0) { if (i === 0) {
return createNick(nick); return createNick(nick);
} else { } else {
@ -214,17 +214,17 @@ class FoldGroup extends Component {
} }
render() { render() {
var msgs = this.props.messages; let msgs = this.props.messages;
var buf = this.props.buffer; let buf = this.props.buffer;
var onNickClick = this.props.onNickClick; let onNickClick = this.props.onNickClick;
function createNick(nick) { function createNick(nick) {
return html` return html`
<${Nick} nick=${nick} onClick=${() => onNickClick(nick)}/> <${Nick} nick=${nick} onClick=${() => onNickClick(nick)}/>
`; `;
} }
var byCommand = { let byCommand = {
"JOIN": [], "JOIN": [],
"PART": [], "PART": [],
"QUIT": [], "QUIT": [],
@ -234,15 +234,15 @@ class FoldGroup extends Component {
byCommand[msg.command].push(msg); byCommand[msg.command].push(msg);
}); });
var first = true; let first = true;
var content = []; let content = [];
["JOIN", "PART", "QUIT"].forEach((cmd) => { ["JOIN", "PART", "QUIT"].forEach((cmd) => {
if (byCommand[cmd].length === 0) { if (byCommand[cmd].length === 0) {
return; return;
} }
var plural = byCommand[cmd].length > 1; let plural = byCommand[cmd].length > 1;
var action; let action;
switch (cmd) { switch (cmd) {
case "JOIN": case "JOIN":
action = plural ? "have joined" : "has joined"; action = plural ? "have joined" : "has joined";
@ -261,7 +261,7 @@ class FoldGroup extends Component {
content.push(", "); content.push(", ");
} }
var nicks = byCommand[cmd].map((msg) => msg.prefix.name); let nicks = byCommand[cmd].map((msg) => msg.prefix.name);
content.push(createNickList(nicks, createNick)); content.push(createNickList(nicks, createNick));
content.push(" " + action); content.push(" " + action);
@ -274,16 +274,16 @@ class FoldGroup extends Component {
content.push(", "); content.push(", ");
} }
var newNick = msg.params[0]; let newNick = msg.params[0];
content.push(html` content.push(html`
${createNick(msg.prefix.name)} is now known as ${createNick(newNick)} ${createNick(msg.prefix.name)} is now known as ${createNick(newNick)}
`); `);
}); });
var lastMsg = msgs[msgs.length - 1]; let lastMsg = msgs[msgs.length - 1];
var firstDate = new Date(msgs[0].tags.time); let firstDate = new Date(msgs[0].tags.time);
var lastDate = new Date(lastMsg.tags.time); let lastDate = new Date(lastMsg.tags.time);
var timestamp = html` let timestamp = html`
<${Timestamp} date=${firstDate} url=${getMessageURL(buf, msgs[0])}/> <${Timestamp} date=${firstDate} url=${getMessageURL(buf, msgs[0])}/>
`; `;
if (lastDate - firstDate > 60 * 100) { if (lastDate - firstDate > 60 * 100) {
@ -307,7 +307,7 @@ class FoldGroup extends Component {
} }
// Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=481856 // Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=481856
var notificationsSupported = false; let notificationsSupported = false;
if (window.Notification) { if (window.Notification) {
notificationsSupported = true; notificationsSupported = true;
if (Notification.permission === "default") { if (Notification.permission === "default") {
@ -369,11 +369,11 @@ class DateSeparator extends Component {
} }
render() { render() {
var date = this.props.date; let date = this.props.date;
var YYYY = date.getFullYear().toString().padStart(4, "0"); let YYYY = date.getFullYear().toString().padStart(4, "0");
var MM = (date.getMonth() + 1).toString().padStart(2, "0"); let MM = (date.getMonth() + 1).toString().padStart(2, "0");
var DD = date.getDate().toString().padStart(2, "0"); let DD = date.getDate().toString().padStart(2, "0");
var text = `${YYYY}-${MM}-${DD}`; let text = `${YYYY}-${MM}-${DD}`;
return html` return html`
<div class="separator date-separator"> <div class="separator date-separator">
${text} ${text}
@ -396,18 +396,18 @@ export default class Buffer extends Component {
} }
render() { render() {
var buf = this.props.buffer; let buf = this.props.buffer;
if (!buf) { if (!buf) {
return null; return null;
} }
var children = []; let children = [];
if (buf.type == BufferType.SERVER) { if (buf.type == BufferType.SERVER) {
children.push(html`<${NotificationNagger}/>`); children.push(html`<${NotificationNagger}/>`);
} }
var onChannelClick = this.props.onChannelClick; let onChannelClick = this.props.onChannelClick;
var onNickClick = this.props.onNickClick; let onNickClick = this.props.onNickClick;
function createLogLine(msg) { function createLogLine(msg) {
return html` return html`
<${LogLine} <${LogLine}
@ -421,8 +421,8 @@ export default class Buffer extends Component {
} }
function createFoldGroup(msgs) { function createFoldGroup(msgs) {
// Filter out PART → JOIN pairs // Filter out PART → JOIN pairs
var partIndexes = new Map(); let partIndexes = new Map();
var keep = []; let keep = [];
msgs.forEach((msg, i) => { msgs.forEach((msg, i) => {
if (msg.command === "PART" || msg.command === "QUIT") { if (msg.command === "PART" || msg.command === "QUIT") {
partIndexes.set(msg.prefix.name, i); partIndexes.set(msg.prefix.name, i);
@ -452,18 +452,18 @@ export default class Buffer extends Component {
`; `;
} }
var hasUnreadSeparator = false; let hasUnreadSeparator = false;
var prevDate = new Date(); let prevDate = new Date();
var foldMessages = []; let foldMessages = [];
buf.messages.forEach((msg) => { buf.messages.forEach((msg) => {
var sep = []; let sep = [];
if (!hasUnreadSeparator && buf.type != BufferType.SERVER && buf.lastReadReceipt && msg.tags.time > buf.lastReadReceipt.time) { if (!hasUnreadSeparator && buf.type != BufferType.SERVER && buf.lastReadReceipt && msg.tags.time > buf.lastReadReceipt.time) {
sep.push(html`<${UnreadSeparator} key="unread"/>`); sep.push(html`<${UnreadSeparator} key="unread"/>`);
hasUnreadSeparator = true; hasUnreadSeparator = true;
} }
var date = new Date(msg.tags.time); let date = new Date(msg.tags.time);
if (!sameDate(prevDate, date)) { if (!sameDate(prevDate, date)) {
sep.push(html`<${DateSeparator} key=${"date-" + date} date=${date}/>`); sep.push(html`<${DateSeparator} key=${"date-" + date} date=${date}/>`);
} }

View File

@ -34,21 +34,21 @@ export default class Composer extends Component {
return; return;
} }
var text = this.state.text; let text = this.state.text;
var i; let i;
for (i = text.length - 1; i >= 0; i--) { for (i = text.length - 1; i >= 0; i--) {
if (text[i] === " ") { if (text[i] === " ") {
break; break;
} }
} }
var prefix = text.slice(i + 1); let prefix = text.slice(i + 1);
if (!prefix) { if (!prefix) {
return; return;
} }
event.preventDefault(); event.preventDefault();
var repl = this.props.autocomplete(prefix); let repl = this.props.autocomplete(prefix);
if (!repl) { if (!repl) {
return; return;
} }

View File

@ -32,8 +32,8 @@ export default class ConnectForm extends Component {
} }
handleChange(event) { handleChange(event) {
var target = event.target; let target = event.target;
var value = target.type == "checkbox" ? target.checked : target.value; let value = target.type == "checkbox" ? target.checked : target.value;
this.setState({ [target.name]: value }); this.setState({ [target.name]: value });
} }
@ -44,7 +44,7 @@ export default class ConnectForm extends Component {
return; return;
} }
var params = { let params = {
url: this.state.url, url: this.state.url,
pass: this.state.pass, pass: this.state.pass,
nick: this.state.nick, nick: this.state.nick,
@ -74,9 +74,9 @@ export default class ConnectForm extends Component {
} }
render() { render() {
var disabled = this.props.connecting; let disabled = this.props.connecting;
var serverURL = null; let serverURL = null;
if (!this.props.params || !this.props.params.url) { if (!this.props.params || !this.props.params.url) {
serverURL = html` serverURL = html`
<label> <label>
@ -87,7 +87,7 @@ export default class ConnectForm extends Component {
`; `;
} }
var status = null; let status = null;
if (this.props.connecting) { if (this.props.connecting) {
status = html` status = html`
<p>Connecting...</p> <p>Connecting...</p>
@ -98,7 +98,7 @@ export default class ConnectForm extends Component {
`; `;
} }
var auth = null; let auth = null;
if (this.props.auth !== "disabled") { if (this.props.auth !== "disabled") {
auth = html` auth = html`
<label> <label>

View File

@ -35,7 +35,7 @@ export default class Dialog extends Component {
componentDidMount() { componentDidMount() {
window.addEventListener("keydown", this.handleKeyDown); window.addEventListener("keydown", this.handleKeyDown);
var autofocus = this.body.current.querySelector("input[autofocus]"); let autofocus = this.body.current.querySelector("input[autofocus]");
if (autofocus) { if (autofocus) {
autofocus.focus(); autofocus.focus();
} }

View File

@ -3,8 +3,8 @@ import { keybindings } from "../keybindings.js";
import commands from "../commands.js"; import commands from "../commands.js";
function KeyBindingsHelp() { function KeyBindingsHelp() {
var l = keybindings.map((binding) => { let l = keybindings.map((binding) => {
var keys = []; let keys = [];
if (binding.ctrlKey) { if (binding.ctrlKey) {
keys.psuh("Ctrl"); keys.psuh("Ctrl");
} }
@ -37,10 +37,10 @@ function KeyBindingsHelp() {
} }
function CommandsHelp() { function CommandsHelp() {
var l = Object.keys(commands).map((name) => { let l = Object.keys(commands).map((name) => {
var cmd = commands[name]; let cmd = commands[name];
var usage = "/" + name; let usage = "/" + name;
if (cmd.usage) { if (cmd.usage) {
usage += " " + cmd.usage; usage += " " + cmd.usage;
} }

View File

@ -13,15 +13,15 @@ export default class JoinForm extends Component {
} }
handleChange(event) { handleChange(event) {
var target = event.target; let target = event.target;
var value = target.type == "checkbox" ? target.checked : target.value; let value = target.type == "checkbox" ? target.checked : target.value;
this.setState({ [target.name]: value }); this.setState({ [target.name]: value });
} }
handleSubmit(event) { handleSubmit(event) {
event.preventDefault(); event.preventDefault();
var params = { let params = {
channel: this.state.channel, channel: this.state.channel,
}; };

View File

@ -50,10 +50,10 @@ class MemberItem extends Component {
} }
function sortMembers(a, b) { function sortMembers(a, b) {
var [nickA, membA] = a, [nickB, membB] = b; let [nickA, membA] = a, [nickB, membB] = b;
const prefixPrivs = ["~", "&", "@", "%", "+"]; // TODO: grab it from ISUPPORT PREFIX const prefixPrivs = ["~", "&", "@", "%", "+"]; // TODO: grab it from ISUPPORT PREFIX
var i = prefixPrivs.indexOf(membA[0]), j = prefixPrivs.indexOf(membB[0]); let i = prefixPrivs.indexOf(membA[0]), j = prefixPrivs.indexOf(membB[0]);
if (i < 0) { if (i < 0) {
i = prefixPrivs.length; i = prefixPrivs.length;
} }

View File

@ -38,15 +38,15 @@ export default class NetworkForm extends Component {
} }
handleChange(event) { handleChange(event) {
var target = event.target; let target = event.target;
var value = target.type == "checkbox" ? target.checked : target.value; let value = target.type == "checkbox" ? target.checked : target.value;
this.setState({ [target.name]: value }); this.setState({ [target.name]: value });
} }
handleSubmit(event) { handleSubmit(event) {
event.preventDefault(); event.preventDefault();
var params = {}; let params = {};
Object.keys(defaultParams).forEach((k) => { Object.keys(defaultParams).forEach((k) => {
if (this.prevParams[k] == this.state[k]) { if (this.prevParams[k] == this.state[k]) {
return; return;
@ -58,7 +58,7 @@ export default class NetworkForm extends Component {
} }
render() { render() {
var removeNetwork = null; let removeNetwork = null;
if (!this.state.isNew) { if (!this.state.isNew) {
removeNetwork = html` removeNetwork = html`
<button type="button" onClick=${() => this.props.onRemove()}> <button type="button" onClick=${() => this.props.onRemove()}>

View File

@ -1,6 +1,6 @@
import { html, Component } from "../lib/index.js"; import { html, Component } from "../lib/index.js";
var store = new Map(); let store = new Map();
export default class ScrollManager extends Component { export default class ScrollManager extends Component {
constructor(props) { constructor(props) {
@ -10,18 +10,18 @@ export default class ScrollManager extends Component {
} }
isAtBottom() { isAtBottom() {
var target = this.props.target.current; let target = this.props.target.current;
return target.scrollTop >= target.scrollHeight - target.offsetHeight; return target.scrollTop >= target.scrollHeight - target.offsetHeight;
} }
saveScrollPosition() { saveScrollPosition() {
var target = this.props.target.current; let target = this.props.target.current;
var sticky = target.querySelectorAll(this.props.stickTo); let sticky = target.querySelectorAll(this.props.stickTo);
var stickToKey = null; let stickToKey = null;
if (!this.isAtBottom()) { if (!this.isAtBottom()) {
for (var i = 0; i < sticky.length; i++) { for (let i = 0; i < sticky.length; i++) {
var el = sticky[i]; let el = sticky[i];
if (el.offsetTop >= target.scrollTop + target.offsetTop) { if (el.offsetTop >= target.scrollTop + target.offsetTop) {
stickToKey = el.dataset.key; stickToKey = el.dataset.key;
break; break;
@ -33,13 +33,13 @@ export default class ScrollManager extends Component {
} }
restoreScrollPosition() { restoreScrollPosition() {
var target = this.props.target.current; let target = this.props.target.current;
var stickToKey = store.get(this.props.scrollKey); let stickToKey = store.get(this.props.scrollKey);
if (!stickToKey) { if (!stickToKey) {
target.firstChild.scrollIntoView({ block: "end" }); target.firstChild.scrollIntoView({ block: "end" });
} else { } else {
var stickTo = target.querySelector("[data-key=\"" + stickToKey + "\"]"); let stickTo = target.querySelector("[data-key=\"" + stickToKey + "\"]");
if (stickTo) { if (stickTo) {
stickTo.scrollIntoView(); stickTo.scrollIntoView();
} }

View File

@ -1,8 +1,8 @@
import { ReceiptType, Unread, BufferType, SERVER_BUFFER } from "./state.js"; import { ReceiptType, Unread, BufferType, SERVER_BUFFER } from "./state.js";
function getSiblingBuffer(buffers, bufID, delta) { function getSiblingBuffer(buffers, bufID, delta) {
var bufList = Array.from(buffers.values()); let bufList = Array.from(buffers.values());
var i = bufList.findIndex((buf) => buf.id === bufID); let i = bufList.findIndex((buf) => buf.id === bufID);
if (i < 0) { if (i < 0) {
return null; return null;
} }
@ -17,10 +17,10 @@ export const keybindings = [
description: "Mark all messages as read", description: "Mark all messages as read",
execute: (app) => { execute: (app) => {
app.setState((state) => { app.setState((state) => {
var buffers = new Map(); let buffers = new Map();
state.buffers.forEach((buf) => { state.buffers.forEach((buf) => {
if (buf.messages.length > 0) { if (buf.messages.length > 0) {
var lastMsg = buf.messages[buf.messages.length - 1]; let lastMsg = buf.messages[buf.messages.length - 1];
app.setReceipt(buf.name, ReceiptType.READ, lastMsg); app.setReceipt(buf.name, ReceiptType.READ, lastMsg);
} }
buffers.set(buf.id, { buffers.set(buf.id, {
@ -38,9 +38,9 @@ export const keybindings = [
description: "Jump to next buffer with activity", description: "Jump to next buffer with activity",
execute: (app) => { execute: (app) => {
// TODO: order by age if same priority // TODO: order by age if same priority
var firstServerBuffer = null; let firstServerBuffer = null;
var target = null; let target = null;
for (var buf of app.state.buffers.values()) { for (let buf of app.state.buffers.values()) {
if (!firstServerBuffer && buf.type === BufferType.SERVER) { if (!firstServerBuffer && buf.type === BufferType.SERVER) {
firstServerBuffer = buf; firstServerBuffer = buf;
} }
@ -66,7 +66,7 @@ export const keybindings = [
altKey: true, altKey: true,
description: "Jump to the previous buffer", description: "Jump to the previous buffer",
execute: (app) => { execute: (app) => {
var prev = getSiblingBuffer(app.state.buffers, app.state.activeBuffer, -1); let prev = getSiblingBuffer(app.state.buffers, app.state.activeBuffer, -1);
if (prev) { if (prev) {
app.switchBuffer(prev); app.switchBuffer(prev);
} }
@ -77,7 +77,7 @@ export const keybindings = [
altKey: true, altKey: true,
description: "Jump to the next buffer", description: "Jump to the next buffer",
execute: (app) => { execute: (app) => {
var next = getSiblingBuffer(app.state.buffers, app.state.activeBuffer, 1); let next = getSiblingBuffer(app.state.buffers, app.state.activeBuffer, 1);
if (next) { if (next) {
app.switchBuffer(next); app.switchBuffer(next);
} }
@ -86,7 +86,7 @@ export const keybindings = [
]; ];
export function setup(app) { export function setup(app) {
var byKey = {}; let byKey = {};
keybindings.forEach((binding) => { keybindings.forEach((binding) => {
if (!byKey[binding.key]) { if (!byKey[binding.key]) {
byKey[binding.key] = []; byKey[binding.key] = [];
@ -95,7 +95,7 @@ export function setup(app) {
}); });
window.addEventListener("keydown", (event) => { window.addEventListener("keydown", (event) => {
var candidates = byKey[event.key]; let candidates = byKey[event.key];
if (!candidates) { if (!candidates) {
return; return;
} }

View File

@ -15,9 +15,9 @@ function isDigit(ch) {
} }
export function strip(text) { export function strip(text) {
var out = ""; let out = "";
for (var i = 0; i < text.length; i++) { for (let i = 0; i < text.length; i++) {
var ch = text[i]; let ch = text[i];
switch (ch) { switch (ch) {
case BOLD: case BOLD:
case ITALIC: case ITALIC:

View File

@ -21,7 +21,7 @@ const permanentCaps = [
const RECONNECT_DELAY_SEC = 10; const RECONNECT_DELAY_SEC = 10;
var lastLabel = 0; let lastLabel = 0;
export default class Client extends EventTarget { export default class Client extends EventTarget {
static Status = { static Status = {
@ -65,7 +65,7 @@ export default class Client extends EventTarget {
} }
reconnect() { reconnect() {
var autoReconnect = this.autoReconnect; let autoReconnect = this.autoReconnect;
this.disconnect(); this.disconnect();
this.autoReconnect = autoReconnect; this.autoReconnect = autoReconnect;
@ -150,7 +150,7 @@ export default class Client extends EventTarget {
} }
handleMessage(event) { handleMessage(event) {
var msg = irc.parseMessage(event.data); let msg = irc.parseMessage(event.data);
console.debug("Received:", msg); console.debug("Received:", msg);
// If the prefix is missing, assume it's coming from the server on the // If the prefix is missing, assume it's coming from the server on the
@ -159,7 +159,7 @@ export default class Client extends EventTarget {
msg.prefix = this.serverPrefix; msg.prefix = this.serverPrefix;
} }
var msgBatch = null; let msgBatch = null;
if (msg.tags["batch"]) { if (msg.tags["batch"]) {
msgBatch = this.batches.get(msg.tags["batch"]); msgBatch = this.batches.get(msg.tags["batch"]);
if (msgBatch) { if (msgBatch) {
@ -167,7 +167,7 @@ export default class Client extends EventTarget {
} }
} }
var deleteBatch = null; let deleteBatch = null;
switch (msg.command) { switch (msg.command) {
case irc.RPL_WELCOME: case irc.RPL_WELCOME:
if (this.params.saslPlain && this.availableCaps["sasl"] === undefined) { if (this.params.saslPlain && this.availableCaps["sasl"] === undefined) {
@ -184,8 +184,8 @@ export default class Client extends EventTarget {
this.setStatus(Client.Status.REGISTERED); this.setStatus(Client.Status.REGISTERED);
break; break;
case irc.RPL_ISUPPORT: case irc.RPL_ISUPPORT:
var tokens = msg.params.slice(1, -1); let tokens = msg.params.slice(1, -1);
var changed = irc.parseISUPPORT(tokens, this.isupport); let changed = irc.parseISUPPORT(tokens, this.isupport);
if (changed.indexOf("CASEMAPPING") >= 0) { if (changed.indexOf("CASEMAPPING") >= 0) {
this.setCaseMapping(this.isupport.get("CASEMAPPING")); this.setCaseMapping(this.isupport.get("CASEMAPPING"));
} }
@ -225,7 +225,7 @@ export default class Client extends EventTarget {
case irc.RPL_WHOISIDLE: case irc.RPL_WHOISIDLE:
case irc.RPL_WHOISCHANNELS: case irc.RPL_WHOISCHANNELS:
case irc.RPL_ENDOFWHOIS: case irc.RPL_ENDOFWHOIS:
var nick = msg.params[1]; let nick = msg.params[1];
if (!this.whoisDB.has(nick)) { if (!this.whoisDB.has(nick)) {
this.whoisDB.set(nick, {}); this.whoisDB.set(nick, {});
} }
@ -243,16 +243,16 @@ export default class Client extends EventTarget {
this.send({ command: "PONG", params: [msg.params[0]] }); this.send({ command: "PONG", params: [msg.params[0]] });
break; break;
case "NICK": case "NICK":
var newNick = msg.params[0]; let newNick = msg.params[0];
if (msg.prefix.name == this.nick) { if (msg.prefix.name == this.nick) {
this.nick = newNick; this.nick = newNick;
} }
break; break;
case "BATCH": case "BATCH":
var enter = msg.params[0].startsWith("+"); let enter = msg.params[0].startsWith("+");
var name = msg.params[0].slice(1); let name = msg.params[0].slice(1);
if (enter) { if (enter) {
var batch = { let batch = {
name, name,
type: msg.params[1], type: msg.params[1],
params: msg.params.slice(2), params: msg.params.slice(2),
@ -302,8 +302,8 @@ export default class Client extends EventTarget {
} }
who(mask) { who(mask) {
var msg = { command: "WHO", params: [mask] }; let msg = { command: "WHO", params: [mask] };
var l = []; let l = [];
return this.roundtrip(msg, (msg) => { return this.roundtrip(msg, (msg) => {
switch (msg.command) { switch (msg.command) {
case irc.RPL_WHOREPLY: case irc.RPL_WHOREPLY:
@ -320,18 +320,19 @@ export default class Client extends EventTarget {
} }
whois(target) { whois(target) {
var targetCM = this.cm(target); let targetCM = this.cm(target);
var msg = { command: "WHOIS", params: [target] }; let msg = { command: "WHOIS", params: [target] };
return this.roundtrip(msg, (msg) => { return this.roundtrip(msg, (msg) => {
let nick;
switch (msg.command) { switch (msg.command) {
case irc.RPL_ENDOFWHOIS: case irc.RPL_ENDOFWHOIS:
var nick = msg.params[1]; nick = msg.params[1];
if (this.cm(nick) === targetCM) { if (this.cm(nick) === targetCM) {
return this.whoisDB.get(nick); return this.whoisDB.get(nick);
} }
break; break;
case irc.ERR_NOSUCHNICK: case irc.ERR_NOSUCHNICK:
var nick = msg.params[1]; nick = msg.params[1];
if (this.cm(nick) === targetCM) { if (this.cm(nick) === targetCM) {
throw msg; throw msg;
} }
@ -341,11 +342,11 @@ export default class Client extends EventTarget {
} }
addAvailableCaps(s) { addAvailableCaps(s) {
var l = s.split(" "); let l = s.split(" ");
l.forEach((s) => { l.forEach((s) => {
var parts = s.split("="); let parts = s.split("=");
var k = parts[0].toLowerCase(); let k = parts[0].toLowerCase();
var v = ""; let v = "";
if (parts.length > 1) { if (parts.length > 1) {
v = parts[1]; v = parts[1];
} }
@ -354,7 +355,7 @@ export default class Client extends EventTarget {
} }
supportsSASL(mech) { supportsSASL(mech) {
var saslCap = this.availableCaps["sasl"]; let saslCap = this.availableCaps["sasl"];
if (saslCap === undefined) { if (saslCap === undefined) {
return false; return false;
} }
@ -362,7 +363,7 @@ export default class Client extends EventTarget {
} }
requestCaps(extra) { requestCaps(extra) {
var reqCaps = extra || []; let reqCaps = extra || [];
permanentCaps.forEach((cap) => { permanentCaps.forEach((cap) => {
if (this.availableCaps[cap] !== undefined && !this.enabledCaps[cap]) { if (this.availableCaps[cap] !== undefined && !this.enabledCaps[cap]) {
@ -376,16 +377,16 @@ export default class Client extends EventTarget {
} }
handleCap(msg) { handleCap(msg) {
var subCmd = msg.params[1]; let subCmd = msg.params[1];
var args = msg.params.slice(2); let args = msg.params.slice(2);
switch (subCmd) { switch (subCmd) {
case "LS": case "LS":
this.addAvailableCaps(args[args.length - 1]); this.addAvailableCaps(args[args.length - 1]);
if (args[0] != "*") { if (args[0] != "*") {
console.log("Available server caps:", this.availableCaps); console.log("Available server caps:", this.availableCaps);
var reqCaps = []; let reqCaps = [];
var capEnd = true; let capEnd = true;
if (this.params.saslPlain && this.supportsSASL("PLAIN")) { if (this.params.saslPlain && this.supportsSASL("PLAIN")) {
// CAP END is deferred after authentication finishes // CAP END is deferred after authentication finishes
reqCaps.push("sasl"); reqCaps.push("sasl");
@ -438,7 +439,7 @@ export default class Client extends EventTarget {
} }
handleAuthenticate(msg) { handleAuthenticate(msg) {
var challengeStr = msg.params[0]; let challengeStr = msg.params[0];
// For now only PLAIN is supported // For now only PLAIN is supported
if (challengeStr != "+") { if (challengeStr != "+") {
@ -447,7 +448,7 @@ export default class Client extends EventTarget {
return; return;
} }
var respStr = btoa("\0" + this.params.saslPlain.username + "\0" + this.params.saslPlain.password); let respStr = btoa("\0" + this.params.saslPlain.username + "\0" + this.params.saslPlain.password);
this.send({ command: "AUTHENTICATE", params: [respStr] }); this.send({ command: "AUTHENTICATE", params: [respStr] });
} }
@ -478,7 +479,7 @@ export default class Client extends EventTarget {
} }
isChannel(name) { isChannel(name) {
var chanTypes = this.isupport.get("CHANTYPES") || irc.STD_CHANTYPES; let chanTypes = this.isupport.get("CHANTYPES") || irc.STD_CHANTYPES;
return chanTypes.indexOf(name[0]) >= 0; return chanTypes.indexOf(name[0]) >= 0;
} }
@ -500,7 +501,7 @@ export default class Client extends EventTarget {
/* Execute a command that expects a response. `done` is called with message /* Execute a command that expects a response. `done` is called with message
* events until it returns a truthy value. */ * events until it returns a truthy value. */
roundtrip(msg, done) { roundtrip(msg, done) {
var label; let label;
if (this.enabledCaps["labeled-response"]) { if (this.enabledCaps["labeled-response"]) {
lastLabel++; lastLabel++;
label = String(lastLabel); label = String(lastLabel);
@ -508,15 +509,15 @@ export default class Client extends EventTarget {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var handleMessage = (event) => { let handleMessage = (event) => {
var msg = event.detail.message; let msg = event.detail.message;
var msgLabel = irc.getMessageLabel(msg); let msgLabel = irc.getMessageLabel(msg);
if (msgLabel && msgLabel != label) { if (msgLabel && msgLabel != label) {
return; return;
} }
var result; let result;
try { try {
result = done(msg); result = done(msg);
} catch (err) { } catch (err) {
@ -537,11 +538,11 @@ export default class Client extends EventTarget {
} }
fetchBatch(msg, batchType) { fetchBatch(msg, batchType) {
var batchName = null; let batchName = null;
var messages = []; let messages = [];
return this.roundtrip(msg, (msg) => { return this.roundtrip(msg, (msg) => {
if (batchName) { if (batchName) {
var batch = msg.batch; let batch = msg.batch;
while (batch) { while (batch) {
if (batch.name === batchName) { if (batch.name === batchName) {
messages.push(msg); messages.push(msg);
@ -553,8 +554,8 @@ export default class Client extends EventTarget {
switch (msg.command) { switch (msg.command) {
case "BATCH": case "BATCH":
var enter = msg.params[0].startsWith("+"); let enter = msg.params[0].startsWith("+");
var name = msg.params[0].slice(1); let name = msg.params[0].slice(1);
if (enter && msg.params[1] === batchType) { if (enter && msg.params[1] === batchType) {
batchName = name; batchName = name;
break; break;
@ -576,7 +577,7 @@ export default class Client extends EventTarget {
// Don't send multiple CHATHISTORY commands in parallel, we can't // Don't send multiple CHATHISTORY commands in parallel, we can't
// properly handle batches and errors. // properly handle batches and errors.
this.pendingHistory = this.pendingHistory.catch(() => {}).then(() => { this.pendingHistory = this.pendingHistory.catch(() => {}).then(() => {
var msg = { let msg = {
command: "CHATHISTORY", command: "CHATHISTORY",
params, params,
}; };
@ -587,7 +588,7 @@ export default class Client extends EventTarget {
chatHistoryPageSize() { chatHistoryPageSize() {
if (this.isupport.has("CHATHISTORY")) { if (this.isupport.has("CHATHISTORY")) {
var pageSize = parseInt(this.isupport.get("CHATHISTORY"), 10); let pageSize = parseInt(this.isupport.get("CHATHISTORY"), 10);
if (pageSize > 0) { if (pageSize > 0) {
return pageSize; return pageSize;
} }
@ -597,8 +598,8 @@ export default class Client extends EventTarget {
/* Fetch one page of history before the given date. */ /* Fetch one page of history before the given date. */
fetchHistoryBefore(target, before, limit) { fetchHistoryBefore(target, before, limit) {
var max = Math.min(limit, this.chatHistoryPageSize()); let max = Math.min(limit, this.chatHistoryPageSize());
var params = ["BEFORE", target, "timestamp=" + before, max]; let params = ["BEFORE", target, "timestamp=" + before, max];
return this.roundtripChatHistory(params).then((messages) => { return this.roundtripChatHistory(params).then((messages) => {
return { more: messages.length >= max }; return { more: messages.length >= max };
}); });
@ -606,8 +607,8 @@ export default class Client extends EventTarget {
/* Fetch history in ascending order. */ /* Fetch history in ascending order. */
fetchHistoryBetween(target, after, before, limit) { fetchHistoryBetween(target, after, before, limit) {
var max = Math.min(limit, this.chatHistoryPageSize()); let max = Math.min(limit, this.chatHistoryPageSize());
var params = ["AFTER", target, "timestamp=" + after.time, max]; let params = ["AFTER", target, "timestamp=" + after.time, max];
return this.roundtripChatHistory(params).then((messages) => { return this.roundtripChatHistory(params).then((messages) => {
limit -= messages.length; limit -= messages.length;
if (limit <= 0) { if (limit <= 0) {
@ -623,7 +624,7 @@ export default class Client extends EventTarget {
} }
fetchHistoryTargets(t1, t2) { fetchHistoryTargets(t1, t2) {
var msg = { let msg = {
command: "CHATHISTORY", command: "CHATHISTORY",
params: ["TARGETS", "timestamp=" + t1, "timestamp=" + t2, 1000], params: ["TARGETS", "timestamp=" + t1, "timestamp=" + t2, 1000],
}; };
@ -645,13 +646,13 @@ export default class Client extends EventTarget {
return Promise.reject(new Error("Server doesn't support the BOUNCER extension")); return Promise.reject(new Error("Server doesn't support the BOUNCER extension"));
} }
var req = { command: "BOUNCER", params: ["LISTNETWORKS"] }; let req = { command: "BOUNCER", params: ["LISTNETWORKS"] };
return this.fetchBatch(req, "soju.im/bouncer-networks").then((batch) => { return this.fetchBatch(req, "soju.im/bouncer-networks").then((batch) => {
var networks = new Map(); let networks = new Map();
for (var msg of batch.messages) { for (let msg of batch.messages) {
console.assert(msg.command === "BOUNCER" && msg.params[0] === "NETWORK"); console.assert(msg.command === "BOUNCER" && msg.params[0] === "NETWORK");
var id = msg.params[1]; let id = msg.params[1];
var params = irc.parseTags(msg.params[2]); let params = irc.parseTags(msg.params[2]);
networks.set(id, params); networks.set(id, params);
} }
return networks; return networks;

View File

@ -71,14 +71,14 @@ function unescapeTag(s) {
} }
export function parseTags(s) { export function parseTags(s) {
var tags = {}; let tags = {};
s.split(";").forEach((s) => { s.split(";").forEach((s) => {
if (!s) { if (!s) {
return; return;
} }
var parts = s.split("=", 2); let parts = s.split("=", 2);
var k = parts[0]; let k = parts[0];
var v = null; let v = null;
if (parts.length == 2) { if (parts.length == 2) {
v = unescapeTag(parts[1]); v = unescapeTag(parts[1]);
if (v.endsWith("\\")) { if (v.endsWith("\\")) {
@ -91,26 +91,26 @@ export function parseTags(s) {
} }
export function formatTags(tags) { export function formatTags(tags) {
var l = []; let l = [];
for (var k in tags) { for (let k in tags) {
if (tags[k] === undefined || tags[k] === null) { if (tags[k] === undefined || tags[k] === null) {
l.push(k); l.push(k);
continue; continue;
} }
var v = escapeTag(tags[k]); let v = escapeTag(tags[k]);
l.push(k + "=" + v); l.push(k + "=" + v);
} }
return l.join(";"); return l.join(";");
} }
function parsePrefix(s) { function parsePrefix(s) {
var prefix = { let prefix = {
name: null, name: null,
user: null, user: null,
host: null, host: null,
}; };
var i = s.indexOf("@"); let i = s.indexOf("@");
if (i < 0) { if (i < 0) {
prefix.name = s; prefix.name = s;
return prefix; return prefix;
@ -118,7 +118,7 @@ function parsePrefix(s) {
prefix.host = s.slice(i + 1); prefix.host = s.slice(i + 1);
s = s.slice(0, i); s = s.slice(0, i);
var i = s.indexOf("!"); i = s.indexOf("!");
if (i < 0) { if (i < 0) {
prefix.name = s; prefix.name = s;
return prefix; return prefix;
@ -143,7 +143,7 @@ export function parseMessage(s) {
s = s.slice(0, s.length - 2); s = s.slice(0, s.length - 2);
} }
var msg = { let msg = {
tags: {}, tags: {},
prefix: null, prefix: null,
command: null, command: null,
@ -151,7 +151,7 @@ export function parseMessage(s) {
}; };
if (s.startsWith("@")) { if (s.startsWith("@")) {
var i = s.indexOf(" "); let i = s.indexOf(" ");
if (i < 0) { if (i < 0) {
throw new Error("expected a space after tags"); throw new Error("expected a space after tags");
} }
@ -160,7 +160,7 @@ export function parseMessage(s) {
} }
if (s.startsWith(":")) { if (s.startsWith(":")) {
var i = s.indexOf(" "); let i = s.indexOf(" ");
if (i < 0) { if (i < 0) {
throw new Error("expected a space after prefix"); throw new Error("expected a space after prefix");
} }
@ -168,7 +168,7 @@ export function parseMessage(s) {
s = s.slice(i + 1); s = s.slice(i + 1);
} }
var i = s.indexOf(" "); let i = s.indexOf(" ");
if (i < 0) { if (i < 0) {
msg.command = s; msg.command = s;
return msg; return msg;
@ -196,7 +196,7 @@ export function parseMessage(s) {
} }
export function formatMessage(msg) { export function formatMessage(msg) {
var s = ""; let s = "";
if (msg.tags && Object.keys(msg.tags).length > 0) { if (msg.tags && Object.keys(msg.tags).length > 0) {
s += "@" + formatTags(msg.tags) + " "; s += "@" + formatTags(msg.tags) + " ";
} }
@ -205,7 +205,7 @@ export function formatMessage(msg) {
} }
s += msg.command; s += msg.command;
if (msg.params && msg.params.length > 0) { if (msg.params && msg.params.length > 0) {
var last = msg.params[msg.params.length - 1]; let last = msg.params[msg.params.length - 1];
if (msg.params.length > 1) { if (msg.params.length > 1) {
s += " " + msg.params.slice(0, -1).join(" "); s += " " + msg.params.slice(0, -1).join(" ");
} }
@ -217,7 +217,7 @@ export function formatMessage(msg) {
/** Split a prefix and a name out of a target. */ /** Split a prefix and a name out of a target. */
export function parseTargetPrefix(s, allowedPrefixes = STD_MEMBERSHIPS) { export function parseTargetPrefix(s, allowedPrefixes = STD_MEMBERSHIPS) {
var i; let i;
for (i = 0; i < s.length; i++) { for (i = 0; i < s.length; i++) {
if (allowedPrefixes.indexOf(s[i]) < 0) { if (allowedPrefixes.indexOf(s[i]) < 0) {
break; break;
@ -262,15 +262,15 @@ export function isHighlight(msg, nick, cm) {
return false; // Our own messages aren't highlights return false; // Our own messages aren't highlights
} }
var text = cm(msg.params[1]); let text = cm(msg.params[1]);
while (true) { while (true) {
var i = text.indexOf(nick); let i = text.indexOf(nick);
if (i < 0) { if (i < 0) {
return false; return false;
} }
// Detect word boundaries // Detect word boundaries
var left = "\x00", right = "\x00"; let left = "\x00", right = "\x00";
if (i > 0) { if (i > 0) {
left = text[i - 1]; left = text[i - 1];
} }
@ -305,13 +305,13 @@ export function isError(cmd) {
export function formatDate(date) { export function formatDate(date) {
// ISO 8601 // ISO 8601
var YYYY = date.getUTCFullYear().toString().padStart(4, "0"); let YYYY = date.getUTCFullYear().toString().padStart(4, "0");
var MM = (date.getUTCMonth() + 1).toString().padStart(2, "0"); let MM = (date.getUTCMonth() + 1).toString().padStart(2, "0");
var DD = date.getUTCDate().toString().padStart(2, "0"); let DD = date.getUTCDate().toString().padStart(2, "0");
var hh = date.getUTCHours().toString().padStart(2, "0"); let hh = date.getUTCHours().toString().padStart(2, "0");
var mm = date.getUTCMinutes().toString().padStart(2, "0"); let mm = date.getUTCMinutes().toString().padStart(2, "0");
var ss = date.getUTCSeconds().toString().padStart(2, "0"); let ss = date.getUTCSeconds().toString().padStart(2, "0");
var sss = date.getUTCMilliseconds().toString().padStart(3, "0"); let sss = date.getUTCMilliseconds().toString().padStart(3, "0");
return `${YYYY}-${MM}-${DD}T${hh}:${mm}:${ss}.${sss}Z`; return `${YYYY}-${MM}-${DD}T${hh}:${mm}:${ss}.${sss}Z`;
} }
@ -320,7 +320,7 @@ export function parseCTCP(msg) {
return null; return null;
} }
var text = msg.params[1]; let text = msg.params[1];
if (!text.startsWith("\x01")) { if (!text.startsWith("\x01")) {
return null; return null;
} }
@ -329,8 +329,8 @@ export function parseCTCP(msg) {
text = text.slice(0, -1); text = text.slice(0, -1);
} }
var ctcp; let ctcp;
var i = text.indexOf(" "); let i = text.indexOf(" ");
if (i >= 0) { if (i >= 0) {
ctcp = { command: text.slice(0, i), param: text.slice(i + 1) }; ctcp = { command: text.slice(0, i), param: text.slice(i + 1) };
} else { } else {
@ -341,16 +341,16 @@ export function parseCTCP(msg) {
} }
export function parseISUPPORT(tokens, params) { export function parseISUPPORT(tokens, params) {
var changed = []; let changed = [];
tokens.forEach((tok) => { tokens.forEach((tok) => {
if (tok.startsWith("-")) { if (tok.startsWith("-")) {
var k = tok.slice(1); let k = tok.slice(1);
params.delete(k.toUpperCase()); params.delete(k.toUpperCase());
return; return;
} }
var i = tok.indexOf("="); let i = tok.indexOf("=");
var k = tok, v = ""; let k = tok, v = "";
if (i >= 0) { if (i >= 0) {
k = tok.slice(0, i); k = tok.slice(0, i);
v = tok.slice(i + 1); v = tok.slice(i + 1);
@ -366,9 +366,9 @@ export function parseISUPPORT(tokens, params) {
export const CaseMapping = { export const CaseMapping = {
ASCII(str) { ASCII(str) {
var out = ""; let out = "";
for (var i = 0; i < str.length; i++) { for (let i = 0; i < str.length; i++) {
var ch = str[i]; let ch = str[i];
if ("A" <= ch && ch <= "Z") { if ("A" <= ch && ch <= "Z") {
ch = ch.toLowerCase(); ch = ch.toLowerCase();
} }
@ -378,9 +378,9 @@ export const CaseMapping = {
}, },
RFC1459(str) { RFC1459(str) {
var out = ""; let out = "";
for (var i = 0; i < str.length; i++) { for (let i = 0; i < str.length; i++) {
var ch = str[i]; let ch = str[i];
if ("A" <= ch && ch <= "Z") { if ("A" <= ch && ch <= "Z") {
ch = ch.toLowerCase(); ch = ch.toLowerCase();
} else if (ch == "{") { } else if (ch == "{") {
@ -398,9 +398,9 @@ export const CaseMapping = {
}, },
RFC1459Strict(str) { RFC1459Strict(str) {
var out = ""; let out = "";
for (var i = 0; i < str.length; i++) { for (let i = 0; i < str.length; i++) {
var ch = str[i]; let ch = str[i];
if ("A" <= ch && ch <= "Z") { if ("A" <= ch && ch <= "Z") {
ch = ch.toLowerCase(); ch = ch.toLowerCase();
} else if (ch == "{") { } else if (ch == "{") {
@ -429,7 +429,7 @@ export const CaseMapping = {
}; };
function createIterator(next) { function createIterator(next) {
var it = { next }; let it = { next };
// Not defining this can lead to surprises when feeding the iterator // Not defining this can lead to surprises when feeding the iterator
// to e.g. Array.from // to e.g. Array.from
it[Symbol.iterator] = () => it; it[Symbol.iterator] = () => it;
@ -438,7 +438,7 @@ function createIterator(next) {
function mapIterator(it, f) { function mapIterator(it, f) {
return createIterator(() => { return createIterator(() => {
var { value, done } = it.next(); let { value, done } = it.next();
if (done) { if (done) {
return { done: true }; return { done: true };
} }
@ -464,7 +464,7 @@ export class CaseMapMap {
this.map = new Map(); this.map = new Map();
if (iterable) { if (iterable) {
for (var [key, value] of iterable) { for (let [key, value] of iterable) {
this.set(key, value); this.set(key, value);
} }
} }
@ -480,7 +480,7 @@ export class CaseMapMap {
} }
get(key) { get(key) {
var kv = this.map.get(this.caseMap(key)); let kv = this.map.get(this.caseMap(key));
if (kv) { if (kv) {
return kv.value; return kv.value;
} }
@ -496,21 +496,21 @@ export class CaseMapMap {
} }
entries() { entries() {
var it = this.map.values(); let it = this.map.values();
return mapIterator(it, (kv) => { return mapIterator(it, (kv) => {
return [kv.key, kv.value]; return [kv.key, kv.value];
}); });
} }
keys() { keys() {
var it = this.map.values(); let it = this.map.values();
return mapIterator(it, (kv) => { return mapIterator(it, (kv) => {
return kv.key; return kv.key;
}); });
} }
values() { values() {
var it = this.map.values(); let it = this.map.values();
return mapIterator(it, (kv) => { return mapIterator(it, (kv) => {
return kv.value; return kv.value;
}); });
@ -527,23 +527,23 @@ export function parseMembershipModes(str) {
throw new Error("malformed ISUPPORT PREFIX value: expected opening parenthesis"); throw new Error("malformed ISUPPORT PREFIX value: expected opening parenthesis");
} }
var sep = str.indexOf(")"); let sep = str.indexOf(")");
if (sep < 0) { if (sep < 0) {
throw new Error("malformed ISUPPORT PREFIX value: expected closing parenthesis"); throw new Error("malformed ISUPPORT PREFIX value: expected closing parenthesis");
} }
var n = str.length - sep - 1; let n = str.length - sep - 1;
var memberships = []; let memberships = [];
for (var i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
var mode = str[i + 1]; let mode = str[i + 1];
var prefix = str[sep + i + 1]; let prefix = str[sep + i + 1];
memberships.push({ mode, prefix }); memberships.push({ mode, prefix });
} }
return memberships; return memberships;
} }
export function findBatchByType(msg, type) { export function findBatchByType(msg, type) {
var batch = msg.batch; let batch = msg.batch;
while (batch) { while (batch) {
if (batch.type === type) { if (batch.type === type) {
return batch; return batch;
@ -558,7 +558,7 @@ export function getMessageLabel(msg) {
return msg.tags.label; return msg.tags.label;
} }
var batch = msg.batch; let batch = msg.batch;
while (batch) { while (batch) {
if (batch.tags.label) { if (batch.tags.label) {
return batch.tags.label; return batch.tags.label;

View File

@ -4,12 +4,12 @@ function linkifyChannel(text, transformChannel) {
// Don't match punctuation at the end of the channel name // Don't match punctuation at the end of the channel name
const channelRegex = /(?:^|\s)(#[^\s]+[^\s.?!():;,])/gid; const channelRegex = /(?:^|\s)(#[^\s]+[^\s.?!():;,])/gid;
var children = []; let children = [];
var match; let match;
var last = 0; let last = 0;
while ((match = channelRegex.exec(text)) !== null) { while ((match = channelRegex.exec(text)) !== null) {
var channel = match[1]; let channel = match[1];
var [start, end] = match.indices[1]; let [start, end] = match.indices[1];
children.push(text.substring(last, start)); children.push(text.substring(last, start));
children.push(transformChannel(channel)); children.push(transformChannel(channel));
@ -34,20 +34,20 @@ export default function linkify(text, onChannelClick) {
>${channel}</a>`; >${channel}</a>`;
} }
var links = anchorme.list(text); let links = anchorme.list(text);
var children = []; let children = [];
var last = 0; let last = 0;
links.forEach((match) => { links.forEach((match) => {
const prefix = text.substring(last, match.start) const prefix = text.substring(last, match.start)
children.push(...linkifyChannel(prefix, transformChannel)); children.push(...linkifyChannel(prefix, transformChannel));
var proto = match.protocol || "https://"; let proto = match.protocol || "https://";
if (match.isEmail) { if (match.isEmail) {
proto = "mailto:"; proto = "mailto:";
} }
var url = match.string; let url = match.string;
if (!url.startsWith(proto)) { if (!url.startsWith(proto)) {
url = proto + url; url = proto + url;
} }

111
state.js
View File

@ -55,7 +55,7 @@ export function getBufferURL(buf) {
} }
export function getMessageURL(buf, msg) { export function getMessageURL(buf, msg) {
var bufURL = getBufferURL(buf); let bufURL = getBufferURL(buf);
if (msg.tags.msgid) { if (msg.tags.msgid) {
return bufURL + "?msgid=" + encodeURIComponent(msg.tags.msgid); return bufURL + "?msgid=" + encodeURIComponent(msg.tags.msgid);
} else { } else {
@ -71,7 +71,7 @@ export function getServerName(server, bouncerNetwork, isBouncer) {
return "bouncer"; return "bouncer";
} }
var netName = server.isupport.get("NETWORK"); let netName = server.isupport.get("NETWORK");
if (netName) { if (netName) {
return netName; return netName;
} }
@ -80,7 +80,7 @@ export function getServerName(server, bouncerNetwork, isBouncer) {
} }
function updateState(state, updater) { function updateState(state, updater) {
var updated; let updated;
if (typeof updater === "function") { if (typeof updater === "function") {
updated = updater(state, state); updated = updater(state, state);
} else { } else {
@ -119,9 +119,9 @@ function insertMessage(list, msg) {
return list.concat(msg); return list.concat(msg);
} }
var insertBefore = -1; let insertBefore = -1;
for (var i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
var other = list[i]; let other = list[i];
if (msg.tags.time < other.tags.time) { if (msg.tags.time < other.tags.time) {
insertBefore = i; insertBefore = i;
break; break;
@ -134,42 +134,42 @@ function insertMessage(list, msg) {
return list; return list;
} }
var lastServerID = 0; let lastServerID = 0;
var lastBufferID = 0; let lastBufferID = 0;
export const State = { export const State = {
updateServer(state, id, updater) { updateServer(state, id, updater) {
var server = state.servers.get(id); let server = state.servers.get(id);
if (!server) { if (!server) {
return; return;
} }
var updated = updateState(server, updater); let updated = updateState(server, updater);
if (!updated) { if (!updated) {
return; return;
} }
var servers = new Map(state.servers); let servers = new Map(state.servers);
servers.set(id, updated); servers.set(id, updated);
return { servers }; return { servers };
}, },
updateBuffer(state, id, updater) { updateBuffer(state, id, updater) {
var buf = State.getBuffer(state, id); let buf = State.getBuffer(state, id);
if (!buf) { if (!buf) {
return; return;
} }
var updated = updateState(buf, updater); let updated = updateState(buf, updater);
if (!updated) { if (!updated) {
return; return;
} }
var buffers = new Map(state.buffers); let buffers = new Map(state.buffers);
buffers.set(buf.id, updated); buffers.set(buf.id, updated);
return { buffers }; return { buffers };
}, },
getActiveServerID(state) { getActiveServerID(state) {
var buf = state.buffers.get(state.activeBuffer); let buf = state.buffers.get(state.activeBuffer);
if (!buf) { if (!buf) {
return null; return null;
} }
@ -184,7 +184,7 @@ export const State = {
return state.buffers.get(id.id); return state.buffers.get(id.id);
} }
var serverID = id.server, name = id.name; let serverID = id.server, name = id.name;
if (!serverID) { if (!serverID) {
serverID = State.getActiveServerID(state); serverID = State.getActiveServerID(state);
} }
@ -192,14 +192,14 @@ export const State = {
name = SERVER_BUFFER; name = SERVER_BUFFER;
} }
var cm = irc.CaseMapping.RFC1459; let cm = irc.CaseMapping.RFC1459;
var server = state.servers.get(serverID); let server = state.servers.get(serverID);
if (server) { if (server) {
cm = irc.CaseMapping.byName(server.isupport.get("CASEMAPPING")) || cm; cm = irc.CaseMapping.byName(server.isupport.get("CASEMAPPING")) || cm;
} }
var nameCM = cm(name); let nameCM = cm(name);
for (var buf of state.buffers.values()) { for (let buf of state.buffers.values()) {
if (buf.server === serverID && cm(buf.name) === nameCM) { if (buf.server === serverID && cm(buf.name) === nameCM) {
return buf; return buf;
} }
@ -211,9 +211,9 @@ export const State = {
}, },
createServer(state) { createServer(state) {
lastServerID++; lastServerID++;
var id = lastServerID; let id = lastServerID;
var servers = new Map(state.servers); let servers = new Map(state.servers);
servers.set(id, { servers.set(id, {
id, id,
status: ServerStatus.DISCONNECTED, status: ServerStatus.DISCONNECTED,
@ -222,15 +222,15 @@ export const State = {
return [id, { servers }]; return [id, { servers }];
}, },
createBuffer(state, name, serverID, client) { createBuffer(state, name, serverID, client) {
var buf = State.getBuffer(state, { server: serverID, name }); let buf = State.getBuffer(state, { server: serverID, name });
if (buf) { if (buf) {
return [buf.id, null]; return [buf.id, null];
} }
lastBufferID++; lastBufferID++;
var id = lastBufferID; let id = lastBufferID;
var type; let type;
if (name == SERVER_BUFFER) { if (name == SERVER_BUFFER) {
type = BufferType.SERVER; type = BufferType.SERVER;
} else if (client.isChannel(name)) { } else if (client.isChannel(name)) {
@ -239,7 +239,7 @@ export const State = {
type = BufferType.NICK; type = BufferType.NICK;
} }
var bufferList = Array.from(state.buffers.values()); let bufferList = Array.from(state.buffers.values());
bufferList.push({ bufferList.push({
id, id,
name, name,
@ -254,7 +254,7 @@ export const State = {
unread: Unread.NONE, unread: Unread.NONE,
}); });
bufferList = bufferList.sort(compareBuffers); bufferList = bufferList.sort(compareBuffers);
var buffers = new Map(bufferList.map((buf) => [buf.id, buf])); let buffers = new Map(bufferList.map((buf) => [buf.id, buf]));
return [id, { buffers }]; return [id, { buffers }];
}, },
handleMessage(state, msg, serverID, client) { handleMessage(state, msg, serverID, client) {
@ -270,21 +270,22 @@ export const State = {
return; return;
} }
let channel, topic;
switch (msg.command) { switch (msg.command) {
case irc.RPL_MYINFO: case irc.RPL_MYINFO:
// TODO: parse available modes // TODO: parse available modes
var serverInfo = { let serverInfo = {
name: msg.params[1], name: msg.params[1],
version: msg.params[2], version: msg.params[2],
}; };
return updateBuffer(SERVER_BUFFER, { serverInfo }); return updateBuffer(SERVER_BUFFER, { serverInfo });
case irc.RPL_ISUPPORT: case irc.RPL_ISUPPORT:
var buffers = new Map(state.buffers); let buffers = new Map(state.buffers);
state.buffers.forEach((buf) => { state.buffers.forEach((buf) => {
if (buf.server != serverID) { if (buf.server != serverID) {
return; return;
} }
var members = new irc.CaseMapMap(buf.members, client.cm); let members = new irc.CaseMapMap(buf.members, client.cm);
buffers.set(buf.id, { ...buf, members }); buffers.set(buf.id, { ...buf, members });
}); });
return { return {
@ -292,23 +293,23 @@ export const State = {
...updateServer({ isupport: new Map(client.isupport) }), ...updateServer({ isupport: new Map(client.isupport) }),
}; };
case irc.RPL_NOTOPIC: case irc.RPL_NOTOPIC:
var channel = msg.params[1]; channel = msg.params[1];
return updateBuffer(channel, { topic: null }); return updateBuffer(channel, { topic: null });
case irc.RPL_TOPIC: case irc.RPL_TOPIC:
var channel = msg.params[1]; channel = msg.params[1];
var topic = msg.params[2]; topic = msg.params[2];
return updateBuffer(channel, { topic }); return updateBuffer(channel, { topic });
case irc.RPL_TOPICWHOTIME: case irc.RPL_TOPICWHOTIME:
// Ignore // Ignore
break; break;
case irc.RPL_NAMREPLY: case irc.RPL_NAMREPLY:
var channel = msg.params[2]; channel = msg.params[2];
var membersList = msg.params[3].split(" "); let membersList = msg.params[3].split(" ");
return updateBuffer(channel, (buf) => { return updateBuffer(channel, (buf) => {
var members = new irc.CaseMapMap(buf.members); let members = new irc.CaseMapMap(buf.members);
membersList.forEach((s) => { membersList.forEach((s) => {
var member = irc.parseTargetPrefix(s); let member = irc.parseTargetPrefix(s);
members.set(member.name, member.prefix); members.set(member.name, member.prefix);
}); });
return { members }; return { members };
@ -316,8 +317,8 @@ export const State = {
case irc.RPL_ENDOFNAMES: case irc.RPL_ENDOFNAMES:
break; break;
case irc.RPL_WHOREPLY: case irc.RPL_WHOREPLY:
var last = msg.params[msg.params.length - 1]; let last = msg.params[msg.params.length - 1];
var who = { let who = {
username: msg.params[2], username: msg.params[2],
hostname: msg.params[3], hostname: msg.params[3],
server: msg.params[4], server: msg.params[4],
@ -327,7 +328,7 @@ export const State = {
}; };
return updateBuffer(who.nick, { who, offline: false }); return updateBuffer(who.nick, { who, offline: false });
case irc.RPL_ENDOFWHO: case irc.RPL_ENDOFWHO:
var target = msg.params[1]; let target = msg.params[1];
if (!client.isChannel(target) && target.indexOf("*") < 0) { if (!client.isChannel(target) && target.indexOf("*") < 0) {
// Not a channel nor a mask, likely a nick // Not a channel nor a mask, likely a nick
return updateBuffer(target, (buf) => { return updateBuffer(target, (buf) => {
@ -341,57 +342,57 @@ export const State = {
} }
break; break;
case "JOIN": case "JOIN":
var channel = msg.params[0]; channel = msg.params[0];
if (client.isMyNick(msg.prefix.name)) { if (client.isMyNick(msg.prefix.name)) {
var [id, update] = State.createBuffer(state, channel, serverID, client); let [id, update] = State.createBuffer(state, channel, serverID, client);
state = { ...state, ...update }; state = { ...state, ...update };
} }
var update = updateBuffer(channel, (buf) => { let update = updateBuffer(channel, (buf) => {
var members = new irc.CaseMapMap(buf.members); let members = new irc.CaseMapMap(buf.members);
members.set(msg.prefix.name, ""); members.set(msg.prefix.name, "");
return { members }; return { members };
}); });
return { ...state, ...update }; return { ...state, ...update };
case "PART": case "PART":
var channel = msg.params[0]; channel = msg.params[0];
return updateBuffer(channel, (buf) => { return updateBuffer(channel, (buf) => {
var members = new irc.CaseMapMap(buf.members); let members = new irc.CaseMapMap(buf.members);
members.delete(msg.prefix.name); members.delete(msg.prefix.name);
return { members }; return { members };
}); });
case "KICK": case "KICK":
var channel = msg.params[0]; channel = msg.params[0];
var nick = msg.params[1]; let nick = msg.params[1];
return updateBuffer(channel, (buf) => { return updateBuffer(channel, (buf) => {
var members = new irc.CaseMapMap(buf.members); let members = new irc.CaseMapMap(buf.members);
members.delete(nick); members.delete(nick);
return { members }; return { members };
}); });
case "SETNAME": case "SETNAME":
return updateBuffer(msg.prefix.name, (buf) => { return updateBuffer(msg.prefix.name, (buf) => {
var who = { ...buf.who, realname: msg.params[0] }; let who = { ...buf.who, realname: msg.params[0] };
return { who }; return { who };
}); });
case "AWAY": case "AWAY":
var awayMessage = msg.params[0]; let awayMessage = msg.params[0];
return updateBuffer(msg.prefix.name, (buf) => { return updateBuffer(msg.prefix.name, (buf) => {
var who = { ...buf.who, away: !!awayMessage }; let who = { ...buf.who, away: !!awayMessage };
return { who }; return { who };
}); });
case "TOPIC": case "TOPIC":
var channel = msg.params[0]; channel = msg.params[0];
var topic = msg.params[1]; topic = msg.params[1];
return updateBuffer(channel, { topic }); return updateBuffer(channel, { topic });
} }
}, },
addMessage(state, msg, bufID) { addMessage(state, msg, bufID) {
return State.updateBuffer(state, bufID, (buf) => { return State.updateBuffer(state, bufID, (buf) => {
var messages = insertMessage(buf.messages, msg); let messages = insertMessage(buf.messages, msg);
return { messages }; return { messages };
}); });
}, },

View File

@ -15,7 +15,7 @@ class Item {
} }
load() { load() {
var v = localStorage.getItem(this.k); let v = localStorage.getItem(this.k);
if (!v) { if (!v) {
return null; return null;
} }
@ -37,7 +37,7 @@ const rawReceipts = new Item("receipts");
export const receipts = { export const receipts = {
load() { load() {
var v = rawReceipts.load(); let v = rawReceipts.load();
return new Map(Object.entries(v || {})); return new Map(Object.entries(v || {}));
}, },
put(m) { put(m) {