mirror of
https://github.com/thelounge/thelounge.git
synced 2024-05-18 14:16:36 +02:00
progress before vue 3
This commit is contained in:
parent
4c98b81e35
commit
aace97056b
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -27,30 +27,32 @@
|
|||
</ChannelWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Vue, {PropType} from "vue";
|
||||
import roundBadgeNumber from "../js/helpers/roundBadgeNumber";
|
||||
import {ClientChan, ClientNetwork} from "../js/types";
|
||||
import ChannelWrapper from "./ChannelWrapper.vue";
|
||||
|
||||
export default {
|
||||
export default Vue.extend({
|
||||
name: "Channel",
|
||||
components: {
|
||||
ChannelWrapper,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
active: Boolean,
|
||||
isFiltering: Boolean,
|
||||
},
|
||||
computed: {
|
||||
unreadCount() {
|
||||
unreadCount(): string {
|
||||
return roundBadgeNumber(this.channel.unread);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
close(): void {
|
||||
this.$root.closeChannel(this.channel);
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -32,15 +32,17 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Vue, {PropType} from "vue";
|
||||
import eventbus from "../js/eventbus";
|
||||
import isChannelCollapsed from "../js/helpers/isChannelCollapsed";
|
||||
import {ClientNetwork, ClientChan} from "../js/types";
|
||||
|
||||
export default {
|
||||
export default Vue.extend({
|
||||
name: "ChannelWrapper",
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
active: Boolean,
|
||||
isFiltering: Boolean,
|
||||
},
|
||||
|
@ -53,7 +55,7 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
getAriaLabel() {
|
||||
getAriaLabel(): string {
|
||||
const extra = [];
|
||||
const type = this.channel.type;
|
||||
|
||||
|
@ -75,14 +77,14 @@ export default {
|
|||
|
||||
return `${type}: ${this.channel.name} ${extra.length ? `(${extra.join(", ")})` : ""}`;
|
||||
},
|
||||
click() {
|
||||
click(): void {
|
||||
if (this.isFiltering) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$root.switchToChannel(this.channel);
|
||||
},
|
||||
openContextMenu(event) {
|
||||
openContextMenu(event): void {
|
||||
eventbus.emit("contextmenu:channel", {
|
||||
event: event,
|
||||
channel: this.channel,
|
||||
|
@ -90,5 +92,5 @@ export default {
|
|||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -145,8 +145,8 @@ export default {
|
|||
MessageSearchForm,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
focused: String,
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -91,8 +91,8 @@ let autocompletionRef = null;
|
|||
export default {
|
||||
name: "ChatInput",
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
},
|
||||
watch: {
|
||||
"channel.id"() {
|
||||
|
|
|
@ -74,7 +74,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
channel: Object,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -48,8 +48,8 @@ export default {
|
|||
},
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -138,7 +138,7 @@ export default {
|
|||
props: {
|
||||
link: Object,
|
||||
keepScrollPosition: Function,
|
||||
channel: Object,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -113,8 +113,8 @@ export default {
|
|||
components: MessageTypes,
|
||||
props: {
|
||||
message: Object,
|
||||
channel: Object,
|
||||
network: Object,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
keepScrollPosition: Function,
|
||||
isPreviousSource: Boolean,
|
||||
focused: Boolean,
|
||||
|
|
|
@ -27,7 +27,7 @@ export default {
|
|||
Message,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
messages: Array,
|
||||
keepScrollPosition: Function,
|
||||
focused: Boolean,
|
||||
|
|
|
@ -84,8 +84,8 @@ form.message-search.opened .input-wrapper {
|
|||
export default {
|
||||
name: "MessageSearchForm",
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -20,7 +20,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ export default {
|
|||
ParsedMessage,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -19,7 +19,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
export default {
|
||||
name: "MessageChannelMode",
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
export default {
|
||||
name: "MessageChannelMode",
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ export default {
|
|||
ParsedMessage,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -15,7 +15,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
export default {
|
||||
name: "MessageTypeRaw",
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -123,7 +123,7 @@ export default {
|
|||
Username,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
message: Object,
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -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({
|
|||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -56,7 +56,7 @@ export default {
|
|||
ChannelWrapper,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
isJoinChannelShown: Boolean,
|
||||
active: Boolean,
|
||||
isFiltering: Boolean,
|
||||
|
|
|
@ -7,7 +7,7 @@ export default {
|
|||
props: {
|
||||
text: String,
|
||||
message: Object,
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
},
|
||||
render(createElement, context) {
|
||||
return parse(
|
||||
|
|
|
@ -27,8 +27,8 @@ export default {
|
|||
ParsedMessage,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
},
|
||||
methods: {
|
||||
localetime(date) {
|
||||
|
|
|
@ -27,8 +27,8 @@ export default {
|
|||
ParsedMessage,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -25,8 +25,8 @@ export default {
|
|||
ParsedMessage,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
},
|
||||
methods: {
|
||||
localetime(date) {
|
||||
|
|
|
@ -29,8 +29,8 @@ export default {
|
|||
ParsedMessage,
|
||||
},
|
||||
props: {
|
||||
network: Object,
|
||||
channel: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
},
|
||||
methods: {
|
||||
localetime(date) {
|
||||
|
|
|
@ -20,8 +20,8 @@ export default {
|
|||
user: Object,
|
||||
active: Boolean,
|
||||
onHover: Function,
|
||||
channel: Object,
|
||||
network: Object,
|
||||
channel: Object as PropType<ClientChan>,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
},
|
||||
computed: {
|
||||
mode() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<Omit<State, "settings">>({
|
||||
const store = new Store<State>({
|
||||
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<Omit<State, "settings">>({
|
|||
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<Omit<State, "settings">>({
|
|||
},
|
||||
},
|
||||
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<Omit<State, "settings">>({
|
|||
|
||||
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<Omit<State, "settings">>({
|
|||
|
||||
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<Omit<State, "settings">>({
|
|||
|
||||
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<Omit<State, "settings">>({
|
|||
.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;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
15
client/js/types.d.ts
vendored
15
client/js/types.d.ts
vendored
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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<Data, Methods, Computed, Props>({
|
||||
switchToChannel: (channel: ClientChan) => void;
|
||||
closeChannel: (channel: ClientChan) => void;
|
||||
|
||||
$store: Store<State>;
|
||||
}
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: "#viewport",
|
||||
router,
|
||||
mounted() {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -15,7 +15,7 @@ export default {
|
|||
props: {
|
||||
text: String,
|
||||
message: Object,
|
||||
network: Object,
|
||||
network: Object as PropType<ClientNetwork>,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -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;
|
||||
};
|
20
yarn.lock
20
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"
|
||||
|
|
Loading…
Reference in a new issue