diff --git a/.eslintignore b/.eslintignore
index f47503b7..d4e5515c 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
public/
coverage/
src/dist/
+dist/
diff --git a/.gitignore b/.gitignore
index 8a787eea..6371eb74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ coverage/
public/
client/dist
src/dist
+dist/
diff --git a/.prettierignore b/.prettierignore
index 3f173ad3..47a38736 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -4,6 +4,7 @@ test/fixtures/.thelounge/logs/
test/fixtures/.thelounge/certificates/
test/fixtures/.thelounge/storage/
src/dist/
+dist/
*.log
*.png
*.svg
diff --git a/client/components/NetworkList.vue b/client/components/NetworkList.vue
index 67efc352..915cda1b 100644
--- a/client/components/NetworkList.vue
+++ b/client/components/NetworkList.vue
@@ -195,7 +195,7 @@
}
-
diff --git a/client/js/autocompletion.ts b/client/js/autocompletion.ts
index d67a3743..9a12a62c 100644
--- a/client/js/autocompletion.ts
+++ b/client/js/autocompletion.ts
@@ -274,10 +274,6 @@ function fuzzyGrep(term: string, array: Array) {
}
function rawNicks() {
- if (!store.state.activeChannel) {
- return [];
- }
-
if (store.state.activeChannel.channel.users.length > 0) {
const users = store.state.activeChannel.channel.users.slice();
diff --git a/client/js/commands/collapse.ts b/client/js/commands/collapse.ts
index 7c8cf6d7..a2a8581c 100644
--- a/client/js/commands/collapse.ts
+++ b/client/js/commands/collapse.ts
@@ -4,7 +4,7 @@ import store from "../store";
function input() {
const messageIds = [];
- for (const message of store.state.activeChannel.channel.messages) {
+ for (const message of store.state.activeChannel?.channel.messages) {
let toggled = false;
for (const preview of message.previews) {
@@ -22,7 +22,7 @@ function input() {
// Tell the server we're toggling so it remembers at page reload
if (!document.body.classList.contains("public") && messageIds.length > 0) {
socket.emit("msg:preview:toggle", {
- target: store.state.activeChannel.channel.id,
+ target: store.state.activeChannel?.channel.id,
messageIds: messageIds,
shown: false,
});
diff --git a/client/js/commands/expand.ts b/client/js/commands/expand.ts
index 6942d795..d8829594 100644
--- a/client/js/commands/expand.ts
+++ b/client/js/commands/expand.ts
@@ -22,7 +22,7 @@ function input() {
// Tell the server we're toggling so it remembers at page reload
if (!document.body.classList.contains("public") && messageIds.length > 0) {
socket.emit("msg:preview:toggle", {
- target: store.state.activeChannel.channel.id,
+ target: store.state.activeChannel?.channel.id,
messageIds: messageIds,
shown: true,
});
diff --git a/client/js/commands/index.ts b/client/js/commands/index.ts
index 05dbafda..247ed3e3 100644
--- a/client/js/commands/index.ts
+++ b/client/js/commands/index.ts
@@ -4,9 +4,9 @@
// directory, so we iterate over its content, which is a map statically built by
// Webpack.
// Second argument says it's recursive, third makes sure we only load javascript.
-const commands = require.context("./", true, /\.js$/);
+const commands = require.context("./", true, /\.ts$/);
-export default commands.keys().reduce((acc, path) => {
+export default commands.keys().reduce>((acc, path) => {
const command = path.substring(2, path.length - 3);
if (command === "index") {
diff --git a/client/js/helpers/contextMenu.ts b/client/js/helpers/contextMenu.ts
index 637267fc..1d75b29b 100644
--- a/client/js/helpers/contextMenu.ts
+++ b/client/js/helpers/contextMenu.ts
@@ -1,6 +1,7 @@
import socket from "../socket";
import eventbus from "../eventbus";
-
+import type {ClientChan, ClientNetwork} from "../types";
+import type {Methods} from "../vue";
type ContextMenuItem =
| ({
label: string;
@@ -18,7 +19,11 @@ type ContextMenuItem =
type: "divider";
};
-export function generateChannelContextMenu($root, channel, network) {
+export function generateChannelContextMenu(
+ $root: Methods,
+ channel: ClientChan,
+ network: ClientNetwork
+) {
const typeMap = {
lobby: "network",
channel: "chan",
diff --git a/client/js/helpers/ircmessageparser/anyIntersection.ts b/client/js/helpers/ircmessageparser/anyIntersection.ts
index 8d773ae7..cf2fd156 100644
--- a/client/js/helpers/ircmessageparser/anyIntersection.ts
+++ b/client/js/helpers/ircmessageparser/anyIntersection.ts
@@ -1,8 +1,9 @@
-import {ParsedStyle} from "./parseStyle";
-
// Return true if any section of "a" or "b" parts (defined by their start/end
+
+import {Part} from "./merge";
+
// markers) intersect each other, false otherwise.
-function anyIntersection(a: ParsedStyle, b: ParsedStyle) {
+function anyIntersection(a: Part, b: Part) {
return (
(a.start <= b.start && b.start < a.end) ||
(a.start < b.end && b.end <= a.end) ||
diff --git a/client/js/helpers/ircmessageparser/cleanIrcMessage.ts b/client/js/helpers/ircmessageparser/cleanIrcMessage.ts
index 7e4fdf35..3b79d2b8 100644
--- a/client/js/helpers/ircmessageparser/cleanIrcMessage.ts
+++ b/client/js/helpers/ircmessageparser/cleanIrcMessage.ts
@@ -1,4 +1,5 @@
const matchFormatting =
+ // eslint-disable-next-line no-control-regex
/\x02|\x1D|\x1F|\x16|\x0F|\x11|\x1E|\x03(?:[0-9]{1,2}(?:,[0-9]{1,2})?)?|\x04(?:[0-9a-f]{6}(?:,[0-9a-f]{6})?)?/gi;
export default (message: string) => message.replace(matchFormatting, "").trim();
diff --git a/client/js/helpers/ircmessageparser/fill.ts b/client/js/helpers/ircmessageparser/fill.ts
index a36262ac..c9e613f1 100644
--- a/client/js/helpers/ircmessageparser/fill.ts
+++ b/client/js/helpers/ircmessageparser/fill.ts
@@ -1,15 +1,16 @@
-import {ParsedStyle} from "./parseStyle";
-
// Create plain text entries corresponding to areas of the text that match no
// existing entries. Returns an empty array if all parts of the text have been
+
+import {Part} from "./merge";
+
// parsed into recognizable entries already.
-function fill(existingEntries: ParsedStyle[], text: string) {
+function fill(existingEntries: Part[], text: string) {
let position = 0;
// Fill inner parts of the text. For example, if text is `foobarbaz` and both
// `foo` and `baz` have matched into an entry, this will return a dummy entry
// corresponding to `bar`.
- const result = existingEntries.reduce((acc: Omit[], textSegment) => {
+ const result = existingEntries.reduce((acc, textSegment) => {
if (textSegment.start > position) {
acc.push({
start: position,
diff --git a/client/js/helpers/ircmessageparser/findChannels.ts b/client/js/helpers/ircmessageparser/findChannels.ts
index 809863d6..0206929f 100644
--- a/client/js/helpers/ircmessageparser/findChannels.ts
+++ b/client/js/helpers/ircmessageparser/findChannels.ts
@@ -2,13 +2,14 @@
// ")", "[", "]", "{", "}", and "|" in string.
// See https://lodash.com/docs/#escapeRegExp
import escapeRegExp from "lodash/escapeRegExp";
+import {Part} from "./merge";
// Given an array of channel prefixes (such as "#" and "&") and an array of user
// modes (such as "@" and "+"), this function extracts channels and nicks from a
// text.
// It returns an array of objects for each channel found with their start index,
// end index and channel name.
-function findChannels(text, channelPrefixes, userModes) {
+function findChannels(text: string, channelPrefixes: string[], userModes: string[]) {
// `userModePattern` is necessary to ignore user modes in /whois responses.
// For example, a voiced user in #thelounge will have a /whois response of:
// > foo is on the following channels: +#thelounge
@@ -18,7 +19,7 @@ function findChannels(text, channelPrefixes, userModes) {
const channelPattern = `(?:^|\\s)[${userModePattern}]*([${channelPrefixPattern}][^ \u0007]+)`;
const channelRegExp = new RegExp(channelPattern, "g");
- const result = [];
+ const result: ChannelPart[] = [];
let match;
do {
@@ -38,4 +39,8 @@ function findChannels(text, channelPrefixes, userModes) {
return result;
}
+export type ChannelPart = Part & {
+ channel: string;
+};
+
export default findChannels;
diff --git a/client/js/helpers/ircmessageparser/findEmoji.ts b/client/js/helpers/ircmessageparser/findEmoji.ts
index 14e8caa9..57e8bdef 100644
--- a/client/js/helpers/ircmessageparser/findEmoji.ts
+++ b/client/js/helpers/ircmessageparser/findEmoji.ts
@@ -1,10 +1,13 @@
-const emojiRegExp = require("emoji-regex")();
+import emojiRegExp from "emoji-regex";
+import {Part} from "./merge";
-function findEmoji(text) {
- const result = [];
+const regExp = emojiRegExp();
+
+function findEmoji(text: string) {
+ const result: EmojiPart[] = [];
let match;
- while ((match = emojiRegExp.exec(text))) {
+ while ((match = regExp.exec(text))) {
result.push({
start: match.index,
end: match.index + match[0].length,
@@ -15,4 +18,8 @@ function findEmoji(text) {
return result;
}
+export type EmojiPart = Part & {
+ emoji: string;
+};
+
export default findEmoji;
diff --git a/client/js/helpers/ircmessageparser/findLinks.ts b/client/js/helpers/ircmessageparser/findLinks.ts
index 9adb0627..95f94029 100644
--- a/client/js/helpers/ircmessageparser/findLinks.ts
+++ b/client/js/helpers/ircmessageparser/findLinks.ts
@@ -1,4 +1,5 @@
import LinkifyIt, {Match} from "linkify-it";
+import {Part} from "./merge";
type OurMatch = Match & {
noschema?: boolean;
@@ -24,7 +25,8 @@ LinkifyIt.prototype.normalize = function normalize(match: OurMatch) {
}
};
-const linkify = LinkifyIt().tlds(require("tlds")).tlds("onion", true);
+import tlds from "tlds";
+const linkify = LinkifyIt().tlds(tlds).tlds("onion", true);
// Known schemes to detect in text
const commonSchemes = [
@@ -68,7 +70,7 @@ function findLinksWithSchema(text: string) {
return matches.filter((url) => !url.noschema).map(returnUrl);
}
-function returnUrl(url: OurMatch) {
+function returnUrl(url: OurMatch): LinkPart {
return {
start: url.index,
end: url.lastIndex,
@@ -76,4 +78,8 @@ function returnUrl(url: OurMatch) {
};
}
+export type LinkPart = Part & {
+ link: string;
+};
+
export {findLinks, findLinksWithSchema};
diff --git a/client/js/helpers/ircmessageparser/findNames.ts b/client/js/helpers/ircmessageparser/findNames.ts
index 5c683150..be16f7e5 100644
--- a/client/js/helpers/ircmessageparser/findNames.ts
+++ b/client/js/helpers/ircmessageparser/findNames.ts
@@ -1,17 +1,23 @@
+import {Part} from "./merge";
+
const nickRegExp = /([\w[\]\\`^{|}-]+)/g;
-function findNames(text, users) {
- const result = [];
+export type NamePart = Part & {
+ nick: string;
+};
+
+function findNames(text: string, nicks: string[]): NamePart[] {
+ const result: NamePart[] = [];
// Return early if we don't have any nicknames to find
- if (users.length === 0) {
+ if (nicks.length === 0) {
return result;
}
let match;
while ((match = nickRegExp.exec(text))) {
- if (users.indexOf(match[1]) > -1) {
+ if (nicks.indexOf(match[1]) > -1) {
result.push({
start: match.index,
end: match.index + match[1].length,
diff --git a/client/js/helpers/ircmessageparser/merge.ts b/client/js/helpers/ircmessageparser/merge.ts
index 8f83763a..82873ba5 100644
--- a/client/js/helpers/ircmessageparser/merge.ts
+++ b/client/js/helpers/ircmessageparser/merge.ts
@@ -1,8 +1,17 @@
import anyIntersection from "./anyIntersection";
import fill from "./fill";
+import {ChannelPart} from "./findChannels";
+import {EmojiPart} from "./findEmoji";
+import {NamePart} from "./findNames";
+
+type TextPart = Part & {
+ text: string;
+};
+
+type Fragment = TextPart;
// Merge text part information within a styling fragment
-function assign(textPart, fragment) {
+function assign(textPart: Part, fragment: Fragment) {
const fragStart = fragment.start;
const start = Math.max(fragment.start, textPart.start);
const end = Math.min(fragment.end, textPart.end);
@@ -11,10 +20,20 @@ function assign(textPart, fragment) {
return Object.assign({}, fragment, {start, end, text});
}
-function sortParts(a, b) {
+function sortParts(a: Part, b: Part) {
return a.start - b.start || b.end - a.end;
}
+export type Part = {
+ start: number;
+ end: number;
+ fragments?: Fragment;
+};
+
+type MergedPart = TextPart | NamePart | EmojiPart | ChannelPart;
+
+type MergedPartWithFragments = MergedPart & {fragments: Fragment[]};
+
// Merge the style fragments within the text parts, taking into account
// boundaries and text sections that have not matched to links or channels.
// For example, given a string "foobar" where "foo" and "bar" have been
@@ -22,9 +41,13 @@ function sortParts(a, b) {
// different styles, the first resulting part will contain fragments "fo" and
// "o", and the second resulting part will contain "b" and "ar". "o" and "b"
// fragments will contain duplicate styling attributes.
-function merge(textParts, styleFragments, cleanText) {
+function merge(
+ textParts: MergedPart[],
+ styleFragments: Fragment[],
+ cleanText: string
+): MergedPart[] {
// Remove overlapping parts
- textParts = textParts.sort(sortParts).reduce((prev, curr) => {
+ textParts = textParts.sort(sortParts).reduce((prev, curr) => {
const intersection = prev.some((p) => anyIntersection(p, curr));
if (intersection) {
@@ -37,11 +60,14 @@ function merge(textParts, styleFragments, cleanText) {
// Every section of the original text that has not been captured in a "part"
// is filled with "text" parts, dummy objects with start/end but no extra
// metadata.
- const allParts = textParts.concat(fill(textParts, cleanText)).sort(sortParts); // Sort all parts identified based on their position in the original text
+
+ const filled = fill(textParts, cleanText) as TextPart[];
+ const allParts: MergedPart[] = [...textParts, ...filled].sort(sortParts); // Sort all parts identified based on their position in the original text
// Distribute the style fragments within the text parts
return allParts.map((textPart) => {
- textPart.fragments = styleFragments
+ // TODO: remove any type casting.
+ (textPart as any).fragments = styleFragments
.filter((fragment) => anyIntersection(textPart, fragment))
.map((fragment) => assign(textPart, fragment));
diff --git a/client/js/helpers/parse.ts b/client/js/helpers/parse.ts
index c604c570..1d876a69 100644
--- a/client/js/helpers/parse.ts
+++ b/client/js/helpers/parse.ts
@@ -9,11 +9,17 @@ import LinkPreviewToggle from "../../components/LinkPreviewToggle.vue";
import LinkPreviewFileSize from "../../components/LinkPreviewFileSize.vue";
import InlineChannel from "../../components/InlineChannel.vue";
import Username from "../../components/Username.vue";
+import {VNode} from "vue";
+import Network from "src/models/network";
+import {Message} from "src/models/msg";
const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]|\u{fe0f}/gu;
+type createElement = (tag: string, props: any, children: any) => VNode;
+
// Create an HTML `span` with styling information for a given fragment
-function createFragment(fragment, createElement) {
+// TODO: remove any
+function createFragment(fragment: Record, createElement: createElement) {
const classes = [];
if (fragment.bold) {
@@ -44,7 +50,7 @@ function createFragment(fragment, createElement) {
classes.push("irc-monospace");
}
- const data = {};
+ const data = {} as any;
let hasData = false;
if (classes.length > 0) {
@@ -68,7 +74,7 @@ function createFragment(fragment, createElement) {
// Transform an IRC message potentially filled with styling control codes, URLs,
// nicknames, and channels into a string of HTML elements to display on the client.
-function parse(createElement, text, message = undefined, network = undefined) {
+function parse(createElement: createElement, text: string, message?: Message, network?: Network) {
// Extract the styling information and get the plain text version from it
const styleFragments = parseStyle(text);
const cleanText = styleFragments.map((fragment) => fragment.text).join("");
@@ -76,14 +82,15 @@ function parse(createElement, text, message = undefined, network = undefined) {
// On the plain text, find channels and URLs, returned as "parts". Parts are
// arrays of objects containing start and end markers, as well as metadata
// depending on what was found (channel or link).
- const channelPrefixes = network ? network.serverOptions.CHANTYPES : ["#", "&"];
+ const channelPrefixes = network?.serverOptions?.CHANTYPES || ["#", "&"];
const userModes = network?.serverOptions?.PREFIX.symbols || ["!", "@", "%", "+"];
const channelParts = findChannels(cleanText, channelPrefixes, userModes);
const linkParts = findLinks(cleanText);
const emojiParts = findEmoji(cleanText);
- const nameParts = findNames(cleanText, message ? message.users || [] : []);
+ // TODO: remove type casting.
+ const nameParts = findNames(cleanText, message ? (message.users as string[]) || [] : []);
- const parts = channelParts.concat(linkParts).concat(emojiParts).concat(nameParts);
+ const parts = [...channelParts, ...linkParts, ...emojiParts, ...nameParts];
// The channel the message belongs to might not exist if the user isn't joined to it.
const messageChannel = message ? message.channel : null;
@@ -91,12 +98,12 @@ function parse(createElement, text, message = undefined, network = undefined) {
// Merge the styling information with the channels / URLs / nicks / text objects and
// generate HTML strings with the resulting fragments
return merge(parts, styleFragments, cleanText).map((textPart) => {
- const fragments = textPart.fragments.map((fragment) =>
+ const fragments = textPart.fragments?.map((fragment) =>
createFragment(fragment, createElement)
);
// Wrap these potentially styled fragments with links and channel buttons
- if (textPart.link) {
+ if ("link" in textPart) {
const preview =
message &&
message.previews &&
diff --git a/client/js/helpers/parseIrcUri.ts b/client/js/helpers/parseIrcUri.ts
index 8066651c..22ed3c56 100644
--- a/client/js/helpers/parseIrcUri.ts
+++ b/client/js/helpers/parseIrcUri.ts
@@ -1,4 +1,4 @@
-export default (stringUri) => {
+export default (stringUri: string) => {
const data = {};
try {
diff --git a/client/js/helpers/roundBadgeNumber.ts b/client/js/helpers/roundBadgeNumber.ts
index 74a777f7..dd7b6fdc 100644
--- a/client/js/helpers/roundBadgeNumber.ts
+++ b/client/js/helpers/roundBadgeNumber.ts
@@ -1,4 +1,4 @@
-export default (count) => {
+export default (count: number) => {
if (count < 1000) {
return count.toString();
}
diff --git a/client/js/socket-events/auth.ts b/client/js/socket-events/auth.ts
index 0d0037dd..248f8a3b 100644
--- a/client/js/socket-events/auth.ts
+++ b/client/js/socket-events/auth.ts
@@ -5,6 +5,12 @@ import store from "../store";
import location from "../location";
let lastServerHash = null;
+declare global {
+ interface Window {
+ g_TheLoungeRemoveLoading: () => void;
+ }
+}
+
socket.on("auth:success", function () {
store.commit("currentUserVisibleError", "Loading messages…");
updateLoadingMessage();
@@ -83,10 +89,10 @@ function showSignIn() {
}
}
-function reloadPage(message) {
+function reloadPage(message: string) {
socket.disconnect();
store.commit("currentUserVisibleError", message);
- location.reload(true);
+ location.reload();
}
function updateLoadingMessage() {
diff --git a/client/js/socket.ts b/client/js/socket.ts
index 7c6068ed..ac08346b 100644
--- a/client/js/socket.ts
+++ b/client/js/socket.ts
@@ -1,6 +1,6 @@
import io, {Socket} from "socket.io-client";
-const socket = io({
+const socket: Socket = io({
transports: JSON.parse(document.body.dataset.transports || "['polling', 'websocket']"),
path: window.location.pathname + "socket.io/",
autoConnect: false,
diff --git a/client/js/store.ts b/client/js/store.ts
index 168992b7..9df4b138 100644
--- a/client/js/store.ts
+++ b/client/js/store.ts
@@ -2,6 +2,7 @@ import Vue from "vue";
import Vuex from "vuex";
import {createSettingsStore} from "./store-settings";
import storage from "./localStorage";
+import {ClientChan, ClientNetwork} from "./types";
const appName = document.title;
@@ -21,15 +22,15 @@ function detectDesktopNotificationState() {
export type State = {
appLoaded: boolean;
- activeChannel?: {
- network: Network;
+ activeChannel: {
+ network: ClientNetwork;
channel: ClientChan;
};
currentUserVisibleError: string | null;
desktopNotificationState: "granted" | "blocked" | "nohttps" | "unsupported";
isAutoCompleting: boolean;
isConnected: boolean;
- networks: Network[];
+ networks: ClientNetwork[];
// TODO: type
mentions: any[];
hasServiceWorker: boolean;
diff --git a/client/index.ts b/client/js/types.d.ts
similarity index 55%
rename from client/index.ts
rename to client/js/types.d.ts
index 9a61ce27..23947726 100644
--- a/client/index.ts
+++ b/client/js/types.d.ts
@@ -1,4 +1,5 @@
-import Chan from "../src/models/chan";
+import Chan from "../../src/models/chan";
+import Network from "../../src/models/network";
declare module "*.vue" {
import Vue from "vue";
@@ -10,4 +11,9 @@ interface LoungeWindow extends Window {
type ClientChan = Chan & {
moreHistoryAvailable: boolean;
+ editTopic: boolean;
+};
+
+type ClientNetwork = Network & {
+ isJoinChannelShown: boolean;
};
diff --git a/client/js/vue.ts b/client/js/vue.ts
index 9713ef22..80833412 100644
--- a/client/js/vue.ts
+++ b/client/js/vue.ts
@@ -12,22 +12,31 @@ import eventbus from "./eventbus";
import "./socket-events";
import "./webpush";
import "./keybinds";
+import {ClientChan} from "./types";
const favicon = document.getElementById("favicon");
const faviconNormal = favicon?.getAttribute("href") || "";
const faviconAlerted = favicon?.dataset.other || "";
-new Vue({
+type Data = {};
+export type Methods = {
+ switchToChannel: (channel: ClientChan) => void;
+ closeChannel: (channel: ClientChan) => void;
+};
+type Computed = {};
+type Props = {};
+
+new Vue({
el: "#viewport",
router,
mounted() {
socket.open();
},
methods: {
- switchToChannel(channel: Channel) {
+ switchToChannel(channel: ClientChan) {
navigate("RoutedChat", {id: channel.id});
},
- closeChannel(channel: Channel) {
+ closeChannel(channel: ClientChan) {
if (channel.type === "lobby") {
eventbus.emit(
"confirm-dialog",
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 80b40556..d2453859 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -6,6 +6,7 @@
"files": [
"../package.json"
] /* If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. When a 'files' property is specified, only those files and those specified by 'include' are included. */,
+ // "exclude": [],
"compilerOptions": {
"sourceMap": false /*Create source map files for emitted JavaScript files. See more: https://www.typescriptlang.org/tsconfig#sourceMap */,
"jsx": "preserve" /* Specify what JSX code is generated. */,
@@ -19,6 +20,8 @@
"module": "es2015",
"moduleResolution": "node",
+ // TODO: Remove eventually, this is due to typescript checking vue files that don't have lang="ts".
+ "checkJs": false,
// TODO: Remove eventually
"noImplicitAny": false /*Enable error reporting for expressions and declarations with an implied any type. See more: https://www.typescriptlang.org/tsconfig#noImplicitAny */
} /* Instructs the TypeScript compiler how to compile .ts files. */
diff --git a/package.json b/package.json
index a235c2d1..f1fbc3e4 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"lint:tsc": "tsc --noEmit",
"start": "node src/dist/src/index start",
"test": "run-p --aggregate-output --continue-on-error lint:* test:*",
- "test:mocha": "NODE_ENV=test webpack --mode=development && nyc --nycrc-path=test/.nycrc-mocha.json mocha --colors --config=test/.mocharc.yml",
+ "test:mocha": "NODE_ENV=test webpack --mode=development && nyc --nycrc-path=test/.nycrc-mocha.json mocha --require ts-node/register --colors --config=test/.mocharc.yml",
"watch": "webpack --watch"
},
"keywords": [
diff --git a/src/client.ts b/src/client.ts
index c45f3c0b..7ebae370 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -17,7 +17,7 @@ import SqliteMessageStorage from "./plugins/messageStorage/sqlite";
import TextFileMessageStorage from "./plugins/messageStorage/text";
import Network, {NetworkWithIrcFramework} from "./models/network";
import ClientManager from "./clientManager";
-import {MessageStorage} from "./types/plugins/messageStorage";
+import {MessageStorage, SearchQuery, SearchResponse} from "./plugins/messageStorage/types";
const events = [
"away",
@@ -598,9 +598,15 @@ class Client {
}
}
- search(query: string) {
+ search(query: SearchQuery): Promise {
if (this.messageProvider === undefined) {
- return Promise.resolve([]);
+ return Promise.resolve({
+ results: [],
+ target: "",
+ networkUuid: "",
+ offset: 0,
+ searchTerm: query?.searchTerm,
+ });
}
return this.messageProvider.search(query);
diff --git a/src/plugins/inputs/index.ts b/src/plugins/inputs/index.ts
index 72036491..6d333b6a 100644
--- a/src/plugins/inputs/index.ts
+++ b/src/plugins/inputs/index.ts
@@ -1,6 +1,7 @@
import Client from "../../client";
import Chan, {Channel} from "../../models/chan";
import Network, {NetworkWithIrcFramework} from "../../models/network";
+import {PackageInfo} from "../packages";
export type PluginInputHandler = (
this: Client,
@@ -97,7 +98,7 @@ const getCommands = () =>
.concat(passThroughCommands)
.sort();
-const addPluginCommand = (packageInfo, command, func) => {
+const addPluginCommand = (packageInfo: PackageInfo, command, func) => {
func.packageInfo = packageInfo;
pluginCommands.set(command, func);
};
diff --git a/src/plugins/messageStorage/sqlite.ts b/src/plugins/messageStorage/sqlite.ts
index cd20cc64..518533d0 100644
--- a/src/plugins/messageStorage/sqlite.ts
+++ b/src/plugins/messageStorage/sqlite.ts
@@ -7,8 +7,9 @@ import Config from "../../config";
import Msg, {Message} from "../../models/msg";
import Client from "../../client";
import Chan, {Channel} from "../../models/chan";
-import type {SqliteMessageStorage as ISqliteMessageStorage} from "../../types/plugins/messageStorage";
+import type {SearchResponse, SqliteMessageStorage as ISqliteMessageStorage} from "./types";
import Network from "../../models/network";
+import {SearchQuery} from "./types";
// TODO; type
let sqlite3: any;
@@ -209,9 +210,15 @@ class SqliteMessageStorage implements ISqliteMessageStorage {
}) as Promise;
}
- search(query: {searchTerm: string; networkUuid: string; channelName: string; offset: string}) {
+ search(query: SearchQuery): Promise {
if (!this.isEnabled) {
- return Promise.resolve([]);
+ return Promise.resolve({
+ results: [],
+ target: "",
+ networkUuid: "",
+ offset: 0,
+ searchTerm: query?.searchTerm,
+ });
}
// Using the '@' character to escape '%' and '_' in patterns.
@@ -243,7 +250,7 @@ class SqliteMessageStorage implements ISqliteMessageStorage {
if (err) {
reject(err);
} else {
- const response = {
+ const response: SearchResponse = {
searchTerm: query.searchTerm,
target: query.channelName,
networkUuid: query.networkUuid,
@@ -263,7 +270,8 @@ class SqliteMessageStorage implements ISqliteMessageStorage {
export default SqliteMessageStorage;
-function parseSearchRowsToMessages(id, rows) {
+// TODO: type any
+function parseSearchRowsToMessages(id: string, rows: any[]) {
const messages: Msg[] = [];
for (const row of rows) {
diff --git a/src/plugins/messageStorage/text.ts b/src/plugins/messageStorage/text.ts
index 2010aebf..b2199ca9 100644
--- a/src/plugins/messageStorage/text.ts
+++ b/src/plugins/messageStorage/text.ts
@@ -4,7 +4,7 @@ import filenamify from "filenamify";
import log from "../../log";
import Config from "../../config";
-import {MessageStorage} from "../../types/plugins/messageStorage";
+import {MessageStorage} from "./types";
import Client from "../../client";
import Channel from "../../models/chan";
import {Message, MessageType} from "../../models/msg";
diff --git a/src/types/plugins/messageStorage/index.d.ts b/src/plugins/messageStorage/types.d.ts
similarity index 65%
rename from src/types/plugins/messageStorage/index.d.ts
rename to src/plugins/messageStorage/types.d.ts
index 1e0ddea5..3dd32ce6 100644
--- a/src/types/plugins/messageStorage/index.d.ts
+++ b/src/plugins/messageStorage/types.d.ts
@@ -22,6 +22,22 @@ interface MessageStorage {
canProvideMessages(): boolean;
}
+export type SearchQuery = {
+ searchTerm: string;
+ networkUuid: string;
+ channelName: string;
+ offset: string;
+};
+
+export type SearchResponse = Omit & {
+ results: Message[];
+ target: string;
+ offset: number;
+};
+
+type SearchFunction = (query: SearchQuery) => Promise;
+
export interface SqliteMessageStorage extends MessageStorage {
database: Database;
+ search: SearchFunction;
}
diff --git a/src/plugins/packages/index.ts b/src/plugins/packages/index.ts
index d01edc92..195663f9 100644
--- a/src/plugins/packages/index.ts
+++ b/src/plugins/packages/index.ts
@@ -12,12 +12,14 @@ import fs from "fs";
import Utils from "../../command-line/utils";
import Client from "../../client";
-type PackageInfo = {
+export type PackageInfo = {
packageName: string;
thelounge?: {supports: any};
version: string;
type?: string;
files?: string[];
+ // Legacy support
+ name?: string;
};
const stylesheets: string[] = [];
diff --git a/src/plugins/packages/publicClient.ts b/src/plugins/packages/publicClient.ts
index b9267f08..109b1c4d 100644
--- a/src/plugins/packages/publicClient.ts
+++ b/src/plugins/packages/publicClient.ts
@@ -1,12 +1,13 @@
+import {PackageInfo} from "./index";
import Client from "../../client";
import Chan from "../../models/chan";
import Msg, {MessageType, UserInMessage} from "../../models/msg";
export default class PublicClient {
private client: Client;
- private packageInfo: any;
+ private packageInfo: PackageInfo;
- constructor(client, packageInfo) {
+ constructor(client: Client, packageInfo: PackageInfo) {
this.client = client;
this.packageInfo = packageInfo;
}
@@ -24,7 +25,7 @@ export default class PublicClient {
*
* @param {Object} attributes
*/
- createChannel(attributes) {
+ createChannel(attributes: Partial) {
return this.client.createChannel(attributes);
}
diff --git a/src/tsconfig.json b/src/tsconfig.json
index 9b88fea2..771a9e6a 100644
--- a/src/tsconfig.json
+++ b/src/tsconfig.json
@@ -1,13 +1,13 @@
{
"extends": "../tsconfig.base.json" /* Path to base configuration file to inherit from. Requires TypeScript version 2.1 or later. */,
"include": [
- "**/*"
+ "**/*",
+ "../client/js/helpers/ircmessageparser/*.ts"
] /* Specifies a list of glob patterns that match files to be included in compilation. If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. Requires TypeScript version 2.0 or later. */,
"files": [
- "../babel.config.cjs",
"../client/js/constants.ts",
- "../client/js/helpers/ircmessageparser/cleanIrcMessage.ts",
- "../client/js/helpers/ircmessageparser/findLinks.ts",
+
+ "../babel.config.cjs",
"../defaults/config.js",
"../package.json",
"../webpack.config.ts"
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
index 18affddd..09595928 100644
--- a/src/types/index.d.ts
+++ b/src/types/index.d.ts
@@ -1,2 +1 @@
import "./modules";
-import "./plugins";
diff --git a/src/types/plugins/index.d.ts b/src/types/plugins/index.d.ts
deleted file mode 100644
index 9020c885..00000000
--- a/src/types/plugins/index.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-import "./messageStorage";
diff --git a/test/.mocharc.yml b/test/.mocharc.yml
index 0c95c50f..06160b3c 100644
--- a/test/.mocharc.yml
+++ b/test/.mocharc.yml
@@ -5,4 +5,4 @@ reporter: dot
interactive: false
spec: "test/**/*.ts"
ignore: "test/client/**"
-require: "test/fixtures/env"
+require: "test/fixtures/env.ts"
diff --git a/test/fixtures/.thelounge/config.js b/test/fixtures/.thelounge/config.js
index 9767acc9..63daf9c3 100644
--- a/test/fixtures/.thelounge/config.js
+++ b/test/fixtures/.thelounge/config.js
@@ -1,13 +1,13 @@
"use strict";
-var config = require("../../../defaults/config.js");
+import config from "../../../defaults/config.js";
config.defaults.name = "Example IRC Server";
config.defaults.host = "irc.example.com";
config.public = true;
config.prefetch = true;
-config.host = config.bind = "127.0.0.1";
+config.host = bind = "127.0.0.1";
config.port = 61337;
config.transports = ["websocket"];
-module.exports = config;
+export default config;
diff --git a/test/fixtures/env.ts b/test/fixtures/env.ts
index 069c7574..9a3c934a 100644
--- a/test/fixtures/env.ts
+++ b/test/fixtures/env.ts
@@ -10,7 +10,7 @@ config.setHome(home);
import STSPolicies from "../../src/plugins/sts"; // Must be imported *after* setHome
-const mochaGlobalTeardown = async function () {
+const mochaGlobalTeardown = function () {
STSPolicies.refresh.cancel(); // Cancel debounced function, so it does not write later
fs.unlinkSync(STSPolicies.stsFile);
};
diff --git a/test/tsconfig.json b/test/tsconfig.json
index ef6a077e..e083fb37 100644
--- a/test/tsconfig.json
+++ b/test/tsconfig.json
@@ -6,7 +6,8 @@
"../src"
] /* Specifies a list of glob patterns that match files to be included in compilation. If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. Requires TypeScript version 2.0 or later. */,
"files": [
- "../babel.config.cjs"
+ "../babel.config.cjs",
+ "../src/helper.ts"
] /* If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. When a 'files' property is specified, only those files and those specified by 'include' are included. */,
"ts-node": {
"files": true
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 4fd84902..96492432 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -47,7 +47,8 @@
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
- // "outDir": "./", /* Specify an output folder for all emitted files. */
+ /* outDir is necessary because otherwise the built output for files like babel.config.cjs would overwrite the input. */
+ "outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
diff --git a/tsconfig.json b/tsconfig.json
index 853611c0..aca37f2f 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,6 +5,7 @@
"./babel.config.cjs",
"./src/helper.ts"
] /* If no 'files' or 'include' property is present in a tsconfig.json, the compiler defaults to including all files in the containing directory and subdirectories except those specified by 'exclude'. When a 'files' property is specified, only those files and those specified by 'include' are included. */,
+ // "exclude": [],
"references": [
{
"path": "./client" /* Path to referenced tsconfig or to folder containing tsconfig. */
diff --git a/webpack.config.ts b/webpack.config.ts
index f980a7ac..41a041e4 100644
--- a/webpack.config.ts
+++ b/webpack.config.ts
@@ -187,6 +187,10 @@ export default (env: any, argv: any) => {
filename: "css/style.css",
}),
+ new MiniCssExtractPlugin({
+ filename: "css/style.css",
+ }),
+
// Client tests that require Vue may end up requireing socket.io
new webpack.NormalModuleReplacementPlugin(
/js(\/|\\)socket\.js/,