Skip to content

Commit b16e9df

Browse files
authored
v1.6
* Optimized YouTube to not translate all words on each new caption * Move tokes to the env file * Optimize Netflix to not translate everything unless the user asks for a word or a whole caption. * version 1.2 * [ Task ] Add proxy service, all requests will be associated with an alternative URL which is the proxy version of each URL. * [ Task ] Add dedicated api_host for Mixpanel * [ Task ] Add loading state when a hovered phrase is waiting to be translated. * Add Sorani lang, and fix manifext violation. * [ Bugfix ] change event of the selected element. * v1.5 * Add support template * Add "web" prefix to module titles * Implemented marking cycle with Pinia state manager * Implemented multiple marking * Add mode for marker [mark, select] * Improve marking process, added click action to mark words when it's marking mode. * Add marker support for Netflix * Add marker border * Add marker guide component * Improve marker guide component * removed logs and unused vars * Fixed missing translation in word detail modal * Add marking guide description on popup frame. * v1.6
1 parent c79233a commit b16e9df

File tree

28 files changed

+951
-100
lines changed

28 files changed

+951
-100
lines changed

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "1.3.0",
2+
"version": "1.6.0",
33
"scripts": {
44
"serve": "webpack --watch",
55
"build": "webpack --mode=production",
@@ -28,10 +28,12 @@
2828
"webpack-cli": "^4.10.0"
2929
},
3030
"dependencies": {
31+
"animejs": "^3.2.1",
3132
"axios": "^1.3.4",
3233
"mixpanel-browser": "^2.45.0",
34+
"pinia": "^2.1.6",
3335
"text-cleaner": "^1.2.1",
3436
"tiny-emitter": "^2.1.0",
3537
"vue": "^3.2.37"
3638
}
37-
}
39+
}
File renamed without changes.

src/common/helper/text.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ export function firstUpper(word: string) {
55

66
try {
77
parts[0] = parts[0].toUpperCase();
8-
} catch (error) {}
8+
} catch (error) { }
99

1010
return parts.join("");
1111
}
1212

1313
export function cleanText(text: string) {
14+
if (!text) return '';
15+
1416
[
1517
"\\",
1618
".",

src/main.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
log("Using version", VERSION);
22

3-
import "./style.scss";
3+
import "./animation.scss";
44
import "./tailwind.css";
55

66
import { App, createApp } from "vue";
77
import components from "./module/subtitle/components/components";
88

9-
import { netflix } from "./module/subtitle/netflix/initializer";
10-
import { youtube } from "./module/subtitle/youtube/initializer";
9+
import { netflix } from "./module/subtitle/web_netflix/initializer";
10+
import { youtube } from "./module/subtitle/web_youtube/initializer";
1111
import { AppInitializer } from "./common/types/general.type";
1212
import { cleanText } from "./common/helper/text";
1313
import { analytic } from "./plugins/mixpanel";
1414
import { VERSION } from "./common/static/global";
1515
import { log } from "./common/helper/log";
16+
import { addPlugins } from "./plugins/install";
17+
import { registerGlobalEvents, unregisterGlobalEvents } from "./module/subtitle/helpers/global-events";
1618

1719
let vueApp!: App;
1820
let appInitializer!: AppInitializer;
@@ -41,7 +43,7 @@ function start() {
4143
) {
4244
initialized = true;
4345

44-
vueApp = createApp(appInitializer.component as any);
46+
vueApp = addPlugins(createApp(appInitializer.component as any));
4547

4648
Object.keys(components).forEach((name) => {
4749
let component = (components as any)[name];
@@ -54,7 +56,10 @@ function start() {
5456

5557
appInitializer
5658
.start(vueApp)
57-
.then((_) => analytic.track("Used In"))
59+
.then((_) => {
60+
analytic.track("Used In")
61+
registerGlobalEvents()
62+
})
5863
.catch((_) => analytic.track("Error on initiating"));
5964
}
6065

@@ -66,6 +71,7 @@ function start() {
6671
!location.pathname.includes(appInitializer.website.path)
6772
) {
6873
initialized = false;
74+
unregisterGlobalEvents();
6975
vueApp.unmount();
7076
}
7177
}, 100);

src/module/popup/App.vue

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,47 @@
11
<template>
2-
<div class="flex flex-col items-center mx-2">
3-
<!-- LOGO -->
4-
<div class="my-10 flex flex-col justify-center items-center">
5-
<logo></logo>
6-
</div>
2+
<transition name="fade">
3+
<div class="flex items-center justify-center mx-2 h-screen space-x-16">
4+
<section>
5+
<!-- LOGO -->
6+
<div class="my-10 flex flex-col justify-center items-center">
7+
<logo></logo>
8+
</div>
79

8-
<!-- LANGUAGE FORM -->
9-
<form>
10-
<select-target />
11-
</form>
10+
<!-- LANGUAGE FORM -->
11+
<form>
12+
<select-target />
13+
</form>
1214

13-
<div class="text-gray-300 mt-12 w-64">
14-
<h3 class="text-lg">How does it work?</h3>
15-
<p>Try to open a video on one of the supported websites, then activate subtitle on a video and try to hover or click a word.</p>
16-
</div>
15+
<div class="mt-12 flex flex-col items-center">
16+
<h3 class="font-bold text-gray-400">Supported websites</h3>
17+
<div class="flex justify-center items-center -mt-3">
18+
<img class="w-32" :src="$getAsset('/svg/netflix_logo.svg')" />
19+
<img class="w-32" :src="$getAsset('/svg/youtube_logo.svg')" />
20+
</div>
21+
</div>
22+
</section>
23+
24+
<div class="text-gray-200 mt-12 w-64 self-start">
25+
<h3 class="text-lg">How does it work?</h3>
26+
<p class="text-gray-400">
27+
Try to open a video on one of the supported websites, then activate
28+
subtitle on a video and try to hover or click a word.
29+
</p>
1730

18-
<div class="mt-12 flex flex-col items-center">
19-
<h3 class="font-bold text-gray-400">Supported websites</h3>
20-
<div class="flex justify-center items-center -mt-3">
21-
<img class="w-32" :src="$getAsset('/svg/netflix_logo.svg')">
22-
<img class="w-32" :src="$getAsset('/svg/youtube_logo.svg')">
31+
<h3 class="text-lg mt-10 animate-pulse">
32+
How to translate multiple words?
33+
</h3>
34+
<ul class="list-decimal text-gray-400">
35+
<li>
36+
Try to press <span class="font-bold">Ctrl</span> key on Windows or
37+
<span class="font-bold">Command</span> key on Mac.
38+
</li>
39+
<li>Then press and drag your mouse to select multiple words.</li>
40+
<li>Then The translation will be shown.</li>
41+
</ul>
2342
</div>
2443
</div>
25-
</div>
44+
</transition>
2645
</template>
2746

2847
<script>
@@ -38,7 +57,7 @@ export default {
3857
<style>
3958
body {
4059
background-color: #001a35;
41-
width: 350px;
42-
min-height: 300px;
60+
width: 800px;
61+
height: 450px;
4362
}
44-
</style>
63+
</style>
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<template>
2+
<SubtitleComponent
3+
:positionRect="position"
4+
:textList="text"
5+
:textStyle="style"
6+
/>
7+
</template>
8+
9+
<script lang="ts">
10+
import { defineComponent } from "vue";
11+
import { SUBTILE_CONTAINER_CLASS, SUBTITLE_CLASS } from "./static";
12+
import { waitUntil } from "../../../common/helper/promise";
13+
import { SubtitleBundingBox } from "../../../common/types/general.type";
14+
15+
import SubtitleComponent from "./components/Subtitle.vue";
16+
17+
import TextCleaner from "text-cleaner";
18+
import { serviceName } from "./initializer";
19+
20+
export default defineComponent({
21+
components: { SubtitleComponent: SubtitleComponent as any },
22+
23+
data(): {
24+
active: boolean;
25+
position: SubtitleBundingBox;
26+
text: string[];
27+
style: CSSStyleDeclaration | {};
28+
observer: null | MutationObserver;
29+
subtitleContainer: null | HTMLElement;
30+
} {
31+
return {
32+
active: false,
33+
position: { width: 0, top: 0, left: 0, height: 0 },
34+
text: [],
35+
style: {},
36+
observer: null,
37+
subtitleContainer: null,
38+
};
39+
},
40+
41+
mounted() {
42+
console.log(`Activated for ${serviceName}`);
43+
44+
this.addWatcherForSubtitleContainer();
45+
window.addEventListener("resize", this.addWatcherForSubtitleContainer);
46+
},
47+
48+
deactivated() {
49+
this.observer?.disconnect();
50+
},
51+
52+
methods: {
53+
onSubtileChange() {
54+
// Hide original Subtitle
55+
//
56+
let originalSubtitle = document.querySelector(
57+
SUBTILE_CONTAINER_CLASS
58+
) as HTMLElement;
59+
60+
if (originalSubtitle) originalSubtitle.style.opacity = "0";
61+
62+
this.active = true;
63+
64+
// Get first line Rect
65+
//
66+
let firstRect = this.subtitleContainer
67+
?.querySelector(SUBTITLE_CLASS)
68+
?.getBoundingClientRect();
69+
70+
this.position = {
71+
width: firstRect?.width || 0,
72+
top: firstRect?.top || 0,
73+
left: firstRect?.left || 0,
74+
height: firstRect?.height || 0,
75+
};
76+
77+
let minLeft = this.position.left;
78+
let minRight = firstRect?.right || 0;
79+
let minBottom = firstRect?.bottom || 0;
80+
81+
// Get All lines
82+
//
83+
let linesWraper = this.subtitleContainer?.querySelectorAll(
84+
SUBTITLE_CLASS
85+
) as unknown as HTMLElement[];
86+
87+
this.text = [];
88+
linesWraper?.forEach((wrapper) => {
89+
// Extract text and <br>
90+
//
91+
let innerHtml = wrapper.innerHTML;
92+
93+
if (!innerHtml.includes("<br")) {
94+
this.text.push(wrapper.textContent as string);
95+
} else {
96+
let innerLines = innerHtml.split("<br>");
97+
98+
innerLines.forEach((line) => {
99+
let text = TextCleaner(line)
100+
.stripHtml()
101+
.condense()
102+
.toLowerCase()
103+
.valueOf();
104+
105+
this.text.push(text);
106+
});
107+
}
108+
109+
// Get line span
110+
//
111+
let span = wrapper.querySelector('[style*="font-size"]');
112+
113+
// Extract styles
114+
//
115+
if (span) {
116+
let styleStr = span.getAttribute("style");
117+
118+
let styleParts = styleStr?.split(";") as string[];
119+
120+
for (let i = 0; i < styleParts.length; i++) {
121+
const stylePart = styleParts[i];
122+
if (!stylePart.length) return;
123+
124+
let key = stylePart.split(":")[0];
125+
let value = stylePart.split(":")[1];
126+
127+
this.style[key] = value;
128+
}
129+
}
130+
131+
// Extract bondary
132+
//
133+
// Check if this span has bigger width
134+
// Then replace it width default Rect width
135+
let spanRect = wrapper.getBoundingClientRect();
136+
137+
if (spanRect.left < minLeft) {
138+
minLeft = spanRect.left;
139+
}
140+
141+
if (spanRect.right > minRight) {
142+
minRight = spanRect.right;
143+
}
144+
145+
if (spanRect.bottom > minBottom) {
146+
minBottom = spanRect.bottom;
147+
}
148+
});
149+
150+
// Caculate subtitle bunding box
151+
this.position.width = minRight - minLeft;
152+
this.position.height = minBottom - this.position.top;
153+
this.position.left = minLeft;
154+
155+
this.text = JSON.parse(JSON.stringify(this.text));
156+
},
157+
158+
async addWatcherForSubtitleContainer() {
159+
await this.findSubtitleContainer();
160+
161+
if (this.observer) {
162+
this.observer.disconnect();
163+
} else {
164+
this.observer = new MutationObserver(this.onSubtileChange);
165+
}
166+
167+
this.observer.observe(this.subtitleContainer as HTMLElement, {
168+
childList: true,
169+
});
170+
},
171+
172+
async findSubtitleContainer() {
173+
await waitUntil(() => !!document.querySelector(SUBTITLE_CLASS));
174+
this.subtitleContainer = document.querySelector(SUBTITLE_CLASS)
175+
?.parentNode as HTMLElement;
176+
},
177+
},
178+
});
179+
</script>
180+
181+
<style scoped>
182+
</style>

src/module/subtitle/netflix/components/Subtitle.vue renamed to src/module/subtitle/_support-template/components/Subtitle.vue

File renamed without changes.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { waitUntil } from "../../../common/helper/promise";
2+
import { AppInitializer } from "../../../common/types/general.type";
3+
import { SUBTITLE_CLASS } from "./static";
4+
5+
import rootComponent from "./Index.vue";
6+
import { analytic } from "../../../plugins/mixpanel";
7+
8+
export const serviceName = 'Service-name';
9+
10+
export const initConfig: AppInitializer = {
11+
website: {
12+
host: "*.com",
13+
path: "/*",
14+
},
15+
component: rootComponent,
16+
start: async (app) => {
17+
await waitUntil(() => !!document.querySelector(SUBTITLE_CLASS));
18+
19+
// Init Analytic
20+
//
21+
analytic.track(serviceName)
22+
23+
let appDiv = document.createElement("div");
24+
25+
// Insert the app div somewhere in the page
26+
// @TODO: Find a better way to do this
27+
28+
appDiv.id = "app";
29+
appDiv.style.position = "relative";
30+
appDiv.style.zIndex = "9999";
31+
32+
app.mount(appDiv);
33+
34+
return app;
35+
},
36+
};
File renamed without changes.

0 commit comments

Comments
 (0)