diff --git a/client/components/ContextMenu.vue b/client/components/ContextMenu.vue index 38079aad..838be596 100644 --- a/client/components/ContextMenu.vue +++ b/client/components/ContextMenu.vue @@ -14,7 +14,10 @@ id="context-menu" ref="contextMenu" role="menu" - :style="style" + :style="{ + top: style.top + 'px', + left: style.left + 'px', + }" tabindex="-1" @mouseleave="activeItem = -1" @keydown.enter.prevent="clickActiveItem" diff --git a/client/components/DateMarker.vue b/client/components/DateMarker.vue index f65063db..6552c094 100644 --- a/client/components/DateMarker.vue +++ b/client/components/DateMarker.vue @@ -24,11 +24,6 @@ export default defineComponent({ }, focused: Boolean, }, - // methods: { - // forceUpdate(){ - // this.$forceUpdate() - // } - // }, setup(props) { const localeDate = computed(() => dayjs(props.message.time).format("D MMMM YYYY")); diff --git a/client/components/MessageList.vue b/client/components/MessageList.vue index 44dd3855..33a6e135 100644 --- a/client/components/MessageList.vue +++ b/client/components/MessageList.vue @@ -66,6 +66,7 @@ import Message from "./Message.vue"; import MessageCondensed from "./MessageCondensed.vue"; import DateMarker from "./DateMarker.vue"; import { + defineExpose, computed, defineComponent, nextTick, @@ -247,7 +248,7 @@ export default defineComponent({ message: Msg | ClientMessage | CondensedMessageContainer, id: number ) => { - const previousMessage = condensedMessages[id - 1]; + const previousMessage = condensedMessages.value[id - 1]; if (!previousMessage) { return true; @@ -431,6 +432,7 @@ export default defineComponent({ shouldDisplayUnreadMarker, keepScrollPosition, isPreviousSource, + jumpToBottom, onLinkPreviewToggle, }; }, diff --git a/client/components/NetworkLobby.vue b/client/components/NetworkLobby.vue index 0adf17a4..a2c08660 100644 --- a/client/components/NetworkLobby.vue +++ b/client/components/NetworkLobby.vue @@ -46,7 +46,7 @@ diff --git a/client/components/Special/ListBans.vue b/client/components/Special/ListBans.vue index 514fc4c8..b9c6a528 100644 --- a/client/components/Special/ListBans.vue +++ b/client/components/Special/ListBans.vue @@ -19,7 +19,7 @@ diff --git a/client/components/Windows/SignIn.vue b/client/components/Windows/SignIn.vue index a6676bb4..30963206 100644 --- a/client/components/Windows/SignIn.vue +++ b/client/components/Windows/SignIn.vue @@ -59,48 +59,65 @@ import storage from "../../js/localStorage"; import socket from "../../js/socket"; import RevealPassword from "../RevealPassword.vue"; -import {defineComponent} from "vue"; +import {defineComponent, onBeforeUnmount, onMounted, ref} from "vue"; export default defineComponent({ name: "SignIn", components: { RevealPassword, }, - data() { - return { - inFlight: false, - errorShown: false, + setup() { + const inFlight = ref(false); + const errorShown = ref(false); + + const username = ref(null); + const password = ref(null); + + const onAuthFailed = () => { + inFlight.value = false; + errorShown.value = true; }; - }, - mounted() { - socket.on("auth:failed", this.onAuthFailed); - }, - beforeUnmount() { - socket.off("auth:failed", this.onAuthFailed); - }, - methods: { - onAuthFailed() { - this.inFlight = false; - this.errorShown = true; - }, - onSubmit(event) { + + const onSubmit = (event: Event) => { event.preventDefault(); - this.inFlight = true; - this.errorShown = false; + if (!username.value || !password.value) { + return; + } + + inFlight.value = true; + errorShown.value = false; const values = { - user: this.$refs.username.value, - password: this.$refs.password.value, + user: username.value?.value, + password: password.value?.value, }; storage.set("user", values.user); socket.emit("auth:perform", values); - }, - getStoredUser() { + }; + + const getStoredUser = () => { return storage.get("user"); - }, + }; + + onMounted(() => { + socket.on("auth:failed", onAuthFailed); + }); + + onBeforeUnmount(() => { + socket.off("auth:failed", onAuthFailed); + }); + + return { + inFlight, + errorShown, + username, + password, + onSubmit, + getStoredUser, + }; }, }); diff --git a/client/css/style.css b/client/css/style.css index 5ababcf9..6e3dffcc 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -587,6 +587,12 @@ p { /* End icons */ + +#app { + height: 100%; + width: 100%; +} + #viewport { display: flex; height: 100%; diff --git a/client/js/helpers/colorClass.ts b/client/js/helpers/colorClass.ts index 9cd4dbf4..c9896f70 100644 --- a/client/js/helpers/colorClass.ts +++ b/client/js/helpers/colorClass.ts @@ -1,5 +1,5 @@ // Generates a string from "color-1" to "color-32" based on an input string -export default (str) => { +export default (str: string) => { let hash = 0; for (let i = 0; i < str.length; i++) { @@ -11,5 +11,5 @@ export default (str) => { due to A being ascii 65 (100 0001) while a being ascii 97 (110 0001) */ - return "color-" + (1 + (hash % 32)); + return "color-" + (1 + (hash % 32)).toString(); }; diff --git a/client/js/helpers/parse.ts b/client/js/helpers/parse.ts index 7b8968f7..d86d543e 100644 --- a/client/js/helpers/parse.ts +++ b/client/js/helpers/parse.ts @@ -120,7 +120,7 @@ function parse(text: string, message?: ClientMessage, network?: ClientNetwork) { rel: "noopener", }, }, - fragments + () => fragments ); if (!preview) { @@ -159,8 +159,9 @@ function parse(text: string, message?: ClientMessage, network?: ClientNetwork) { dir: "auto", }, }, - linkEls + () => linkEls ); + // @ts-ignore } else if (textPart.channel) { return createElement( @@ -171,8 +172,9 @@ function parse(text: string, message?: ClientMessage, network?: ClientNetwork) { channel: textPart.channel, }, }, - fragments + () => fragments ); + // @ts-ignore } else if (textPart.emoji) { // @ts-ignore @@ -191,7 +193,7 @@ function parse(text: string, message?: ClientMessage, network?: ClientNetwork) { title: title, }, }, - fragments + () => fragments ); // @ts-ignore } else if (textPart.nick) { @@ -205,14 +207,14 @@ function parse(text: string, message?: ClientMessage, network?: ClientNetwork) { nick: textPart.nick, }, // @ts-ignore - channel: messageChannel, + channel: textPart.channel, network, }, attrs: { dir: "auto", }, }, - fragments + () => fragments ); } diff --git a/client/js/types.d.ts b/client/js/types.d.ts index 459f98c4..1a8eec32 100644 --- a/client/js/types.d.ts +++ b/client/js/types.d.ts @@ -79,13 +79,10 @@ declare module "*.vue" { } declare module "vue" { - interface ComponentCustomProperties { - // vue-router should do this for us - $router: import("vue-router").Router; - - // TODO: Vue struggles with typing using the options API, so we should switch to composition API - // $root - } + // interface ComponentCustomProperties { + // // TODO: Vue struggles with typing using the options API, so we should switch to composition API + // // $root + // } } declare module "vue-router" { diff --git a/client/service-worker.js b/client/service-worker.js index eea74070..3cd18c5d 100644 --- a/client/service-worker.js +++ b/client/service-worker.js @@ -43,7 +43,7 @@ self.addEventListener("fetch", function (event) { return; } - event.respondWith(networkOrCache(event)); + return event.respondWith(networkOrCache(event)); }); async function putInCache(request, response) { diff --git a/test/client/js/helpers/parse.ts b/test/client/js/helpers/parse.ts index eccdc448..1a3f108a 100644 --- a/test/client/js/helpers/parse.ts +++ b/test/client/js/helpers/parse.ts @@ -1,29 +1,27 @@ import {expect} from "chai"; -import {renderToString} from "@vue/server-test-utils"; -import ParsedMessageTestWrapper from "../../components/ParsedMessageTestWrapper.vue"; +import {mount} from "@vue/test-utils"; +import ParsedMessage from "../../../../client/components/ParsedMessage.vue"; +import {ClientMessage} from "../../../../client/js/types"; -async function getParsedMessageContents(text: any, message: any) { - let contents = await renderToString(ParsedMessageTestWrapper, { - propsData: { +function getParsedMessageContents(text: string, message?: any) { + const wrapper = mount(ParsedMessage, { + props: { text, message, }, }); - // The wrapper adds a surrounding div to the message html, so we clean that out here - contents = contents.replace(/^
([^]+)<\/div>$/m, "$1"); - - return contents; + return wrapper.html(); } describe("IRC formatted message parser", () => { - it("should not introduce xss", async () => { + it("should not introduce xss", () => { const testCases = [ { input: "", expected: - '<img onerror=\'location.href="//youtube.com"\'>', + '<img onerror=\'location.href="//youtube.com"\'>', }, { input: '#&">bug', @@ -32,16 +30,13 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("should skip all <32 ASCII codes except linefeed", async () => { + it("should skip all <32 ASCII codes except linefeed", () => { const testCases = [ { input: "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1B\x1D\x1D\x1E\x1Ftext\x0Awithcontrolcodestest", @@ -50,16 +45,13 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("should find urls", async () => { + it("should find urls", () => { const testCases = [ { input: "irc://irc.example.com/thelounge", @@ -102,16 +94,13 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("url with a dot parsed correctly", async () => { + it("url with a dot parsed correctly", () => { const input = "bonuspunkt: your URL parser misparses this URL: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644989(v=vs.85).aspx"; const correctResult = @@ -120,13 +109,12 @@ describe("IRC formatted message parser", () => { "https://msdn.microsoft.com/en-us/library/windows/desktop/ms644989(v=vs.85).aspx" + ""; - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - const actual = await getParsedMessageContents(input); + const actual = getParsedMessageContents(input); expect(actual).to.deep.equal(correctResult); }); - it("should balance brackets", async () => { + it("should balance brackets", () => { const testCases = [ { input: "", @@ -162,16 +150,13 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("should not find urls", async () => { + it("should not find urls", () => { const testCases = [ { input: "text www. text", @@ -183,16 +168,13 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("should find channels", async () => { + it("should find channels", () => { const testCases = [ { input: "#a", @@ -241,16 +223,13 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("should not find channels", async () => { + it("should not find channels", () => { const testCases = [ { input: "hi#test", @@ -262,10 +241,7 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -362,9 +338,8 @@ describe("IRC formatted message parser", () => { 'bold' + " " + 'bold', }, ].forEach(({name, input, expected}) => { - it(`should handle style characters: ${name}`, async () => { - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - expect(await getParsedMessageContents(input)).to.equal(expected); + it(`should handle style characters: ${name}`, () => { + expect(getParsedMessageContents(input)).to.equal(expected); }); }); @@ -391,7 +366,7 @@ describe("IRC formatted message parser", () => { expect(actual).to.deep.equal(expected); }); - it("should not find nicks", async () => { + it("should not find nicks", () => { const testCases = [ { users: ["MaxLeiter, test"], @@ -411,16 +386,13 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("should go bonkers like mirc", async () => { + it("should go bonkers like mirc", () => { const testCases = [ { input: "\x02irc\x0f://\x1dirc.example.com\x0f/\x034,8thelounge", @@ -443,10 +415,7 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); @@ -521,13 +490,12 @@ describe("IRC formatted message parser", () => { '#i❤️thelounge', }, ].forEach(({name, input, expected}) => { - it(`should find emoji: ${name}`, async () => { - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - expect(await getParsedMessageContents(input)).to.equal(expected); + it(`should find emoji: ${name}`, () => { + expect(getParsedMessageContents(input)).to.equal(expected); }); }); - it("should optimize generated html", async () => { + it("should optimize generated html", () => { const testCases = [ { input: 'test \x0312#\x0312\x0312"te\x0312st\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312a', @@ -539,16 +507,13 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("should trim common protocols", async () => { + it("should trim common protocols", () => { const testCases = [ { input: "like..http://example.com", @@ -568,16 +533,13 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("should not find channel in fragment", async () => { + it("should not find channel in fragment", () => { const testCases = [ { input: "http://example.com/#hash", @@ -588,19 +550,15 @@ describe("IRC formatted message parser", () => { }, ]; - const actual = await Promise.all( - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - testCases.map((testCase) => getParsedMessageContents(testCase.input)) - ); + const actual = testCases.map((testCase) => getParsedMessageContents(testCase.input)); const expected = testCases.map((testCase) => testCase.expected); expect(actual).to.deep.equal(expected); }); - it("should not overlap parts", async () => { + it("should not overlap parts", () => { const input = "Url: http://example.com/path Channel: ##channel"; - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - const actual = await getParsedMessageContents(input); + const actual = getParsedMessageContents(input); expect(actual).to.equal( 'Url: http://example.com/path ' + @@ -608,10 +566,9 @@ describe("IRC formatted message parser", () => { ); }); - it("should handle overlapping parts by using first starting", async () => { + it("should handle overlapping parts by using first starting", () => { const input = "#test-https://example.com"; - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - const actual = await getParsedMessageContents(input); + const actual = getParsedMessageContents(input); expect(actual).to.equal( '' + @@ -620,10 +577,9 @@ describe("IRC formatted message parser", () => { ); }); - it("should find links separated by tab character", async () => { + it("should find links separated by tab character", () => { const input = "example.com\texample.org"; - // @ts-expect-error ts-migrate(2554) FIXME: Expected 2 arguments, but got 1. - const actual = await getParsedMessageContents(input); + const actual = getParsedMessageContents(input); expect(actual).to.equal( 'example.com' +