mirror of
https://github.com/thelounge/thelounge.git
synced 2024-06-07 00:02:44 +02:00
fix sidebar buttons, channel loading, parting in ctxt menu
This commit is contained in:
parent
f189e9766c
commit
4740d1d574
|
@ -43,7 +43,7 @@ import {
|
||||||
import {useStore} from "../js/store";
|
import {useStore} from "../js/store";
|
||||||
import type {DebouncedFunc} from "lodash";
|
import type {DebouncedFunc} from "lodash";
|
||||||
|
|
||||||
const imageViewerKey = Symbol() as InjectionKey<Ref<typeof ImageViewer | null>>;
|
export const imageViewerKey = Symbol() as InjectionKey<Ref<typeof ImageViewer | null>>;
|
||||||
const contextMenuKey = Symbol() as InjectionKey<Ref<typeof ContextMenu | null>>;
|
const contextMenuKey = Symbol() as InjectionKey<Ref<typeof ContextMenu | null>>;
|
||||||
const confirmDialogKey = Symbol() as InjectionKey<Ref<typeof ConfirmDialog | null>>;
|
const confirmDialogKey = Symbol() as InjectionKey<Ref<typeof ConfirmDialog | null>>;
|
||||||
|
|
||||||
|
@ -51,14 +51,6 @@ export const useImageViewer = () => {
|
||||||
return inject(imageViewerKey) as Ref<typeof ImageViewer | null>;
|
return inject(imageViewerKey) as Ref<typeof ImageViewer | null>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useContextMenu = () => {
|
|
||||||
return inject(contextMenuKey) as Ref<typeof ContextMenu | null>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useConfirmDialog = () => {
|
|
||||||
return inject(confirmDialogKey) as Ref<typeof ConfirmDialog | null>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "App",
|
name: "App",
|
||||||
components: {
|
components: {
|
||||||
|
@ -70,7 +62,6 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
const overlay = ref(null);
|
const overlay = ref(null);
|
||||||
const loungeWindow = ref(null);
|
const loungeWindow = ref(null);
|
||||||
const imageViewer = ref(null);
|
const imageViewer = ref(null);
|
||||||
|
|
|
@ -17,12 +17,12 @@
|
||||||
<span class="parted-channel-icon" />
|
<span class="parted-channel-icon" />
|
||||||
</span>
|
</span>
|
||||||
<span class="close-tooltip tooltipped tooltipped-w" aria-label="Leave">
|
<span class="close-tooltip tooltipped tooltipped-w" aria-label="Leave">
|
||||||
<button class="close" aria-label="Leave" @click.stop="close" />
|
<button class="close" aria-label="Leave" @click.stop="close()" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="close-tooltip tooltipped tooltipped-w" aria-label="Close">
|
<span class="close-tooltip tooltipped tooltipped-w" aria-label="Close">
|
||||||
<button class="close" aria-label="Close" @click.stop="close" />
|
<button class="close" aria-label="Close" @click.stop="close()" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</ChannelWrapper>
|
</ChannelWrapper>
|
||||||
|
|
|
@ -152,7 +152,8 @@ export default defineComponent({
|
||||||
channel: {type: Object as PropType<ClientChan>, required: true},
|
channel: {type: Object as PropType<ClientChan>, required: true},
|
||||||
focused: String,
|
focused: String,
|
||||||
},
|
},
|
||||||
setup(props) {
|
emits: ["channel-changed"],
|
||||||
|
setup(props, {emit}) {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
const messageList = ref<typeof MessageList>();
|
const messageList = ref<typeof MessageList>();
|
||||||
|
@ -175,8 +176,9 @@ export default defineComponent({
|
||||||
|
|
||||||
const channelChanged = () => {
|
const channelChanged = () => {
|
||||||
// Triggered when active channel is set or changed
|
// Triggered when active channel is set or changed
|
||||||
props.channel.highlight = 0;
|
// props.channel.highlight = 0;
|
||||||
props.channel.unread = 0;
|
// props.channel.unread = 0;
|
||||||
|
emit("channel-changed", props.channel);
|
||||||
|
|
||||||
socket.emit("open", props.channel.id);
|
socket.emit("open", props.channel.id);
|
||||||
|
|
||||||
|
@ -229,9 +231,12 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(props.channel, () => {
|
watch(
|
||||||
channelChanged();
|
() => props.channel,
|
||||||
});
|
() => {
|
||||||
|
channelChanged();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const editTopicRef = ref(props.channel.editTopic);
|
const editTopicRef = ref(props.channel.editTopic);
|
||||||
watch(editTopicRef, (newTopic) => {
|
watch(editTopicRef, (newTopic) => {
|
||||||
|
|
|
@ -222,17 +222,21 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const channelId = ref(props.channel.id);
|
watch(
|
||||||
watch(channelId, () => {
|
() => props.channel.id,
|
||||||
if (autocompletionRef.value) {
|
() => {
|
||||||
autocompletionRef.value.hide();
|
if (autocompletionRef.value) {
|
||||||
|
autocompletionRef.value.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
const pendingMessage = ref(props.channel.pendingMessage);
|
watch(
|
||||||
watch(pendingMessage, () => {
|
() => props.channel.pendingMessage,
|
||||||
setInputSize();
|
() => {
|
||||||
});
|
setInputSize();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
eventbus.on("escapekey", blurInput);
|
eventbus.on("escapekey", blurInput);
|
||||||
|
@ -347,8 +351,6 @@ export default defineComponent({
|
||||||
openFileUpload,
|
openFileUpload,
|
||||||
blurInput,
|
blurInput,
|
||||||
onBlur,
|
onBlur,
|
||||||
channelId,
|
|
||||||
pendingMessage,
|
|
||||||
setInputSize,
|
setInputSize,
|
||||||
upload,
|
upload,
|
||||||
getInputPlaceholder,
|
getInputPlaceholder,
|
||||||
|
|
|
@ -83,7 +83,6 @@ export default defineComponent({
|
||||||
const userSearchInput = ref("");
|
const userSearchInput = ref("");
|
||||||
const activeUser = ref<UserInMessage | null>();
|
const activeUser = ref<UserInMessage | null>();
|
||||||
const userlist = ref<HTMLDivElement>();
|
const userlist = ref<HTMLDivElement>();
|
||||||
|
|
||||||
const filteredUsers = computed(() => {
|
const filteredUsers = computed(() => {
|
||||||
if (!userSearchInput.value) {
|
if (!userSearchInput.value) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import calendar from "dayjs/plugin/calendar";
|
import calendar from "dayjs/plugin/calendar";
|
||||||
import Vue, {computed, defineComponent, onBeforeUnmount, onMounted, PropType} from "vue";
|
import {computed, defineComponent, onBeforeUnmount, onMounted, PropType} from "vue";
|
||||||
import eventbus from "../js/eventbus";
|
import eventbus from "../js/eventbus";
|
||||||
import {ClientMessage} from "../js/types";
|
import {ClientMessage} from "../js/types";
|
||||||
|
|
||||||
|
@ -33,8 +33,8 @@ export default defineComponent({
|
||||||
|
|
||||||
const dayChange = () => {
|
const dayChange = () => {
|
||||||
// TODO: this is nasty. and maybe doesnt work?
|
// TODO: this is nasty. and maybe doesnt work?
|
||||||
const instance = Vue.getCurrentInstance();
|
// const instance = Vue.getCurrentInstance();
|
||||||
instance?.proxy?.$forceUpdate();
|
// instance?.proxy?.$forceUpdate();
|
||||||
|
|
||||||
if (hoursPassed() >= 48) {
|
if (hoursPassed() >= 48) {
|
||||||
eventbus.off("daychange", dayChange);
|
eventbus.off("daychange", dayChange);
|
||||||
|
|
|
@ -130,24 +130,23 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue, {
|
import {
|
||||||
computed,
|
computed,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
|
inject,
|
||||||
nextTick,
|
nextTick,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
onMounted,
|
onMounted,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
PropType,
|
PropType,
|
||||||
ref,
|
ref,
|
||||||
inject,
|
|
||||||
Ref,
|
|
||||||
watch,
|
watch,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import eventbus from "../js/eventbus";
|
import eventbus from "../js/eventbus";
|
||||||
import friendlysize from "../js/helpers/friendlysize";
|
import friendlysize from "../js/helpers/friendlysize";
|
||||||
import {useStore} from "../js/store";
|
import {useStore} from "../js/store";
|
||||||
import type {ClientChan, ClientLinkPreview} from "../js/types";
|
import type {ClientChan, ClientLinkPreview} from "../js/types";
|
||||||
import {useImageViewer} from "./App.vue";
|
import {imageViewerKey, useImageViewer} from "./App.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "LinkPreview",
|
name: "LinkPreview",
|
||||||
|
@ -235,7 +234,7 @@ export default defineComponent({
|
||||||
|
|
||||||
const onThumbnailClick = (e: MouseEvent) => {
|
const onThumbnailClick = (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const imageViewer = useImageViewer();
|
const imageViewer = inject(imageViewerKey);
|
||||||
|
|
||||||
if (!imageViewer.value) {
|
if (!imageViewer.value) {
|
||||||
return;
|
return;
|
||||||
|
@ -280,11 +279,13 @@ export default defineComponent({
|
||||||
|
|
||||||
updateShownState();
|
updateShownState();
|
||||||
|
|
||||||
const linkTypeRef = ref(props.link.type);
|
watch(
|
||||||
watch(linkTypeRef, () => {
|
() => props.link.type,
|
||||||
updateShownState();
|
() => {
|
||||||
onPreviewUpdate();
|
updateShownState();
|
||||||
});
|
onPreviewUpdate();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
eventbus.on("resize", handleResize);
|
eventbus.on("resize", handleResize);
|
||||||
|
@ -301,6 +302,20 @@ export default defineComponent({
|
||||||
// Otherwise the browser can cause a resize on video elements
|
// Otherwise the browser can cause a resize on video elements
|
||||||
props.link.sourceLoaded = false;
|
props.link.sourceLoaded = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
moreButtonLabel,
|
||||||
|
imageMaxSize,
|
||||||
|
onThumbnailClick,
|
||||||
|
onThumbnailError,
|
||||||
|
onMoreClick,
|
||||||
|
onPreviewReady,
|
||||||
|
onPreviewUpdate,
|
||||||
|
showMoreButton,
|
||||||
|
isContentShown,
|
||||||
|
content,
|
||||||
|
container,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<template v-else> in unknown channel </template>
|
<template v-else> in unknown channel </template>
|
||||||
</span>
|
</span>
|
||||||
<span :title="message.localetime" class="time">
|
<span :title="message.localetime" class="time">
|
||||||
{{ messageTime(message.time) }}
|
{{ messageTime(message.time.toString()) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content" dir="auto">
|
<div class="content" dir="auto">
|
||||||
<ParsedMessage :network="null" :message="message" />
|
<ParsedMessage message="message" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -179,9 +179,12 @@ export default defineComponent({
|
||||||
return messages.filter((message) => !message.channel?.channel.muted);
|
return messages.filter((message) => !message.channel?.channel.muted);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(store.state.mentions, () => {
|
watch(
|
||||||
isLoading.value = false;
|
() => store.state.mentions,
|
||||||
});
|
() => {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const messageTime = (time: string) => {
|
const messageTime = (time: string) => {
|
||||||
return dayjs(time).fromNow();
|
return dayjs(time).fromNow();
|
||||||
|
|
|
@ -89,6 +89,9 @@ type CondensedMessageContainer = {
|
||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO; move into component
|
||||||
|
let unreadMarkerShown = false;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "MessageList",
|
name: "MessageList",
|
||||||
components: {
|
components: {
|
||||||
|
@ -108,7 +111,6 @@ export default defineComponent({
|
||||||
const loadMoreButton = ref<HTMLButtonElement | null>(null);
|
const loadMoreButton = ref<HTMLButtonElement | null>(null);
|
||||||
const historyObserver = ref<IntersectionObserver | null>(null);
|
const historyObserver = ref<IntersectionObserver | null>(null);
|
||||||
const skipNextScrollEvent = ref(false);
|
const skipNextScrollEvent = ref(false);
|
||||||
const unreadMarkerShown = ref(false);
|
|
||||||
|
|
||||||
const isWaitingForNextTick = ref(false);
|
const isWaitingForNextTick = ref(false);
|
||||||
|
|
||||||
|
@ -265,8 +267,8 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
|
|
||||||
const shouldDisplayUnreadMarker = (id: number) => {
|
const shouldDisplayUnreadMarker = (id: number) => {
|
||||||
if (!unreadMarkerShown.value && id > props.channel.firstUnread) {
|
if (!unreadMarkerShown && id > props.channel.firstUnread) {
|
||||||
unreadMarkerShown.value = true;
|
unreadMarkerShown = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,35 +382,41 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const channelId = ref(props.channel.id);
|
watch(
|
||||||
watch(channelId, () => {
|
() => props.channel.id,
|
||||||
props.channel.scrolledToBottom = true;
|
() => {
|
||||||
|
props.channel.scrolledToBottom = true;
|
||||||
|
|
||||||
// Re-add the intersection observer to trigger the check again on channel switch
|
// Re-add the intersection observer to trigger the check again on channel switch
|
||||||
// Otherwise if last channel had the button visible, switching to a new channel won't trigger the history
|
// Otherwise if last channel had the button visible, switching to a new channel won't trigger the history
|
||||||
if (historyObserver.value && loadMoreButton.value) {
|
if (historyObserver.value && loadMoreButton.value) {
|
||||||
historyObserver.value.unobserve(loadMoreButton.value);
|
historyObserver.value.unobserve(loadMoreButton.value);
|
||||||
historyObserver.value.observe(loadMoreButton.value);
|
historyObserver.value.observe(loadMoreButton.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
const channelMessages = ref(props.channel.messages);
|
watch(
|
||||||
watch(channelMessages, () => {
|
() => props.channel.messages,
|
||||||
keepScrollPosition();
|
() => {
|
||||||
});
|
|
||||||
|
|
||||||
const pendingMessage = ref(props.channel.pendingMessage);
|
|
||||||
watch(pendingMessage, () => {
|
|
||||||
nextTick(() => {
|
|
||||||
// Keep the scroll stuck when input gets resized while typing
|
|
||||||
keepScrollPosition();
|
keepScrollPosition();
|
||||||
}).catch(() => {
|
}
|
||||||
// no-op
|
);
|
||||||
});
|
|
||||||
});
|
watch(
|
||||||
|
() => props.channel.pendingMessage,
|
||||||
|
() => {
|
||||||
|
nextTick(() => {
|
||||||
|
// Keep the scroll stuck when input gets resized while typing
|
||||||
|
keepScrollPosition();
|
||||||
|
}).catch(() => {
|
||||||
|
// no-op
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
onBeforeUpdate(() => {
|
onBeforeUpdate(() => {
|
||||||
unreadMarkerShown.value = false;
|
unreadMarkerShown = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|
|
@ -499,27 +499,30 @@ export default defineComponent({
|
||||||
)}px`;
|
)}px`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const commands = ref(props.defaults?.commands);
|
watch(
|
||||||
|
() => props.defaults?.commands,
|
||||||
watch(commands, () => {
|
() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
resizeCommandsInput();
|
resizeCommandsInput();
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
// no-op
|
// no-op
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const tls = ref(props.defaults?.tls);
|
|
||||||
watch(tls, (isSecureChecked) => {
|
|
||||||
const ports = [6667, 6697];
|
|
||||||
const newPort = isSecureChecked ? 0 : 1;
|
|
||||||
|
|
||||||
// If you disable TLS and current port is 6697,
|
|
||||||
// set it to 6667, and vice versa
|
|
||||||
if (props.defaults?.port === ports[newPort]) {
|
|
||||||
props.defaults.port = ports[1 - newPort];
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.defaults?.tls,
|
||||||
|
(isSecureChecked) => {
|
||||||
|
const ports = [6667, 6697];
|
||||||
|
const newPort = isSecureChecked ? 0 : 1;
|
||||||
|
|
||||||
|
// If you disable TLS and current port is 6697,
|
||||||
|
// set it to 6667, and vice versa
|
||||||
|
if (props.defaults?.port === ports[newPort]) {
|
||||||
|
props.defaults.port = ports[1 - newPort];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const setSaslAuth = (type: string) => {
|
const setSaslAuth = (type: string) => {
|
||||||
if (props.defaults) {
|
if (props.defaults) {
|
||||||
|
@ -559,10 +562,8 @@ export default defineComponent({
|
||||||
config,
|
config,
|
||||||
displayPasswordField,
|
displayPasswordField,
|
||||||
publicPassword,
|
publicPassword,
|
||||||
commands,
|
|
||||||
commandsInput,
|
commandsInput,
|
||||||
resizeCommandsInput,
|
resizeCommandsInput,
|
||||||
tls,
|
|
||||||
setSaslAuth,
|
setSaslAuth,
|
||||||
usernameInput,
|
usernameInput,
|
||||||
onNickChanged,
|
onNickChanged,
|
||||||
|
|
|
@ -8,12 +8,9 @@ export default defineComponent({
|
||||||
functional: true,
|
functional: true,
|
||||||
props: {
|
props: {
|
||||||
text: String,
|
text: String,
|
||||||
message: {type: Object as PropType<ClientMessage>, required: false},
|
message: {type: Object as PropType<ClientMessage | string>, required: false},
|
||||||
network: {type: Object as PropType<ClientNetwork>, required: false},
|
network: {type: Object as PropType<ClientNetwork>, required: false},
|
||||||
},
|
},
|
||||||
setup(props) {
|
|
||||||
//
|
|
||||||
},
|
|
||||||
render(context) {
|
render(context) {
|
||||||
return parse(
|
return parse(
|
||||||
typeof context.text !== "undefined" ? context.text : context.message.text,
|
typeof context.text !== "undefined" ? context.text : context.message.text,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
:network="activeChannel.network"
|
:network="activeChannel.network"
|
||||||
:channel="activeChannel.channel"
|
:channel="activeChannel.channel"
|
||||||
:focused="(route.query.focused as string)"
|
:focused="(route.query.focused as string)"
|
||||||
|
@channel-changed="channelChanged"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@
|
||||||
import {watch, computed, defineComponent, onMounted} from "vue";
|
import {watch, computed, defineComponent, onMounted} from "vue";
|
||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
import {useStore} from "../js/store";
|
import {useStore} from "../js/store";
|
||||||
|
import {ClientChan} from "../js/types";
|
||||||
|
|
||||||
// Temporary component for routing channels and lobbies
|
// Temporary component for routing channels and lobbies
|
||||||
import Chat from "./Chat.vue";
|
import Chat from "./Chat.vue";
|
||||||
|
@ -44,9 +46,20 @@ export default defineComponent({
|
||||||
setActiveChannel();
|
setActiveChannel();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const channelChanged = (channel: ClientChan) => {
|
||||||
|
const chanId = channel.id;
|
||||||
|
const chanInStore = store.getters.findChannel(chanId);
|
||||||
|
|
||||||
|
if (chanInStore?.channel) {
|
||||||
|
chanInStore.channel.unread = 0;
|
||||||
|
chanInStore.channel.highlight = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
route,
|
route,
|
||||||
activeChannel,
|
activeChannel,
|
||||||
|
channelChanged,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -103,7 +103,7 @@ import socket from "../../js/socket";
|
||||||
import RevealPassword from "../RevealPassword.vue";
|
import RevealPassword from "../RevealPassword.vue";
|
||||||
import Session from "../Session.vue";
|
import Session from "../Session.vue";
|
||||||
import {computed, defineComponent, onMounted, ref} from "vue";
|
import {computed, defineComponent, onMounted, ref} from "vue";
|
||||||
import store from "../../js/store";
|
import {useStore} from "../../js/store";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "UserSettings",
|
name: "UserSettings",
|
||||||
|
@ -112,6 +112,7 @@ export default defineComponent({
|
||||||
Session,
|
Session,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const store = useStore();
|
||||||
const settingsForm = ref<HTMLFormElement>();
|
const settingsForm = ref<HTMLFormElement>();
|
||||||
const passwordErrors = {
|
const passwordErrors = {
|
||||||
missing_fields: "Please enter a new password",
|
missing_fields: "Please enter a new password",
|
||||||
|
|
|
@ -161,7 +161,7 @@ export default defineComponent({
|
||||||
name: "NotificationSettings",
|
name: "NotificationSettings",
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
console.log(store);
|
|
||||||
const isIOS = computed(
|
const isIOS = computed(
|
||||||
() =>
|
() =>
|
||||||
[
|
[
|
||||||
|
@ -176,10 +176,12 @@ export default defineComponent({
|
||||||
(navigator.userAgent.includes("Mac") && "ontouchend" in document)
|
(navigator.userAgent.includes("Mac") && "ontouchend" in document)
|
||||||
);
|
);
|
||||||
|
|
||||||
const playNotification = async () => {
|
const playNotification = () => {
|
||||||
const pop = new Audio();
|
const pop = new Audio();
|
||||||
pop.src = "audio/pop.wav";
|
pop.src = "audio/pop.wav";
|
||||||
await pop.play();
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
pop.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPushButtonClick = () => {
|
const onPushButtonClick = () => {
|
||||||
|
@ -188,9 +190,9 @@ export default defineComponent({
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isIOS,
|
isIOS,
|
||||||
|
store,
|
||||||
playNotification,
|
playNotification,
|
||||||
onPushButtonClick,
|
onPushButtonClick,
|
||||||
store,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<li :aria-label="name">
|
<li :aria-label="name" role="tab" :aria-selected="route.name === name" aria-controls="settings">
|
||||||
<router-link
|
<router-link v-slot:default="{navigate, isExactActive}" :to="'/settings/' + to" custom>
|
||||||
v-slot:default="{navigate, isExactActive}"
|
<button
|
||||||
:to="'/settings/' + to"
|
:class="['icon', className, {active: isExactActive}]"
|
||||||
:class="['icon', className]"
|
@click="navigate"
|
||||||
:aria-label="name"
|
@keypress.enter="navigate"
|
||||||
role="tab"
|
>
|
||||||
aria-controls="settings"
|
|
||||||
:aria-selected="route.name === name"
|
|
||||||
custom
|
|
||||||
>
|
|
||||||
<button :class="{active: isExactActive}" @click="navigate" @keypress.enter="navigate">
|
|
||||||
{{ name }}
|
{{ name }}
|
||||||
</button>
|
</button>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
@ -18,6 +13,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
// v-slot:default="{navigate, isExactActive}"
|
||||||
|
// :to="'/settings/' + to"
|
||||||
|
// :class="['icon', className]"
|
||||||
|
// :aria-label="name"
|
||||||
|
// role="tab"
|
||||||
|
// aria-controls="settings"
|
||||||
|
// :aria-selected="route.name === name"
|
||||||
|
// custom
|
||||||
import {defineComponent} from "vue";
|
import {defineComponent} from "vue";
|
||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
|
|
||||||
|
|
|
@ -34,26 +34,32 @@
|
||||||
class="tooltipped tooltipped-n tooltipped-no-touch"
|
class="tooltipped tooltipped-n tooltipped-no-touch"
|
||||||
aria-label="Connect to network"
|
aria-label="Connect to network"
|
||||||
><router-link
|
><router-link
|
||||||
|
v-slot:default="{navigate, isActive}"
|
||||||
to="/connect"
|
to="/connect"
|
||||||
tag="button"
|
|
||||||
active-class="active"
|
|
||||||
:class="['icon', 'connect']"
|
|
||||||
aria-label="Connect to network"
|
|
||||||
role="tab"
|
role="tab"
|
||||||
aria-controls="connect"
|
aria-controls="connect"
|
||||||
:aria-selected="route.name === 'Connect'"
|
>
|
||||||
/></span>
|
<button
|
||||||
|
:class="['icon', 'connect', {active: isActive}]"
|
||||||
|
:aria-selected="isActive"
|
||||||
|
@click="navigate"
|
||||||
|
@keypress.enter="navigate"
|
||||||
|
/> </router-link
|
||||||
|
></span>
|
||||||
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Settings"
|
<span class="tooltipped tooltipped-n tooltipped-no-touch" aria-label="Settings"
|
||||||
><router-link
|
><router-link
|
||||||
|
v-slot:default="{navigate, isActive}"
|
||||||
to="/settings"
|
to="/settings"
|
||||||
tag="button"
|
|
||||||
active-class="active"
|
|
||||||
:class="['icon', 'settings']"
|
|
||||||
aria-label="Settings"
|
|
||||||
role="tab"
|
role="tab"
|
||||||
aria-controls="settings"
|
aria-controls="settings"
|
||||||
:aria-selected="route.name === 'General'"
|
>
|
||||||
/></span>
|
<button
|
||||||
|
:class="['icon', 'settings', {active: isActive}]"
|
||||||
|
:aria-selected="isActive"
|
||||||
|
@click="navigate"
|
||||||
|
@keypress.enter="navigate"
|
||||||
|
></button> </router-link
|
||||||
|
></span>
|
||||||
<span
|
<span
|
||||||
class="tooltipped tooltipped-n tooltipped-no-touch"
|
class="tooltipped tooltipped-n tooltipped-no-touch"
|
||||||
:aria-label="
|
:aria-label="
|
||||||
|
@ -62,19 +68,23 @@
|
||||||
: 'Help'
|
: 'Help'
|
||||||
"
|
"
|
||||||
><router-link
|
><router-link
|
||||||
|
v-slot:default="{navigate, isActive}"
|
||||||
to="/help"
|
to="/help"
|
||||||
tag="button"
|
|
||||||
active-class="active"
|
|
||||||
:class="[
|
|
||||||
'icon',
|
|
||||||
'help',
|
|
||||||
{notified: store.state.serverConfiguration?.isUpdateAvailable},
|
|
||||||
]"
|
|
||||||
aria-label="Help"
|
|
||||||
role="tab"
|
role="tab"
|
||||||
aria-controls="help"
|
aria-controls="help"
|
||||||
:aria-selected="route.name === 'Help'"
|
>
|
||||||
/></span>
|
<button
|
||||||
|
:aria-selected="route.name === 'Help'"
|
||||||
|
:class="[
|
||||||
|
'icon',
|
||||||
|
'help',
|
||||||
|
{notified: store.state.serverConfiguration?.isUpdateAvailable},
|
||||||
|
{active: isActive},
|
||||||
|
]"
|
||||||
|
@click="navigate"
|
||||||
|
@keypress.enter="navigate"
|
||||||
|
></button> </router-link
|
||||||
|
></span>
|
||||||
</footer>
|
</footer>
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default defineComponent({
|
||||||
return props.user.mode;
|
return props.user.mode;
|
||||||
});
|
});
|
||||||
|
|
||||||
const nickColor = computed(() => colorClass(props.user.nick));
|
const nickColor = computed(() => colorClass(props.user.nick!));
|
||||||
|
|
||||||
const hover = () => {
|
const hover = () => {
|
||||||
if (props.onHover) {
|
if (props.onHover) {
|
||||||
|
|
|
@ -50,11 +50,12 @@ export default defineComponent({
|
||||||
// this.setNetworkData();
|
// this.setNetworkData();
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
watch(route.params, (newValue) => {
|
watch(
|
||||||
if (newValue.uuid) {
|
() => route.params.uuid,
|
||||||
|
(newValue) => {
|
||||||
setNetworkData();
|
setNetworkData();
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setNetworkData();
|
setNetworkData();
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
<!-- TODO: this was message.date -->
|
<!-- TODO: this was message.date -->
|
||||||
<DateMarker
|
<DateMarker
|
||||||
v-if="shouldDisplayDateMarker(message, id)"
|
v-if="shouldDisplayDateMarker(message, id)"
|
||||||
:key="message.when.toString()"
|
:key="message.time"
|
||||||
:message="message"
|
:message="message"
|
||||||
/>
|
/>
|
||||||
<!-- todo channel and network ! -->
|
<!-- todo channel and network ! -->
|
||||||
|
@ -257,18 +257,21 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const routeIdRef = ref(route.params.id);
|
watch(
|
||||||
const routeQueryRef = ref(route.query);
|
() => route.params.id,
|
||||||
|
() => {
|
||||||
|
doSearch();
|
||||||
|
setActiveChannel();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
watch(routeIdRef, () => {
|
watch(
|
||||||
doSearch();
|
() => route.query,
|
||||||
setActiveChannel();
|
() => {
|
||||||
});
|
doSearch();
|
||||||
|
setActiveChannel();
|
||||||
watch(routeQueryRef, () => {
|
}
|
||||||
doSearch();
|
);
|
||||||
setActiveChannel();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(messages, () => {
|
watch(messages, () => {
|
||||||
moreResultsAvailable.value = !!(
|
moreResultsAvailable.value = !!(
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {TextareaEditor} from "@textcomplete/textarea/dist/TextareaEditor";
|
||||||
import fuzzy from "fuzzy";
|
import fuzzy from "fuzzy";
|
||||||
|
|
||||||
import emojiMap from "./helpers/simplemap.json";
|
import emojiMap from "./helpers/simplemap.json";
|
||||||
import store from "./store";
|
import {store} from "./store";
|
||||||
|
|
||||||
export default enableAutocomplete;
|
export default enableAutocomplete;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
function input() {
|
function input() {
|
||||||
const messageIds = [];
|
const messageIds = [];
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
function input() {
|
function input() {
|
||||||
const messageIds = [];
|
const messageIds = [];
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import {switchToChannel} from "../router";
|
import {switchToChannel} from "../router";
|
||||||
|
|
||||||
function input(args) {
|
function input(args) {
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import {router} from "../router";
|
import {router} from "../router";
|
||||||
|
|
||||||
function input(args) {
|
function input(args: string[]) {
|
||||||
if (!store.state.settings.searchEnabled) {
|
if (!store.state.settings.searchEnabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push({
|
router
|
||||||
name: "SearchResults",
|
.push({
|
||||||
params: {
|
name: "SearchResults",
|
||||||
id: store.state.activeChannel.channel.id,
|
params: {
|
||||||
},
|
id: store.state.activeChannel.channel.id,
|
||||||
query: {
|
},
|
||||||
q: args.join(" "),
|
query: {
|
||||||
},
|
q: args.join(" "),
|
||||||
});
|
},
|
||||||
|
})
|
||||||
|
.catch((e: Error) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`Failed to push SearchResults route: ${e.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
// Generates a string from "color-1" to "color-32" based on an input string
|
// Generates a string from "color-1" to "color-32" based on an input string
|
||||||
export default (str: string) => {
|
export default (str: string) => {
|
||||||
|
if (!str) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import eventbus from "../eventbus";
|
||||||
import type {ClientChan, ClientNetwork, ClientUser} from "../types";
|
import type {ClientChan, ClientNetwork, ClientUser} from "../types";
|
||||||
import {switchToChannel} from "../router";
|
import {switchToChannel} from "../router";
|
||||||
import {TypedStore} from "../store";
|
import {TypedStore} from "../store";
|
||||||
import closeChannel from "../hooks/use-close-channel";
|
import useCloseChannel from "../hooks/use-close-channel";
|
||||||
|
|
||||||
type BaseContextMenuItem = {
|
type BaseContextMenuItem = {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -32,6 +32,8 @@ export function generateChannelContextMenu(
|
||||||
channel: ClientChan,
|
channel: ClientChan,
|
||||||
network: ClientNetwork
|
network: ClientNetwork
|
||||||
): ContextMenuItem[] {
|
): ContextMenuItem[] {
|
||||||
|
const closeChannel = useCloseChannel(channel);
|
||||||
|
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
lobby: "network",
|
lobby: "network",
|
||||||
channel: "chan",
|
channel: "chan",
|
||||||
|
@ -229,7 +231,7 @@ export function generateChannelContextMenu(
|
||||||
type: "item",
|
type: "item",
|
||||||
class: "close",
|
class: "close",
|
||||||
action() {
|
action() {
|
||||||
closeChannel(channel);
|
closeChannel();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
export default (network, channel) => {
|
export default (network, channel) => {
|
||||||
if (!network.isCollapsed || channel.highlight || channel.type === "lobby") {
|
if (!network.isCollapsed || channel.highlight || channel.type === "lobby") {
|
||||||
|
|
|
@ -112,13 +112,11 @@ function parse(text: string, message?: ClientMessage, network?: ClientNetwork) {
|
||||||
const link = createElement(
|
const link = createElement(
|
||||||
"a",
|
"a",
|
||||||
{
|
{
|
||||||
attrs: {
|
// @ts-ignore
|
||||||
// @ts-ignore
|
"^href": textPart.link,
|
||||||
href: textPart.link,
|
"^dir": preview ? null : "auto",
|
||||||
dir: preview ? null : "auto",
|
"^target": "_blank",
|
||||||
target: "_blank",
|
"^rel": "noopener",
|
||||||
rel: "noopener",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
() => fragments
|
() => fragments
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
|
|
||||||
import store from "./store";
|
import {store} from "./store";
|
||||||
import {switchToChannel, router, navigate} from "./router";
|
import {switchToChannel, router, navigate} from "./router";
|
||||||
import isChannelCollapsed from "./helpers/isChannelCollapsed";
|
import isChannelCollapsed from "./helpers/isChannelCollapsed";
|
||||||
import isIgnoredKeybind from "./helpers/isIgnoredKeybind";
|
import isIgnoredKeybind from "./helpers/isIgnoredKeybind";
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Changelog from "../components/Windows/Changelog.vue";
|
||||||
import NetworkEdit from "../components/Windows/NetworkEdit.vue";
|
import NetworkEdit from "../components/Windows/NetworkEdit.vue";
|
||||||
import SearchResults from "../components/Windows/SearchResults.vue";
|
import SearchResults from "../components/Windows/SearchResults.vue";
|
||||||
import RoutedChat from "../components/RoutedChat.vue";
|
import RoutedChat from "../components/RoutedChat.vue";
|
||||||
import store from "./store";
|
import {store} from "./store";
|
||||||
|
|
||||||
import AppearanceSettings from "../components/Settings/Appearance.vue";
|
import AppearanceSettings from "../components/Settings/Appearance.vue";
|
||||||
import GeneralSettings from "../components/Settings/General.vue";
|
import GeneralSettings from "../components/Settings/General.vue";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "./socket";
|
import socket from "./socket";
|
||||||
import {TypedStore} from "./store";
|
import type {TypedStore} from "./store";
|
||||||
|
|
||||||
const defaultSettingConfig = {
|
const defaultSettingConfig = {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
@ -35,7 +35,14 @@ const defaultConfig = {
|
||||||
default: false,
|
default: false,
|
||||||
sync: "never",
|
sync: "never",
|
||||||
apply(store: TypedStore, value: boolean) {
|
apply(store: TypedStore, value: boolean) {
|
||||||
// TODO: investigate ignores
|
// TODO: investigate
|
||||||
|
if (!store) {
|
||||||
|
return;
|
||||||
|
// throw new Error("store is not defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit a mutation. options can have root: true that allows to commit root mutations in namespaced modules.
|
||||||
|
// https://vuex.vuejs.org/api/#store-instance-methods. not typed?
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
store.commit("refreshDesktopNotificationState", null, {root: true});
|
store.commit("refreshDesktopNotificationState", null, {root: true});
|
||||||
|
|
||||||
|
@ -87,14 +94,24 @@ const defaultConfig = {
|
||||||
theme: {
|
theme: {
|
||||||
default: document.getElementById("theme")?.dataset.serverTheme,
|
default: document.getElementById("theme")?.dataset.serverTheme,
|
||||||
apply(store: TypedStore, value: string) {
|
apply(store: TypedStore, value: string) {
|
||||||
const themeEl = document.getElementById("theme") as any;
|
const themeEl = document.getElementById("theme");
|
||||||
const themeUrl = `themes/${value}.css`;
|
const themeUrl = `themes/${value}.css`;
|
||||||
|
|
||||||
if (themeEl?.attributes.href.value === themeUrl) {
|
if (!(themeEl instanceof HTMLLinkElement)) {
|
||||||
|
throw new Error("theme element is not a link");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hrefAttr = themeEl.attributes.getNamedItem("href");
|
||||||
|
|
||||||
|
if (!hrefAttr) {
|
||||||
|
throw new Error("theme is missing href attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hrefAttr.value === themeUrl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
themeEl.attributes.href.value = themeUrl;
|
hrefAttr.value = themeUrl;
|
||||||
|
|
||||||
if (!store.state.serverConfiguration) {
|
if (!store.state.serverConfiguration) {
|
||||||
return;
|
return;
|
||||||
|
@ -106,9 +123,13 @@ const defaultConfig = {
|
||||||
|
|
||||||
const metaSelector = document.querySelector('meta[name="theme-color"]');
|
const metaSelector = document.querySelector('meta[name="theme-color"]');
|
||||||
|
|
||||||
|
if (!(metaSelector instanceof HTMLMetaElement)) {
|
||||||
|
throw new Error("theme meta element is not a meta element");
|
||||||
|
}
|
||||||
|
|
||||||
if (metaSelector) {
|
if (metaSelector) {
|
||||||
const themeColor = newTheme.themeColor || (metaSelector as any).content;
|
const themeColor = newTheme.themeColor || metaSelector.content;
|
||||||
(metaSelector as any).content = themeColor;
|
metaSelector.content = themeColor;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import storage from "../localStorage";
|
import storage from "../localStorage";
|
||||||
import {router, navigate} from "../router";
|
import {router, navigate} from "../router";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import location from "../location";
|
import location from "../location";
|
||||||
let lastServerHash = null;
|
let lastServerHash = null;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("changelog", function (data) {
|
socket.on("changelog", function (data) {
|
||||||
store.commit("versionData", data);
|
store.commit("versionData", data);
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import upload from "../upload";
|
import upload from "../upload";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.once("configuration", function (data) {
|
socket.once("configuration", function (data) {
|
||||||
store.commit("serverConfiguration", data);
|
store.commit("serverConfiguration", data);
|
||||||
|
|
||||||
// 'theme' setting depends on serverConfiguration.themes so
|
// 'theme' setting depends on serverConfiguration.themes so
|
||||||
// settings cannot be applied before this point
|
// settings cannot be applied before this point
|
||||||
store.dispatch("settings/applyAll");
|
void store.dispatch("settings/applyAll");
|
||||||
|
|
||||||
if (data.fileUpload) {
|
if (data.fileUpload) {
|
||||||
upload.initialize();
|
upload.initialize();
|
||||||
|
@ -18,9 +18,14 @@ socket.once("configuration", function (data) {
|
||||||
const currentTheme = data.themes.find((t) => t.name === store.state.settings.theme);
|
const currentTheme = data.themes.find((t) => t.name === store.state.settings.theme);
|
||||||
|
|
||||||
if (currentTheme === undefined) {
|
if (currentTheme === undefined) {
|
||||||
store.dispatch("settings/update", {name: "theme", value: data.defaultTheme, sync: true});
|
void store.dispatch("settings/update", {
|
||||||
|
name: "theme",
|
||||||
|
value: data.defaultTheme,
|
||||||
|
sync: true,
|
||||||
|
});
|
||||||
} else if (currentTheme.themeColor) {
|
} else if (currentTheme.themeColor) {
|
||||||
document.querySelector('meta[name="theme-color"]').content = currentTheme.themeColor;
|
(document.querySelector('meta[name="theme-color"]') as HTMLMetaElement).content =
|
||||||
|
currentTheme.themeColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.body.classList.contains("public")) {
|
if (document.body.classList.contains("public")) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
|
|
||||||
socket.on("disconnect", handleDisconnect);
|
socket.on("disconnect", handleDisconnect);
|
||||||
|
@ -26,7 +26,7 @@ socket.on("connect", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDisconnect(data) {
|
function handleDisconnect(data) {
|
||||||
const message = data.message || data;
|
const message = (data.message || data) as string;
|
||||||
|
|
||||||
store.commit("isConnected", false);
|
store.commit("isConnected", false);
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ function handleDisconnect(data) {
|
||||||
// If the server shuts down, socket.io skips reconnection
|
// If the server shuts down, socket.io skips reconnection
|
||||||
// and we have to manually call connect to start the process
|
// and we have to manually call connect to start the process
|
||||||
// However, do not reconnect if TL client manually closed the connection
|
// However, do not reconnect if TL client manually closed the connection
|
||||||
|
// @ts-ignore TODO
|
||||||
if (socket.io.skipReconnect && message !== "io client disconnect") {
|
if (socket.io.skipReconnect && message !== "io client disconnect") {
|
||||||
requestIdleCallback(() => socket.connect(), 2000);
|
requestIdleCallback(() => socket.connect(), 2000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("history:clear", function (data) {
|
socket.on("history:clear", function (data) {
|
||||||
const {channel} = store.getters.findChannel(data.target);
|
const {channel} = store.getters.findChannel(data.target);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {nextTick} from "vue";
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import storage from "../localStorage";
|
import storage from "../localStorage";
|
||||||
import {router, switchToChannel, navigate} from "../router";
|
import {router, switchToChannel, navigate} from "../router";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import parseIrcUri from "../helpers/parseIrcUri";
|
import parseIrcUri from "../helpers/parseIrcUri";
|
||||||
import {ClientNetwork, InitClientChan} from "../types";
|
import {ClientNetwork, InitClientChan} from "../types";
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import {switchToChannel} from "../router";
|
import {switchToChannel} from "../router";
|
||||||
|
|
||||||
socket.on("join", function (data) {
|
socket.on("join", function (data) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("mentions:list", function (data) {
|
socket.on("mentions:list", function (data) {
|
||||||
store.commit("mentions", data);
|
store.commit("mentions", data);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {nextTick} from "vue";
|
import {nextTick} from "vue";
|
||||||
|
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("more", function (data) {
|
socket.on("more", function (data) {
|
||||||
const channel = store.getters.findChannel(data.chan)?.channel;
|
const channel = store.getters.findChannel(data.chan)?.channel;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import cleanIrcMessage from "../helpers/ircmessageparser/cleanIrcMessage";
|
import cleanIrcMessage from "../helpers/ircmessageparser/cleanIrcMessage";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import {switchToChannel} from "../router";
|
import {switchToChannel} from "../router";
|
||||||
|
|
||||||
let pop;
|
let pop;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("msg:preview", function (data) {
|
socket.on("msg:preview", function (data) {
|
||||||
const {channel} = store.getters.findChannel(data.chan);
|
const {channel} = store.getters.findChannel(data.chan);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import {switchToChannel} from "../router";
|
import {switchToChannel} from "../router";
|
||||||
|
|
||||||
socket.on("msg:special", function (data) {
|
socket.on("msg:special", function (data) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("mute:changed", (response) => {
|
socket.on("mute:changed", (response) => {
|
||||||
const {target, status} = response;
|
const {target, status} = response;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("names", function (data) {
|
socket.on("names", function (data) {
|
||||||
const channel = store.getters.findChannel(data.id);
|
const channel = store.getters.findChannel(data.id);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import {switchToChannel} from "../router";
|
import {switchToChannel} from "../router";
|
||||||
|
|
||||||
socket.on("network", function (data) {
|
socket.on("network", function (data) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("nick", function (data) {
|
socket.on("nick", function (data) {
|
||||||
const network = store.getters.findNetwork(data.network);
|
const network = store.getters.findNetwork(data.network);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
// Sync unread badge and marker when other clients open a channel
|
// Sync unread badge and marker when other clients open a channel
|
||||||
socket.on("open", function (id) {
|
socket.on("open", function (id) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
import {switchToChannel} from "../router";
|
import {switchToChannel} from "../router";
|
||||||
|
|
||||||
socket.on("part", function (data) {
|
socket.on("part", function (data) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import {switchToChannel, navigate} from "../router";
|
import {switchToChannel, navigate} from "../router";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("quit", function (data) {
|
socket.on("quit", function (data) {
|
||||||
// If we're in a channel, and it's on the network that is being removed,
|
// If we're in a channel, and it's on the network that is being removed,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("search:results", (response) => {
|
socket.on("search:results", (response) => {
|
||||||
store.commit("messageSearchInProgress", false);
|
store.commit("messageSearchInProgress", false);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("sessions:list", function (data) {
|
socket.on("sessions:list", function (data) {
|
||||||
data.sort((a, b) => b.lastUse - a.lastUse);
|
data.sort((a, b) => b.lastUse - a.lastUse);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("setting:new", function (data) {
|
socket.on("setting:new", function (data) {
|
||||||
const name = data.name;
|
const name = data.name;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("sync_sort", function (data) {
|
socket.on("sync_sort", function (data) {
|
||||||
const order = data.order;
|
const order = data.order;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("topic", function (data) {
|
socket.on("topic", function (data) {
|
||||||
const channel = store.getters.findChannel(data.chan);
|
const channel = store.getters.findChannel(data.chan);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "../socket";
|
import socket from "../socket";
|
||||||
import store from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("users", function (data) {
|
socket.on("users", function (data) {
|
||||||
if (store.state.activeChannel && store.state.activeChannel.channel.id === data.chan) {
|
if (store.state.activeChannel && store.state.activeChannel.channel.id === data.chan) {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import {ActionContext, createStore, Store, useStore as baseUseStore} from "vuex"
|
||||||
import {createSettingsStore} from "./store-settings";
|
import {createSettingsStore} from "./store-settings";
|
||||||
import storage from "./localStorage";
|
import storage from "./localStorage";
|
||||||
import type {
|
import type {
|
||||||
Mention,
|
|
||||||
ClientChan,
|
ClientChan,
|
||||||
ClientConfiguration,
|
ClientConfiguration,
|
||||||
ClientNetwork,
|
ClientNetwork,
|
||||||
|
@ -13,16 +12,8 @@ import type {
|
||||||
ClientMessage,
|
ClientMessage,
|
||||||
ClientMention,
|
ClientMention,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import type {InjectionKey, WatchOptions} from "vue";
|
import type {InjectionKey} from "vue";
|
||||||
|
|
||||||
// import {
|
|
||||||
// useAccessor,
|
|
||||||
// getterTree,
|
|
||||||
// mutationTree,
|
|
||||||
// actionTree,
|
|
||||||
// getAccessorType,
|
|
||||||
// } from 'typed-vuex'
|
|
||||||
import {VueApp} from "./vue";
|
|
||||||
import {SettingsState} from "./settings";
|
import {SettingsState} from "./settings";
|
||||||
|
|
||||||
const appName = document.title;
|
const appName = document.title;
|
||||||
|
@ -397,14 +388,8 @@ const storePattern = {
|
||||||
getters,
|
getters,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const store = createStore(storePattern);
|
|
||||||
|
|
||||||
const settingsStore = createSettingsStore(store);
|
const settingsStore = createSettingsStore(store);
|
||||||
|
|
||||||
// Settings module is registered dynamically because it benefits
|
|
||||||
// from a direct reference to the store
|
|
||||||
store.registerModule("settings", settingsStore);
|
|
||||||
|
|
||||||
// https://vuex.vuejs.org/guide/typescript-support.html#typing-usestore-composition-function
|
// https://vuex.vuejs.org/guide/typescript-support.html#typing-usestore-composition-function
|
||||||
export const key: InjectionKey<Store<State>> = Symbol();
|
export const key: InjectionKey<Store<State>> = Symbol();
|
||||||
|
|
||||||
|
@ -417,7 +402,11 @@ export type TypedStore = Omit<Store<State>, "getters" | "commit"> & {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default store;
|
export const store = createStore(storePattern) as TypedStore;
|
||||||
|
|
||||||
|
// Settings module is registered dynamically because it benefits
|
||||||
|
// from a direct reference to the store
|
||||||
|
store.registerModule("settings", settingsStore);
|
||||||
|
|
||||||
export function useStore() {
|
export function useStore() {
|
||||||
return baseUseStore(key) as TypedStore;
|
return baseUseStore(key) as TypedStore;
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import {update as updateCursor} from "undate";
|
import {update as updateCursor} from "undate";
|
||||||
|
|
||||||
import socket from "./socket";
|
import socket from "./socket";
|
||||||
import store from "./store";
|
import {store} from "./store";
|
||||||
|
|
||||||
class Uploader {
|
class Uploader {
|
||||||
init() {
|
xhr: XMLHttpRequest | null = null;
|
||||||
this.xhr = null;
|
fileQueue: File[] = [];
|
||||||
this.fileQueue = [];
|
tokenKeepAlive: NodeJS.Timeout | null = null;
|
||||||
this.tokenKeepAlive = null;
|
|
||||||
|
|
||||||
|
overlay: HTMLDivElement | null = null;
|
||||||
|
uploadProgressbar: HTMLSpanElement | null = null;
|
||||||
|
|
||||||
|
init() {
|
||||||
document.addEventListener("dragenter", (e) => this.dragEnter(e));
|
document.addEventListener("dragenter", (e) => this.dragEnter(e));
|
||||||
document.addEventListener("dragover", (e) => this.dragOver(e));
|
document.addEventListener("dragover", (e) => this.dragOver(e));
|
||||||
document.addEventListener("dragleave", (e) => this.dragLeave(e));
|
document.addEventListener("dragleave", (e) => this.dragLeave(e));
|
||||||
|
@ -19,45 +22,45 @@ class Uploader {
|
||||||
}
|
}
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.overlay = document.getElementById("upload-overlay");
|
this.overlay = document.getElementById("upload-overlay") as HTMLDivElement;
|
||||||
this.uploadProgressbar = document.getElementById("upload-progressbar");
|
this.uploadProgressbar = document.getElementById("upload-progressbar") as HTMLSpanElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
dragOver(event) {
|
dragOver(event: DragEvent) {
|
||||||
if (event.dataTransfer.types.includes("Files")) {
|
if (event.dataTransfer?.types.includes("Files")) {
|
||||||
// Prevent dragover event completely and do nothing with it
|
// Prevent dragover event completely and do nothing with it
|
||||||
// This stops the browser from trying to guess which cursor to show
|
// This stops the browser from trying to guess which cursor to show
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dragEnter(event) {
|
dragEnter(event: DragEvent) {
|
||||||
// relatedTarget is the target where we entered the drag from
|
// relatedTarget is the target where we entered the drag from
|
||||||
// when dragging from another window, the target is null, otherwise its a DOM element
|
// when dragging from another window, the target is null, otherwise its a DOM element
|
||||||
if (!event.relatedTarget && event.dataTransfer.types.includes("Files")) {
|
if (!event.relatedTarget && event.dataTransfer?.types.includes("Files")) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
this.overlay.classList.add("is-dragover");
|
this.overlay?.classList.add("is-dragover");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dragLeave(event) {
|
dragLeave(event: DragEvent) {
|
||||||
// If relatedTarget is null, that means we are no longer dragging over the page
|
// If relatedTarget is null, that means we are no longer dragging over the page
|
||||||
if (!event.relatedTarget) {
|
if (!event.relatedTarget) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.overlay.classList.remove("is-dragover");
|
this.overlay?.classList.remove("is-dragover");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(event) {
|
drop(event: DragEvent) {
|
||||||
if (!event.dataTransfer.types.includes("Files")) {
|
if (!event.dataTransfer?.types.includes("Files")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.overlay.classList.remove("is-dragover");
|
this.overlay?.classList.remove("is-dragover");
|
||||||
|
|
||||||
let files;
|
let files: (File | null)[];
|
||||||
|
|
||||||
if (event.dataTransfer.items) {
|
if (event.dataTransfer.items) {
|
||||||
files = Array.from(event.dataTransfer.items)
|
files = Array.from(event.dataTransfer.items)
|
||||||
|
@ -70,13 +73,17 @@ class Uploader {
|
||||||
this.triggerUpload(files);
|
this.triggerUpload(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
paste(event) {
|
paste(event: ClipboardEvent) {
|
||||||
const items = event.clipboardData.items;
|
const items = event.clipboardData?.items;
|
||||||
const files = [];
|
const files: (File | null)[] = [];
|
||||||
|
|
||||||
for (const item of items) {
|
if (!items) {
|
||||||
if (item.kind === "file") {
|
return;
|
||||||
files.push(item.getAsFile());
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].kind === "file") {
|
||||||
|
files.push(items[i].getAsFile());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +95,7 @@ class Uploader {
|
||||||
this.triggerUpload(files);
|
this.triggerUpload(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerUpload(files) {
|
triggerUpload(files: (File | null)[]) {
|
||||||
if (!files.length) {
|
if (!files.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -102,9 +109,13 @@ class Uploader {
|
||||||
}
|
}
|
||||||
|
|
||||||
const wasQueueEmpty = this.fileQueue.length === 0;
|
const wasQueueEmpty = this.fileQueue.length === 0;
|
||||||
const maxFileSize = store.state.serverConfiguration?.fileUploadMaxFileSize;
|
const maxFileSize = store.state.serverConfiguration?.fileUploadMaxFileSize || 0;
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (maxFileSize > 0 && file.size > maxFileSize) {
|
if (maxFileSize > 0 && file.size > maxFileSize) {
|
||||||
this.handleResponse({
|
this.handleResponse({
|
||||||
error: `File ${file.name} is over the maximum allowed size`,
|
error: `File ${file.name} is over the maximum allowed size`,
|
||||||
|
@ -127,14 +138,22 @@ class Uploader {
|
||||||
socket.emit("upload:auth");
|
socket.emit("upload:auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
setProgress(value) {
|
setProgress(value: number) {
|
||||||
|
if (!this.uploadProgressbar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.uploadProgressbar.classList.toggle("upload-progressbar-visible", value > 0);
|
this.uploadProgressbar.classList.toggle("upload-progressbar-visible", value > 0);
|
||||||
this.uploadProgressbar.style.width = value + "%";
|
this.uploadProgressbar.style.width = `${value}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadNextFileInQueue(token) {
|
uploadNextFileInQueue(token: string) {
|
||||||
const file = this.fileQueue.shift();
|
const file = this.fileQueue.shift();
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Tell the server that we are still upload to this token
|
// Tell the server that we are still upload to this token
|
||||||
// so it does not become invalidated and fail the upload.
|
// so it does not become invalidated and fail the upload.
|
||||||
// This issue only happens if The Lounge is proxied through other software
|
// This issue only happens if The Lounge is proxied through other software
|
||||||
|
@ -153,7 +172,7 @@ class Uploader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderImage(file, callback) {
|
renderImage(file: File, callback: (file: File) => void) {
|
||||||
const fileReader = new FileReader();
|
const fileReader = new FileReader();
|
||||||
|
|
||||||
fileReader.onabort = () => callback(file);
|
fileReader.onabort = () => callback(file);
|
||||||
|
@ -169,20 +188,25 @@ class Uploader {
|
||||||
canvas.width = img.width;
|
canvas.width = img.width;
|
||||||
canvas.height = img.height;
|
canvas.height = img.height;
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("Could not get canvas context in upload");
|
||||||
|
}
|
||||||
|
|
||||||
ctx.drawImage(img, 0, 0);
|
ctx.drawImage(img, 0, 0);
|
||||||
|
|
||||||
canvas.toBlob((blob) => {
|
canvas.toBlob((blob) => {
|
||||||
callback(new File([blob], file.name));
|
callback(new File([blob!], file.name));
|
||||||
}, file.type);
|
}, file.type);
|
||||||
};
|
};
|
||||||
|
|
||||||
img.src = fileReader.result;
|
img.src = fileReader.result as string;
|
||||||
};
|
};
|
||||||
|
|
||||||
fileReader.readAsDataURL(file);
|
fileReader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
performUpload(token, file) {
|
performUpload(token: string, file: File) {
|
||||||
this.xhr = new XMLHttpRequest();
|
this.xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
this.xhr.upload.addEventListener(
|
this.xhr.upload.addEventListener(
|
||||||
|
@ -195,7 +219,7 @@ class Uploader {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.xhr.onreadystatechange = () => {
|
this.xhr.onreadystatechange = () => {
|
||||||
if (this.xhr.readyState === XMLHttpRequest.DONE) {
|
if (this.xhr?.readyState === XMLHttpRequest.DONE) {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -227,7 +251,7 @@ class Uploader {
|
||||||
this.xhr.send(formData);
|
this.xhr.send(formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResponse(response) {
|
handleResponse(response: {error?: string; url?: string}) {
|
||||||
this.setProgress(0);
|
this.setProgress(0);
|
||||||
|
|
||||||
if (this.tokenKeepAlive) {
|
if (this.tokenKeepAlive) {
|
||||||
|
@ -245,9 +269,14 @@ class Uploader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insertUploadUrl(url) {
|
insertUploadUrl(url: string) {
|
||||||
const fullURL = new URL(url, location).toString();
|
const fullURL = new URL(url, location.toString()).toString();
|
||||||
const textbox = document.getElementById("input");
|
const textbox = document.getElementById("input");
|
||||||
|
|
||||||
|
if (!textbox) {
|
||||||
|
throw new Error("Could not find textbox in upload");
|
||||||
|
}
|
||||||
|
|
||||||
const initStart = textbox.selectionStart;
|
const initStart = textbox.selectionStart;
|
||||||
|
|
||||||
// Get the text before the cursor, and add a space if it's not in the beginning
|
// Get the text before the cursor, and add a space if it's not in the beginning
|
||||||
|
|
|
@ -2,7 +2,7 @@ import constants from "./constants";
|
||||||
|
|
||||||
import "../css/style.css";
|
import "../css/style.css";
|
||||||
import {createApp} from "vue";
|
import {createApp} from "vue";
|
||||||
import store, {CallableGetters, key, State, TypedStore} from "./store";
|
import {store, CallableGetters, key} from "./store";
|
||||||
import App from "../components/App.vue";
|
import App from "../components/App.vue";
|
||||||
import storage from "./localStorage";
|
import storage from "./localStorage";
|
||||||
import {router, navigate} from "./router";
|
import {router, navigate} from "./router";
|
||||||
|
@ -12,7 +12,6 @@ import eventbus from "./eventbus";
|
||||||
import "./socket-events";
|
import "./socket-events";
|
||||||
import "./webpush";
|
import "./webpush";
|
||||||
import "./keybinds";
|
import "./keybinds";
|
||||||
import {Store} from "vuex";
|
|
||||||
import {LoungeWindow} from "./types";
|
import {LoungeWindow} from "./types";
|
||||||
|
|
||||||
const favicon = document.getElementById("favicon");
|
const favicon = document.getElementById("favicon");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import socket from "./socket";
|
import socket from "./socket";
|
||||||
import store from "./store";
|
import {store} from "./store";
|
||||||
|
|
||||||
export default {togglePushSubscription};
|
export default {togglePushSubscription};
|
||||||
|
|
||||||
|
|
|
@ -598,7 +598,7 @@ class Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
search(query: SearchQuery): Promise<SearchResponse> {
|
search(query: SearchQuery) {
|
||||||
if (this.messageProvider === undefined) {
|
if (this.messageProvider === undefined) {
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
results: [],
|
results: [],
|
||||||
|
|
Loading…
Reference in a new issue