2021-03-02 16:46:48 -05:00
|
|
|
|
import * as irc from "../lib/irc.js";
|
|
|
|
|
import Client from "../lib/client.js";
|
|
|
|
|
import Buffer from "./buffer.js";
|
|
|
|
|
import BufferList from "./buffer-list.js";
|
|
|
|
|
import BufferHeader from "./buffer-header.js";
|
|
|
|
|
import MemberList from "./member-list.js";
|
2021-03-09 07:25:31 -05:00
|
|
|
|
import ConnectForm from "./connect-form.js";
|
|
|
|
|
import JoinForm from "./join-form.js";
|
2021-03-08 11:05:48 -05:00
|
|
|
|
import Help from "./help.js";
|
2021-03-08 12:15:04 -05:00
|
|
|
|
import NetworkForm from "./network-form.js";
|
2021-11-21 10:40:46 -05:00
|
|
|
|
import AuthForm from "./auth-form.js";
|
2021-11-16 07:32:54 -05:00
|
|
|
|
import RegisterForm from "./register-form.js";
|
|
|
|
|
import VerifyForm from "./verify-form.js";
|
2021-03-02 16:46:48 -05:00
|
|
|
|
import Composer from "./composer.js";
|
|
|
|
|
import ScrollManager from "./scroll-manager.js";
|
2021-03-08 10:23:16 -05:00
|
|
|
|
import Dialog from "./dialog.js";
|
2021-03-02 16:46:48 -05:00
|
|
|
|
import { html, Component, createRef } from "../lib/index.js";
|
|
|
|
|
import { strip as stripANSI } from "../lib/ansi.js";
|
2022-02-11 10:37:58 -05:00
|
|
|
|
import { SERVER_BUFFER, BufferType, ReceiptType, ServerStatus, Unread, State, getServerName, isMessageBeforeReceipt } from "../state.js";
|
2021-03-02 16:46:48 -05:00
|
|
|
|
import commands from "../commands.js";
|
|
|
|
|
import { setup as setupKeybindings } from "../keybindings.js";
|
2021-05-26 12:43:11 -04:00
|
|
|
|
import * as store from "../store.js";
|
2020-06-26 04:35:38 -04:00
|
|
|
|
|
2021-06-10 12:34:34 -04:00
|
|
|
|
const baseConfig = {
|
|
|
|
|
server: {},
|
|
|
|
|
};
|
|
|
|
|
|
2021-05-26 17:17:23 -04:00
|
|
|
|
const configPromise = fetch("./config.json")
|
2021-05-25 06:33:22 -04:00
|
|
|
|
.then((resp) => {
|
|
|
|
|
if (resp.ok) {
|
|
|
|
|
return resp.json();
|
|
|
|
|
}
|
|
|
|
|
if (resp.status !== 404) {
|
|
|
|
|
console.error("Failed to fetch config: HTTP error:", resp.status, resp.statusText);
|
|
|
|
|
}
|
|
|
|
|
return {};
|
|
|
|
|
})
|
|
|
|
|
.catch((err) => {
|
|
|
|
|
console.error("Failed to fetch config:", err);
|
|
|
|
|
return {};
|
2021-06-10 12:34:34 -04:00
|
|
|
|
})
|
|
|
|
|
.then((config) => {
|
|
|
|
|
return {
|
|
|
|
|
...baseConfig,
|
|
|
|
|
...config,
|
|
|
|
|
};
|
2021-05-25 06:33:22 -04:00
|
|
|
|
});
|
|
|
|
|
|
2020-07-15 12:21:09 -04:00
|
|
|
|
const CHATHISTORY_MAX_SIZE = 4000;
|
|
|
|
|
|
2021-12-01 05:40:59 -05:00
|
|
|
|
function isProduction() {
|
|
|
|
|
// NODE_ENV is set by the Parcel build system
|
|
|
|
|
try {
|
|
|
|
|
return process.env.NODE_ENV === "production";
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-18 08:23:08 -04:00
|
|
|
|
function parseQueryString() {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let query = window.location.search.substring(1);
|
|
|
|
|
let params = {};
|
2020-06-18 08:23:08 -04:00
|
|
|
|
query.split('&').forEach((s) => {
|
|
|
|
|
if (!s) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let pair = s.split('=');
|
2020-06-18 08:23:08 -04:00
|
|
|
|
params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
|
|
|
|
|
});
|
|
|
|
|
return params;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-16 05:52:38 -05:00
|
|
|
|
function splitHostPort(str) {
|
|
|
|
|
let host = str;
|
|
|
|
|
let port = null;
|
|
|
|
|
|
|
|
|
|
// Literal IPv6 addresses contain colons and are enclosed in square brackets
|
|
|
|
|
let i = str.lastIndexOf(":");
|
|
|
|
|
if (i > 0 && !str.endsWith("]")) {
|
|
|
|
|
host = str.slice(0, i);
|
|
|
|
|
port = parseInt(str.slice(i + 1), 10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (host.startsWith("[") && host.endsWith("]")) {
|
|
|
|
|
host = host.slice(1, host.length - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { host, port };
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-31 11:57:17 -04:00
|
|
|
|
function fillConnectParams(params) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let host = window.location.host || "localhost:8080";
|
|
|
|
|
let proto = "wss:";
|
2021-05-31 12:11:33 -04:00
|
|
|
|
if (window.location.protocol != "https:") {
|
|
|
|
|
proto = "ws:";
|
|
|
|
|
}
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let path = window.location.pathname || "/";
|
2021-05-31 12:11:33 -04:00
|
|
|
|
if (!window.location.host) {
|
|
|
|
|
path = "/";
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-31 11:57:17 -04:00
|
|
|
|
params = { ...params };
|
2021-05-31 12:11:33 -04:00
|
|
|
|
if (!params.url) {
|
|
|
|
|
params.url = proto + "//" + host + path + "socket";
|
|
|
|
|
}
|
|
|
|
|
if (params.url.startsWith("/")) {
|
|
|
|
|
params.url = proto + "//" + host + params.url;
|
|
|
|
|
}
|
2021-06-20 14:51:49 -04:00
|
|
|
|
if (params.url.indexOf("://") < 0) {
|
|
|
|
|
params.url = proto + "//" + params.url;
|
|
|
|
|
}
|
2021-05-31 11:57:17 -04:00
|
|
|
|
if (!params.username) {
|
|
|
|
|
params.username = params.nick;
|
|
|
|
|
}
|
|
|
|
|
if (!params.realname) {
|
|
|
|
|
params.realname = params.nick;
|
|
|
|
|
}
|
|
|
|
|
return params;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 11:01:26 -04:00
|
|
|
|
function showNotification(title, options) {
|
|
|
|
|
if (!window.Notification || Notification.permission !== "granted") {
|
2022-02-11 13:16:11 -05:00
|
|
|
|
return null;
|
2021-06-10 11:01:26 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This can still fail due to:
|
|
|
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=481856
|
|
|
|
|
try {
|
|
|
|
|
return new Notification(title, options);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to show notification: ", err);
|
2022-02-11 13:16:11 -05:00
|
|
|
|
return null;
|
2021-06-10 11:01:26 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 10:30:46 -05:00
|
|
|
|
function receiptFromMessage(msg) {
|
|
|
|
|
// At this point all messages are supposed to have a time tag.
|
|
|
|
|
// App.addMessage ensures this is the case even if the server doesn't
|
|
|
|
|
// support server-time.
|
|
|
|
|
if (!msg.tags.time) {
|
|
|
|
|
throw new Error("Missing time message tag");
|
|
|
|
|
}
|
|
|
|
|
return { time: msg.tags.time };
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-17 04:12:36 -05:00
|
|
|
|
let lastErrorID = 0;
|
|
|
|
|
|
2022-02-11 14:59:31 -05:00
|
|
|
|
function getReceipt(stored, type) {
|
|
|
|
|
if (!stored || !stored.receipts) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return stored.receipts[ReceiptType.READ];
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 10:20:42 -05:00
|
|
|
|
function getLatestReceipt(bufferStore, server, type) {
|
|
|
|
|
let buffers = bufferStore.list(server);
|
|
|
|
|
let last = null;
|
|
|
|
|
for (let buf of buffers) {
|
|
|
|
|
if (!buf.receipts || buf.name === "*") {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
let receipt = buf.receipts[type];
|
|
|
|
|
if (!receipt) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!last || receipt.time > last.time) {
|
|
|
|
|
last = receipt;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return last;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-18 08:23:08 -04:00
|
|
|
|
export default class App extends Component {
|
|
|
|
|
state = {
|
2021-09-21 06:33:22 -04:00
|
|
|
|
...State.create(),
|
2020-06-18 08:23:08 -04:00
|
|
|
|
connectParams: {
|
2021-05-31 12:04:02 -04:00
|
|
|
|
url: null,
|
|
|
|
|
pass: null,
|
2020-06-18 08:23:08 -04:00
|
|
|
|
username: null,
|
|
|
|
|
realname: null,
|
|
|
|
|
nick: null,
|
|
|
|
|
saslPlain: null,
|
2021-10-12 11:29:56 -04:00
|
|
|
|
saslExternal: false,
|
2020-06-24 12:14:46 -04:00
|
|
|
|
autoconnect: false,
|
2020-06-18 08:23:08 -04:00
|
|
|
|
autojoin: [],
|
2022-02-04 09:48:29 -05:00
|
|
|
|
ping: 0,
|
2020-06-18 08:23:08 -04:00
|
|
|
|
},
|
2021-06-06 05:33:00 -04:00
|
|
|
|
connectForm: true,
|
2021-07-04 15:29:15 -04:00
|
|
|
|
loading: true,
|
2021-03-08 10:23:16 -05:00
|
|
|
|
dialog: null,
|
2021-07-04 15:41:36 -04:00
|
|
|
|
dialogData: null,
|
2020-08-08 00:08:51 -04:00
|
|
|
|
error: null,
|
2021-05-27 10:35:33 -04:00
|
|
|
|
openPanels: {
|
|
|
|
|
bufferList: false,
|
|
|
|
|
memberList: false,
|
|
|
|
|
},
|
2020-06-18 08:23:08 -04:00
|
|
|
|
};
|
2021-12-01 05:40:59 -05:00
|
|
|
|
debug = !isProduction();
|
2021-06-10 12:34:34 -04:00
|
|
|
|
config = { ...baseConfig };
|
2021-01-22 11:36:53 -05:00
|
|
|
|
clients = new Map();
|
2020-06-29 03:06:47 -04:00
|
|
|
|
endOfHistory = new Map();
|
2020-07-15 12:21:09 -04:00
|
|
|
|
receipts = new Map();
|
2020-06-25 06:03:05 -04:00
|
|
|
|
buffer = createRef();
|
2020-06-18 08:23:08 -04:00
|
|
|
|
composer = createRef();
|
2021-05-25 06:40:33 -04:00
|
|
|
|
switchToChannel = null;
|
2021-11-08 06:33:02 -05:00
|
|
|
|
/**
|
|
|
|
|
* Parsed irc:// URL to automatically open. The user will be prompted for
|
|
|
|
|
* confirmation for security reasons.
|
|
|
|
|
*/
|
|
|
|
|
autoOpenURL = null;
|
2022-02-11 13:27:45 -05:00
|
|
|
|
messageNotifications = new Set();
|
2020-06-18 08:23:08 -04:00
|
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
|
|
this.handleConnectSubmit = this.handleConnectSubmit.bind(this);
|
2021-03-08 10:23:16 -05:00
|
|
|
|
this.handleJoinSubmit = this.handleJoinSubmit.bind(this);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
this.handleBufferListClick = this.handleBufferListClick.bind(this);
|
2021-10-17 13:33:02 -04:00
|
|
|
|
this.handleBufferListClose = this.handleBufferListClose.bind(this);
|
2021-05-27 10:35:33 -04:00
|
|
|
|
this.toggleBufferList = this.toggleBufferList.bind(this);
|
|
|
|
|
this.toggleMemberList = this.toggleMemberList.bind(this);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
this.handleComposerSubmit = this.handleComposerSubmit.bind(this);
|
2021-05-31 22:39:35 -04:00
|
|
|
|
this.handleChannelClick = this.handleChannelClick.bind(this);
|
2020-06-25 12:45:41 -04:00
|
|
|
|
this.handleNickClick = this.handleNickClick.bind(this);
|
2020-06-29 06:36:17 -04:00
|
|
|
|
this.autocomplete = this.autocomplete.bind(this);
|
2020-06-29 03:06:47 -04:00
|
|
|
|
this.handleBufferScrollTop = this.handleBufferScrollTop.bind(this);
|
2021-07-04 15:41:36 -04:00
|
|
|
|
this.dismissDialog = this.dismissDialog.bind(this);
|
2021-03-08 12:15:04 -05:00
|
|
|
|
this.handleAddNetworkClick = this.handleAddNetworkClick.bind(this);
|
2021-03-09 13:10:22 -05:00
|
|
|
|
this.handleNetworkSubmit = this.handleNetworkSubmit.bind(this);
|
|
|
|
|
this.handleNetworkRemove = this.handleNetworkRemove.bind(this);
|
2021-11-17 04:12:36 -05:00
|
|
|
|
this.handleDismissError = this.handleDismissError.bind(this);
|
2021-11-21 10:40:46 -05:00
|
|
|
|
this.handleAuthSubmit = this.handleAuthSubmit.bind(this);
|
2021-11-16 07:32:54 -05:00
|
|
|
|
this.handleRegisterSubmit = this.handleRegisterSubmit.bind(this);
|
2021-11-30 09:13:34 -05:00
|
|
|
|
this.handleVerifyClick = this.handleVerifyClick.bind(this);
|
2021-11-16 07:32:54 -05:00
|
|
|
|
this.handleVerifySubmit = this.handleVerifySubmit.bind(this);
|
2020-06-24 12:14:46 -04:00
|
|
|
|
|
2021-08-23 06:02:36 -04:00
|
|
|
|
this.bufferStore = new store.Buffer();
|
2020-07-01 06:25:57 -04:00
|
|
|
|
|
2021-05-25 06:33:22 -04:00
|
|
|
|
configPromise.then((config) => {
|
|
|
|
|
this.handleConfig(config);
|
|
|
|
|
return config;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-27 04:36:03 -04:00
|
|
|
|
/**
|
|
|
|
|
* Handle configuration data and populate the connection parameters.
|
|
|
|
|
*
|
|
|
|
|
* The priority order is:
|
|
|
|
|
*
|
|
|
|
|
* - URL params
|
|
|
|
|
* - Saved parameters in local storage
|
|
|
|
|
* - Configuration data (fetched from the config.json file)
|
2021-10-09 04:10:51 -04:00
|
|
|
|
* - Default server URL constructed from the current URL location (this is
|
|
|
|
|
* done in fillConnectParams)
|
2021-05-27 04:36:03 -04:00
|
|
|
|
*/
|
2021-05-25 06:33:22 -04:00
|
|
|
|
handleConfig(config) {
|
2021-07-04 15:29:15 -04:00
|
|
|
|
this.setState({ loading: false });
|
|
|
|
|
|
2021-10-09 04:10:51 -04:00
|
|
|
|
let connectParams = { ...this.state.connectParams };
|
2021-05-25 06:33:22 -04:00
|
|
|
|
|
|
|
|
|
if (config.server) {
|
2021-10-09 03:57:54 -04:00
|
|
|
|
if (typeof config.server.url === "string") {
|
|
|
|
|
connectParams.url = config.server.url;
|
|
|
|
|
}
|
2021-05-25 06:33:22 -04:00
|
|
|
|
if (Array.isArray(config.server.autojoin)) {
|
|
|
|
|
connectParams.autojoin = config.server.autojoin;
|
2021-10-09 03:57:54 -04:00
|
|
|
|
} else if (typeof config.server.autojoin === "string") {
|
2021-05-25 06:33:22 -04:00
|
|
|
|
connectParams.autojoin = [config.server.autojoin];
|
2020-06-24 12:14:46 -04:00
|
|
|
|
}
|
2021-10-09 04:17:52 -04:00
|
|
|
|
if (typeof config.server.nick === "string") {
|
|
|
|
|
connectParams.nick = config.server.nick;
|
|
|
|
|
}
|
2021-10-09 04:14:27 -04:00
|
|
|
|
if (typeof config.server.autoconnect === "boolean") {
|
|
|
|
|
connectParams.autoconnect = config.server.autoconnect;
|
|
|
|
|
}
|
2021-10-12 11:29:56 -04:00
|
|
|
|
if (config.server.auth === "external") {
|
|
|
|
|
connectParams.saslExternal = true;
|
|
|
|
|
}
|
2022-02-04 09:48:29 -05:00
|
|
|
|
if (typeof config.server.ping === "number") {
|
|
|
|
|
connectParams.ping = config.server.ping;
|
|
|
|
|
}
|
2021-05-25 06:33:22 -04:00
|
|
|
|
}
|
2020-06-24 12:14:46 -04:00
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let autoconnect = store.autoconnect.load();
|
2021-05-27 04:36:03 -04:00
|
|
|
|
if (autoconnect) {
|
|
|
|
|
connectParams = {
|
|
|
|
|
...connectParams,
|
|
|
|
|
...autoconnect,
|
|
|
|
|
autoconnect: true,
|
2021-09-21 13:25:12 -04:00
|
|
|
|
autojoin: [], // handled by store.Buffer
|
2021-05-27 04:36:03 -04:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-08 07:01:54 -05:00
|
|
|
|
let autojoin = [];
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let queryParams = parseQueryString();
|
2021-10-09 07:33:01 -04:00
|
|
|
|
// Don't allow to silently override the server URL if there's one in
|
|
|
|
|
// config.json, because this has security implications. But still allow
|
|
|
|
|
// setting server to an empty string to reveal the server field in the
|
|
|
|
|
// connect form.
|
|
|
|
|
if (typeof queryParams.server === "string" && (!connectParams.url || !queryParams.server)) {
|
2021-05-31 12:11:33 -04:00
|
|
|
|
connectParams.url = queryParams.server;
|
2021-06-10 12:28:01 -04:00
|
|
|
|
|
|
|
|
|
// When using a custom server, some configuration options don't
|
|
|
|
|
// make sense anymore.
|
2021-06-10 12:34:34 -04:00
|
|
|
|
config.server.auth = null;
|
2020-06-24 12:14:46 -04:00
|
|
|
|
}
|
2021-06-22 04:44:20 -04:00
|
|
|
|
if (typeof queryParams.nick === "string") {
|
2021-05-27 06:32:22 -04:00
|
|
|
|
connectParams.nick = queryParams.nick;
|
|
|
|
|
}
|
2021-10-09 04:34:51 -04:00
|
|
|
|
if (typeof queryParams.channels === "string") {
|
2021-11-08 07:01:54 -05:00
|
|
|
|
autojoin = queryParams.channels.split(",");
|
2020-07-15 12:21:09 -04:00
|
|
|
|
}
|
2021-11-08 06:33:02 -05:00
|
|
|
|
if (typeof queryParams.open === "string") {
|
|
|
|
|
this.autoOpenURL = irc.parseURL(queryParams.open);
|
|
|
|
|
}
|
2021-12-01 05:40:59 -05:00
|
|
|
|
if (queryParams.debug === "1") {
|
|
|
|
|
this.debug = true;
|
|
|
|
|
}
|
2021-11-08 06:33:02 -05:00
|
|
|
|
|
2021-05-26 16:57:21 -04:00
|
|
|
|
if (window.location.hash) {
|
2021-11-08 07:01:54 -05:00
|
|
|
|
autojoin = window.location.hash.split(",");
|
2021-05-26 16:57:21 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:28:01 -04:00
|
|
|
|
this.config = config;
|
|
|
|
|
|
2021-11-08 07:01:54 -05:00
|
|
|
|
if (autojoin.length > 0) {
|
|
|
|
|
if (connectParams.autoconnect) {
|
|
|
|
|
// Ask the user whether they want to join that new channel.
|
|
|
|
|
// TODO: support multiple channels here
|
|
|
|
|
this.autoOpenURL = { host: "", entity: autojoin[0] };
|
|
|
|
|
} else {
|
|
|
|
|
connectParams.autojoin = autojoin;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-09 04:10:51 -04:00
|
|
|
|
this.setState({ connectParams: connectParams });
|
2021-05-27 04:36:03 -04:00
|
|
|
|
|
|
|
|
|
if (connectParams.autoconnect) {
|
2021-06-06 05:33:00 -04:00
|
|
|
|
this.setState({ connectForm: false });
|
2021-05-27 04:36:03 -04:00
|
|
|
|
this.connect(connectParams);
|
|
|
|
|
}
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-06 17:09:30 -05:00
|
|
|
|
showError(err) {
|
|
|
|
|
console.error("App error: ", err);
|
|
|
|
|
|
|
|
|
|
let text;
|
|
|
|
|
if (err instanceof Error) {
|
|
|
|
|
let l = [];
|
|
|
|
|
while (err) {
|
|
|
|
|
l.push(err.message);
|
|
|
|
|
err = err.cause;
|
|
|
|
|
}
|
|
|
|
|
text = l.join(": ");
|
|
|
|
|
} else {
|
|
|
|
|
text = String(err);
|
|
|
|
|
}
|
|
|
|
|
this.setState({ error: text });
|
2021-11-17 04:12:36 -05:00
|
|
|
|
lastErrorID++;
|
|
|
|
|
return lastErrorID;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dismissError(id) {
|
|
|
|
|
if (id && id !== lastErrorID) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-08-08 00:08:51 -04:00
|
|
|
|
this.setState({ error: null });
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-17 04:12:36 -05:00
|
|
|
|
handleDismissError(event) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
this.dismissError();
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-03 05:46:50 -04:00
|
|
|
|
setServerState(id, updater, callback) {
|
2021-01-21 13:01:50 -05:00
|
|
|
|
this.setState((state) => {
|
2021-06-04 12:03:03 -04:00
|
|
|
|
return State.updateServer(state, id, updater);
|
2021-01-21 13:01:50 -05:00
|
|
|
|
}, callback);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-21 14:41:44 -05:00
|
|
|
|
setBufferState(id, updater, callback) {
|
2020-06-18 08:23:08 -04:00
|
|
|
|
this.setState((state) => {
|
2021-06-04 12:03:03 -04:00
|
|
|
|
return State.updateBuffer(state, id, updater);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}, callback);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-23 08:17:58 -04:00
|
|
|
|
syncBufferUnread(serverID, name) {
|
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
|
|
|
|
|
let stored = this.bufferStore.get({ name, server: client.params });
|
2021-12-10 09:34:51 -05:00
|
|
|
|
if (client.caps.enabled.has("draft/chathistory") && stored) {
|
2021-08-23 08:17:58 -04:00
|
|
|
|
this.setBufferState({ server: serverID, name }, { unread: stored.unread });
|
|
|
|
|
}
|
|
|
|
|
if (!stored) {
|
|
|
|
|
this.bufferStore.put({
|
|
|
|
|
name,
|
|
|
|
|
server: client.params,
|
|
|
|
|
unread: Unread.NONE,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 04:56:18 -04:00
|
|
|
|
createBuffer(serverID, name) {
|
2021-08-23 06:02:36 -04:00
|
|
|
|
let client = this.clients.get(serverID);
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let id = null;
|
2021-09-21 12:41:14 -04:00
|
|
|
|
let isNew = false;
|
2020-06-18 08:23:08 -04:00
|
|
|
|
this.setState((state) => {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let updated;
|
2021-06-04 12:37:34 -04:00
|
|
|
|
[id, updated] = State.createBuffer(state, name, serverID, client);
|
2021-09-21 12:41:14 -04:00
|
|
|
|
isNew = !!updated;
|
2021-06-04 12:37:34 -04:00
|
|
|
|
return updated;
|
2020-06-18 08:23:08 -04:00
|
|
|
|
});
|
2021-09-21 12:41:14 -04:00
|
|
|
|
if (isNew) {
|
|
|
|
|
this.syncBufferUnread(serverID, name);
|
|
|
|
|
}
|
2021-06-10 04:56:18 -04:00
|
|
|
|
return id;
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 12:21:17 -05:00
|
|
|
|
sendReadReceipt(client, storedBuffer) {
|
|
|
|
|
if (!client.caps.enabled.has("soju.im/read")) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let readReceipt = storedBuffer.receipts[ReceiptType.READ];
|
|
|
|
|
if (storedBuffer.name === "*" || !readReceipt) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
client.send({
|
|
|
|
|
command: "READ",
|
|
|
|
|
params: [storedBuffer.name, "timestamp="+readReceipt.time],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-21 16:15:33 -05:00
|
|
|
|
switchBuffer(id) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let buf;
|
2021-01-21 14:41:44 -05:00
|
|
|
|
this.setState((state) => {
|
2021-06-04 12:03:03 -04:00
|
|
|
|
buf = State.getBuffer(state, id);
|
2021-01-21 14:41:44 -05:00
|
|
|
|
if (!buf) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-01-21 16:15:33 -05:00
|
|
|
|
|
2022-02-11 10:20:42 -05:00
|
|
|
|
let client = this.clients.get(buf.server);
|
|
|
|
|
let stored = this.bufferStore.get({ name: buf.name, server: client.params });
|
2022-02-11 14:59:31 -05:00
|
|
|
|
let prevReadReceipt = getReceipt(stored, ReceiptType.READ);
|
2021-01-21 16:15:33 -05:00
|
|
|
|
// TODO: only mark as read if user scrolled at the bottom
|
2022-02-11 09:48:56 -05:00
|
|
|
|
let update = State.updateBuffer(state, buf.id, {
|
2021-01-21 16:15:33 -05:00
|
|
|
|
unread: Unread.NONE,
|
2021-08-24 08:01:57 -04:00
|
|
|
|
prevReadReceipt,
|
2021-01-21 16:15:33 -05:00
|
|
|
|
});
|
|
|
|
|
|
2022-02-11 09:48:56 -05:00
|
|
|
|
return { ...update, activeBuffer: buf.id };
|
|
|
|
|
}, () => {
|
|
|
|
|
if (!buf) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 08:44:20 -04:00
|
|
|
|
if (this.buffer.current) {
|
|
|
|
|
this.buffer.current.focus();
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
2020-07-15 12:21:09 -04:00
|
|
|
|
|
2022-02-11 13:27:45 -05:00
|
|
|
|
let client = this.clients.get(buf.server);
|
|
|
|
|
|
|
|
|
|
for (let notif of this.messageNotifications) {
|
|
|
|
|
if (client.cm(notif.data.bufferName) === client.cm(buf.name)) {
|
|
|
|
|
notif.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-08 09:03:05 -05:00
|
|
|
|
if (buf.messages.length > 0) {
|
2022-02-11 10:20:42 -05:00
|
|
|
|
let lastMsg = buf.messages[buf.messages.length - 1];
|
2022-02-11 12:21:17 -05:00
|
|
|
|
let stored = {
|
2021-11-08 09:03:05 -05:00
|
|
|
|
name: buf.name,
|
|
|
|
|
server: client.params,
|
|
|
|
|
unread: Unread.NONE,
|
2022-02-11 10:20:42 -05:00
|
|
|
|
receipts: { [ReceiptType.READ]: receiptFromMessage(lastMsg) },
|
2022-02-11 12:21:17 -05:00
|
|
|
|
};
|
|
|
|
|
if (this.bufferStore.put(stored)) {
|
|
|
|
|
this.sendReadReceipt(client, stored);
|
|
|
|
|
}
|
2020-07-15 12:21:09 -04:00
|
|
|
|
}
|
2021-09-21 11:06:32 -04:00
|
|
|
|
|
|
|
|
|
let server = this.state.servers.get(buf.server);
|
|
|
|
|
if (buf.type === BufferType.NICK && !server.users.has(buf.name)) {
|
|
|
|
|
this.whoUserBuffer(buf.name, buf.server);
|
|
|
|
|
}
|
2020-07-15 12:21:09 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-03 05:46:50 -04:00
|
|
|
|
addMessage(serverID, bufName, msg) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = this.clients.get(serverID);
|
2021-01-22 11:36:53 -05:00
|
|
|
|
|
2021-06-23 13:52:45 -04:00
|
|
|
|
// Treat server-wide broadcasts as highlights. They're sent by server
|
|
|
|
|
// operators and can contain important information.
|
|
|
|
|
msg.isHighlight = irc.isHighlight(msg, client.nick, client.cm) || irc.isServerBroadcast(msg);
|
2020-08-13 05:58:36 -04:00
|
|
|
|
|
2020-06-18 08:23:08 -04:00
|
|
|
|
if (!msg.tags) {
|
|
|
|
|
msg.tags = {};
|
|
|
|
|
}
|
2020-06-29 03:06:47 -04:00
|
|
|
|
if (!msg.tags.time) {
|
|
|
|
|
msg.tags.time = irc.formatDate(new Date());
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 10:20:42 -05:00
|
|
|
|
let stored = this.bufferStore.get({ name: bufName, server: client.params });
|
2022-02-11 14:59:31 -05:00
|
|
|
|
let deliveryReceipt = getReceipt(stored, ReceiptType.DELIVERED);
|
|
|
|
|
let readReceipt = getReceipt(stored, ReceiptType.READ);
|
|
|
|
|
let isDelivered = isMessageBeforeReceipt(msg, deliveryReceipt);
|
|
|
|
|
let isRead = isMessageBeforeReceipt(msg, readReceipt);
|
2022-02-11 10:20:42 -05:00
|
|
|
|
|
2020-07-15 12:21:09 -04:00
|
|
|
|
// TODO: messages coming from infinite scroll shouldn't trigger notifications
|
2020-06-18 08:23:08 -04:00
|
|
|
|
|
2021-06-24 12:04:26 -04:00
|
|
|
|
if (client.isMyNick(msg.prefix.name)) {
|
|
|
|
|
isRead = true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let msgUnread = Unread.NONE;
|
2020-07-15 12:21:09 -04:00
|
|
|
|
if ((msg.command == "PRIVMSG" || msg.command == "NOTICE") && !isRead) {
|
2021-06-10 12:57:57 -04:00
|
|
|
|
let target = msg.params[0];
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let text = msg.params[1];
|
2020-06-29 05:50:42 -04:00
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let kind;
|
2020-08-13 05:58:36 -04:00
|
|
|
|
if (msg.isHighlight) {
|
2020-06-29 05:08:47 -04:00
|
|
|
|
msgUnread = Unread.HIGHLIGHT;
|
2020-06-29 05:50:42 -04:00
|
|
|
|
kind = "highlight";
|
2021-06-10 12:57:57 -04:00
|
|
|
|
} else if (client.isMyNick(target)) {
|
2020-06-29 05:50:42 -04:00
|
|
|
|
msgUnread = Unread.HIGHLIGHT;
|
|
|
|
|
kind = "private message";
|
2020-06-29 05:08:47 -04:00
|
|
|
|
} else {
|
|
|
|
|
msgUnread = Unread.MESSAGE;
|
|
|
|
|
}
|
2020-06-29 05:50:42 -04:00
|
|
|
|
|
2021-06-10 11:01:26 -04:00
|
|
|
|
if (msgUnread == Unread.HIGHLIGHT && !isDelivered && !irc.parseCTCP(msg)) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let title = "New " + kind + " from " + msg.prefix.name;
|
2021-06-04 13:13:59 -04:00
|
|
|
|
if (client.isChannel(bufName)) {
|
2021-06-03 07:29:32 -04:00
|
|
|
|
title += " in " + bufName;
|
2020-06-29 05:50:42 -04:00
|
|
|
|
}
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let notif = showNotification(title, {
|
2020-08-13 09:38:12 -04:00
|
|
|
|
body: stripANSI(text),
|
2020-06-29 05:50:42 -04:00
|
|
|
|
requireInteraction: true,
|
2022-02-11 13:01:27 -05:00
|
|
|
|
tag: "msg,server=" + serverID + ",from=" + msg.prefix.name + ",to=" + bufName,
|
2022-02-11 13:27:45 -05:00
|
|
|
|
data: { bufferName: bufName, message: msg },
|
2020-06-29 05:50:42 -04:00
|
|
|
|
});
|
2022-02-11 13:16:11 -05:00
|
|
|
|
if (notif) {
|
|
|
|
|
notif.addEventListener("click", () => {
|
|
|
|
|
// TODO: scroll to message
|
|
|
|
|
this.switchBuffer({ server: serverID, name: bufName });
|
|
|
|
|
});
|
2022-02-11 13:27:45 -05:00
|
|
|
|
notif.addEventListener("close", () => {
|
|
|
|
|
this.messageNotifications.delete(notif);
|
|
|
|
|
});
|
|
|
|
|
this.messageNotifications.add(notif);
|
2022-02-11 13:16:11 -05:00
|
|
|
|
}
|
2020-06-29 05:50:42 -04:00
|
|
|
|
}
|
2020-06-24 10:56:28 -04:00
|
|
|
|
}
|
2021-06-03 05:04:32 -04:00
|
|
|
|
if (msg.command === "INVITE" && client.isMyNick(msg.params[0])) {
|
|
|
|
|
msgUnread = Unread.HIGHLIGHT;
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let channel = msg.params[1];
|
|
|
|
|
let notif = new Notification("Invitation to " + channel, {
|
2021-06-10 11:01:26 -04:00
|
|
|
|
body: msg.prefix.name + " has invited you to " + channel,
|
|
|
|
|
requireInteraction: true,
|
2022-02-11 13:01:27 -05:00
|
|
|
|
tag: "invite,server=" + serverID + ",from=" + msg.prefix.name + ",channel=" + channel,
|
2021-06-10 11:01:26 -04:00
|
|
|
|
actions: [{
|
|
|
|
|
action: "accept",
|
|
|
|
|
title: "Accept",
|
|
|
|
|
}],
|
|
|
|
|
});
|
2022-02-11 13:16:11 -05:00
|
|
|
|
if (notif) {
|
|
|
|
|
notif.addEventListener("click", (event) => {
|
|
|
|
|
if (event.action === "accept") {
|
|
|
|
|
let stored = {
|
|
|
|
|
name: bufName,
|
|
|
|
|
server: client.params,
|
|
|
|
|
receipts: { [ReceiptType.READ]: receiptFromMessage(msg) },
|
|
|
|
|
};
|
|
|
|
|
if (this.bufferStore.put(stored)) {
|
|
|
|
|
this.sendReadReceipt(client, stored);
|
|
|
|
|
}
|
|
|
|
|
this.open(channel, serverID);
|
|
|
|
|
} else {
|
|
|
|
|
// TODO: scroll to message
|
|
|
|
|
this.switchBuffer({ server: serverID, name: bufName });
|
2022-02-11 12:21:17 -05:00
|
|
|
|
}
|
2022-02-11 13:16:11 -05:00
|
|
|
|
});
|
|
|
|
|
}
|
2021-06-03 05:04:32 -04:00
|
|
|
|
}
|
2020-06-24 10:56:28 -04:00
|
|
|
|
|
2021-08-24 08:46:51 -04:00
|
|
|
|
// Open a new buffer if the message doesn't come from me or is a
|
|
|
|
|
// self-message
|
|
|
|
|
if ((!client.isMyNick(msg.prefix.name) || client.isMyNick(bufName)) && (msg.command != "PART" && msg.comand != "QUIT")) {
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.createBuffer(serverID, bufName);
|
2020-06-25 12:28:54 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let bufID = { server: serverID, name: bufName };
|
2021-06-04 13:07:14 -04:00
|
|
|
|
this.setState((state) => State.addMessage(state, msg, bufID));
|
|
|
|
|
this.setBufferState(bufID, (buf) => {
|
2020-07-15 12:21:09 -04:00
|
|
|
|
// TODO: set unread if scrolled up
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let unread = buf.unread;
|
2021-08-30 04:27:24 -04:00
|
|
|
|
let prevReadReceipt = buf.prevReadReceipt;
|
2022-02-11 10:20:42 -05:00
|
|
|
|
let receipts = { [ReceiptType.DELIVERED]: receiptFromMessage(msg) };
|
2021-08-30 04:27:24 -04:00
|
|
|
|
|
2021-08-23 06:25:43 -04:00
|
|
|
|
if (this.state.activeBuffer !== buf.id) {
|
2020-06-24 11:46:43 -04:00
|
|
|
|
unread = Unread.union(unread, msgUnread);
|
2020-07-15 12:21:09 -04:00
|
|
|
|
} else {
|
2022-02-11 10:20:42 -05:00
|
|
|
|
receipts[ReceiptType.READ] = receiptFromMessage(msg);
|
2020-06-24 10:56:28 -04:00
|
|
|
|
}
|
2021-08-30 04:27:24 -04:00
|
|
|
|
|
|
|
|
|
// Don't show unread marker for my own messages
|
2022-02-11 10:37:58 -05:00
|
|
|
|
if (client.isMyNick(msg.prefix.name) && !isMessageBeforeReceipt(msg, prevReadReceipt)) {
|
2022-02-11 10:30:46 -05:00
|
|
|
|
prevReadReceipt = receiptFromMessage(msg);
|
2021-08-30 04:27:24 -04:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 12:21:17 -05:00
|
|
|
|
let stored = {
|
2021-08-23 06:25:43 -04:00
|
|
|
|
name: buf.name,
|
|
|
|
|
server: client.params,
|
|
|
|
|
unread,
|
2022-02-11 10:20:42 -05:00
|
|
|
|
receipts,
|
2022-02-11 12:21:17 -05:00
|
|
|
|
};
|
|
|
|
|
if (this.bufferStore.put(stored)) {
|
|
|
|
|
this.sendReadReceipt(client, stored);
|
|
|
|
|
}
|
2021-08-30 04:27:24 -04:00
|
|
|
|
return { unread, prevReadReceipt };
|
2020-06-18 08:23:08 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-22 12:44:06 -05:00
|
|
|
|
connect(params) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let serverID = null;
|
2021-01-21 13:01:50 -05:00
|
|
|
|
this.setState((state) => {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let update;
|
2021-06-10 04:54:33 -04:00
|
|
|
|
[serverID, update] = State.createServer(state);
|
|
|
|
|
return update;
|
2021-01-21 13:01:50 -05:00
|
|
|
|
});
|
|
|
|
|
this.setState({ connectParams: params });
|
2020-06-18 08:23:08 -04:00
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = new Client(fillConnectParams(params));
|
2021-12-01 05:40:59 -05:00
|
|
|
|
client.debug = this.debug;
|
|
|
|
|
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.clients.set(serverID, client);
|
2021-06-10 04:54:33 -04:00
|
|
|
|
this.setServerState(serverID, { status: client.status });
|
2021-01-22 11:36:53 -05:00
|
|
|
|
|
2021-11-17 04:12:36 -05:00
|
|
|
|
let errorID = null;
|
|
|
|
|
|
2021-01-22 12:29:22 -05:00
|
|
|
|
client.addEventListener("status", () => {
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.setServerState(serverID, { status: client.status });
|
2021-11-29 05:44:45 -05:00
|
|
|
|
switch (client.status) {
|
|
|
|
|
case Client.Status.DISCONNECTED:
|
|
|
|
|
this.setServerState(serverID, { account: null });
|
|
|
|
|
this.setState((state) => {
|
|
|
|
|
let buffers = new Map(state.buffers);
|
|
|
|
|
state.buffers.forEach((buf) => {
|
|
|
|
|
if (buf.server !== serverID) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
buffers.set(buf.id, { ...buf, joined: false });
|
|
|
|
|
});
|
|
|
|
|
return { buffers };
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
case Client.Status.REGISTERED:
|
2021-06-06 05:33:00 -04:00
|
|
|
|
this.setState({ connectForm: false });
|
2021-11-17 04:12:36 -05:00
|
|
|
|
if (errorID) {
|
|
|
|
|
this.dismissError(errorID);
|
|
|
|
|
}
|
2021-11-29 05:44:45 -05:00
|
|
|
|
break;
|
2021-06-06 05:33:00 -04:00
|
|
|
|
}
|
2021-01-21 13:01:50 -05:00
|
|
|
|
});
|
2020-06-18 08:23:08 -04:00
|
|
|
|
|
2021-01-22 11:36:53 -05:00
|
|
|
|
client.addEventListener("message", (event) => {
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.handleMessage(serverID, event.detail.message);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
});
|
|
|
|
|
|
2021-01-22 11:36:53 -05:00
|
|
|
|
client.addEventListener("error", (event) => {
|
2021-11-17 04:12:36 -05:00
|
|
|
|
errorID = this.showError(event.detail);
|
2020-08-08 00:08:51 -04:00
|
|
|
|
});
|
|
|
|
|
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.createBuffer(serverID, SERVER_BUFFER);
|
2021-03-09 03:38:55 -05:00
|
|
|
|
if (!this.state.activeBuffer) {
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.switchBuffer({ server: serverID, name: SERVER_BUFFER });
|
2021-03-09 03:38:55 -05:00
|
|
|
|
}
|
2021-05-25 06:40:33 -04:00
|
|
|
|
|
|
|
|
|
if (params.autojoin.length > 0) {
|
|
|
|
|
this.switchToChannel = params.autojoin[0];
|
|
|
|
|
}
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-03 05:46:50 -04:00
|
|
|
|
disconnect(serverID) {
|
|
|
|
|
if (!serverID) {
|
2021-06-04 12:03:03 -04:00
|
|
|
|
serverID = State.getActiveServerID(this.state);
|
2021-01-22 04:38:07 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = this.clients.get(serverID);
|
2021-01-22 11:36:53 -05:00
|
|
|
|
if (client) {
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.clients.delete(serverID);
|
2021-01-22 12:29:22 -05:00
|
|
|
|
client.disconnect();
|
2021-01-12 04:35:38 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-03 05:46:50 -04:00
|
|
|
|
reconnect(serverID) {
|
|
|
|
|
if (!serverID) {
|
2021-06-04 12:03:03 -04:00
|
|
|
|
serverID = State.getActiveServerID(this.state);
|
2021-01-22 04:38:07 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = this.clients.get(serverID);
|
2021-01-22 12:44:06 -05:00
|
|
|
|
if (client) {
|
|
|
|
|
client.reconnect();
|
|
|
|
|
}
|
2021-01-12 04:35:38 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-03 05:46:50 -04:00
|
|
|
|
serverFromBouncerNetwork(bouncerNetworkID) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
for (let [id, client] of this.clients) {
|
2021-03-10 05:00:33 -05:00
|
|
|
|
if (client.params.bouncerNetwork === bouncerNetworkID) {
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-23 17:01:32 -04:00
|
|
|
|
routeMessage(serverID, msg) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
let chatHistoryBatch = irc.findBatchByType(msg, "chathistory");
|
2021-06-04 12:27:21 -04:00
|
|
|
|
|
2021-10-23 17:01:32 -04:00
|
|
|
|
let target, channel, affectedBuffers;
|
|
|
|
|
switch (msg.command) {
|
|
|
|
|
case "MODE":
|
|
|
|
|
target = msg.params[0];
|
|
|
|
|
if (client.isChannel(target)) {
|
|
|
|
|
return [target];
|
|
|
|
|
}
|
2021-11-03 16:44:24 -04:00
|
|
|
|
return [SERVER_BUFFER];
|
2021-10-23 17:01:32 -04:00
|
|
|
|
case "NOTICE":
|
|
|
|
|
case "PRIVMSG":
|
|
|
|
|
target = msg.params[0];
|
|
|
|
|
if (client.isMyNick(target)) {
|
|
|
|
|
if (client.cm(msg.prefix.name) === client.cm(client.serverPrefix.name)) {
|
|
|
|
|
target = SERVER_BUFFER;
|
|
|
|
|
} else {
|
|
|
|
|
target = msg.prefix.name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (msg.command === "NOTICE" && !State.getBuffer(this.state, { server: serverID, name: target })) {
|
|
|
|
|
// Don't open a new buffer if this is just a NOTICE
|
|
|
|
|
target = SERVER_BUFFER;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-07 06:09:10 -05:00
|
|
|
|
let allowedPrefixes = client.isupport.statusMsg();
|
2021-10-23 17:01:32 -04:00
|
|
|
|
if (allowedPrefixes) {
|
|
|
|
|
let parts = irc.parseTargetPrefix(target, allowedPrefixes);
|
|
|
|
|
if (client.isChannel(parts.name)) {
|
|
|
|
|
target = parts.name;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return [target];
|
|
|
|
|
case "JOIN":
|
|
|
|
|
channel = msg.params[0];
|
|
|
|
|
if (!client.isMyNick(msg.prefix.name)) {
|
|
|
|
|
return [channel];
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
case "PART":
|
|
|
|
|
channel = msg.params[0];
|
|
|
|
|
return [channel];
|
|
|
|
|
case "KICK":
|
|
|
|
|
channel = msg.params[0];
|
|
|
|
|
return [channel];
|
|
|
|
|
case "QUIT":
|
|
|
|
|
affectedBuffers = [];
|
|
|
|
|
if (chatHistoryBatch) {
|
|
|
|
|
affectedBuffers.push(chatHistoryBatch.params[0]);
|
|
|
|
|
} else {
|
|
|
|
|
this.state.buffers.forEach((buf) => {
|
|
|
|
|
if (buf.server != serverID) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!buf.members.has(msg.prefix.name) && client.cm(buf.name) !== client.cm(msg.prefix.name)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
affectedBuffers.push(buf.name);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return affectedBuffers;
|
|
|
|
|
case "NICK":
|
|
|
|
|
let newNick = msg.params[0];
|
|
|
|
|
|
|
|
|
|
affectedBuffers = [];
|
|
|
|
|
if (chatHistoryBatch) {
|
|
|
|
|
affectedBuffers.push(chatHistoryBatch.params[0]);
|
|
|
|
|
} else {
|
|
|
|
|
this.state.buffers.forEach((buf) => {
|
|
|
|
|
if (buf.server != serverID) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!buf.members.has(msg.prefix.name)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
affectedBuffers.push(buf.name);
|
|
|
|
|
});
|
2021-11-03 16:49:53 -04:00
|
|
|
|
if (client.isMyNick(newNick)) {
|
|
|
|
|
affectedBuffers.push(SERVER_BUFFER);
|
|
|
|
|
}
|
2021-10-23 17:01:32 -04:00
|
|
|
|
}
|
|
|
|
|
return affectedBuffers;
|
|
|
|
|
case "TOPIC":
|
|
|
|
|
channel = msg.params[0];
|
|
|
|
|
return [channel];
|
|
|
|
|
case "INVITE":
|
|
|
|
|
channel = msg.params[1];
|
|
|
|
|
|
|
|
|
|
// TODO: find a more reliable way to do this
|
|
|
|
|
let bufName = channel;
|
|
|
|
|
if (!State.getBuffer(this.state, { server: serverID, name: channel })) {
|
|
|
|
|
bufName = SERVER_BUFFER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [bufName];
|
|
|
|
|
case irc.RPL_CHANNELMODEIS:
|
|
|
|
|
case irc.RPL_CREATIONTIME:
|
|
|
|
|
case irc.RPL_INVITELIST:
|
|
|
|
|
case irc.RPL_ENDOFINVITELIST:
|
|
|
|
|
case irc.RPL_EXCEPTLIST:
|
|
|
|
|
case irc.RPL_ENDOFEXCEPTLIST:
|
|
|
|
|
case irc.RPL_BANLIST:
|
|
|
|
|
case irc.RPL_ENDOFBANLIST:
|
|
|
|
|
case irc.RPL_QUIETLIST:
|
|
|
|
|
case irc.RPL_ENDOFQUIETLIST:
|
|
|
|
|
channel = msg.params[1];
|
|
|
|
|
return [channel];
|
|
|
|
|
case irc.RPL_INVITING:
|
|
|
|
|
channel = msg.params[2];
|
|
|
|
|
return [channel];
|
|
|
|
|
case irc.RPL_YOURHOST:
|
|
|
|
|
case irc.RPL_MYINFO:
|
|
|
|
|
case irc.RPL_ISUPPORT:
|
|
|
|
|
case irc.RPL_ENDOFMOTD:
|
|
|
|
|
case irc.ERR_NOMOTD:
|
2021-11-23 11:58:49 -05:00
|
|
|
|
case irc.RPL_AWAY:
|
2021-10-23 17:01:32 -04:00
|
|
|
|
case irc.RPL_NOTOPIC:
|
|
|
|
|
case irc.RPL_TOPIC:
|
|
|
|
|
case irc.RPL_TOPICWHOTIME:
|
|
|
|
|
case irc.RPL_NAMREPLY:
|
|
|
|
|
case irc.RPL_ENDOFNAMES:
|
|
|
|
|
case irc.RPL_MONONLINE:
|
|
|
|
|
case irc.RPL_MONOFFLINE:
|
|
|
|
|
case irc.RPL_SASLSUCCESS:
|
|
|
|
|
case "AWAY":
|
|
|
|
|
case "SETNAME":
|
|
|
|
|
case "CHGHOST":
|
|
|
|
|
case "ACCOUNT":
|
|
|
|
|
case "CAP":
|
|
|
|
|
case "AUTHENTICATE":
|
|
|
|
|
case "PING":
|
|
|
|
|
case "PONG":
|
|
|
|
|
case "BATCH":
|
|
|
|
|
case "TAGMSG":
|
|
|
|
|
case "CHATHISTORY":
|
|
|
|
|
case "ACK":
|
|
|
|
|
case "BOUNCER":
|
2022-02-11 12:21:17 -05:00
|
|
|
|
case "READ":
|
2021-10-23 17:01:32 -04:00
|
|
|
|
// Ignore these
|
|
|
|
|
return [];
|
|
|
|
|
default:
|
|
|
|
|
return [SERVER_BUFFER];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleMessage(serverID, msg) {
|
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
|
2021-11-07 07:51:39 -05:00
|
|
|
|
let destBuffers = this.routeMessage(serverID, msg);
|
2021-10-23 17:01:32 -04:00
|
|
|
|
|
|
|
|
|
if (irc.findBatchByType(msg, "chathistory")) {
|
|
|
|
|
destBuffers.forEach((bufName) => {
|
|
|
|
|
this.addMessage(serverID, bufName, msg);
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-04 12:27:21 -04:00
|
|
|
|
this.setState((state) => State.handleMessage(state, msg, serverID, client));
|
|
|
|
|
|
2021-10-23 17:01:32 -04:00
|
|
|
|
let target, channel;
|
2020-06-18 08:23:08 -04:00
|
|
|
|
switch (msg.command) {
|
|
|
|
|
case irc.RPL_WELCOME:
|
2022-02-11 10:20:42 -05:00
|
|
|
|
let lastReceipt = getLatestReceipt(this.bufferStore, client.params, ReceiptType.DELIVERED);
|
2022-02-11 15:02:34 -05:00
|
|
|
|
if (lastReceipt && client.caps.enabled.has("draft/chathistory") && (!client.caps.enabled.has("soju.im/bouncer-networks") || client.params.bouncerNetwork)) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let now = irc.formatDate(new Date());
|
2021-05-18 10:53:52 -04:00
|
|
|
|
client.fetchHistoryTargets(now, lastReceipt.time).then((targets) => {
|
|
|
|
|
targets.forEach((target) => {
|
2022-02-11 15:02:34 -05:00
|
|
|
|
// Maybe we've just received a READ update from the
|
|
|
|
|
// server, avoid over-fetching history
|
2021-08-23 08:17:58 -04:00
|
|
|
|
let from = lastReceipt;
|
2022-02-11 15:02:34 -05:00
|
|
|
|
let stored = this.bufferStore.get({ name: target.name, server: client.params });
|
|
|
|
|
let readReceipt = getReceipt(stored, ReceiptType.READ);
|
|
|
|
|
if (readReceipt && readReceipt.time > from.time) {
|
|
|
|
|
from = readReceipt;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-29 10:34:50 -04:00
|
|
|
|
let to = { time: msg.tags.time || now };
|
2021-05-18 10:53:52 -04:00
|
|
|
|
this.fetchBacklog(client, target.name, from, to);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-06-18 08:23:08 -04:00
|
|
|
|
break;
|
2021-09-21 12:31:11 -04:00
|
|
|
|
case irc.RPL_ENDOFMOTD:
|
|
|
|
|
case irc.ERR_NOMOTD:
|
|
|
|
|
// These messages are used to indicate the end of the ISUPPORT list
|
|
|
|
|
|
2021-09-21 13:20:39 -04:00
|
|
|
|
// Restore opened channel and user buffers
|
|
|
|
|
let join = [];
|
2021-09-21 12:31:11 -04:00
|
|
|
|
for (let buf of this.bufferStore.list(client.params)) {
|
2021-09-21 13:20:39 -04:00
|
|
|
|
if (buf.name === "*") {
|
2021-09-21 12:31:11 -04:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2021-09-21 13:20:39 -04:00
|
|
|
|
|
|
|
|
|
if (client.isChannel(buf.name)) {
|
2021-12-10 09:34:51 -05:00
|
|
|
|
if (client.caps.enabled.has("soju.im/bouncer-networks")) {
|
2021-09-21 13:20:39 -04:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
join.push(buf.name);
|
|
|
|
|
} else {
|
|
|
|
|
this.createBuffer(serverID, buf.name);
|
|
|
|
|
this.whoUserBuffer(buf.name, serverID);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Auto-join channels given at connect-time
|
2021-11-07 13:47:49 -05:00
|
|
|
|
let server = this.state.servers.get(serverID);
|
2021-12-07 06:09:10 -05:00
|
|
|
|
let bouncerNetID = server.bouncerNetID;
|
2021-11-07 13:47:49 -05:00
|
|
|
|
let bouncerNetwork = null;
|
|
|
|
|
if (bouncerNetID) {
|
|
|
|
|
bouncerNetwork = this.state.bouncerNetworks.get(bouncerNetID);
|
|
|
|
|
}
|
|
|
|
|
if (!bouncerNetwork || bouncerNetwork.state === "connected") {
|
|
|
|
|
join = join.concat(client.params.autojoin);
|
|
|
|
|
client.params.autojoin = [];
|
|
|
|
|
}
|
2021-09-21 13:20:39 -04:00
|
|
|
|
|
|
|
|
|
if (join.length > 0) {
|
|
|
|
|
client.send({
|
|
|
|
|
command: "JOIN",
|
|
|
|
|
params: [join.join(",")],
|
|
|
|
|
});
|
2021-09-21 12:31:11 -04:00
|
|
|
|
}
|
2021-11-08 06:33:02 -05:00
|
|
|
|
|
|
|
|
|
let serverHost = bouncerNetwork ? bouncerNetwork.host : "";
|
|
|
|
|
if (this.autoOpenURL && serverHost === this.autoOpenURL.host) {
|
|
|
|
|
this.openURL(this.autoOpenURL);
|
|
|
|
|
this.autoOpenURL = null;
|
|
|
|
|
}
|
2020-06-18 08:23:08 -04:00
|
|
|
|
case "JOIN":
|
2021-06-10 12:11:11 -04:00
|
|
|
|
channel = msg.params[0];
|
2020-06-18 08:23:08 -04:00
|
|
|
|
|
2021-08-25 02:46:37 -04:00
|
|
|
|
if (client.isMyNick(msg.prefix.name)) {
|
|
|
|
|
this.syncBufferUnread(serverID, channel);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
2021-05-25 06:40:33 -04:00
|
|
|
|
if (channel == this.switchToChannel) {
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.switchBuffer({ server: serverID, name: channel });
|
2021-05-25 06:40:33 -04:00
|
|
|
|
this.switchToChannel = null;
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
break;
|
2021-03-10 05:00:33 -05:00
|
|
|
|
case "BOUNCER":
|
|
|
|
|
if (msg.params[0] !== "NETWORK") {
|
|
|
|
|
break; // We're only interested in network updates
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-07 06:09:10 -05:00
|
|
|
|
if (client.isupport.bouncerNetID()) {
|
2021-10-23 17:01:32 -04:00
|
|
|
|
// This can happen if the user has specified a network to bind
|
2021-05-25 11:11:06 -04:00
|
|
|
|
// to via other means, e.g. "<username>/<network>".
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let id = msg.params[1];
|
|
|
|
|
let attrs = null;
|
2021-03-10 05:00:33 -05:00
|
|
|
|
if (msg.params[2] !== "*") {
|
|
|
|
|
attrs = irc.parseTags(msg.params[2]);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let isNew = false;
|
2021-03-10 05:00:33 -05:00
|
|
|
|
this.setState((state) => {
|
|
|
|
|
if (!attrs) {
|
2022-02-04 08:22:50 -05:00
|
|
|
|
return State.deleteBouncerNetwork(state, id);
|
2021-03-10 05:00:33 -05:00
|
|
|
|
} else {
|
2022-02-04 08:22:50 -05:00
|
|
|
|
isNew = !state.bouncerNetworks.has(id);
|
|
|
|
|
return State.storeBouncerNetwork(state, id, attrs);
|
2021-03-10 05:00:33 -05:00
|
|
|
|
}
|
|
|
|
|
}, () => {
|
|
|
|
|
if (!attrs) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let serverID = this.serverFromBouncerNetwork(id);
|
2021-06-03 05:46:50 -04:00
|
|
|
|
if (serverID) {
|
|
|
|
|
this.close({ server: serverID, name: SERVER_BUFFER });
|
2021-03-10 05:00:33 -05:00
|
|
|
|
}
|
|
|
|
|
} else if (isNew) {
|
|
|
|
|
this.connect({
|
|
|
|
|
...client.params,
|
|
|
|
|
bouncerNetwork: id,
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-11-07 13:47:49 -05:00
|
|
|
|
|
|
|
|
|
if (attrs && attrs.state === "connected") {
|
|
|
|
|
let serverID = this.serverFromBouncerNetwork(id);
|
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
if (client && client.status === Client.Status.REGISTERED && client.params.autojoin && client.params.autojoin.length > 0) {
|
|
|
|
|
client.send({
|
|
|
|
|
command: "JOIN",
|
|
|
|
|
params: [client.params.autojoin.join(",")],
|
|
|
|
|
});
|
|
|
|
|
client.params.autojoin = [];
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-10 05:00:33 -05:00
|
|
|
|
});
|
|
|
|
|
break;
|
2021-11-08 06:33:02 -05:00
|
|
|
|
case "BATCH":
|
|
|
|
|
if (!msg.params[0].startsWith("-")) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
let name = msg.params[0].slice(1);
|
|
|
|
|
let batch = client.batches.get(name);
|
|
|
|
|
if (!batch || batch.type !== "soju.im/bouncer-networks") {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We've received a BOUNCER NETWORK batch. If we have a URL to
|
|
|
|
|
// auto-open and no existing network matches it, ask the user to
|
|
|
|
|
// create a new network.
|
|
|
|
|
if (this.autoOpenURL && this.autoOpenURL.host && !this.findBouncerNetIDByHost(this.autoOpenURL.host)) {
|
|
|
|
|
this.openURL(this.autoOpenURL);
|
|
|
|
|
this.autoOpenURL = null;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-02-11 12:21:17 -05:00
|
|
|
|
case "READ":
|
|
|
|
|
target = msg.params[0];
|
|
|
|
|
let bound = msg.params[1];
|
|
|
|
|
if (!client.isMyNick(msg.prefix.name) || bound === "*" || !bound.startsWith("timestamp=")) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
let readReceipt = { time: bound.replace("timestamp=", "") };
|
2022-02-11 15:09:11 -05:00
|
|
|
|
let updated = this.bufferStore.put({
|
2022-02-11 12:21:17 -05:00
|
|
|
|
name: target,
|
|
|
|
|
server: client.params,
|
|
|
|
|
receipts: { [ReceiptType.READ]: readReceipt },
|
|
|
|
|
});
|
2022-02-11 15:09:11 -05:00
|
|
|
|
if (!updated) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-02-11 13:28:18 -05:00
|
|
|
|
for (let notif of this.messageNotifications) {
|
|
|
|
|
if (client.cm(notif.data.bufferName) !== client.cm(target)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (isMessageBeforeReceipt(notif.data.message, readReceipt)) {
|
|
|
|
|
notif.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-11 12:21:17 -05:00
|
|
|
|
this.setBufferState({ server: serverID, name: target }, (buf) => {
|
|
|
|
|
// Re-compute unread status
|
|
|
|
|
let unread = Unread.NONE;
|
|
|
|
|
for (let i = buf.messages.length - 1; i >= 0; i--) {
|
|
|
|
|
let msg = buf.messages[i];
|
|
|
|
|
if (msg.command !== "PRIVMSG" && msg.command !== "NOTICE") {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (isMessageBeforeReceipt(msg, readReceipt)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (msg.isHighlight || client.isMyNick(buf.name)) {
|
|
|
|
|
unread = Unread.HIGHLIGHT;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unread = Unread.MESSAGE;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 15:09:11 -05:00
|
|
|
|
return { unread };
|
2022-02-11 12:21:17 -05:00
|
|
|
|
});
|
|
|
|
|
break;
|
2020-06-18 08:23:08 -04:00
|
|
|
|
default:
|
2021-05-27 05:30:23 -04:00
|
|
|
|
if (irc.isError(msg.command) && msg.command != irc.ERR_NOMOTD) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let description = msg.params[msg.params.length - 1];
|
2021-11-17 04:12:36 -05:00
|
|
|
|
this.showError(description);
|
2021-05-27 05:30:23 -04:00
|
|
|
|
}
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
2021-10-23 17:01:32 -04:00
|
|
|
|
|
|
|
|
|
destBuffers.forEach((bufName) => {
|
|
|
|
|
this.addMessage(serverID, bufName, msg);
|
|
|
|
|
});
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleConnectSubmit(connectParams) {
|
2021-11-17 04:12:36 -05:00
|
|
|
|
this.dismissError();
|
2020-08-10 08:57:54 -04:00
|
|
|
|
|
2021-05-26 12:43:11 -04:00
|
|
|
|
if (connectParams.autoconnect) {
|
|
|
|
|
store.autoconnect.put(connectParams);
|
|
|
|
|
} else {
|
|
|
|
|
store.autoconnect.put(null);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-07 07:50:26 -05:00
|
|
|
|
// Disconnect previous server, if any
|
|
|
|
|
let activeBuffer = this.state.buffers.get(this.state.activeBuffer);
|
|
|
|
|
if (activeBuffer) {
|
|
|
|
|
this.close(activeBuffer.server);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-22 12:44:06 -05:00
|
|
|
|
this.connect(connectParams);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-13 10:18:59 -04:00
|
|
|
|
handleChannelClick(event) {
|
2021-11-08 06:33:02 -05:00
|
|
|
|
let handled = this.openURL(event.target.href);
|
|
|
|
|
if (handled) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
findBouncerNetIDByHost(host) {
|
|
|
|
|
for (let [id, bouncerNetwork] of this.state.bouncerNetworks) {
|
|
|
|
|
if (bouncerNetwork.host === host) {
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
openURL(url) {
|
|
|
|
|
if (typeof url === "string") {
|
|
|
|
|
url = irc.parseURL(url);
|
|
|
|
|
}
|
2021-10-13 10:18:59 -04:00
|
|
|
|
if (!url) {
|
2021-11-08 06:33:02 -05:00
|
|
|
|
return false;
|
2021-10-13 10:18:59 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-16 05:52:38 -05:00
|
|
|
|
let { host, port } = splitHostPort(url.host);
|
|
|
|
|
|
2021-10-13 10:18:59 -04:00
|
|
|
|
let serverID;
|
|
|
|
|
if (!url.host) {
|
|
|
|
|
serverID = State.getActiveServerID(this.state);
|
|
|
|
|
} else {
|
2021-11-16 05:52:38 -05:00
|
|
|
|
let bouncerNetID = this.findBouncerNetIDByHost(host);
|
2021-10-13 10:18:59 -04:00
|
|
|
|
if (!bouncerNetID) {
|
2021-10-13 10:40:34 -04:00
|
|
|
|
// Open dialog to create network if bouncer
|
|
|
|
|
let client = this.clients.values().next().value;
|
2021-12-10 09:34:51 -05:00
|
|
|
|
if (!client || !client.caps.enabled.has("soju.im/bouncer-networks")) {
|
2021-11-08 06:33:02 -05:00
|
|
|
|
return false;
|
2021-10-13 10:40:34 -04:00
|
|
|
|
}
|
2021-11-08 06:33:02 -05:00
|
|
|
|
|
2021-11-16 05:52:38 -05:00
|
|
|
|
let params = { host };
|
|
|
|
|
if (typeof port === "number") {
|
|
|
|
|
params.port = port;
|
|
|
|
|
}
|
2021-11-08 06:33:02 -05:00
|
|
|
|
this.openDialog("network", { params, autojoin: url.entity });
|
|
|
|
|
return true;
|
2021-10-13 10:18:59 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let [id, server] of this.state.servers) {
|
2021-12-07 06:09:10 -05:00
|
|
|
|
if (server.bouncerNetID === bouncerNetID) {
|
2021-10-13 10:18:59 -04:00
|
|
|
|
serverID = id;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!serverID) {
|
2021-11-08 06:33:02 -05:00
|
|
|
|
return false;
|
2021-10-13 10:18:59 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-20 08:33:16 -04:00
|
|
|
|
let buf = State.getBuffer(this.state, { server: serverID, name: url.entity || SERVER_BUFFER });
|
2021-05-31 22:39:35 -04:00
|
|
|
|
if (buf) {
|
|
|
|
|
this.switchBuffer(buf.id);
|
|
|
|
|
} else {
|
2021-11-08 04:44:10 -05:00
|
|
|
|
this.openDialog("join", { server: serverID, channel: url.entity });
|
2021-05-31 22:39:35 -04:00
|
|
|
|
}
|
2021-11-08 06:33:02 -05:00
|
|
|
|
return true;
|
2021-05-31 22:39:35 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-25 12:45:41 -04:00
|
|
|
|
handleNickClick(nick) {
|
|
|
|
|
this.open(nick);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 10:53:52 -04:00
|
|
|
|
fetchBacklog(client, target, after, before) {
|
|
|
|
|
client.fetchHistoryBetween(target, after, before, CHATHISTORY_MAX_SIZE).catch((err) => {
|
2021-11-10 03:53:17 -05:00
|
|
|
|
console.error("Failed to fetch backlog for '" + target + "': ", err);
|
2021-11-17 04:12:36 -05:00
|
|
|
|
this.showError("Failed to fetch backlog for '" + target + "'");
|
2021-05-18 10:53:52 -04:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 10:58:00 -04:00
|
|
|
|
whoUserBuffer(target, serverID) {
|
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
|
|
|
|
|
client.who(target, {
|
|
|
|
|
fields: ["flags", "hostname", "nick", "realname", "username", "account"],
|
|
|
|
|
});
|
|
|
|
|
client.monitor(target);
|
2022-02-11 12:21:17 -05:00
|
|
|
|
|
|
|
|
|
if (client.caps.enabled.has("soju.im/read")) {
|
|
|
|
|
client.send({
|
|
|
|
|
command: "READ",
|
|
|
|
|
params: [target],
|
|
|
|
|
});
|
|
|
|
|
}
|
2021-09-21 10:58:00 -04:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-02 11:40:19 -05:00
|
|
|
|
open(target, serverID, password) {
|
2021-06-03 05:46:50 -04:00
|
|
|
|
if (!serverID) {
|
2021-06-04 12:03:03 -04:00
|
|
|
|
serverID = State.getActiveServerID(this.state);
|
2021-06-03 05:04:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = this.clients.get(serverID);
|
2021-06-10 06:03:50 -04:00
|
|
|
|
if (client.isServer(target)) {
|
|
|
|
|
this.switchBuffer({ server: serverID });
|
|
|
|
|
} else if (client.isChannel(target)) {
|
2021-05-27 13:10:42 -04:00
|
|
|
|
this.switchToChannel = target;
|
2022-02-02 11:40:19 -05:00
|
|
|
|
client.join(target, password).catch((err) => {
|
2021-12-04 11:44:23 -05:00
|
|
|
|
this.showError(err);
|
|
|
|
|
});
|
2020-06-26 06:00:10 -04:00
|
|
|
|
} else {
|
2021-09-21 10:58:00 -04:00
|
|
|
|
this.whoUserBuffer(target, serverID);
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.createBuffer(serverID, target);
|
|
|
|
|
this.switchBuffer({ server: serverID, name: target });
|
2020-06-25 12:45:41 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-22 04:26:53 -05:00
|
|
|
|
close(id) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let buf = State.getBuffer(this.state, id);
|
2021-01-22 04:26:53 -05:00
|
|
|
|
if (!buf) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = this.clients.get(buf.server);
|
2021-01-22 04:26:53 -05:00
|
|
|
|
switch (buf.type) {
|
|
|
|
|
case BufferType.SERVER:
|
2021-03-10 04:59:39 -05:00
|
|
|
|
this.setState((state) => {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let buffers = new Map(state.buffers);
|
|
|
|
|
for (let [id, b] of state.buffers) {
|
2021-06-03 05:46:50 -04:00
|
|
|
|
if (b.server === buf.server) {
|
2021-03-10 04:59:39 -05:00
|
|
|
|
buffers.delete(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let activeBuffer = state.activeBuffer;
|
2021-06-03 05:46:50 -04:00
|
|
|
|
if (activeBuffer && state.buffers.get(activeBuffer).server === buf.server) {
|
2021-03-10 04:59:39 -05:00
|
|
|
|
if (buffers.size > 0) {
|
|
|
|
|
activeBuffer = buffers.keys().next().value;
|
|
|
|
|
} else {
|
|
|
|
|
activeBuffer = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { buffers, activeBuffer };
|
2021-01-11 12:12:28 -05:00
|
|
|
|
});
|
2021-03-10 04:59:39 -05:00
|
|
|
|
|
2021-12-10 09:34:51 -05:00
|
|
|
|
let disconnectAll = client && !client.params.bouncerNetwork && client.caps.enabled.has("soju.im/bouncer-networks");
|
2021-03-10 04:59:39 -05:00
|
|
|
|
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.disconnect(buf.server);
|
2021-03-10 04:59:39 -05:00
|
|
|
|
|
2021-01-22 04:41:28 -05:00
|
|
|
|
this.setState((state) => {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let servers = new Map(state.servers);
|
2021-06-03 05:46:50 -04:00
|
|
|
|
servers.delete(buf.server);
|
2021-06-10 12:06:45 -04:00
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let connectForm = state.connectForm;
|
2021-06-10 12:06:45 -04:00
|
|
|
|
if (servers.size == 0) {
|
|
|
|
|
connectForm = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { servers, connectForm };
|
2021-01-22 04:41:28 -05:00
|
|
|
|
});
|
2021-03-10 04:59:39 -05:00
|
|
|
|
|
|
|
|
|
if (disconnectAll) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
for (let serverID of this.clients.keys()) {
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.close({ server: serverID, name: SERVER_BUFFER });
|
2021-03-10 04:59:39 -05:00
|
|
|
|
}
|
2021-08-23 06:02:36 -04:00
|
|
|
|
this.bufferStore.clear();
|
|
|
|
|
} else {
|
|
|
|
|
this.bufferStore.clear(client.params);
|
2021-03-10 04:59:39 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-23 06:02:36 -04:00
|
|
|
|
// TODO: only clear autoconnect if this server is stored there
|
2021-06-03 05:46:50 -04:00
|
|
|
|
if (buf.server == 1) {
|
2021-05-26 12:43:11 -04:00
|
|
|
|
store.autoconnect.put(null);
|
2021-05-25 06:57:40 -04:00
|
|
|
|
}
|
2021-01-22 04:26:53 -05:00
|
|
|
|
break;
|
|
|
|
|
case BufferType.CHANNEL:
|
2021-11-05 06:49:56 -04:00
|
|
|
|
if (buf.joined) {
|
|
|
|
|
client.send({ command: "PART", params: [buf.name] });
|
|
|
|
|
}
|
2021-01-22 04:26:53 -05:00
|
|
|
|
// fallthrough
|
|
|
|
|
case BufferType.NICK:
|
|
|
|
|
this.switchBuffer({ name: SERVER_BUFFER });
|
|
|
|
|
this.setState((state) => {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let buffers = new Map(state.buffers);
|
2021-03-03 03:59:40 -05:00
|
|
|
|
buffers.delete(buf.id);
|
2021-01-22 04:26:53 -05:00
|
|
|
|
return { buffers };
|
|
|
|
|
});
|
2020-07-15 12:21:09 -04:00
|
|
|
|
|
2021-08-24 06:53:46 -04:00
|
|
|
|
client.unmonitor(buf.name);
|
|
|
|
|
|
2021-08-23 06:02:36 -04:00
|
|
|
|
this.bufferStore.delete({ name: buf.name, server: client.params });
|
2021-01-22 04:26:53 -05:00
|
|
|
|
break;
|
2020-06-25 12:28:54 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-18 08:23:08 -04:00
|
|
|
|
executeCommand(s) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let parts = s.split(" ");
|
|
|
|
|
let name = parts[0].toLowerCase().slice(1);
|
|
|
|
|
let args = parts.slice(1);
|
2020-07-13 11:22:24 -04:00
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let cmd = commands[name];
|
2020-07-13 11:22:24 -04:00
|
|
|
|
if (!cmd) {
|
2021-11-17 04:12:36 -05:00
|
|
|
|
this.showError(`Unknown command "${name}" (run "/help" to get a command list)`);
|
2020-07-13 11:22:24 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2021-03-08 11:25:00 -05:00
|
|
|
|
cmd.execute(this, args);
|
2020-08-08 00:08:51 -04:00
|
|
|
|
} catch (error) {
|
2021-05-27 06:54:30 -04:00
|
|
|
|
console.error(`Failed to execute command "${name}":`, error);
|
2021-11-17 04:12:36 -05:00
|
|
|
|
this.showError(error.message);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-28 03:29:39 -04:00
|
|
|
|
privmsg(target, text) {
|
|
|
|
|
if (target == SERVER_BUFFER) {
|
2021-11-17 04:12:36 -05:00
|
|
|
|
this.showError("Cannot send message in server buffer");
|
2020-06-28 03:29:39 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let serverID = State.getActiveServerID(this.state);
|
|
|
|
|
let client = this.clients.get(serverID);
|
2021-01-22 11:36:53 -05:00
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let msg = { command: "PRIVMSG", params: [target, text] };
|
2021-01-22 11:36:53 -05:00
|
|
|
|
client.send(msg);
|
2020-06-28 03:29:39 -04:00
|
|
|
|
|
2021-12-10 09:34:51 -05:00
|
|
|
|
if (!client.caps.enabled.has("echo-message")) {
|
2021-01-22 11:36:53 -05:00
|
|
|
|
msg.prefix = { name: client.nick };
|
2021-06-03 05:46:50 -04:00
|
|
|
|
this.addMessage(serverID, target, msg);
|
2020-06-28 03:29:39 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-18 08:23:08 -04:00
|
|
|
|
handleComposerSubmit(text) {
|
|
|
|
|
if (!text) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (text.startsWith("//")) {
|
|
|
|
|
text = text.slice(1);
|
|
|
|
|
} else if (text.startsWith("/")) {
|
|
|
|
|
this.executeCommand(text);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let buf = this.state.buffers.get(this.state.activeBuffer);
|
2021-01-21 14:41:44 -05:00
|
|
|
|
if (!buf) {
|
2020-06-18 08:23:08 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-21 14:41:44 -05:00
|
|
|
|
this.privmsg(buf.name, text);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-22 15:43:58 -05:00
|
|
|
|
handleBufferListClick(id) {
|
|
|
|
|
this.switchBuffer(id);
|
2021-05-27 10:35:33 -04:00
|
|
|
|
this.closeBufferList();
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-17 13:33:02 -04:00
|
|
|
|
handleBufferListClose(id) {
|
|
|
|
|
this.close(id);
|
|
|
|
|
this.closeBufferList();
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-27 10:35:33 -04:00
|
|
|
|
toggleBufferList() {
|
|
|
|
|
this.setState((state) => {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let openPanels = {
|
2021-05-27 10:35:33 -04:00
|
|
|
|
...state.openPanels,
|
|
|
|
|
bufferList: !state.openPanels.bufferList,
|
|
|
|
|
};
|
|
|
|
|
return { openPanels };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggleMemberList() {
|
|
|
|
|
this.setState((state) => {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let openPanels = {
|
2021-05-27 10:35:33 -04:00
|
|
|
|
...state.openPanels,
|
|
|
|
|
memberList: !state.openPanels.memberList,
|
|
|
|
|
};
|
|
|
|
|
return { openPanels };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
closeBufferList() {
|
|
|
|
|
this.setState((state) => {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let openPanels = {
|
2021-05-27 10:35:33 -04:00
|
|
|
|
...state.openPanels,
|
|
|
|
|
bufferList: false,
|
|
|
|
|
};
|
|
|
|
|
return { openPanels };
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
closeMemberList() {
|
|
|
|
|
this.setState((state) => {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let openPanels = {
|
2021-05-27 10:35:33 -04:00
|
|
|
|
...state.openPanels,
|
|
|
|
|
memberList: false,
|
|
|
|
|
};
|
|
|
|
|
return { openPanels };
|
|
|
|
|
});
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-05 06:49:56 -04:00
|
|
|
|
handleJoinClick(buf) {
|
|
|
|
|
switch (buf.type) {
|
|
|
|
|
case BufferType.SERVER:
|
|
|
|
|
this.openDialog("join", { server: buf.server });
|
|
|
|
|
break;
|
|
|
|
|
case BufferType.CHANNEL:
|
|
|
|
|
let client = this.clients.get(buf.server);
|
|
|
|
|
client.send({ command: "JOIN", params: [buf.name] });
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-03-08 10:23:16 -05:00
|
|
|
|
}
|
2021-01-22 11:36:53 -05:00
|
|
|
|
|
2021-03-08 10:23:16 -05:00
|
|
|
|
handleJoinSubmit(data) {
|
2021-11-08 04:44:10 -05:00
|
|
|
|
this.open(data.channel, this.state.dialogData.server);
|
2021-07-04 15:41:36 -04:00
|
|
|
|
this.dismissDialog();
|
2020-06-29 04:12:46 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-29 06:36:17 -04:00
|
|
|
|
autocomplete(prefix) {
|
2020-07-13 11:28:49 -04:00
|
|
|
|
function fromList(l, prefix) {
|
|
|
|
|
prefix = prefix.toLowerCase();
|
2021-06-30 16:20:40 -04:00
|
|
|
|
let repl = [];
|
2021-06-10 12:11:11 -04:00
|
|
|
|
for (let item of l) {
|
2020-07-13 11:28:49 -04:00
|
|
|
|
if (item.toLowerCase().startsWith(prefix)) {
|
2021-06-30 16:20:40 -04:00
|
|
|
|
repl.push(item);
|
2020-07-13 11:28:49 -04:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return repl;
|
2020-06-29 06:36:17 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-13 11:28:49 -04:00
|
|
|
|
if (prefix.startsWith("/")) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let repl = fromList(Object.keys(commands), prefix.slice(1));
|
2021-06-30 16:20:40 -04:00
|
|
|
|
return repl.map(cmd => "/" + cmd);
|
2020-07-13 11:28:49 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-23 09:21:24 -04:00
|
|
|
|
// TODO: consider using the CHANTYPES ISUPPORT token here
|
|
|
|
|
if (prefix.startsWith("#")) {
|
|
|
|
|
let chanNames = [];
|
|
|
|
|
for (const buf of this.state.buffers.values()) {
|
|
|
|
|
if (buf.name.startsWith("#")) {
|
|
|
|
|
chanNames.push(buf.name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return fromList(chanNames, prefix);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let buf = this.state.buffers.get(this.state.activeBuffer);
|
2021-01-21 14:41:44 -05:00
|
|
|
|
if (!buf || !buf.members) {
|
2021-06-30 16:20:40 -04:00
|
|
|
|
return [];
|
2020-06-29 06:36:17 -04:00
|
|
|
|
}
|
2020-07-13 11:28:49 -04:00
|
|
|
|
return fromList(buf.members.keys(), prefix);
|
2020-06-29 06:36:17 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-08 11:05:48 -05:00
|
|
|
|
openHelp() {
|
2021-07-04 15:41:36 -04:00
|
|
|
|
this.openDialog("help");
|
2021-03-08 11:05:48 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-29 03:06:47 -04:00
|
|
|
|
handleBufferScrollTop() {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let buf = this.state.buffers.get(this.state.activeBuffer);
|
2021-01-21 14:41:44 -05:00
|
|
|
|
if (!buf || buf.type == BufferType.SERVER) {
|
2020-06-29 03:06:47 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
2021-01-22 11:36:53 -05:00
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = this.clients.get(buf.server);
|
2021-01-22 11:36:53 -05:00
|
|
|
|
|
2021-12-10 09:34:51 -05:00
|
|
|
|
if (!client || !client.caps.enabled.has("draft/chathistory") || !client.caps.enabled.has("server-time")) {
|
2020-06-29 03:06:47 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
2021-01-22 09:49:22 -05:00
|
|
|
|
if (this.endOfHistory.get(buf.id)) {
|
2020-06-29 03:06:47 -04:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let before;
|
2020-06-29 03:06:47 -04:00
|
|
|
|
if (buf.messages.length > 0) {
|
|
|
|
|
before = buf.messages[0].tags["time"];
|
|
|
|
|
} else {
|
|
|
|
|
before = irc.formatDate(new Date());
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-15 12:21:09 -04:00
|
|
|
|
// Avoids sending multiple CHATHISTORY commands in parallel
|
2021-01-22 09:49:22 -05:00
|
|
|
|
this.endOfHistory.set(buf.id, true);
|
2020-07-15 12:21:09 -04:00
|
|
|
|
|
2021-11-03 11:31:12 -04:00
|
|
|
|
let limit = 100;
|
2021-12-10 09:34:51 -05:00
|
|
|
|
if (client.caps.enabled.has("draft/event-playback")) {
|
2021-11-03 11:31:12 -04:00
|
|
|
|
limit = 200;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client.fetchHistoryBefore(buf.name, before, limit).then((result) => {
|
2021-01-23 06:16:57 -05:00
|
|
|
|
this.endOfHistory.set(buf.id, !result.more);
|
2020-07-15 12:21:09 -04:00
|
|
|
|
});
|
2020-06-29 03:06:47 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 15:41:36 -04:00
|
|
|
|
openDialog(name, data) {
|
|
|
|
|
this.setState({ dialog: name, dialogData: data });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dismissDialog() {
|
|
|
|
|
this.setState({ dialog: null, dialogData: null });
|
2021-03-08 10:23:16 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-30 10:05:08 -05:00
|
|
|
|
setDialogLoading(promise) {
|
|
|
|
|
const setLoading = (loading) => {
|
|
|
|
|
this.setState((state) => {
|
|
|
|
|
return { dialogData: { ...state.dialogData, loading } };
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
promise.finally(() => setLoading(false));
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-21 10:40:46 -05:00
|
|
|
|
handleAuthClick(serverID) {
|
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
this.openDialog("auth", { username: client.nick });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleAuthSubmit(username, password) {
|
|
|
|
|
let serverID = State.getActiveServerID(this.state);
|
|
|
|
|
let client = this.clients.get(serverID);
|
2021-11-30 10:05:08 -05:00
|
|
|
|
let promise = client.authenticate("PLAIN", { username, password }).then(() => {
|
|
|
|
|
this.dismissDialog();
|
|
|
|
|
|
2021-11-21 10:40:46 -05:00
|
|
|
|
let firstClient = this.clients.values().next().value;
|
|
|
|
|
if (client !== firstClient) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let autoconnect = store.autoconnect.load();
|
|
|
|
|
if (!autoconnect) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("Saving SASL PLAIN credentials");
|
|
|
|
|
autoconnect = {
|
|
|
|
|
...autoconnect,
|
|
|
|
|
saslPlain: { username, password },
|
|
|
|
|
};
|
|
|
|
|
store.autoconnect.put(autoconnect);
|
|
|
|
|
});
|
2021-11-30 10:05:08 -05:00
|
|
|
|
this.setDialogLoading(promise);
|
2021-11-21 10:40:46 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-16 07:32:54 -05:00
|
|
|
|
handleRegisterClick(serverID) {
|
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
let emailRequired = client.checkAccountRegistrationCap("email-required");
|
|
|
|
|
this.openDialog("register", { emailRequired });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleRegisterSubmit(email, password) {
|
|
|
|
|
let serverID = State.getActiveServerID(this.state);
|
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
// TODO: show registration status (pending/error) in dialog
|
2021-11-30 09:49:52 -05:00
|
|
|
|
let promise = client.registerAccount(email, password).then((data) => {
|
2021-11-16 07:32:54 -05:00
|
|
|
|
this.dismissDialog();
|
|
|
|
|
|
|
|
|
|
if (data.verificationRequired) {
|
2021-11-30 09:13:34 -05:00
|
|
|
|
this.handleVerifyClick(data.account, data.message);
|
2021-11-16 07:32:54 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let firstClient = this.clients.values().next().value;
|
|
|
|
|
if (client !== firstClient) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let autoconnect = store.autoconnect.load();
|
|
|
|
|
if (!autoconnect) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log("Saving account registration credentials");
|
|
|
|
|
autoconnect = {
|
|
|
|
|
...autoconnect,
|
|
|
|
|
saslPlain: { username: data.account, password },
|
|
|
|
|
};
|
|
|
|
|
store.autoconnect.put(autoconnect);
|
|
|
|
|
});
|
2021-11-30 09:49:52 -05:00
|
|
|
|
this.setDialogLoading(promise);
|
2021-11-16 07:32:54 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-11-30 09:13:34 -05:00
|
|
|
|
handleVerifyClick(account, message) {
|
|
|
|
|
this.openDialog("verify", { account, message });
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-16 07:32:54 -05:00
|
|
|
|
handleVerifySubmit(code) {
|
|
|
|
|
let serverID = State.getActiveServerID(this.state);
|
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
// TODO: display verification status (pending/error) in dialog
|
2021-11-30 09:49:52 -05:00
|
|
|
|
let promise = client.verifyAccount(this.state.dialogData.account, code).then(() => {
|
2021-11-16 07:32:54 -05:00
|
|
|
|
this.dismissDialog();
|
|
|
|
|
});
|
2021-11-30 09:49:52 -05:00
|
|
|
|
this.setDialogLoading(promise);
|
2021-11-16 07:32:54 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-08 12:15:04 -05:00
|
|
|
|
handleAddNetworkClick() {
|
2021-07-04 15:41:36 -04:00
|
|
|
|
this.openDialog("network");
|
2021-03-08 12:15:04 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-03 05:46:50 -04:00
|
|
|
|
handleManageNetworkClick(serverID) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let server = this.state.servers.get(serverID);
|
2021-12-07 06:09:10 -05:00
|
|
|
|
let bouncerNetID = server.bouncerNetID;
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let bouncerNetwork = this.state.bouncerNetworks.get(bouncerNetID);
|
2021-07-04 15:41:36 -04:00
|
|
|
|
this.openDialog("network", {
|
|
|
|
|
id: bouncerNetID,
|
|
|
|
|
params: bouncerNetwork,
|
2021-03-09 13:10:22 -05:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-07 13:47:49 -05:00
|
|
|
|
handleNetworkSubmit(attrs, autojoin) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = this.clients.values().next().value;
|
2021-03-09 13:10:22 -05:00
|
|
|
|
|
2021-07-04 15:41:36 -04:00
|
|
|
|
if (this.state.dialogData && this.state.dialogData.id) {
|
2021-03-09 13:10:22 -05:00
|
|
|
|
if (Object.keys(attrs).length == 0) {
|
2021-07-04 15:41:36 -04:00
|
|
|
|
this.dismissDialog();
|
2021-03-09 13:10:22 -05:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client.send({
|
|
|
|
|
command: "BOUNCER",
|
2021-07-04 15:41:36 -04:00
|
|
|
|
params: ["CHANGENETWORK", this.state.dialogData.id, irc.formatTags(attrs)],
|
2021-03-09 13:10:22 -05:00
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
attrs = { ...attrs, tls: "1" };
|
2021-11-07 13:47:49 -05:00
|
|
|
|
client.createBouncerNetwork(attrs).then((id) => {
|
|
|
|
|
if (!autojoin) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// By this point, bouncer-networks-notify should've advertised
|
|
|
|
|
// the new network
|
|
|
|
|
let serverID = this.serverFromBouncerNetwork(id);
|
|
|
|
|
let client = this.clients.get(serverID);
|
|
|
|
|
client.params.autojoin = [autojoin];
|
|
|
|
|
|
|
|
|
|
this.switchToChannel = autojoin;
|
2021-03-09 13:10:22 -05:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-04 15:41:36 -04:00
|
|
|
|
this.dismissDialog();
|
2021-03-09 13:10:22 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleNetworkRemove() {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let client = this.clients.values().next().value;
|
2021-03-09 13:10:22 -05:00
|
|
|
|
|
2021-03-08 12:15:04 -05:00
|
|
|
|
client.send({
|
|
|
|
|
command: "BOUNCER",
|
2021-07-04 15:41:36 -04:00
|
|
|
|
params: ["DELNETWORK", this.state.dialogData.id],
|
2021-03-08 12:15:04 -05:00
|
|
|
|
});
|
2021-03-09 13:10:22 -05:00
|
|
|
|
|
2021-07-04 15:41:36 -04:00
|
|
|
|
this.dismissDialog();
|
2021-03-08 12:15:04 -05:00
|
|
|
|
}
|
|
|
|
|
|
2020-06-18 08:23:08 -04:00
|
|
|
|
componentDidMount() {
|
2020-07-23 03:58:05 -04:00
|
|
|
|
setupKeybindings(this);
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
2021-07-04 15:29:15 -04:00
|
|
|
|
if (this.state.loading) {
|
|
|
|
|
return html`<section id="connect"></section>`;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let activeBuffer = null, activeServer = null, activeBouncerNetwork = null;
|
2021-05-10 09:09:49 -04:00
|
|
|
|
if (this.state.buffers.get(this.state.activeBuffer)) {
|
2020-06-18 08:23:08 -04:00
|
|
|
|
activeBuffer = this.state.buffers.get(this.state.activeBuffer);
|
2021-06-03 05:46:50 -04:00
|
|
|
|
activeServer = this.state.servers.get(activeBuffer.server);
|
2021-03-08 12:15:04 -05:00
|
|
|
|
|
2021-12-07 06:09:10 -05:00
|
|
|
|
let bouncerNetID = activeServer.bouncerNetID;
|
2021-03-10 05:48:58 -05:00
|
|
|
|
if (bouncerNetID) {
|
|
|
|
|
activeBouncerNetwork = this.state.bouncerNetworks.get(bouncerNetID);
|
|
|
|
|
}
|
2020-06-18 08:23:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 05:33:00 -04:00
|
|
|
|
if (this.state.connectForm) {
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let status = activeServer ? activeServer.status : ServerStatus.DISCONNECTED;
|
|
|
|
|
let connecting = status === ServerStatus.CONNECTING || status === ServerStatus.REGISTERING;
|
2021-01-21 13:01:50 -05:00
|
|
|
|
return html`
|
|
|
|
|
<section id="connect">
|
2021-03-09 12:11:59 -05:00
|
|
|
|
<${ConnectForm}
|
|
|
|
|
error=${this.state.error}
|
|
|
|
|
params=${this.state.connectParams}
|
2021-06-10 12:34:34 -04:00
|
|
|
|
auth=${this.config.server.auth}
|
2021-06-06 05:33:00 -04:00
|
|
|
|
connecting=${connecting}
|
2021-03-09 12:11:59 -05:00
|
|
|
|
onSubmit=${this.handleConnectSubmit}
|
|
|
|
|
/>
|
2021-01-21 13:01:50 -05:00
|
|
|
|
</section>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let bufferHeader = null;
|
2020-06-26 06:08:14 -04:00
|
|
|
|
if (activeBuffer) {
|
2021-09-21 07:33:15 -04:00
|
|
|
|
let activeUser = null;
|
|
|
|
|
if (activeBuffer.type == BufferType.NICK) {
|
|
|
|
|
activeUser = activeServer.users.get(activeBuffer.name);
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-26 09:16:07 -04:00
|
|
|
|
bufferHeader = html`
|
|
|
|
|
<section id="buffer-header">
|
2021-03-09 12:11:59 -05:00
|
|
|
|
<${BufferHeader}
|
|
|
|
|
buffer=${activeBuffer}
|
2021-06-03 05:46:50 -04:00
|
|
|
|
server=${activeServer}
|
2021-09-21 07:33:15 -04:00
|
|
|
|
user=${activeUser}
|
2021-03-10 05:48:58 -05:00
|
|
|
|
bouncerNetwork=${activeBouncerNetwork}
|
2021-05-31 22:39:35 -04:00
|
|
|
|
onChannelClick=${this.handleChannelClick}
|
2021-03-09 12:11:59 -05:00
|
|
|
|
onClose=${() => this.close(activeBuffer)}
|
2021-11-05 06:49:56 -04:00
|
|
|
|
onJoin=${() => this.handleJoinClick(activeBuffer)}
|
2021-12-07 07:39:02 -05:00
|
|
|
|
onReconnect=${() => this.reconnect()}
|
2021-03-08 12:15:04 -05:00
|
|
|
|
onAddNetwork=${this.handleAddNetworkClick}
|
2021-06-03 05:46:50 -04:00
|
|
|
|
onManageNetwork=${() => this.handleManageNetworkClick(activeBuffer.server)}
|
2021-03-09 12:11:59 -05:00
|
|
|
|
/>
|
2020-06-25 12:28:54 -04:00
|
|
|
|
</section>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let memberList = null;
|
2020-06-26 08:32:56 -04:00
|
|
|
|
if (activeBuffer && activeBuffer.type == BufferType.CHANNEL) {
|
|
|
|
|
memberList = html`
|
2021-05-27 10:35:33 -04:00
|
|
|
|
<section
|
|
|
|
|
id="member-list"
|
|
|
|
|
class=${this.state.openPanels.memberList ? "expand" : ""}
|
|
|
|
|
>
|
|
|
|
|
<button
|
|
|
|
|
class="expander"
|
|
|
|
|
onClick=${this.toggleMemberList}
|
|
|
|
|
>
|
|
|
|
|
<span></span>
|
|
|
|
|
<span></span>
|
|
|
|
|
</button>
|
|
|
|
|
<section>
|
|
|
|
|
<section id="member-list-header">
|
|
|
|
|
${activeBuffer.members.size} users
|
|
|
|
|
</section>
|
|
|
|
|
<${MemberList}
|
|
|
|
|
members=${activeBuffer.members}
|
2021-09-21 08:19:30 -04:00
|
|
|
|
users=${activeServer.users}
|
2021-05-27 10:35:33 -04:00
|
|
|
|
onNickClick=${this.handleNickClick}
|
|
|
|
|
/>
|
|
|
|
|
</section>
|
2020-06-26 08:32:56 -04:00
|
|
|
|
</section>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let dialog = null;
|
2021-11-08 04:44:10 -05:00
|
|
|
|
let dialogData = this.state.dialogData || {};
|
2021-11-30 09:49:52 -05:00
|
|
|
|
let dialogBody;
|
2021-03-08 10:23:16 -05:00
|
|
|
|
switch (this.state.dialog) {
|
2021-03-09 13:10:22 -05:00
|
|
|
|
case "network":
|
2021-11-08 04:44:10 -05:00
|
|
|
|
let isNew = !dialogData.id;
|
2021-10-13 10:40:34 -04:00
|
|
|
|
let title = isNew ? "Add network" : "Edit network";
|
2021-03-08 12:15:04 -05:00
|
|
|
|
dialog = html`
|
2021-07-04 15:41:36 -04:00
|
|
|
|
<${Dialog} title=${title} onDismiss=${this.dismissDialog}>
|
2021-03-09 13:10:22 -05:00
|
|
|
|
<${NetworkForm}
|
|
|
|
|
onSubmit=${this.handleNetworkSubmit}
|
|
|
|
|
onRemove=${this.handleNetworkRemove}
|
2021-11-08 04:44:10 -05:00
|
|
|
|
params=${dialogData.params}
|
|
|
|
|
autojoin=${dialogData.autojoin}
|
2021-10-13 10:40:34 -04:00
|
|
|
|
isNew=${isNew}
|
2021-03-09 13:10:22 -05:00
|
|
|
|
/>
|
2021-03-08 12:15:04 -05:00
|
|
|
|
</>
|
|
|
|
|
`;
|
|
|
|
|
break;
|
2021-03-08 11:05:48 -05:00
|
|
|
|
case "help":
|
|
|
|
|
dialog = html`
|
2021-07-04 15:41:36 -04:00
|
|
|
|
<${Dialog} title="Help" onDismiss=${this.dismissDialog}>
|
2021-03-08 11:05:48 -05:00
|
|
|
|
<${Help}/>
|
|
|
|
|
</>
|
|
|
|
|
`;
|
|
|
|
|
break;
|
2021-03-08 10:23:16 -05:00
|
|
|
|
case "join":
|
|
|
|
|
dialog = html`
|
2021-07-04 15:41:36 -04:00
|
|
|
|
<${Dialog} title="Join channel" onDismiss=${this.dismissDialog}>
|
2021-11-08 04:44:10 -05:00
|
|
|
|
<${JoinForm} channel=${dialogData.channel} onSubmit=${this.handleJoinSubmit}/>
|
2021-03-08 10:23:16 -05:00
|
|
|
|
</>
|
|
|
|
|
`;
|
|
|
|
|
break;
|
2021-11-21 10:40:46 -05:00
|
|
|
|
case "auth":
|
2021-11-30 10:05:08 -05:00
|
|
|
|
if (dialogData.loading) {
|
|
|
|
|
dialogBody = html`<p>Logging in…</p>`;
|
|
|
|
|
} else {
|
|
|
|
|
dialogBody = html`
|
|
|
|
|
<${AuthForm} username=${dialogData.username} onSubmit=${this.handleAuthSubmit}/>
|
|
|
|
|
`;
|
|
|
|
|
}
|
2021-11-21 10:40:46 -05:00
|
|
|
|
dialog = html`
|
2021-12-07 07:16:07 -05:00
|
|
|
|
<${Dialog} title="Login to ${getServerName(activeServer, activeBouncerNetwork)}" onDismiss=${this.dismissDialog}>
|
2021-11-30 10:05:08 -05:00
|
|
|
|
${dialogBody}
|
2021-11-21 10:40:46 -05:00
|
|
|
|
</>
|
|
|
|
|
`;
|
|
|
|
|
break;
|
2021-11-16 07:32:54 -05:00
|
|
|
|
case "register":
|
2021-11-30 09:49:52 -05:00
|
|
|
|
if (dialogData.loading) {
|
|
|
|
|
dialogBody = html`<p>Creating account…</p>`;
|
|
|
|
|
} else {
|
|
|
|
|
dialogBody = html`
|
|
|
|
|
<${RegisterForm} emailRequired=${dialogData.emailRequired} onSubmit=${this.handleRegisterSubmit}/>
|
|
|
|
|
`;
|
|
|
|
|
}
|
2021-11-16 07:32:54 -05:00
|
|
|
|
dialog = html`
|
2021-12-07 07:16:07 -05:00
|
|
|
|
<${Dialog} title="Register a new ${getServerName(activeServer, activeBouncerNetwork)} account" onDismiss=${this.dismissDialog}>
|
2021-11-30 09:49:52 -05:00
|
|
|
|
${dialogBody}
|
2021-11-16 07:32:54 -05:00
|
|
|
|
</>
|
|
|
|
|
`;
|
|
|
|
|
break;
|
|
|
|
|
case "verify":
|
2021-11-30 09:49:52 -05:00
|
|
|
|
if (dialogData.loading) {
|
|
|
|
|
dialogBody = html`<p>Verifying account…</p>`;
|
|
|
|
|
} else {
|
|
|
|
|
dialogBody = html`
|
|
|
|
|
<${VerifyForm} account=${dialogData.account} message=${dialogData.message} onSubmit=${this.handleVerifySubmit}/>
|
|
|
|
|
`;
|
|
|
|
|
}
|
2021-11-16 07:32:54 -05:00
|
|
|
|
dialog = html`
|
2021-12-07 07:16:07 -05:00
|
|
|
|
<${Dialog} title="Verify ${getServerName(activeServer, activeBouncerNetwork)} account" onDismiss=${this.dismissDialog}>
|
2021-11-30 09:49:52 -05:00
|
|
|
|
${dialogBody}
|
2021-11-16 07:32:54 -05:00
|
|
|
|
</>
|
|
|
|
|
`;
|
2021-11-30 09:49:52 -05:00
|
|
|
|
break;
|
2021-03-08 10:23:16 -05:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let error = null;
|
2021-03-08 09:05:43 -05:00
|
|
|
|
if (this.state.error) {
|
|
|
|
|
error = html`
|
2021-06-22 09:48:42 -04:00
|
|
|
|
<div id="error-msg">
|
2021-03-09 12:11:59 -05:00
|
|
|
|
${this.state.error}
|
2021-03-09 15:47:39 -05:00
|
|
|
|
${" "}
|
2021-11-17 04:12:36 -05:00
|
|
|
|
<button onClick=${this.handleDismissError}>×</button>
|
2021-06-22 09:48:42 -04:00
|
|
|
|
</div>
|
2021-03-08 09:05:43 -05:00
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 12:11:11 -04:00
|
|
|
|
let composerReadOnly = false;
|
2021-06-03 05:46:50 -04:00
|
|
|
|
if (activeServer && activeServer.status !== ServerStatus.REGISTERED) {
|
2021-05-31 06:04:54 -04:00
|
|
|
|
composerReadOnly = true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-20 15:09:10 -05:00
|
|
|
|
let commandOnly = false
|
|
|
|
|
if (activeBuffer && activeBuffer.type === BufferType.SERVER) {
|
|
|
|
|
commandOnly = true
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-18 08:23:08 -04:00
|
|
|
|
return html`
|
2021-05-27 10:35:33 -04:00
|
|
|
|
<section
|
|
|
|
|
id="buffer-list"
|
|
|
|
|
class=${this.state.openPanels.bufferList ? "expand" : ""}
|
|
|
|
|
>
|
2021-03-09 12:11:59 -05:00
|
|
|
|
<${BufferList}
|
|
|
|
|
buffers=${this.state.buffers}
|
2021-06-03 05:46:50 -04:00
|
|
|
|
servers=${this.state.servers}
|
2021-01-22 15:01:03 -05:00
|
|
|
|
bouncerNetworks=${this.state.bouncerNetworks}
|
2021-03-09 12:11:59 -05:00
|
|
|
|
activeBuffer=${this.state.activeBuffer}
|
|
|
|
|
onBufferClick=${this.handleBufferListClick}
|
2021-10-17 13:33:02 -04:00
|
|
|
|
onBufferClose=${this.handleBufferListClose}
|
2021-03-09 12:11:59 -05:00
|
|
|
|
/>
|
2021-05-27 10:35:33 -04:00
|
|
|
|
<button
|
|
|
|
|
class="expander"
|
|
|
|
|
onClick=${this.toggleBufferList}
|
|
|
|
|
>
|
|
|
|
|
<span></span>
|
|
|
|
|
<span></span>
|
|
|
|
|
</button>
|
2020-06-18 08:23:08 -04:00
|
|
|
|
</section>
|
2020-06-26 09:16:07 -04:00
|
|
|
|
${bufferHeader}
|
2021-03-09 12:11:59 -05:00
|
|
|
|
<${ScrollManager}
|
|
|
|
|
target=${this.buffer}
|
|
|
|
|
stickTo=".logline"
|
|
|
|
|
scrollKey=${this.state.activeBuffer}
|
|
|
|
|
onScrollTop=${this.handleBufferScrollTop}
|
|
|
|
|
>
|
2021-06-22 08:44:20 -04:00
|
|
|
|
<section id="buffer" ref=${this.buffer} tabindex="-1">
|
2021-05-31 22:39:35 -04:00
|
|
|
|
<${Buffer}
|
|
|
|
|
buffer=${activeBuffer}
|
2021-06-11 05:18:29 -04:00
|
|
|
|
server=${activeServer}
|
2021-11-16 07:32:54 -05:00
|
|
|
|
bouncerNetwork=${activeBouncerNetwork}
|
2021-05-31 22:39:35 -04:00
|
|
|
|
onChannelClick=${this.handleChannelClick}
|
2021-11-21 10:40:46 -05:00
|
|
|
|
onNickClick=${this.handleNickClick}
|
|
|
|
|
onAuthClick=${() => this.handleAuthClick(activeBuffer.server)}
|
2021-11-16 07:32:54 -05:00
|
|
|
|
onRegisterClick=${() => this.handleRegisterClick(activeBuffer.server)}
|
2021-11-30 09:13:34 -05:00
|
|
|
|
onVerifyClick=${this.handleVerifyClick}
|
2021-11-21 10:40:46 -05:00
|
|
|
|
/>
|
2020-06-25 06:03:05 -04:00
|
|
|
|
</section>
|
|
|
|
|
</>
|
2020-06-26 08:32:56 -04:00
|
|
|
|
${memberList}
|
2021-03-09 12:11:59 -05:00
|
|
|
|
<${Composer}
|
|
|
|
|
ref=${this.composer}
|
2021-05-31 06:04:54 -04:00
|
|
|
|
readOnly=${composerReadOnly}
|
2021-03-09 12:11:59 -05:00
|
|
|
|
onSubmit=${this.handleComposerSubmit}
|
|
|
|
|
autocomplete=${this.autocomplete}
|
2021-12-20 15:09:10 -05:00
|
|
|
|
commandOnly=${commandOnly}
|
2021-03-09 12:11:59 -05:00
|
|
|
|
/>
|
2021-03-08 10:23:16 -05:00
|
|
|
|
${dialog}
|
2021-03-08 09:05:43 -05:00
|
|
|
|
${error}
|
2020-06-18 08:23:08 -04:00
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
}
|