Vue 3 upgrade
This commit is contained in:
parent
5329483a40
commit
0bd65e5f19
|
@ -71,6 +71,6 @@ plugins:
|
|||
|
||||
extends:
|
||||
- eslint:recommended
|
||||
- plugin:vue/recommended
|
||||
- plugin:vue/vue3-recommended
|
||||
- prettier
|
||||
- prettier/vue
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -28,7 +28,7 @@ export default {
|
|||
eventbus.on("daychange", this.dayChange);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
eventbus.off("daychange", this.dayChange);
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -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: "",
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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()">
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -61,6 +61,7 @@ export default {
|
|||
active: Boolean,
|
||||
isFiltering: Boolean,
|
||||
},
|
||||
emits: ["toggle-join-channel"],
|
||||
computed: {
|
||||
channel() {
|
||||
return this.network.channels[0];
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -74,7 +74,7 @@ export default {
|
|||
mounted() {
|
||||
socket.on("auth:failed", this.onAuthFailed);
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
socket.off("auth:failed", this.onAuthFailed);
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -555,6 +555,10 @@ p {
|
|||
|
||||
/* End icons */
|
||||
|
||||
#viewport-mount {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#viewport {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -80,7 +80,7 @@ function showSignIn() {
|
|||
window.g_TheLoungeRemoveLoading();
|
||||
}
|
||||
|
||||
if (router.currentRoute.name !== "SignIn") {
|
||||
if (router.currentRoute.value.name !== "SignIn") {
|
||||
navigate("SignIn");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
21
package.json
21
package.json
|
@ -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",
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue