diff --git a/.gitignore b/.gitignore
index f42094d0..f7a6ae59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ pnpm-debug.log*
*.pub
*.zip
artifacts
+.eslintcache
diff --git a/package.json b/package.json
index 425ea83c..0201b34f 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
"eslint-plugin-promise": "4",
"eslint-plugin-svelte3": "^3.2.0",
"express": "4.17.1",
+ "iframe-translator": "^0.2.2",
"postcss": "^8.3.6",
"postcss-extend": "^1.0.5",
"postcss-import": "^14.0.2",
diff --git a/src/components/Hyperchat.svelte b/src/components/Hyperchat.svelte
index da35a770..09e2a36f 100644
--- a/src/components/Hyperchat.svelte
+++ b/src/components/Hyperchat.svelte
@@ -20,7 +20,8 @@
showProfileIcons,
showUsernames,
showTimestamps,
- showUserBadges
+ showUserBadges,
+ refreshScroll
} from '../ts/storage';
const welcome = { welcome: true, message: { messageId: 'welcome' } };
@@ -174,12 +175,19 @@
});
};
- afterUpdate(() => {
+ const onRefresh = () => {
if (isAtBottom) {
scrollToBottom();
}
tick().then(checkAtBottom);
- });
+ };
+
+ $: if ($refreshScroll) {
+ onRefresh();
+ $refreshScroll = false;
+ }
+
+ afterUpdate(onRefresh);
onDestroy(() => {
port.disconnect();
@@ -193,7 +201,7 @@
);
const containerClass = 'h-screen w-screen text-black dark:text-white dark:bg-black dark:bg-opacity-25';
- const contentClass = 'content absolute overflow-y-scroll w-full h-full flex-1 px-2';
+ const contentClass = 'content absolute overflow-y-scroll w-full h-full flex-1';
const pinnedClass = 'absolute top-2 inset-x-2';
@@ -202,7 +210,7 @@
{#each messageActions as action (action.message.messageId)}
-
+
{#if isWelcome(action)}
{:else if (action.message.superChat || action.message.superSticker)}
@@ -247,4 +255,11 @@
.content::-webkit-scrollbar-thumb:hover {
background: #555;
}
+ .hover-highlight {
+ transition: 0.1s;
+ background-color: transparent;
+ }
+ .hover-highlight:hover {
+ background-color: #80808040;
+ }
diff --git a/src/components/Message.svelte b/src/components/Message.svelte
index 58f2760e..ff295d27 100644
--- a/src/components/Message.svelte
+++ b/src/components/Message.svelte
@@ -54,11 +54,14 @@
($showUserBadges && (moderator || verified || member));
-
- {#if !hideName}
+
+
+ {#if !hideName && $showProfileIcons}

@@ -82,7 +85,7 @@
build
{:else if verified}
verified
diff --git a/src/components/MessageRuns.svelte b/src/components/MessageRuns.svelte
index 64f98920..aa06b068 100644
--- a/src/components/MessageRuns.svelte
+++ b/src/components/MessageRuns.svelte
@@ -1,4 +1,6 @@
+
+ {
+ if (translatedMessage) {
+ showOriginal = !showOriginal;
+ $refreshScroll = true;
+ }
+ }}
+>
+ {#if !showTL}
+
+ {text}
+
+ {/if}
+ {#if showTL}
+
+ {translatedMessage}
+
+ {/if}
+ {#if translatedMessage}
+
+
+ translate
+
+
+ {/if}
+
+
+
diff --git a/src/components/common/DropdownStore.svelte b/src/components/common/DropdownStore.svelte
new file mode 100644
index 00000000..d13733aa
--- /dev/null
+++ b/src/components/common/DropdownStore.svelte
@@ -0,0 +1,41 @@
+
+
+
+
+
diff --git a/src/components/settings/InterfaceSettings.svelte b/src/components/settings/InterfaceSettings.svelte
index f31565b4..0b9721c1 100644
--- a/src/components/settings/InterfaceSettings.svelte
+++ b/src/components/settings/InterfaceSettings.svelte
@@ -11,6 +11,7 @@
import Radio from '../common/RadioGroupStore.svelte';
import Checkbox from '../common/CheckboxStore.svelte';
import dark from 'smelte/src/dark';
+ import MessageTranslationSettings from './MessageTranslationSettings.svelte';
const darkStore = dark();
$: switch ($theme) {
@@ -49,3 +50,5 @@
+
+
diff --git a/src/components/settings/MessageTranslationSettings.svelte b/src/components/settings/MessageTranslationSettings.svelte
new file mode 100644
index 00000000..a5dc85ff
--- /dev/null
+++ b/src/components/settings/MessageTranslationSettings.svelte
@@ -0,0 +1,51 @@
+
+
+
+
+
+ {#if $enabled}
+ !priority.includes(e))
+ ]} />
+ {/if}
+
+
\ No newline at end of file
diff --git a/src/scripts/chat-background.ts b/src/scripts/chat-background.ts
index 213e1a82..d255ad71 100644
--- a/src/scripts/chat-background.ts
+++ b/src/scripts/chat-background.ts
@@ -318,8 +318,8 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
chrome.windows.create({
url: request.url,
type: 'popup',
- height: 300,
- width: 600
+ height: 420,
+ width: 690
}, () => {});
}
});
diff --git a/src/ts/chat-constants.ts b/src/ts/chat-constants.ts
index 43b6bfff..fcc524a5 100644
--- a/src/ts/chat-constants.ts
+++ b/src/ts/chat-constants.ts
@@ -35,6 +35,6 @@ export const enum Theme {
export const themeItems = [
{ value: Theme.YOUTUBE, label: 'Use YouTube theme' },
- { value: Theme.LIGHT, label: 'Force light theme' },
- { value: Theme.DARK, label: 'Force dark theme' }
+ { value: Theme.LIGHT, label: 'Light theme' },
+ { value: Theme.DARK, label: 'Dark theme' }
];
diff --git a/src/ts/component-utils.ts b/src/ts/component-utils.ts
new file mode 100644
index 00000000..c2564d88
--- /dev/null
+++ b/src/ts/component-utils.ts
@@ -0,0 +1,32 @@
+import { tick } from 'svelte';
+
+interface Rect { top: number, right: number, bottom: number, left: number }
+
+export const getRelativeRect = (element: HTMLElement, boundingElement: HTMLElement): Rect => {
+ const rect = element.getBoundingClientRect();
+ const boundingRect = boundingElement.getBoundingClientRect();
+ return {
+ top: rect.top - boundingRect.top,
+ right: boundingElement.clientWidth - (boundingRect.right - rect.right),
+ bottom: boundingElement.clientHeight - (boundingRect.bottom - rect.bottom),
+ left: rect.left - boundingRect.left
+ };
+};
+
+export const getDropdownOffsetY = async (div: HTMLElement, boundingDiv: HTMLElement): Promise => {
+ await tick();
+
+ const wrapper = div.querySelector('.dropdown-wrapper');
+ const options = wrapper?.querySelector('.dropdown-options');
+ if (wrapper == null || options == null) {
+ console.error('Dropdown wrapper or options not found');
+ return '';
+ }
+
+ const relativeRect = getRelativeRect(wrapper as HTMLElement, boundingDiv);
+ if (relativeRect.bottom + options.clientHeight > boundingDiv.clientHeight) {
+ return 'bottom-12';
+ } else {
+ return 'top-12';
+ }
+};
diff --git a/src/ts/storage.ts b/src/ts/storage.ts
index 0dd496a2..6b866afe 100644
--- a/src/ts/storage.ts
+++ b/src/ts/storage.ts
@@ -1,11 +1,39 @@
import { webExtStores } from 'svelte-webext-stores';
+import { readable, writable } from 'svelte/store';
+import { getClient } from 'iframe-translator';
+import type { IframeTranslatorClient } from 'iframe-translator';
import { Theme } from './chat-constants';
export const stores = webExtStores();
export const hcEnabled = stores.addSyncStore('hc.enabled', true);
+export const translateTargetLanguage = stores.addSyncStore('hc.translateTargetLanguage', '');
+export const translatorClient = readable(null as (null | IframeTranslatorClient), (set) => {
+ let client: IframeTranslatorClient | null = null;
+ const destroyIf = (): void => {
+ if (client !== null) {
+ client.destroy();
+ client = null;
+ }
+ set(null);
+ };
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
+ const unsub = translateTargetLanguage.subscribe(async ($translateTargetLanguage) => {
+ if (!$translateTargetLanguage) {
+ destroyIf();
+ return;
+ }
+ if (client) return;
+ client = await getClient();
+ set(client);
+ });
+ return () => {
+ unsub();
+ destroyIf();
+ };
+});
+export const refreshScroll = writable(false);
export const theme = stores.addSyncStore('hc.theme', Theme.YOUTUBE);
-
export const showProfileIcons = stores.addSyncStore('hc.messages.showProfileIcons', false);
export const showUsernames = stores.addSyncStore('hc.messages.showUsernames', true);
export const showTimestamps = stores.addSyncStore('hc.messages.showTimestamps', false);
diff --git a/tailwind.config.js b/tailwind.config.js
index fac03a1d..6e8f2b54 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -31,6 +31,10 @@ const smelteConfig = {
deleted: {
light: '#6E6B6B',
dark: '#898888'
+ },
+ translated: {
+ light: '#0050da',
+ dark: '#b9d9ff'
}
}
}
diff --git a/yarn.lock b/yarn.lock
index 4b3356cc..1cc16cda 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3279,6 +3279,11 @@ ieee754@^1.1.13:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+iframe-translator@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/iframe-translator/-/iframe-translator-0.2.2.tgz#467f3c24836f78f20aff61d138047a1c78140b72"
+ integrity sha512-/1yYDJG/TEGwGNySsW9q+bMXNmIgRp/befLy6C6UItBEQyGQGq4irHSBLorXRN7d/nuu/kr+7sTT5ZAL58WFvg==
+
ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz"