From aace97056ba80ebe116925a1e6e83d52d385e420 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sat, 21 May 2022 17:27:51 -0700 Subject: [PATCH] progress before vue 3 --- .eslintrc.cjs | 11 +- client/components/App.vue | 9 - client/components/Channel.vue | 16 +- client/components/ChannelWrapper.vue | 18 +- client/components/Chat.vue | 4 +- client/components/ChatInput.vue | 4 +- client/components/ChatUserList.vue | 2 +- client/components/JoinChannel.vue | 4 +- client/components/LinkPreview.vue | 2 +- client/components/Message.vue | 4 +- client/components/MessageCondensed.vue | 2 +- client/components/MessageSearchForm.vue | 4 +- client/components/MessageTypes/away.vue | 2 +- client/components/MessageTypes/back.vue | 2 +- client/components/MessageTypes/chghost.vue | 2 +- client/components/MessageTypes/ctcp.vue | 2 +- .../components/MessageTypes/ctcp_request.vue | 2 +- client/components/MessageTypes/error.vue | 2 +- client/components/MessageTypes/invite.vue | 2 +- client/components/MessageTypes/join.vue | 2 +- client/components/MessageTypes/kick.vue | 2 +- client/components/MessageTypes/mode.vue | 2 +- .../components/MessageTypes/mode_channel.vue | 2 +- client/components/MessageTypes/mode_user.vue | 2 +- .../MessageTypes/monospace_block.vue | 2 +- client/components/MessageTypes/nick.vue | 2 +- client/components/MessageTypes/part.vue | 2 +- client/components/MessageTypes/quit.vue | 2 +- client/components/MessageTypes/raw.vue | 2 +- client/components/MessageTypes/topic.vue | 2 +- .../components/MessageTypes/topic_set_by.vue | 2 +- client/components/MessageTypes/whois.vue | 2 +- client/components/NetworkList.vue | 4 +- client/components/NetworkLobby.vue | 2 +- client/components/ParsedMessage.vue | 2 +- client/components/Special/ListBans.vue | 4 +- client/components/Special/ListChannels.vue | 4 +- client/components/Special/ListIgnored.vue | 4 +- client/components/Special/ListInvites.vue | 4 +- client/components/Username.vue | 4 +- client/js/socket-events/init.ts | 22 +- client/js/store.ts | 50 +++-- client/js/types.d.ts | 15 ++ client/js/vue.ts | 24 ++- package.json | 1 + src/plugins/dev-server.ts | 15 +- .../components/ParsedMessageTestWrapper.vue | 2 +- webpack.config.js | 189 ------------------ yarn.lock | 20 ++ 49 files changed, 183 insertions(+), 303 deletions(-) delete mode 100644 webpack.config.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs index bb707a9a..c1950464 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -84,6 +84,8 @@ const vueRules = defineConfig({ "vue/no-v-html": "off", "vue/require-default-prop": "off", "vue/v-slot-style": ["error", "longform"], + // Should be fixable in Vue 3 / when components use Vue.extend() + "@typescript-eslint/unbound-method": "off", }, }).rules; @@ -113,6 +115,9 @@ const tsRulesTemp = defineConfig({ module.exports = defineConfig({ root: true, + parserOptions: { + ecmaVersion: 2022, + }, overrides: [ { files: [ @@ -132,7 +137,11 @@ module.exports = defineConfig({ "plugin:@typescript-eslint/recommended-requiring-type-checking", "prettier", ], - rules: {...baseRules, ...tsRules, ...tsRulesTemp}, + rules: { + ...baseRules, + ...tsRules, + ...tsRulesTemp, + }, }, // TODO: verify { diff --git a/client/components/App.vue b/client/components/App.vue index 0e6fdbb9..a84d7005 100644 --- a/client/components/App.vue +++ b/client/components/App.vue @@ -31,15 +31,6 @@ import ConfirmDialog from "./ConfirmDialog.vue"; import Mentions from "./Mentions.vue"; import VueApp from "vue"; -// This stops Vue from complaining about adding objects to the component context -declare module "vue/types/vue" { - interface Vue { - debouncedResize: () => void; - // TODO; type as Timeout - dayChangeTimeout: any; - } -} - export default VueApp.extend({ name: "App", components: { diff --git a/client/components/Channel.vue b/client/components/Channel.vue index 72551f9b..9ca65e56 100644 --- a/client/components/Channel.vue +++ b/client/components/Channel.vue @@ -27,30 +27,32 @@ - diff --git a/client/components/ChannelWrapper.vue b/client/components/ChannelWrapper.vue index 572881bc..4444989d 100644 --- a/client/components/ChannelWrapper.vue +++ b/client/components/ChannelWrapper.vue @@ -32,15 +32,17 @@ - diff --git a/client/components/Chat.vue b/client/components/Chat.vue index 9838d1b6..326f005a 100644 --- a/client/components/Chat.vue +++ b/client/components/Chat.vue @@ -145,8 +145,8 @@ export default { MessageSearchForm, }, props: { - network: Object, - channel: Object, + network: Object as PropType, + channel: Object as PropType, focused: String, }, computed: { diff --git a/client/components/ChatInput.vue b/client/components/ChatInput.vue index 401ee7ef..0db71452 100644 --- a/client/components/ChatInput.vue +++ b/client/components/ChatInput.vue @@ -91,8 +91,8 @@ let autocompletionRef = null; export default { name: "ChatInput", props: { - network: Object, - channel: Object, + network: Object as PropType, + channel: Object as PropType, }, watch: { "channel.id"() { diff --git a/client/components/ChatUserList.vue b/client/components/ChatUserList.vue index 7eaa7c8b..4566ca4a 100644 --- a/client/components/ChatUserList.vue +++ b/client/components/ChatUserList.vue @@ -74,7 +74,7 @@ export default { Username, }, props: { - channel: Object, + channel: Object as PropType, }, data() { return { diff --git a/client/components/JoinChannel.vue b/client/components/JoinChannel.vue index 129d5383..648b3b66 100644 --- a/client/components/JoinChannel.vue +++ b/client/components/JoinChannel.vue @@ -48,8 +48,8 @@ export default { }, }, props: { - network: Object, - channel: Object, + network: Object as PropType, + channel: Object as PropType, }, data() { return { diff --git a/client/components/LinkPreview.vue b/client/components/LinkPreview.vue index 9ae10b74..cd8f7333 100644 --- a/client/components/LinkPreview.vue +++ b/client/components/LinkPreview.vue @@ -138,7 +138,7 @@ export default { props: { link: Object, keepScrollPosition: Function, - channel: Object, + channel: Object as PropType, }, data() { return { diff --git a/client/components/Message.vue b/client/components/Message.vue index ed172798..8df22067 100644 --- a/client/components/Message.vue +++ b/client/components/Message.vue @@ -113,8 +113,8 @@ export default { components: MessageTypes, props: { message: Object, - channel: Object, - network: Object, + channel: Object as PropType, + network: Object as PropType, keepScrollPosition: Function, isPreviousSource: Boolean, focused: Boolean, diff --git a/client/components/MessageCondensed.vue b/client/components/MessageCondensed.vue index 8d593f6a..7e32661a 100644 --- a/client/components/MessageCondensed.vue +++ b/client/components/MessageCondensed.vue @@ -27,7 +27,7 @@ export default { Message, }, props: { - network: Object, + network: Object as PropType, messages: Array, keepScrollPosition: Function, focused: Boolean, diff --git a/client/components/MessageSearchForm.vue b/client/components/MessageSearchForm.vue index 6f22fce3..587c5460 100644 --- a/client/components/MessageSearchForm.vue +++ b/client/components/MessageSearchForm.vue @@ -84,8 +84,8 @@ form.message-search.opened .input-wrapper { export default { name: "MessageSearchForm", props: { - network: Object, - channel: Object, + network: Object as PropType, + channel: Object as PropType, }, data() { return { diff --git a/client/components/MessageTypes/away.vue b/client/components/MessageTypes/away.vue index 6963257c..9921d407 100644 --- a/client/components/MessageTypes/away.vue +++ b/client/components/MessageTypes/away.vue @@ -20,7 +20,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/back.vue b/client/components/MessageTypes/back.vue index 4feac170..942eb72f 100644 --- a/client/components/MessageTypes/back.vue +++ b/client/components/MessageTypes/back.vue @@ -19,7 +19,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/chghost.vue b/client/components/MessageTypes/chghost.vue index e54c245d..f7dd40f0 100644 --- a/client/components/MessageTypes/chghost.vue +++ b/client/components/MessageTypes/chghost.vue @@ -23,7 +23,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/ctcp.vue b/client/components/MessageTypes/ctcp.vue index 4e9cc53c..5f782ea7 100644 --- a/client/components/MessageTypes/ctcp.vue +++ b/client/components/MessageTypes/ctcp.vue @@ -16,7 +16,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/ctcp_request.vue b/client/components/MessageTypes/ctcp_request.vue index 592441cd..ed9c42ab 100644 --- a/client/components/MessageTypes/ctcp_request.vue +++ b/client/components/MessageTypes/ctcp_request.vue @@ -17,7 +17,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/error.vue b/client/components/MessageTypes/error.vue index 0453f10a..3e5ebb5b 100644 --- a/client/components/MessageTypes/error.vue +++ b/client/components/MessageTypes/error.vue @@ -13,7 +13,7 @@ export default { ParsedMessage, }, props: { - network: Object, + network: Object as PropType, message: Object, }, computed: { diff --git a/client/components/MessageTypes/invite.vue b/client/components/MessageTypes/invite.vue index e976091e..e9e16b6a 100644 --- a/client/components/MessageTypes/invite.vue +++ b/client/components/MessageTypes/invite.vue @@ -19,7 +19,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/join.vue b/client/components/MessageTypes/join.vue index 9e635742..f7e4abd1 100644 --- a/client/components/MessageTypes/join.vue +++ b/client/components/MessageTypes/join.vue @@ -23,7 +23,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/kick.vue b/client/components/MessageTypes/kick.vue index 14a776c8..c0a33b40 100644 --- a/client/components/MessageTypes/kick.vue +++ b/client/components/MessageTypes/kick.vue @@ -20,7 +20,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/mode.vue b/client/components/MessageTypes/mode.vue index 4ca48bd2..2fcedf4b 100644 --- a/client/components/MessageTypes/mode.vue +++ b/client/components/MessageTypes/mode.vue @@ -17,7 +17,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/mode_channel.vue b/client/components/MessageTypes/mode_channel.vue index 109950bf..cefd7cfe 100644 --- a/client/components/MessageTypes/mode_channel.vue +++ b/client/components/MessageTypes/mode_channel.vue @@ -8,7 +8,7 @@ export default { name: "MessageChannelMode", props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/mode_user.vue b/client/components/MessageTypes/mode_user.vue index 8c3185eb..84cec2f5 100644 --- a/client/components/MessageTypes/mode_user.vue +++ b/client/components/MessageTypes/mode_user.vue @@ -8,7 +8,7 @@ export default { name: "MessageChannelMode", props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/monospace_block.vue b/client/components/MessageTypes/monospace_block.vue index 9647335a..c72aeb33 100644 --- a/client/components/MessageTypes/monospace_block.vue +++ b/client/components/MessageTypes/monospace_block.vue @@ -13,7 +13,7 @@ export default { ParsedMessage, }, props: { - network: Object, + network: Object as PropType, message: Object, }, computed: { diff --git a/client/components/MessageTypes/nick.vue b/client/components/MessageTypes/nick.vue index 53d300b6..e8f428fa 100644 --- a/client/components/MessageTypes/nick.vue +++ b/client/components/MessageTypes/nick.vue @@ -15,7 +15,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/part.vue b/client/components/MessageTypes/part.vue index b2b18c2a..afa41934 100644 --- a/client/components/MessageTypes/part.vue +++ b/client/components/MessageTypes/part.vue @@ -20,7 +20,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/quit.vue b/client/components/MessageTypes/quit.vue index a34cd58c..036b9c60 100644 --- a/client/components/MessageTypes/quit.vue +++ b/client/components/MessageTypes/quit.vue @@ -20,7 +20,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/raw.vue b/client/components/MessageTypes/raw.vue index 520e5087..dae809c3 100644 --- a/client/components/MessageTypes/raw.vue +++ b/client/components/MessageTypes/raw.vue @@ -6,7 +6,7 @@ export default { name: "MessageTypeRaw", props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/topic.vue b/client/components/MessageTypes/topic.vue index 7ec96e95..e77d5716 100644 --- a/client/components/MessageTypes/topic.vue +++ b/client/components/MessageTypes/topic.vue @@ -21,7 +21,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, }; diff --git a/client/components/MessageTypes/topic_set_by.vue b/client/components/MessageTypes/topic_set_by.vue index 66559880..b6b4e9f0 100644 --- a/client/components/MessageTypes/topic_set_by.vue +++ b/client/components/MessageTypes/topic_set_by.vue @@ -16,7 +16,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, computed: { diff --git a/client/components/MessageTypes/whois.vue b/client/components/MessageTypes/whois.vue index 5a080788..c67bc640 100644 --- a/client/components/MessageTypes/whois.vue +++ b/client/components/MessageTypes/whois.vue @@ -123,7 +123,7 @@ export default { Username, }, props: { - network: Object, + network: Object as PropType, message: Object, }, methods: { diff --git a/client/components/NetworkList.vue b/client/components/NetworkList.vue index 915cda1b..4cd2fe11 100644 --- a/client/components/NetworkList.vue +++ b/client/components/NetworkList.vue @@ -209,7 +209,7 @@ import isIgnoredKeybind from "../js/helpers/isIgnoredKeybind"; import distance from "../js/helpers/distance"; import eventbus from "../js/eventbus"; -export default Vue.extend({ +export default { name: "NetworkList", components: { JoinChannel, @@ -481,5 +481,5 @@ export default Vue.extend({ }); }, }, -}); +}; diff --git a/client/components/NetworkLobby.vue b/client/components/NetworkLobby.vue index 1b94c22d..a4fc35ce 100644 --- a/client/components/NetworkLobby.vue +++ b/client/components/NetworkLobby.vue @@ -56,7 +56,7 @@ export default { ChannelWrapper, }, props: { - network: Object, + network: Object as PropType, isJoinChannelShown: Boolean, active: Boolean, isFiltering: Boolean, diff --git a/client/components/ParsedMessage.vue b/client/components/ParsedMessage.vue index 842cbb34..cbfe38a9 100644 --- a/client/components/ParsedMessage.vue +++ b/client/components/ParsedMessage.vue @@ -7,7 +7,7 @@ export default { props: { text: String, message: Object, - network: Object, + network: Object as PropType, }, render(createElement, context) { return parse( diff --git a/client/components/Special/ListBans.vue b/client/components/Special/ListBans.vue index 781be944..82d2b202 100644 --- a/client/components/Special/ListBans.vue +++ b/client/components/Special/ListBans.vue @@ -27,8 +27,8 @@ export default { ParsedMessage, }, props: { - network: Object, - channel: Object, + network: Object as PropType, + channel: Object as PropType, }, methods: { localetime(date) { diff --git a/client/components/Special/ListChannels.vue b/client/components/Special/ListChannels.vue index bc415346..16f7d906 100644 --- a/client/components/Special/ListChannels.vue +++ b/client/components/Special/ListChannels.vue @@ -27,8 +27,8 @@ export default { ParsedMessage, }, props: { - network: Object, - channel: Object, + network: Object as PropType, + channel: Object as PropType, }, }; diff --git a/client/components/Special/ListIgnored.vue b/client/components/Special/ListIgnored.vue index be672266..21317e53 100644 --- a/client/components/Special/ListIgnored.vue +++ b/client/components/Special/ListIgnored.vue @@ -25,8 +25,8 @@ export default { ParsedMessage, }, props: { - network: Object, - channel: Object, + network: Object as PropType, + channel: Object as PropType, }, methods: { localetime(date) { diff --git a/client/components/Special/ListInvites.vue b/client/components/Special/ListInvites.vue index 9f59645e..8d7cd04e 100644 --- a/client/components/Special/ListInvites.vue +++ b/client/components/Special/ListInvites.vue @@ -29,8 +29,8 @@ export default { ParsedMessage, }, props: { - network: Object, - channel: Object, + network: Object as PropType, + channel: Object as PropType, }, methods: { localetime(date) { diff --git a/client/components/Username.vue b/client/components/Username.vue index 92ebb60f..6d6aadce 100644 --- a/client/components/Username.vue +++ b/client/components/Username.vue @@ -20,8 +20,8 @@ export default { user: Object, active: Boolean, onHover: Function, - channel: Object, - network: Object, + channel: Object as PropType, + network: Object as PropType, }, computed: { mode() { diff --git a/client/js/socket-events/init.ts b/client/js/socket-events/init.ts index 79d5d38a..397f07cc 100644 --- a/client/js/socket-events/init.ts +++ b/client/js/socket-events/init.ts @@ -4,6 +4,7 @@ import storage from "../localStorage"; import {router, switchToChannel, navigate} from "../router"; import store from "../store"; import parseIrcUri from "../helpers/parseIrcUri"; +import {ClientChan, ClientNetwork, InitClientChan} from "../types"; socket.on("init", function (data) { store.commit("networks", mergeNetworkData(data.networks)); @@ -47,8 +48,9 @@ socket.on("init", function (data) { } }); -function mergeNetworkData(newNetworks) { - const collapsedNetworks = new Set(JSON.parse(storage.get("thelounge.networks.collapsed"))); +function mergeNetworkData(newNetworks: ClientNetwork[]) { + const stored = storage.get("thelounge.networks.collapsed"); + const collapsedNetworks = stored ? new Set(JSON.parse(stored)) : new Set(); for (let n = 0; n < newNetworks.length; n++) { const network = newNetworks[n]; @@ -74,7 +76,7 @@ function mergeNetworkData(newNetworks) { if (key === "channels") { currentNetwork.channels = mergeChannelData( currentNetwork.channels, - network.channels + network.channels as InitClientChan[] ); } else { currentNetwork[key] = network[key]; @@ -87,7 +89,7 @@ function mergeNetworkData(newNetworks) { return newNetworks; } -function mergeChannelData(oldChannels, newChannels) { +function mergeChannelData(oldChannels: InitClientChan[], newChannels: InitClientChan[]) { for (let c = 0; c < newChannels.length; c++) { const channel = newChannels[c]; const currentChannel = oldChannels.find((chan) => chan.id === channel.id); @@ -131,7 +133,7 @@ function mergeChannelData(oldChannels, newChannels) { // on the client, and decide whether theres more messages to load from server if (key === "totalMessages") { currentChannel.moreHistoryAvailable = - channel.totalMessages > currentChannel.messages.length; + channel.totalMessages! > currentChannel.messages.length; continue; } @@ -167,10 +169,12 @@ function handleQueryParams() { if (params.has("uri")) { // Set default connection settings from IRC protocol links const uri = params.get("uri"); - const queryParams = parseIrcUri(uri); + const queryParams = parseIrcUri(uri as string); cleanParams(); - router.push({name: "Connect", query: queryParams}); + router.push({name: "Connect", query: queryParams}).catch(() => { + // Ignore errors + }); return true; } else if (document.body.classList.contains("public") && document.location.search) { @@ -178,7 +182,9 @@ function handleQueryParams() { const queryParams = Object.fromEntries(params.entries()); cleanParams(); - router.push({name: "Connect", query: queryParams}); + router.push({name: "Connect", query: queryParams}).catch(() => { + // Ignore errors + }); return true; } diff --git a/client/js/store.ts b/client/js/store.ts index 9df4b138..9fd27e63 100644 --- a/client/js/store.ts +++ b/client/js/store.ts @@ -1,8 +1,8 @@ import Vue from "vue"; -import Vuex from "vuex"; +import Vuex, {GetterTree, Store} from "vuex"; import {createSettingsStore} from "./store-settings"; import storage from "./localStorage"; -import {ClientChan, ClientNetwork} from "./types"; +import type {ClientChan, ClientNetwork, InitClientChan} from "./types"; const appName = document.title; @@ -20,7 +20,7 @@ function detectDesktopNotificationState() { return "blocked"; } -export type State = { +export interface State { appLoaded: boolean; activeChannel: { network: ClientNetwork; @@ -54,13 +54,15 @@ export type State = { } | null; messageSearchInProgress: boolean; searchEnabled: boolean; -}; +} -export type SettingsState = {}; -const store = new Vuex.Store>({ +const store = new Store({ state: { appLoaded: false, - activeChannel: null, + activeChannel: { + network: {} as ClientNetwork, + channel: {} as ClientChan, + }, currentUserVisibleError: null, desktopNotificationState: detectDesktopNotificationState(), isAutoCompleting: false, @@ -162,14 +164,16 @@ const store = new Vuex.Store>({ state.messageSearchResults = value; }, addMessageSearchResults(state, value) { - if (state.messageSearchResults!.results) { - // Append the search results and add networks and channels to new messages - value.results = [...state.messageSearchResults!.results, ...value.results]; - } else { - value.results = value.results; + // Append the search results and add networks and channels to new messages + if (!state.messageSearchResults) { + state.messageSearchResults = {results: []}; } - state.messageSearchResults = value; + const results = [...state.messageSearchResults.results, ...value.results]; + + state.messageSearchResults = { + results, + }; }, }, actions: { @@ -179,11 +183,11 @@ const store = new Vuex.Store>({ }, }, getters: { - findChannelOnCurrentNetwork: (state) => (name) => { + findChannelOnCurrentNetwork: (state) => (name: string) => { name = name.toLowerCase(); return state.activeChannel?.network.channels.find((c) => c.name.toLowerCase() === name); }, - findChannelOnNetwork: (state) => (networkUuid, channelName) => { + findChannelOnNetwork: (state) => (networkUuid: string, channelName: string) => { for (const network of state.networks) { if (network.uuid !== networkUuid) { continue; @@ -198,7 +202,7 @@ const store = new Vuex.Store>({ return null; }, - findChannel: (state) => (id) => { + findChannel: (state) => (id: number) => { for (const network of state.networks) { for (const channel of network.channels) { if (channel.id === id) { @@ -209,7 +213,7 @@ const store = new Vuex.Store>({ return null; }, - findNetwork: (state) => (uuid) => { + findNetwork: (state) => (uuid: string) => { for (const network of state.networks) { if (network.uuid === uuid) { return network; @@ -233,14 +237,16 @@ const store = new Vuex.Store>({ return highlightCount; }, + // TODO: type title(state, getters) { - const alertEventCount = getters.highlightCount ? `(${getters.highlightCount}) ` : ""; + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + const alertEventCount = getters?.highlightCount ? `(${getters.highlightCount}) ` : ""; const channelname = state.activeChannel ? `${state.activeChannel.channel.name} — ` : ""; return alertEventCount + channelname + appName; }, - initChannel: () => (channel) => { + initChannel: () => (channel: InitClientChan) => { // TODO: This should be a mutation channel.pendingMessage = ""; channel.inputHistoryPosition = 0; @@ -250,20 +256,20 @@ const store = new Vuex.Store>({ .filter((m) => m.self && m.text && m.type === "message") .map((m) => m.text) .reverse() - .slice(null, 99) + .slice(0, 99) ); channel.historyLoading = false; channel.scrolledToBottom = true; channel.editTopic = false; - channel.moreHistoryAvailable = channel.totalMessages > channel.messages.length; + channel.moreHistoryAvailable = channel.totalMessages! > channel.messages.length; delete channel.totalMessages; if (channel.type === "channel") { channel.usersOutdated = true; } - return channel; + return channel as ClientChan; }, }, }); diff --git a/client/js/types.d.ts b/client/js/types.d.ts index 23947726..84d2c9d1 100644 --- a/client/js/types.d.ts +++ b/client/js/types.d.ts @@ -5,6 +5,7 @@ declare module "*.vue" { import Vue from "vue"; export default Vue; } + interface LoungeWindow extends Window { g_TheLoungeRemoveLoading?: () => void; } @@ -12,8 +13,22 @@ interface LoungeWindow extends Window { type ClientChan = Chan & { moreHistoryAvailable: boolean; editTopic: boolean; + + // these are added in store/initChannel + pendingMessage: string; + inputHistoryPosition: number; + inputHistory: string[]; + historyLoading: boolean; + scrolledToBottom: boolean; + usersOutdated: boolean; +}; + +type InitClientChan = ClientChan & { + // total messages is deleted after its use when init event is sent/handled + totalMessages?: number; }; type ClientNetwork = Network & { isJoinChannelShown: boolean; + isCollapsed: boolean; }; diff --git a/client/js/vue.ts b/client/js/vue.ts index 80833412..cee2afaa 100644 --- a/client/js/vue.ts +++ b/client/js/vue.ts @@ -2,7 +2,7 @@ import constants from "./constants"; import "../css/style.css"; import Vue from "vue"; -import store from "./store"; +import store, {State} from "./store"; import App from "../components/App.vue"; import storage from "./localStorage"; import {router, navigate} from "./router"; @@ -13,20 +13,26 @@ import "./socket-events"; import "./webpush"; import "./keybinds"; import {ClientChan} from "./types"; +import {Store} from "vuex"; const favicon = document.getElementById("favicon"); const faviconNormal = favicon?.getAttribute("href") || ""; const faviconAlerted = favicon?.dataset.other || ""; -type Data = {}; -export type Methods = { - switchToChannel: (channel: ClientChan) => void; - closeChannel: (channel: ClientChan) => void; -}; -type Computed = {}; -type Props = {}; +declare module "vue/types/vue" { + interface Vue { + debouncedResize: () => void; + // TODO; type as Timeout + dayChangeTimeout: any; -new Vue({ + switchToChannel: (channel: ClientChan) => void; + closeChannel: (channel: ClientChan) => void; + + $store: Store; + } +} + +new Vue({ el: "#viewport", router, mounted() { diff --git a/package.json b/package.json index f1fbc3e4..6a12eefa 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "@typescript-eslint/parser": "5.22.0", "@vue/babel-helper-vue-jsx-merge-props": "1.2.1", "@vue/babel-preset-jsx": "1.2.4", + "@vue/runtime-core": "3.2.35", "@vue/runtime-dom": "3.2.33", "@vue/server-test-utils": "1.3.0", "@vue/test-utils": "1.3.0", diff --git a/src/plugins/dev-server.ts b/src/plugins/dev-server.ts index 3aa073c0..e9e55981 100644 --- a/src/plugins/dev-server.ts +++ b/src/plugins/dev-server.ts @@ -4,11 +4,22 @@ import express from "express"; import log from "../log"; +import webpack from "webpack"; +import config from "../../webpack.config"; + export default (app: express.Application) => { log.debug("Starting server in development mode"); - const webpack = require("webpack"); - const webpackConfig = require("../../webpack.config.js")(undefined, { mode: "production" }); + const webpackConfig = config(undefined, {mode: "production"}); + + if ( + !webpackConfig || + !webpackConfig.plugins?.length || + !webpackConfig.entry || + !webpackConfig.entry["js/bundle.js"] + ) { + throw new Error("No valid production webpack config found"); + } webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); webpackConfig.entry["js/bundle.js"].push( diff --git a/test/client/components/ParsedMessageTestWrapper.vue b/test/client/components/ParsedMessageTestWrapper.vue index 10e4333a..7913f41d 100644 --- a/test/client/components/ParsedMessageTestWrapper.vue +++ b/test/client/components/ParsedMessageTestWrapper.vue @@ -15,7 +15,7 @@ export default { props: { text: String, message: Object, - network: Object, + network: Object as PropType, }, }; diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index 018533a5..00000000 --- a/webpack.config.js +++ /dev/null @@ -1,189 +0,0 @@ -"use strict"; - -const webpack = require("webpack"); -const path = require("path"); -const CopyPlugin = require("copy-webpack-plugin"); -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); -const VueLoaderPlugin = require("vue-loader/lib/plugin"); -const Helper = require("./src/helper.js"); -const babelConfig = require("./babel.config.cjs"); - -const isProduction = process.env.NODE_ENV === "production"; -const config = { - mode: isProduction ? "production" : "development", - entry: { - "js/bundle.js": [path.resolve(__dirname, "client/js/vue.js")], - }, - devtool: "source-map", - output: { - clean: true, // Clean the output directory before emit. - path: path.resolve(__dirname, "public"), - filename: "[name]", - publicPath: "/", - }, - performance: { - hints: false, - }, - module: { - rules: [ - { - test: /\.vue$/, - use: { - loader: "vue-loader", - options: { - compilerOptions: { - preserveWhitespace: false, - }, - }, - }, - }, - { - test: /\.css$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: { - esModule: false, - }, - }, - { - loader: "css-loader", - options: { - url: false, - importLoaders: 1, - sourceMap: true, - }, - }, - { - loader: "postcss-loader", - options: { - sourceMap: true, - }, - }, - ], - }, - { - test: /\.js$/, - include: [path.resolve(__dirname, "client")], - use: { - loader: "babel-loader", - options: babelConfig, - }, - }, - ], - }, - optimization: { - splitChunks: { - cacheGroups: { - commons: { - test: /[\\/]node_modules[\\/]/, - name: "js/bundle.vendor.js", - chunks: "all", - }, - }, - }, - }, - externals: { - json3: "JSON", // socket.io uses json3.js, but we do not target any browsers that need it - }, - plugins: [ - new VueLoaderPlugin(), - new MiniCssExtractPlugin({ - filename: "css/style.css", - }), - new CopyPlugin({ - patterns: [ - { - from: "./node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff*", - to: "fonts/[name][ext]", - }, - { - from: "./client/js/loading-error-handlers.js", - to: "js/[name][ext]", - }, - { - from: "./client/*", - to: "[name][ext]", - globOptions: { - ignore: ["**/index.html.tpl", "**/service-worker.js"], - }, - }, - { - from: "./client/service-worker.js", - to: "[name][ext]", - transform(content) { - return content - .toString() - .replace( - "__HASH__", - isProduction ? Helper.getVersionCacheBust() : "dev" - ); - }, - }, - { - from: "./client/audio/*", - to: "audio/[name][ext]", - }, - { - from: "./client/img/*", - to: "img/[name][ext]", - }, - { - from: "./client/themes/*", - to: "themes/[name][ext]", - }, - ], - }), - // socket.io uses debug, we don't need it - new webpack.NormalModuleReplacementPlugin( - /debug/, - path.resolve(__dirname, "scripts/noop.js") - ), - ], -}; - -module.exports = (env, argv) => { - if (argv.mode === "development") { - config.target = "node"; - config.devtool = "eval"; - config.stats = "errors-only"; - config.output.path = path.resolve(__dirname, "test/public"); - config.entry = { - "testclient.js": [path.resolve(__dirname, "test/client/index.js")], - }; - - // Add the istanbul plugin to babel-loader options - for (const rule of config.module.rules) { - if (rule.use.loader === "babel-loader") { - rule.use.options.plugins = ["istanbul"]; - } - } - - // `optimization.splitChunks` is incompatible with a `target` of `node`. See: - // - https://github.com/zinserjan/mocha-webpack/issues/84 - // - https://github.com/webpack/webpack/issues/6727#issuecomment-372589122 - config.optimization.splitChunks = false; - - // Disable plugins like copy files, it is not required - config.plugins = [ - new VueLoaderPlugin(), - new MiniCssExtractPlugin({ - filename: "css/style.css", - }), - // Client tests that require Vue may end up requireing socket.io - new webpack.NormalModuleReplacementPlugin( - /js(\/|\\)socket\.js/, - path.resolve(__dirname, "scripts/noop.js") - ), - - // "Fixes" Critical dependency: the request of a dependency is an expression - new webpack.ContextReplacementPlugin(/vue-server-renderer$/), - ]; - } - - if (argv.mode === "production") { - // ... - } - - return config; -}; diff --git a/yarn.lock b/yarn.lock index 11c14dc5..c02bfa86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1874,6 +1874,13 @@ dependencies: "@vue/shared" "3.2.33" +"@vue/reactivity@3.2.35": + version "3.2.35" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.35.tgz#c66af289f3beda6aba63c264db9c6acd607d1c73" + integrity sha512-6j9N9R1SwHVcJas4YqAzwdRS/cgmj3Z9aUert5Mv1jk5B9H9ivN/zot/fgMUbseWXigkkmX60OsfRbz49o8kCw== + dependencies: + "@vue/shared" "3.2.35" + "@vue/runtime-core@3.2.33": version "3.2.33" resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.33.tgz#2df8907c85c37c3419fbd1bdf1a2df097fa40df2" @@ -1882,6 +1889,14 @@ "@vue/reactivity" "3.2.33" "@vue/shared" "3.2.33" +"@vue/runtime-core@3.2.35": + version "3.2.35" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.35.tgz#a87bd5214ff31f9dc6542f5c498d4f3543c6ea8f" + integrity sha512-P8AeGPRGyIiYdOdvLc/7KR8VSdbUGG8Jxdx6Xlj5okEjyV9IYxeHRIQIoye85K0lZXBH4zuh1syD1mX+oZ0KqQ== + dependencies: + "@vue/reactivity" "3.2.35" + "@vue/shared" "3.2.35" + "@vue/runtime-dom@3.2.33": version "3.2.33" resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.33.tgz#123b8969247029ea0d9c1983676d4706a962d848" @@ -1904,6 +1919,11 @@ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.33.tgz#69a8c99ceb37c1b031d5cc4aec2ff1dc77e1161e" integrity sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg== +"@vue/shared@3.2.35": + version "3.2.35" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.35.tgz#fb60530fa009dc21473386a7639eed833877cb0f" + integrity sha512-/sxDqMcy0MsfQ3LQixKYDxIinDYNy1dXTsF2Am0pv0toImWabymFQ8cFmPJnPt+gh5ElKwwn7KzQcDbLHar60A== + "@vue/test-utils@1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.3.0.tgz#d563decdcd9c68a7bca151d4179a2bfd6d5c3e15"