Vue 3 upgrade

This commit is contained in:
Max Leiter 2021-03-28 19:55:35 -07:00
parent 5329483a40
commit 0bd65e5f19
No known key found for this signature in database
GPG Key ID: A3512F2F2F17EBDA
33 changed files with 1336 additions and 855 deletions

View File

@ -71,6 +71,6 @@ plugins:
extends:
- eslint:recommended
- plugin:vue/recommended
- plugin:vue/vue3-recommended
- prettier
- prettier/vue

View File

@ -18,6 +18,9 @@ import Mousetrap from "mousetrap";
import throttle from "lodash/throttle";
import storage from "../js/localStorage";
import isIgnoredKeybind from "../js/helpers/isIgnoredKeybind";
import socket from "../js/socket";
import {navigate} from "../js/router";
import Sidebar from "./Sidebar.vue";
import ImageViewer from "./ImageViewer.vue";
@ -67,8 +70,10 @@ export default {
};
this.dayChangeTimeout = setTimeout(emitDayChange, this.msUntilNextDay());
socket.open();
},
beforeDestroy() {
beforeUnmount() {
Mousetrap.unbind("esc", this.escapeKey);
Mousetrap.unbind("alt+u", this.toggleUserList);
Mousetrap.unbind("alt+s", this.toggleSidebar);
@ -124,6 +129,41 @@ export default {
this.$store.commit("userlistOpen", isUserlistOpen === "true");
},
switchToChannel(channel) {
navigate("RoutedChat", {id: channel.id});
},
closeChannel(channel) {
if (channel.type === "lobby") {
eventbus.emit(
"confirm-dialog",
{
title: "Remove network",
text: `Are you sure you want to quit and remove ${channel.name}? This cannot be undone.`,
button: "Remove network",
},
(result) => {
if (!result) {
return;
}
channel.closed = true;
socket.emit("input", {
target: Number(channel.id),
text: "/quit",
});
}
);
return;
}
channel.closed = true;
socket.emit("input", {
target: Number(channel.id),
text: "/close",
});
},
},
};
</script>

View File

@ -103,7 +103,7 @@ export default {
this.setInputSize();
},
},
mounted() {
beforeUnmount() {
eventbus.on("escapekey", this.blurInput);
if (this.$store.state.settings.autocomplete) {
@ -165,7 +165,7 @@ export default {
upload.mounted();
}
},
destroyed() {
unmounted() {
eventbus.off("escapekey", this.blurInput);
if (autocompletionRef) {

View File

@ -65,7 +65,7 @@ export default {
eventbus.on("escapekey", this.close);
eventbus.on("confirm-dialog", this.open);
},
destroyed() {
unmounted() {
eventbus.off("escapekey", this.close);
eventbus.off("confirm-dialog", this.open);
},

View File

@ -18,9 +18,8 @@
@mouseleave="activeItem = -1"
@keydown.enter.prevent="clickActiveItem"
>
<template v-for="(item, id) of items">
<template v-for="(item, id) of items" :key="item.name">
<li
:key="item.name"
:class="[
'context-menu-' + item.type,
item.class ? 'context-menu-' + item.class : null,
@ -63,7 +62,7 @@ export default {
eventbus.on("contextmenu:user", this.openUserContextMenu);
eventbus.on("contextmenu:channel", this.openChannelContextMenu);
},
destroyed() {
unmounted() {
eventbus.off("escapekey", this.close);
eventbus.off("contextmenu:user", this.openUserContextMenu);
eventbus.off("contextmenu:channel", this.openChannelContextMenu);

View File

@ -28,7 +28,7 @@ export default {
eventbus.on("daychange", this.dayChange);
}
},
beforeDestroy() {
beforeUnmount() {
eventbus.off("daychange", this.dayChange);
},
methods: {

View File

@ -42,7 +42,7 @@ export default {
name: "JoinChannel",
directives: {
focus: {
inserted(el) {
mounted(el) {
el.focus();
},
},
@ -51,6 +51,7 @@ export default {
network: Object,
channel: Object,
},
emits: ["toggle-join-channel"],
data() {
return {
inputChannel: "",

View File

@ -172,10 +172,10 @@ export default {
this.onPreviewUpdate();
},
beforeDestroy() {
beforeUnmount() {
eventbus.off("resize", this.handleResize);
},
destroyed() {
unmounted() {
// Let this preview go through load/canplay events again,
// Otherwise the browser can cause a resize on video elements
this.link.sourceLoaded = false;
@ -199,8 +199,6 @@ export default {
}
},
onPreviewReady() {
this.$set(this.link, "sourceLoaded", true);
this.keepScrollPosition();
if (this.link.type === "link") {

View File

@ -20,8 +20,8 @@
<p v-if="isLoading">Loading</p>
<p v-else>You have no recent mentions.</p>
</template>
<template v-for="message in resolvedMessages" v-else>
<div :key="message.msgId" :class="['msg', message.type]">
<template v-for="message in resolvedMessages" v-else :key="message.msgId">
<div :class="['msg', message.type]">
<div class="mentions-info">
<div>
<span class="from">
@ -177,14 +177,17 @@ export default {
},
},
watch: {
"$store.state.mentions"() {
this.isLoading = false;
"$store.state.mentions": {
handler() {
this.isLoading = false;
},
deep: true,
},
},
mounted() {
eventbus.on("mentions:toggle", this.openPopup);
},
destroyed() {
unmounted() {
eventbus.off("mentions:toggle", this.openPopup);
},
methods: {

View File

@ -15,7 +15,7 @@
<template v-if="message.type === 'unhandled'">
<span class="from">[{{ message.command }}]</span>
<span class="content">
<span v-for="(param, id) in message.params" :key="id">{{ param }} </span>
<span class="content">{{ message.params.join(" ") }}</span>
</span>
</template>
<template v-else-if="isAction()">

View File

@ -18,12 +18,8 @@
aria-relevant="additions"
@copy="onCopy"
>
<template v-for="(message, id) in condensedMessages">
<DateMarker
v-if="shouldDisplayDateMarker(message, id)"
:key="message.id + '-date'"
:message="message"
/>
<template v-for="(message, id) in condensedMessages" :key="message.id + '-date'">
<DateMarker v-if="shouldDisplayDateMarker(message, id)" :message="message" />
<div
v-if="shouldDisplayUnreadMarker(message.id)"
:key="message.id + '-unread'"
@ -148,8 +144,11 @@ export default {
this.historyObserver.observe(this.$refs.loadMoreButton);
}
},
"channel.messages"() {
this.keepScrollPosition();
"channel.messages": {
handler() {
this.keepScrollPosition();
},
deep: true,
},
"channel.pendingMessage"() {
this.$nextTick(() => {
@ -187,11 +186,11 @@ export default {
beforeUpdate() {
unreadMarkerShown = false;
},
beforeDestroy() {
beforeUnmount() {
eventbus.off("resize", this.handleResize);
this.$refs.chat.removeEventListener("scroll", this.handleScroll);
},
destroyed() {
unmounted() {
if (this.historyObserver) {
this.historyObserver.disconnect();
}

View File

@ -50,9 +50,9 @@
</template>
<template v-if="message.whois.special">
<template v-for="special in message.whois.special">
<dt :key="special">Special:</dt>
<dd :key="special">{{ special }}</dd>
<template v-for="special in message.whois.special" :key="special">
<dt>Special:</dt>
<dd>{{ special }}</dd>
</template>
</template>

View File

@ -104,10 +104,9 @@
@start="onDragStart"
@end="onDragEnd"
>
<template v-for="(channel, index) in network.channels">
<template v-for="(channel, index) in network.channels" :key="channel.id">
<Channel
v-if="index > 0"
:key="channel.id"
:channel="channel"
:network="network"
:active="
@ -245,7 +244,7 @@ export default {
Mousetrap.bind("alt+shift+left", this.collapseNetwork);
Mousetrap.bind("alt+j", this.toggleSearch);
},
beforeDestroy() {
beforeUnmount() {
Mousetrap.unbind("alt+shift+right", this.expandNetwork);
Mousetrap.unbind("alt+shift+left", this.collapseNetwork);
Mousetrap.unbind("alt+j", this.toggleSearch);

View File

@ -61,6 +61,7 @@ export default {
active: Boolean,
isFiltering: Boolean,
},
emits: ["toggle-join-channel"],
computed: {
channel() {
return this.network.channels[0];

View File

@ -1,23 +1,19 @@
<script>
import parse from "../js/helpers/parse";
export default {
name: "ParsedMessage",
functional: true,
props: {
text: String,
message: Object,
network: Object,
},
render(createElement, context) {
return parse(
createElement,
typeof context.props.text !== "undefined"
? context.props.text
: context.props.message.text,
context.props.message,
context.props.network
);
setup(props) {
return () =>
parse(
typeof props.text !== "undefined" ? props.text : props.message.text,
props.message,
props.network
);
},
};
</script>

View File

@ -39,7 +39,7 @@
aria-label="Connect to network"
role="tab"
aria-controls="connect"
:aria-selected="$route.name === 'Connect'"
:aria-selected="doesRouteMatch('Connect')"
/></span>
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Settings"
><router-link
@ -50,7 +50,7 @@
aria-label="Settings"
role="tab"
aria-controls="settings"
:aria-selected="$route.name === 'Settings'"
:aria-selected="doesRouteMatch('Settings')"
/></span>
<span
class="tooltipped tooltipped-n tooltipped-no-touch"
@ -71,7 +71,7 @@
aria-label="Help"
role="tab"
aria-controls="help"
:aria-selected="$route.name === 'Help'"
:aria-selected="doesRouteMatch('Help')"
/></span>
</footer>
</aside>
@ -195,6 +195,9 @@ export default {
},
methods: {
isPublic: () => document.body.classList.contains("public"),
doesRouteMatch(route) {
return this.$route.name === route ? true : null;
},
},
};
</script>

View File

@ -0,0 +1,246 @@
<template>
<div id="chat-container" class="window">
<div
id="chat"
:class="{
'colored-nicks': $store.state.settings.coloredNicks,
'time-seconds': $store.state.settings.showSeconds,
'time-12h': $store.state.settings.use12hClock,
}"
>
<div
class="chat-view"
data-type="search-results"
aria-label="Search results"
role="tabpanel"
>
<div class="header">
<SidebarToggle />
<span class="title"
>Searching in <span class="channel-name">{{ channel.name }}</span> for</span
>
<span class="topic">{{ $route.query.q }}</span>
<MessageSearchForm :network="network" :channel="channel" />
<button
class="close"
aria-label="Close search window"
title="Close search window"
@click="closeSearch"
/>
</div>
<div class="chat-content">
<div ref="chat" class="chat" tabindex="-1">
<div v-show="moreResultsAvailable" class="show-more">
<button
ref="loadMoreButton"
:disabled="
$store.state.messageSearchInProgress ||
!$store.state.isConnected
"
class="btn"
@click="onShowMoreClick"
>
<span v-if="$store.state.messageSearchInProgress">Loading</span>
<span v-else>Show older messages</span>
</button>
</div>
<div
v-if="$store.state.messageSearchInProgress && !offset"
class="search-status"
>
Searching
</div>
<div v-else-if="!messages.length && !offset" class="search-status">
No results found.
</div>
<div
v-else
class="messages"
role="log"
aria-live="polite"
aria-relevant="additions"
>
<template v-for="(message, id) in messages" :key="message.id">
<div class="result" @:click="jump(message, id)">
<DateMarker
v-if="shouldDisplayDateMarker(message, id)"
:key="message.date"
:message="message"
/>
<Message
:key="message.id"
:channel="channel"
:network="network"
:message="message"
:data-id="message.id"
/>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style>
.channel-name {
font-weight: 700;
}
</style>
<script>
import socket from "../../js/socket";
import SidebarToggle from "../SidebarToggle.vue";
import Message from "../Message.vue";
import MessageSearchForm from "../MessageSearchForm.vue";
import DateMarker from "../DateMarker.vue";
export default {
name: "SearchResults",
components: {
SidebarToggle,
Message,
DateMarker,
MessageSearchForm,
},
data() {
return {
offset: 0,
moreResultsAvailable: false,
oldScrollTop: 0,
oldChatHeight: 0,
};
},
computed: {
search() {
return this.$store.state.messageSearchResults;
},
messages() {
if (!this.search) {
return [];
}
return this.search.results;
},
chan() {
const chanId = parseInt(this.$route.params.id, 10);
return this.$store.getters.findChannel(chanId);
},
network() {
if (!this.chan) {
return null;
}
return this.chan.network;
},
channel() {
if (!this.chan) {
return null;
}
return this.chan.channel;
},
},
watch: {
"$route.params.id"() {
this.doSearch();
this.setActiveChannel();
},
"$route.query.q"() {
this.doSearch();
this.setActiveChannel();
},
messages() {
this.moreResultsAvailable = this.messages.length && !(this.messages.length % 100);
if (!this.offset) {
this.jumpToBottom();
} else {
this.$nextTick(() => {
const currentChatHeight = this.$refs.chat.scrollHeight;
this.$refs.chat.scrollTop =
this.oldScrollTop + currentChatHeight - this.oldChatHeight;
});
}
},
},
mounted() {
this.setActiveChannel();
this.doSearch();
this.$root.$on("re-search", this.doSearch); // Enable MessageSearchForm to search for the same query again
},
beforeUnmount() {
this.$root.$off("re-search");
},
methods: {
setActiveChannel() {
this.$store.commit("activeChannel", this.chan);
},
closeSearch() {
this.$root.switchToChannel(this.channel);
},
shouldDisplayDateMarker(message, id) {
const previousMessage = this.messages[id - 1];
if (!previousMessage) {
return true;
}
return new Date(previousMessage.time).getDay() !== new Date(message.time).getDay();
},
doSearch() {
this.offset = 0;
this.$store.commit("messageSearchInProgress", true);
if (!this.offset) {
this.$store.commit("messageSearchResults", null); // Only reset if not getting offset
}
socket.emit("search", {
networkUuid: this.network.uuid,
channelName: this.channel.name,
searchTerm: this.$route.query.q,
offset: this.offset,
});
},
onShowMoreClick() {
this.offset += 100;
this.$store.commit("messageSearchInProgress", true);
this.oldScrollTop = this.$refs.chat.scrollTop;
this.oldChatHeight = this.$refs.chat.scrollHeight;
socket.emit("search", {
networkUuid: this.network.uuid,
channelName: this.channel.name,
searchTerm: this.$route.query.q,
offset: this.offset + 1,
});
},
jumpToBottom() {
this.$nextTick(() => {
const el = this.$refs.chat;
el.scrollTop = el.scrollHeight;
});
},
jump(message, id) {
// TODO: Implement jumping to messages!
// This is difficult because it means client will need to handle a potentially nonlinear message set
// (loading IntersectionObserver both before AND after the messages)
this.$router.push({
name: "MessageList",
params: {
id: this.chan.id,
},
query: {
focused: id,
},
});
},
},
};
</script>

View File

@ -74,7 +74,7 @@ export default {
mounted() {
socket.on("auth:failed", this.onAuthFailed);
},
beforeDestroy() {
beforeUnmount() {
socket.off("auth:failed", this.onAuthFailed);
},
methods: {

View File

@ -555,6 +555,10 @@ p {
/* End icons */
#viewport-mount {
height: 100%;
}
#viewport {
display: flex;
height: 100%;

View File

@ -48,7 +48,7 @@
</head>
<body class="<%- public ? " public" : "" %>" data-transports="<%- JSON.stringify(transports) %>">
<div id="viewport"></div>
<div id="viewport-mount"></div>
<div id="loading">
<div class="window">
<div id="loading-status-container">

View File

@ -1,5 +1,6 @@
"use strict";
import {h as createElement} from "vue";
import parseStyle from "./ircmessageparser/parseStyle";
import findChannels from "./ircmessageparser/findChannels";
import {findLinks} from "./ircmessageparser/findLinks";
@ -15,7 +16,7 @@ import Username from "../../components/Username.vue";
const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]|\u{fe0f}/gu;
// Create an HTML `span` with styling information for a given fragment
function createFragment(fragment, createElement) {
function createFragment(fragment) {
const classes = [];
if (fragment.bold) {
@ -70,7 +71,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(text, message = undefined, network = undefined) {
// Extract the styling information and get the plain text version from it
const styleFragments = parseStyle(text);
const cleanText = styleFragments.map((fragment) => fragment.text).join("");
@ -90,9 +91,7 @@ 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) =>
createFragment(fragment, createElement)
);
const fragments = textPart.fragments.map((fragment) => createFragment(fragment));
// Wrap these potentially styled fragments with links and channel buttons
if (textPart.link) {
@ -103,12 +102,10 @@ function parse(createElement, text, message = undefined, network = undefined) {
const link = createElement(
"a",
{
attrs: {
href: textPart.link,
dir: preview ? null : "auto",
target: "_blank",
rel: "noopener",
},
href: textPart.link,
dir: preview ? null : "auto",
target: "_blank",
rel: "noopener",
},
fragments
);
@ -122,18 +119,14 @@ function parse(createElement, text, message = undefined, network = undefined) {
if (preview.size > 0) {
linkEls.push(
createElement(LinkPreviewFileSize, {
props: {
size: preview.size,
},
size: preview.size,
})
);
}
linkEls.push(
createElement(LinkPreviewToggle, {
props: {
link: preview,
},
link: preview,
})
);
@ -142,9 +135,7 @@ function parse(createElement, text, message = undefined, network = undefined) {
return createElement(
"span",
{
attrs: {
dir: "auto",
},
dir: "auto",
},
linkEls
);
@ -152,11 +143,11 @@ function parse(createElement, text, message = undefined, network = undefined) {
return createElement(
InlineChannel,
{
props: {
channel: textPart.channel,
},
channel: textPart.channel,
},
fragments
{
default: () => fragments,
}
);
} else if (textPart.emoji) {
const emojiWithoutModifiers = textPart.emoji.replace(emojiModifiersRegex, "");
@ -168,11 +159,9 @@ function parse(createElement, text, message = undefined, network = undefined) {
"span",
{
class: ["emoji"],
attrs: {
role: "img",
"aria-label": title,
title: title,
},
role: "img",
"aria-label": title,
title: title,
},
fragments
);
@ -180,16 +169,14 @@ function parse(createElement, text, message = undefined, network = undefined) {
return createElement(
Username,
{
props: {
user: {
nick: textPart.nick,
},
},
attrs: {
dir: "auto",
user: {
nick: textPart.nick,
},
dir: "auto",
},
fragments
{
default: () => fragments,
}
);
}

View File

@ -2,10 +2,7 @@
const constants = require("./constants");
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
import {createRouter, createWebHashHistory} from "vue-router";
import SignIn from "../components/Windows/SignIn.vue";
import Connect from "../components/Windows/Connect.vue";
@ -16,7 +13,8 @@ import NetworkEdit from "../components/Windows/NetworkEdit.vue";
import RoutedChat from "../components/RoutedChat.vue";
import store from "./store";
const router = new VueRouter({
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
name: "SignIn",
@ -100,19 +98,19 @@ router.beforeEach((to, from, next) => {
return;
}
// Handle closing image viewer with the browser back button
if (!router.app.$refs.app) {
next();
return;
}
// // Handle closing image viewer with the browser back button
// if (router.app.$refs.app) {
// next();
// return;
// }
const imageViewer = router.app.$root.$refs.app.$refs.imageViewer;
// const imageViewer = router.app.$refs.app.$refs.imageViewer;
if (imageViewer && imageViewer.link) {
imageViewer.closeViewer();
next(false);
return;
}
// if (imageViewer && imageViewer.link) {
// imageViewer.closeViewer();
// next(false);
// return;
// }
next();
});
@ -144,7 +142,7 @@ router.afterEach((to) => {
});
function navigate(routeName, params = {}) {
if (router.currentRoute.name) {
if (router.currentRoute.value.name) {
router.push({name: routeName, params}).catch(() => {});
} else {
// If current route is null, replace the history entry

View File

@ -80,7 +80,7 @@ function showSignIn() {
window.g_TheLoungeRemoveLoading();
}
if (router.currentRoute.name !== "SignIn") {
if (router.currentRoute.value.name !== "SignIn") {
navigate("SignIn");
}
}

View File

@ -1,6 +1,6 @@
"use strict";
import Vue from "vue";
import {nextTick} from "vue";
import socket from "../socket";
import storage from "../localStorage";
import {router, switchToChannel, navigate} from "../router";
@ -25,13 +25,16 @@ socket.on("init", function (data) {
window.g_TheLoungeRemoveLoading();
}
Vue.nextTick(() => {
nextTick(() => {
// If we handled query parameters like irc:// links or just general
// connect parameters in public mode, then nothing to do here
if (!handleQueryParams()) {
// If we are on an unknown route or still on SignIn component
// then we can open last known channel on server, or Connect window if none
if (!router.currentRoute.name || router.currentRoute.name === "SignIn") {
if (
!router.currentRoute.value.name ||
router.currentRoute.value.name === "SignIn"
) {
const channel = store.getters.findChannel(data.active);
if (channel) {

View File

@ -1,7 +1,6 @@
"use strict";
import Vue from "vue";
import {nextTick} from "vue";
import socket from "../socket";
import store from "../store";
@ -16,7 +15,7 @@ socket.on("more", function (data) {
data.totalMessages > channel.channel.messages.length + data.messages.length;
channel.channel.messages.unshift(...data.messages);
Vue.nextTick(() => {
nextTick(() => {
channel.channel.historyLoading = false;
});
});

View File

@ -1,7 +1,5 @@
"use strict";
import Vue from "vue";
import socket from "../socket";
import store from "../store";
@ -16,6 +14,7 @@ socket.on("msg:preview", function (data) {
const previewIndex = message.previews.findIndex((m) => m.link === data.preview.link);
if (previewIndex > -1) {
Vue.set(message.previews, previewIndex, data.preview);
// TODO: Does this work?
message.previews[previewIndex] = data.preview;
}
});

View File

@ -1,7 +1,5 @@
"use strict";
import Vue from "vue";
import socket from "../socket";
import store from "../store";
import {switchToChannel} from "../router";
@ -61,7 +59,7 @@ socket.on("network:info", function (data) {
}
for (const key in data) {
Vue.set(network, key, data[key]);
network[key] = data[key];
}
});

View File

@ -1,12 +1,9 @@
import Vue from "vue";
import Vuex from "vuex";
import {createStore} from "vuex";
import {createSettingsStore} from "./store-settings";
import storage from "./localStorage";
const appName = document.title;
Vue.use(Vuex);
function detectDesktopNotificationState() {
if (!("Notification" in window)) {
return "unsupported";
@ -17,7 +14,7 @@ function detectDesktopNotificationState() {
return "blocked";
}
const store = new Vuex.Store({
const store = createStore({
state: {
appLoaded: false,
activeChannel: null,

View File

@ -3,12 +3,11 @@
const constants = require("./constants");
import "../css/style.css";
import Vue from "vue";
import {createApp} from "vue";
import store from "./store";
import App from "../components/App.vue";
import storage from "./localStorage";
import {router, navigate} from "./router";
import socket from "./socket";
import {router} from "./router";
import eventbus from "./eventbus";
import "./socket-events";
@ -19,57 +18,13 @@ const favicon = document.getElementById("favicon");
const faviconNormal = favicon.getAttribute("href");
const faviconAlerted = favicon.dataset.other;
new Vue({
el: "#viewport",
router,
mounted() {
socket.open();
},
methods: {
switchToChannel(channel) {
navigate("RoutedChat", {id: channel.id});
},
closeChannel(channel) {
if (channel.type === "lobby") {
eventbus.emit(
"confirm-dialog",
{
title: "Remove network",
text: `Are you sure you want to quit and remove ${channel.name}? This cannot be undone.`,
button: "Remove network",
},
(result) => {
if (!result) {
return;
}
const vueApp = createApp(App);
vueApp.use(store);
vueApp.use(router);
// https://next.router.vuejs.org/guide/migration/#removal-of-router-app
router.app = vueApp;
channel.closed = true;
socket.emit("input", {
target: Number(channel.id),
text: "/quit",
});
}
);
return;
}
channel.closed = true;
socket.emit("input", {
target: Number(channel.id),
text: "/close",
});
},
},
render(createElement) {
return createElement(App, {
ref: "app",
props: this,
});
},
store,
});
vueApp.mount("#viewport-mount");
store.watch(
(state) => state.sidebarOpen,
@ -112,7 +67,11 @@ store.watch(
}
);
Vue.config.errorHandler = function (e) {
vueApp.config.errorHandler = function (e) {
store.commit("currentUserVisibleError", `Vue error: ${e.message}`);
console.error(e); // eslint-disable-line
};
export default {
app: vueApp,
};

View File

@ -40,6 +40,7 @@
"node": ">=10.15.0"
},
"dependencies": {
"@vue/compat": "3.2.23",
"bcryptjs": "2.4.3",
"busboy": "0.3.1",
"chalk": "4.1.0",
@ -71,11 +72,12 @@
"sqlite3": "5.0.2"
},
"devDependencies": {
"@babel/core": "7.13.8",
"@babel/preset-env": "7.13.9",
"@fortawesome/fontawesome-free": "5.15.2",
"@vue/server-test-utils": "1.1.3",
"@vue/test-utils": "1.1.3",
"@babel/core": "7.15.5",
"@babel/preset-env": "7.15.6",
"@fortawesome/fontawesome-free": "5.15.4",
"@vue/compiler-sfc": "3.2.22",
"@vue/server-test-utils": "1.3.0",
"@vue/test-utils": "2.0.0-rc.16",
"babel-loader": "8.2.2",
"babel-plugin-istanbul": "6.0.0",
"chai": "4.3.0",
@ -108,13 +110,12 @@
"stylelint-config-standard": "20.0.0",
"textcomplete": "0.18.2",
"undate": "0.3.0",
"vue": "2.6.12",
"vue-loader": "15.9.6",
"vue-router": "3.5.1",
"vue": "3.0.9",
"vue-loader": "16.8.3",
"vue-router": "4.0.12",
"vue-server-renderer": "2.6.12",
"vue-template-compiler": "2.6.12",
"vuedraggable": "2.24.3",
"vuex": "3.6.2",
"vuex": "4.0.2",
"webpack": "5.21.2",
"webpack-cli": "4.5.0",
"webpack-dev-middleware": "4.1.0",

View File

@ -3,7 +3,7 @@
const webpack = require("webpack");
const fs = require("fs");
const path = require("path");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
const {VueLoaderPlugin} = require("vue-loader");
const config = require("./webpack.config.js");
const testFile = path.resolve(__dirname, "test/public/testclient.js");

View File

@ -4,7 +4,7 @@ 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 {VueLoaderPlugin} = require("vue-loader");
const Helper = require("./src/helper.js");
const isProduction = process.env.NODE_ENV === "production";
@ -22,6 +22,11 @@ const config = {
performance: {
hints: false,
},
resolve: {
alias: {
vue: "@vue/compat",
},
},
module: {
rules: [
{
@ -31,6 +36,9 @@ const config = {
options: {
compilerOptions: {
preserveWhitespace: false,
compatConfig: {
MODE: 3,
},
},
},
},
@ -87,6 +95,10 @@ const config = {
json3: "JSON", // socket.io uses json3.js, but we do not target any browsers that need it
},
plugins: [
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
}),
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: "css/style.css",

1569
yarn.lock

File diff suppressed because it is too large Load Diff