From 52c13f49c1fa939299c470c9235ec1ec8cc48573 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Mon, 23 May 2022 02:27:10 -0700 Subject: [PATCH] progress --- client/components/App.vue | 32 +- client/components/Chat.vue | 190 ++++++---- client/components/ChatInput.vue | 364 +++++++++++-------- client/components/DateMarker.vue | 2 +- client/components/ImageViewer.vue | 30 +- client/components/LinkPreview.vue | 229 +++++++----- client/components/LinkPreviewToggle.vue | 4 +- client/components/MessageList.vue | 23 +- client/components/NetworkList.vue | 8 +- client/components/ParsedMessage.vue | 19 +- client/components/Settings/Notifications.vue | 2 +- client/components/Sidebar.vue | 2 +- client/components/SidebarToggle.vue | 10 +- client/js/router.ts | 6 +- client/js/types.d.ts | 15 +- client/js/vue.ts | 4 - src/plugins/irc-events/link.ts | 2 +- 17 files changed, 555 insertions(+), 387 deletions(-) diff --git a/client/components/App.vue b/client/components/App.vue index d4a4e5b1..9b251653 100644 --- a/client/components/App.vue +++ b/client/components/App.vue @@ -29,10 +29,36 @@ import ImageViewer from "./ImageViewer.vue"; import ContextMenu from "./ContextMenu.vue"; import ConfirmDialog from "./ConfirmDialog.vue"; import Mentions from "./Mentions.vue"; -import {computed, defineComponent, onBeforeUnmount, onMounted, ref} from "vue"; +import { + computed, + provide, + defineComponent, + onBeforeUnmount, + onMounted, + ref, + Ref, + InjectionKey, + inject, +} from "vue"; import {useStore} from "../js/store"; import type {DebouncedFunc} from "lodash"; +const imageViewerKey = Symbol() as InjectionKey>; +const contextMenuKey = Symbol() as InjectionKey>; +const confirmDialogKey = Symbol() as InjectionKey>; + +export const useImageViewer = () => { + return inject(imageViewerKey) as Ref; +}; + +export const useContextMenu = () => { + return inject(contextMenuKey) as Ref; +}; + +export const useConfirmDialog = () => { + return inject(confirmDialogKey) as Ref; +}; + export default defineComponent({ name: "App", components: { @@ -51,6 +77,10 @@ export default defineComponent({ const contextMenu = ref(null); const confirmDialog = ref(null); + provide(imageViewerKey, imageViewer); + provide(contextMenuKey, contextMenu); + provide(confirmDialogKey, confirmDialog); + const viewportClasses = computed(() => { return { notified: store.getters.highlightCount > 0, diff --git a/client/components/Chat.vue b/client/components/Chat.vue index c6908028..130898fb 100644 --- a/client/components/Chat.vue +++ b/client/components/Chat.vue @@ -3,10 +3,10 @@
@@ -95,7 +95,7 @@ {'scroll-down-shown': !channel.scrolledToBottom}, ]" aria-label="Jump to recent messages" - @click="$refs.messageList.jumpToBottom()" + @click="messageList?.jumpToBottom()" >
@@ -110,11 +110,11 @@
- {{ $store.state.currentUserVisibleError }} + {{ store.state.currentUserVisibleError }}
@@ -133,8 +133,9 @@ import ListBans from "./Special/ListBans.vue"; import ListInvites from "./Special/ListInvites.vue"; import ListChannels from "./Special/ListChannels.vue"; import ListIgnored from "./Special/ListIgnored.vue"; -import {Component, defineComponent, PropType} from "vue"; +import {defineComponent, PropType, ref, computed, watch, nextTick, onMounted, Component} from "vue"; import type {ClientNetwork, ClientChan} from "../js/types"; +import {useStore} from "../js/store"; export default defineComponent({ name: "Chat", @@ -151,92 +152,123 @@ export default defineComponent({ channel: {type: Object as PropType, required: true}, focused: String, }, - computed: { - specialComponent(): Component { - switch (this.channel.special) { + setup(props) { + const store = useStore(); + + const messageList = ref(); + const topicInput = ref(null); + + const specialComponent = computed(() => { + switch (props.channel.special) { case "list_bans": - return ListBans; + return ListBans as Component; case "list_invites": - return ListInvites; + return ListInvites as Component; case "list_channels": - return ListChannels; + return ListChannels as Component; case "list_ignored": - return ListIgnored; + return ListIgnored as Component; } return undefined; - }, - }, - watch: { - channel() { - this.channelChanged(); - }, - "channel.editTopic"(newValue) { - if (newValue) { - this.$nextTick(() => { - this.$refs.topicInput.focus(); + }); + + const channelChanged = () => { + // Triggered when active channel is set or changed + props.channel.highlight = 0; + props.channel.unread = 0; + + socket.emit("open", props.channel.id); + + if (props.channel.usersOutdated) { + props.channel.usersOutdated = false; + + socket.emit("names", { + target: props.channel.id, + }); + } + }; + + const hideUserVisibleError = () => { + store.commit("currentUserVisibleError", null); + }; + + const editTopic = () => { + if (props.channel.type === "channel") { + props.channel.editTopic = true; + } + }; + + const saveTopic = () => { + props.channel.editTopic = false; + + if (!topicInput.value) { + return; + } + + const newTopic = topicInput.value.value; + + if (props.channel.topic !== newTopic) { + const target = props.channel.id; + const text = `/raw TOPIC ${props.channel.name} :${newTopic}`; + socket.emit("input", {target, text}); + } + }; + + const openContextMenu = (event: any) => { + eventbus.emit("contextmenu:channel", { + event: event, + channel: props.channel, + network: props.network, + }); + }; + + const openMentions = (event: any) => { + eventbus.emit("mentions:toggle", { + event: event, + }); + }; + + watch(props.channel, () => { + channelChanged(); + }); + + const editTopicRef = ref(props.channel.editTopic); + watch(editTopicRef, (newTopic) => { + if (newTopic) { + nextTick(() => { + topicInput.value?.focus(); }).catch((e) => { // eslint-disable-next-line no-console console.error(e); }); } - }, - }, - mounted() { - this.channelChanged(); + }); - if (this.channel.editTopic) { - this.$nextTick(() => { - this.$refs.topicInput.focus(); - }); - } - }, - methods: { - channelChanged() { - // Triggered when active channel is set or changed - this.channel.highlight = 0; - this.channel.unread = 0; + onMounted(() => { + channelChanged(); - socket.emit("open", this.channel.id); - - if (this.channel.usersOutdated) { - this.channel.usersOutdated = false; - - socket.emit("names", { - target: this.channel.id, + if (props.channel.editTopic) { + nextTick(() => { + topicInput.value?.focus(); + }).catch(() => { + // no-op }); } - }, - hideUserVisibleError() { - this.$store.commit("currentUserVisibleError", null); - }, - editTopic() { - if (this.channel.type === "channel") { - this.channel.editTopic = true; - } - }, - saveTopic() { - this.channel.editTopic = false; - const newTopic = this.$refs.topicInput.value; + }); - if (this.channel.topic !== newTopic) { - const target = this.channel.id; - const text = `/raw TOPIC ${this.channel.name} :${newTopic}`; - socket.emit("input", {target, text}); - } - }, - openContextMenu(event) { - eventbus.emit("contextmenu:channel", { - event: event, - channel: this.channel, - network: this.network, - }); - }, - openMentions(event) { - eventbus.emit("mentions:toggle", { - event: event, - }); - }, + return { + store, + messageList, + topicInput, + specialComponent, + editTopicRef, + hideUserVisibleError, + editTopic, + saveTopic, + openContextMenu, + openMentions, + }; }, }); diff --git a/client/components/ChatInput.vue b/client/components/ChatInput.vue index 5f6fcda2..186a2cc3 100644 --- a/client/components/ChatInput.vue +++ b/client/components/ChatInput.vue @@ -16,7 +16,7 @@ @blur="onBlur" /> @@ -60,8 +60,9 @@ import commands from "../js/commands/index"; import socket from "../js/socket"; import upload from "../js/upload"; import eventbus from "../js/eventbus"; -import {defineComponent, PropType} from "vue"; +import {watch, defineComponent, nextTick, onMounted, PropType, ref, onUnmounted} from "vue"; import type {ClientNetwork, ClientChan} from "../js/types"; +import {useStore} from "../js/store"; const formattingHotkeys = { "mod+k": "\x03", @@ -88,178 +89,103 @@ const bracketWraps = { _: "_", }; -let autocompletionRef = null; - export default defineComponent({ name: "ChatInput", props: { network: {type: Object as PropType, required: true}, channel: {type: Object as PropType, required: true}, }, - watch: { - "channel.id"() { - if (autocompletionRef) { - autocompletionRef.hide(); - } - }, - "channel.pendingMessage"() { - this.setInputSize(); - }, - }, - mounted() { - eventbus.on("escapekey", this.blurInput); + setup(props) { + const store = useStore(); + const input = ref(); + const uploadInput = ref(); + const autocompletionRef = ref>(); - if (this.$accessor.settings.autocomplete) { - autocompletionRef = autocompletion(this.$refs.input); - } - - const inputTrap = Mousetrap(this.$refs.input); - - inputTrap.bind(Object.keys(formattingHotkeys), function (e, key) { - const modifier = formattingHotkeys[key]; - - wrapCursor( - e.target, - modifier, - e.target.selectionStart === e.target.selectionEnd ? "" : modifier - ); - - return false; - }); - - inputTrap.bind(Object.keys(bracketWraps), function (e, key) { - if (e.target?.selectionStart !== e.target.selectionEnd) { - wrapCursor(e.target, key, bracketWraps[key]); - - return false; - } - }); - - inputTrap.bind(["up", "down"], (e, key) => { - if ( - this.$accessor.isAutoCompleting || - e.target.selectionStart !== e.target.selectionEnd - ) { - return; - } - - const onRow = ( - this.$refs.input.value.slice(null, this.$refs.input.selectionStart).match(/\n/g) || - [] - ).length; - const totalRows = (this.$refs.input.value.match(/\n/g) || []).length; - - const {channel} = this; - - if (channel.inputHistoryPosition === 0) { - channel.inputHistory[channel.inputHistoryPosition] = channel.pendingMessage; - } - - if (key === "up" && onRow === 0) { - if (channel.inputHistoryPosition < channel.inputHistory.length - 1) { - channel.inputHistoryPosition++; - } else { - return; - } - } else if (key === "down" && channel.inputHistoryPosition > 0 && onRow === totalRows) { - channel.inputHistoryPosition--; - } else { - return; - } - - channel.pendingMessage = channel.inputHistory[channel.inputHistoryPosition]; - this.$refs.input.value = channel.pendingMessage; - this.setInputSize(); - - return false; - }); - - if (this.$accessor.serverConfiguration.fileUpload) { - upload.mounted(); - } - }, - unmounted() { - eventbus.off("escapekey", this.blurInput); - - if (autocompletionRef) { - autocompletionRef.destroy(); - autocompletionRef = null; - } - - upload.abort(); - }, - methods: { - setPendingMessage(e) { - this.channel.pendingMessage = e.target.value; - this.channel.inputHistoryPosition = 0; - this.setInputSize(); - }, - setInputSize() { - this.$nextTick(() => { - if (!this.$refs.input) { + const setInputSize = () => { + nextTick(() => { + if (!input.value) { return; } - const style = window.getComputedStyle(this.$refs.input); - const lineHeight = parseFloat(style.lineHeight, 10) || 1; + const style = window.getComputedStyle(input.value); + const lineHeight = parseFloat(style.lineHeight) || 1; // Start by resetting height before computing as scrollHeight does not // decrease when deleting characters - this.$refs.input.style.height = ""; + input.value.style.height = ""; // Use scrollHeight to calculate how many lines there are in input, and ceil the value // because some browsers tend to incorrently round the values when using high density // displays or using page zoom feature - this.$refs.input.style.height = - Math.ceil(this.$refs.input.scrollHeight / lineHeight) * lineHeight + "px"; + input.value.style.height = `${ + Math.ceil(input.value.scrollHeight / lineHeight) * lineHeight + }px`; + }).catch(() => { + // no-op }); - }, - getInputPlaceholder(channel) { + }; + + const setPendingMessage = (e: Event) => { + props.channel.pendingMessage = (e.target as HTMLInputElement).value; + props.channel.inputHistoryPosition = 0; + setInputSize(); + }; + + const getInputPlaceholder = (channel: ClientChan) => { if (channel.type === "channel" || channel.type === "query") { return `Write to ${channel.name}`; } return ""; - }, - onSubmit() { + }; + + const onSubmit = () => { + if (!input.value) { + return; + } + // Triggering click event opens the virtual keyboard on mobile // This can only be called from another interactive event (e.g. button click) - this.$refs.input.click(); - this.$refs.input.focus(); + input.value.click(); + input.value.focus(); - if (!this.$accessor.isConnected) { + if (!store.state.isConnected) { return false; } - const target = this.channel.id; - const text = this.channel.pendingMessage; + const target = props.channel.id; + const text = props.channel.pendingMessage; if (text.length === 0) { return false; } - if (autocompletionRef) { - autocompletionRef.hide(); + if (autocompletionRef.value) { + autocompletionRef.value.hide(); } - this.channel.inputHistoryPosition = 0; - this.channel.pendingMessage = ""; - this.$refs.input.value = ""; - this.setInputSize(); + props.channel.inputHistoryPosition = 0; + props.channel.pendingMessage = ""; + input.value.value = ""; + setInputSize(); // Store new message in history if last message isn't already equal - if (this.channel.inputHistory[1] !== text) { - this.channel.inputHistory.splice(1, 0, text); + if (props.channel.inputHistory[1] !== text) { + props.channel.inputHistory.splice(1, 0, text); } // Limit input history to a 100 entries - if (this.channel.inputHistory.length > 100) { - this.channel.inputHistory.pop(); + if (props.channel.inputHistory.length > 100) { + props.channel.inputHistory.pop(); } if (text[0] === "/") { - const args = text.substr(1).split(" "); - const cmd = args.shift().toLowerCase(); + const args = text.substring(1).split(" "); + const cmd = args.shift()?.toLowerCase(); + + if (!cmd) { + return false; + } if ( Object.prototype.hasOwnProperty.call(commands, cmd) && @@ -270,23 +196,165 @@ export default defineComponent({ } socket.emit("input", {target, text}); - }, - onUploadInputChange() { - const files = Array.from(this.$refs.uploadInput.files); - upload.triggerUpload(files); - this.$refs.uploadInput.value = ""; // Reset element so you can upload the same file - }, - openFileUpload() { - this.$refs.uploadInput.click(); - }, - blurInput() { - this.$refs.input.blur(); - }, - onBlur() { - if (autocompletionRef) { - autocompletionRef.hide(); + }; + + const onUploadInputChange = () => { + if (!uploadInput.value || !uploadInput.value.files) { + return; } - }, + + const files = Array.from(uploadInput.value.files); + upload.triggerUpload(files); + uploadInput.value.value = ""; // Reset element so you can upload the same file + }; + + const openFileUpload = () => { + uploadInput.value?.click(); + }; + + const blurInput = () => { + input.value?.blur(); + }; + + const onBlur = () => { + if (autocompletionRef.value) { + autocompletionRef.value.hide(); + } + }; + + const channelId = ref(props.channel.id); + watch(channelId, () => { + if (autocompletionRef.value) { + autocompletionRef.value.hide(); + } + }); + + const pendingMessage = ref(props.channel.pendingMessage); + watch(pendingMessage, () => { + setInputSize(); + }); + + onMounted(() => { + eventbus.on("escapekey", blurInput); + + if (store.state.settings.autocomplete) { + if (!input.value) { + throw new Error("ChatInput autocomplete: input element is not available"); + } + + autocompletionRef.value = autocompletion(input.value); + } + + const inputTrap = Mousetrap(input.value); + + inputTrap.bind(Object.keys(formattingHotkeys), function (e, key) { + const modifier = formattingHotkeys[key]; + + if (!e.target) { + return; + } + + // TODO; investigate types + wrapCursor( + e.target as HTMLTextAreaElement, + modifier, + (e.target as HTMLTextAreaElement).selectionStart === + (e.target as HTMLTextAreaElement).selectionEnd + ? "" + : modifier + ); + + return false; + }); + + inputTrap.bind(Object.keys(bracketWraps), function (e, key) { + if ( + (e.target as HTMLTextAreaElement)?.selectionStart !== + (e.target as HTMLTextAreaElement).selectionEnd + ) { + wrapCursor(e.target as HTMLTextAreaElement, key, bracketWraps[key]); + + return false; + } + }); + + inputTrap.bind(["up", "down"], (e, key) => { + if ( + store.state.isAutoCompleting || + (e.target as HTMLTextAreaElement).selectionStart !== + (e.target as HTMLTextAreaElement).selectionEnd || + !input.value + ) { + return; + } + + const onRow = ( + input.value.value.slice(undefined, input.value.selectionStart).match(/\n/g) || + [] + ).length; + const totalRows = (input.value.value.match(/\n/g) || []).length; + + const {channel} = props; + + if (channel.inputHistoryPosition === 0) { + channel.inputHistory[channel.inputHistoryPosition] = channel.pendingMessage; + } + + if (key === "up" && onRow === 0) { + if (channel.inputHistoryPosition < channel.inputHistory.length - 1) { + channel.inputHistoryPosition++; + } else { + return; + } + } else if ( + key === "down" && + channel.inputHistoryPosition > 0 && + onRow === totalRows + ) { + channel.inputHistoryPosition--; + } else { + return; + } + + channel.pendingMessage = channel.inputHistory[channel.inputHistoryPosition]; + input.value.value = channel.pendingMessage; + setInputSize(); + + return false; + }); + + if (store.state.serverConfiguration?.fileUpload) { + upload.mounted(); + } + }); + + onUnmounted(() => { + eventbus.off("escapekey", blurInput); + + if (autocompletionRef.value) { + autocompletionRef.value.destroy(); + autocompletionRef.value = undefined; + } + + upload.abort(); + }); + + return { + store, + input, + uploadInput, + onUploadInputChange, + openFileUpload, + blurInput, + onBlur, + channelId, + pendingMessage, + setInputSize, + upload, + getInputPlaceholder, + onSubmit, + setPendingMessage, + }; }, }); diff --git a/client/components/DateMarker.vue b/client/components/DateMarker.vue index 9919770b..f65063db 100644 --- a/client/components/DateMarker.vue +++ b/client/components/DateMarker.vue @@ -33,7 +33,7 @@ export default defineComponent({ const localeDate = computed(() => dayjs(props.message.time).format("D MMMM YYYY")); const hoursPassed = () => { - return (Date.now() - Date.parse(props.message.time?.toISOString())) / 3600000; + return (Date.now() - Date.parse(props.message.time.toString())) / 3600000; }; const dayChange = () => { diff --git a/client/components/ImageViewer.vue b/client/components/ImageViewer.vue index 715d99ac..d2924e26 100644 --- a/client/components/ImageViewer.vue +++ b/client/components/ImageViewer.vue @@ -42,7 +42,7 @@ import Mousetrap from "mousetrap"; import {computed, defineComponent, ref, watch} from "vue"; import eventbus from "../js/eventbus"; -import {ClientChan, ClientMessage, LinkPreview} from "../js/types"; +import {ClientChan, ClientMessage, ClientLinkPreview} from "../js/types"; export default defineComponent({ name: "ImageViewer", @@ -50,9 +50,9 @@ export default defineComponent({ const viewer = ref(); const image = ref(); - const link = ref(null); - const previousImage = ref(); - const nextImage = ref(); + const link = ref(null); + const previousImage = ref(); + const nextImage = ref(); const channel = ref(); const position = ref<{ @@ -98,7 +98,7 @@ export default defineComponent({ }; const setPrevNextImages = () => { - if (!channel.value) { + if (!channel.value || !link.value) { return null; } @@ -107,7 +107,7 @@ export default defineComponent({ .flat() .filter((preview) => preview.thumb); - const currentIndex = links.indexOf(this.link); + const currentIndex = links.indexOf(link.value); previousImage.value = links[currentIndex - 1] || null; nextImage.value = links[currentIndex + 1] || null; @@ -125,10 +125,6 @@ export default defineComponent({ } }; - const onImageLoad = () => { - prepareImage(); - }; - const prepareImage = () => { const viewerEl = viewer.value; const imageEl = image.value; @@ -148,6 +144,10 @@ export default defineComponent({ transform.value.y = height / 2; }; + const onImageLoad = () => { + prepareImage(); + }; + const calculateZoomShift = (newScale: number, x: number, y: number, oldScale: number) => { if (!image.value || !viewer.value) { return; @@ -241,7 +241,7 @@ export default defineComponent({ // 1. Move around by dragging it with one finger // 2. Change image scale by using two fingers const onImageTouchStart = (e: TouchEvent) => { - const image = this.$refs.image; + const img = image.value; let touch = reduceTouches(e.touches); let currentTouches = e.touches; let touchEndFingers = 0; @@ -313,12 +313,12 @@ export default defineComponent({ correctPosition(); - image.removeEventListener("touchmove", touchMove, {passive: true}); - image.removeEventListener("touchend", touchEnd, {passive: true}); + img?.removeEventListener("touchmove", touchMove); + img?.removeEventListener("touchend", touchEnd); }; - image.addEventListener("touchmove", touchMove, {passive: true}); - image.addEventListener("touchend", touchEnd, {passive: true}); + img?.addEventListener("touchmove", touchMove, {passive: true}); + img?.addEventListener("touchend", touchEnd, {passive: true}); }; // Image mouse manipulation: diff --git a/client/components/LinkPreview.vue b/client/components/LinkPreview.vue index d95595f3..e71a263b 100644 --- a/client/components/LinkPreview.vue +++ b/client/components/LinkPreview.vue @@ -130,144 +130,177 @@ diff --git a/client/components/LinkPreviewToggle.vue b/client/components/LinkPreviewToggle.vue index bd030066..c65b79f1 100644 --- a/client/components/LinkPreviewToggle.vue +++ b/client/components/LinkPreviewToggle.vue @@ -9,12 +9,12 @@ diff --git a/client/components/Settings/Notifications.vue b/client/components/Settings/Notifications.vue index 09da0dc7..172bf9ed 100644 --- a/client/components/Settings/Notifications.vue +++ b/client/components/Settings/Notifications.vue @@ -161,7 +161,7 @@ export default defineComponent({ name: "NotificationSettings", setup() { const store = useStore(); - + console.log(store); const isIOS = computed( () => [ diff --git a/client/components/Sidebar.vue b/client/components/Sidebar.vue index 2df88deb..35426408 100644 --- a/client/components/Sidebar.vue +++ b/client/components/Sidebar.vue @@ -91,7 +91,7 @@ export default defineComponent({ NetworkList, }, props: { - overlay: {type: Object as PropType, required: true}, + overlay: {type: Object as PropType, required: true}, }, setup(props) { const isDevelopment = process.env.NODE_ENV !== "production"; diff --git a/client/components/SidebarToggle.vue b/client/components/SidebarToggle.vue index d38a930e..c260cac7 100644 --- a/client/components/SidebarToggle.vue +++ b/client/components/SidebarToggle.vue @@ -1,11 +1,19 @@ diff --git a/client/js/router.ts b/client/js/router.ts index 91d65359..b6e6e1e5 100644 --- a/client/js/router.ts +++ b/client/js/router.ts @@ -160,11 +160,11 @@ router.afterEach((to) => { } // When switching out of a channel, mark everything as read - if (channel.messages.length > 0) { + if (channel.messages?.length > 0) { channel.firstUnread = channel.messages[channel.messages.length - 1].id; } - if (channel.messages.length > 100) { + if (channel.messages?.length > 100) { channel.messages.splice(0, channel.messages.length - 100); channel.moreHistoryAvailable = true; } @@ -172,7 +172,7 @@ router.afterEach((to) => { }); function navigate(routeName: string, params: any = {}) { - if (router.currentRoute.name) { + if (router.currentRoute.value.name) { // eslint-disable-next-line @typescript-eslint/no-empty-function router.push({name: routeName, params}).catch(() => {}); } else { diff --git a/client/js/types.d.ts b/client/js/types.d.ts index adb4f491..459f98c4 100644 --- a/client/js/types.d.ts +++ b/client/js/types.d.ts @@ -27,10 +27,15 @@ type ClientUser = User & { // }; -type ClientChan = Omit & { +type ClientMessage = Message & { + time: number; +}; + +type ClientChan = Omit & { moreHistoryAvailable: boolean; editTopic: boolean; users: ClientUser[]; + messages: ClientMessage[]; // these are added in store/initChannel pendingMessage: string; @@ -53,10 +58,6 @@ type ClientNetwork = Omit & { channels: ClientChan[]; }; -type ClientMessage = Message & { - // -}; - type NetChan = { channel: ClientChan; network: ClientNetwork; @@ -68,7 +69,9 @@ type ClientMention = Mention & { channel: NetChan | null; }; -type LinkPreview = LinkPreview; +type ClientLinkPreview = LinkPreview & { + sourceLoaded?: boolean; +}; declare module "*.vue" { const Component: ReturnType; diff --git a/client/js/vue.ts b/client/js/vue.ts index b7381cfd..5ab06c3d 100644 --- a/client/js/vue.ts +++ b/client/js/vue.ts @@ -83,7 +83,3 @@ VueApp.config.errorHandler = function (e) { // eslint-disable-next-line no-console console.error(e); }; - -VueApp.config.globalProperties = { - $store: store as TypedStore, -}; diff --git a/src/plugins/irc-events/link.ts b/src/plugins/irc-events/link.ts index db2f3be1..a6c17095 100644 --- a/src/plugins/irc-events/link.ts +++ b/src/plugins/irc-events/link.ts @@ -28,7 +28,7 @@ export type LinkPreview = { size: number; link: string; // Send original matched link to the client shown: boolean | null; - error: undefined | any; + error: undefined | string; message: undefined | string; };