Skip to content

Commit 5ed1e49

Browse files
authored
Merge pull request #3 from apicore-engineering/develop
Refine behavior and UI
2 parents bb23d6e + 280901b commit 5ed1e49

File tree

5 files changed

+99
-68
lines changed

5 files changed

+99
-68
lines changed

dist/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export type TranslationMeta = {
88
type LiveTranslatorPluginOptions = {
99
translationLink: (meta: TranslationMeta) => string;
1010
persist?: boolean;
11+
root?: HTMLElement;
12+
refreshRate?: number;
1113
};
1214
export declare function encodeMessages(messagesObject: any): any;
1315
export declare const LiveTranslatorPlugin: {

dist/index.js

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,13 @@ const css = `
2525
border-radius: 100%;
2626
background-color: red;
2727
}
28-
.live-translator-badge-wrapper {
29-
position: relative !important;
30-
width: 0px;
31-
height: 0px;
32-
}
3328
.live-translator-badge-container {
3429
position: absolute !important;
30+
background: rgba(255,255,255,0.5);
31+
outline: solid 2px rgba(255,255,255,0.5);
32+
border-radius: 5px;
3533
display: flex;
34+
gap: 2px;
3635
z-index: 10000;
3736
}
3837
.live-translator-badge {
@@ -60,17 +59,16 @@ const css = `
6059
box-shadow: 0px 0px 5px #00c0ff !important;
6160
}
6261
.live-translator-box {
63-
outline: solid 2px green;
64-
background: green;
65-
opacity: 0.1;
62+
outline: solid 2px lightgreen !important;
63+
box-shadow: 0px 0px 5px lightgreen !important;
6664
position: absolute;
67-
border-radius: 4px;
65+
border-radius: 7px;
6866
z-index: 9999;
6967
display: none;
7068
}
7169
.live-translator-box.attribute {
72-
outline: solid 2px blue;
73-
background: blue;
70+
outline: solid 2px #00c0ff !important;
71+
box-shadow: 0px 0px 5px #00c0ff !important;
7472
}
7573
`;
7674
function deepForIn(object, fn) {
@@ -157,6 +155,7 @@ class LiveTranslatorManager {
157155
_enableButton;
158156
_indicator;
159157
_box;
158+
_wrapper;
160159
constructor(options) {
161160
this._enabled = false;
162161
this._options = options;
@@ -184,13 +183,16 @@ class LiveTranslatorManager {
184183
this.render();
185184
});
186185
document.body.appendChild(this._enableButton);
186+
this._wrapper = document.createElement('div');
187+
this._wrapper.classList.add('live-translator-wrapper');
188+
document.body.prepend(this._wrapper);
187189
this._box = document.createElement('div');
188190
this._box.classList.add('live-translator-box');
189-
document.body.appendChild(this._box);
191+
this._wrapper.appendChild(this._box);
190192
// initialize encode
191193
// encode is moved to i18n.ts file
192194
// initialize decode & render
193-
const throttler = throttle(() => this.render(), 800);
195+
const throttler = throttle(() => this.render(), this._options.refreshRate || 50);
194196
const observer = new MutationObserver(throttler);
195197
observer.observe(document.documentElement, {
196198
subtree: true,
@@ -199,10 +201,13 @@ class LiveTranslatorManager {
199201
childList: false,
200202
});
201203
document.documentElement.addEventListener('mousemove', throttler);
202-
window.setInterval(throttler, 1000);
204+
window.setInterval(throttler, (this._options.refreshRate || 50) * 2);
203205
// render for the first time
204206
this.render();
205207
}
208+
get root() {
209+
return this._options.root || document.documentElement;
210+
}
206211
toggle(enable) {
207212
if (enable !== undefined) {
208213
this._enabled = enable;
@@ -216,22 +221,21 @@ class LiveTranslatorManager {
216221
console.log(`%c Live Translator ${this._enabled ? 'ON' : 'OFF'} `, 'background: #222; color: #bada55');
217222
}
218223
render() {
219-
const badgeWrappers = document.querySelectorAll('.live-translator-badge-wrapper');
220224
this._box.style.display = 'none';
221-
badgeWrappers.forEach((wrapper) => {
222-
wrapper.remove();
225+
document.
226+
querySelectorAll('.live-translator-badge-container').
227+
forEach((elem) => {
228+
elem.remove();
223229
});
224230
this._indicator.style.background = this._enabled ? 'lightgreen' : 'red';
225231
if (!this._enabled) {
226232
return;
227233
}
228234
const re = new RegExp(ZeroWidthEncoder.PATTERN, 'gm');
229-
const queue = [document.documentElement];
235+
const queue = [this.root];
230236
while (queue.length > 0) {
231237
const node = queue.pop();
232238
const badges = [];
233-
const parent = node.parentElement;
234-
const rect = getBoundingClientRect(node);
235239
if (node instanceof Text) {
236240
const matches = node.textContent.match(re);
237241
for (const match of matches ?? []) {
@@ -253,21 +257,27 @@ class LiveTranslatorManager {
253257
}
254258
}
255259
if (badges.length) {
256-
let container;
257-
if (node.previousElementSibling && node.previousElementSibling.classList.contains('live-translator-badge-container')) {
258-
container = node.previousElementSibling;
260+
let position = { top: 0, left: 0 };
261+
try {
262+
if (node instanceof Text) {
263+
const clientRect = getBoundingClientRect(node);
264+
position.top = clientRect.top + window.scrollY;
265+
position.left = clientRect.left + window.screenX;
266+
}
267+
else {
268+
const clientRect = node.getClientRects()[0];
269+
position.top = clientRect.top + clientRect.height - 10 + window.scrollY;
270+
position.left = clientRect.left + window.screenX;
271+
}
259272
}
260-
else {
261-
const parentRect = getBoundingClientRect(node instanceof Text ? parent : node);
262-
container = document.createElement('span');
263-
container.classList.add('live-translator-badge-container');
264-
container.style.top = rect.top - parentRect.top + 'px';
265-
container.style.left = rect.left - parentRect.left + 'px';
266-
const relativeWrapper = document.createElement('span');
267-
relativeWrapper.classList.add('live-translator-badge-wrapper');
268-
relativeWrapper.appendChild(container);
269-
parent.insertBefore(relativeWrapper, node);
273+
catch (error) {
274+
// console.warn('Could not get bounding box for', node);
270275
}
276+
const container = document.createElement('span');
277+
container.classList.add('live-translator-badge-container');
278+
container.style.top = position.top + 'px';
279+
container.style.left = position.left + 'px';
280+
this._wrapper.appendChild(container);
271281
for (const badge of badges) {
272282
container.appendChild(badge);
273283
}
@@ -311,6 +321,7 @@ const createBadge = (meta, options, node, attribute) => {
311321
badge.href = options.translationLink(meta);
312322
badge.target = 'popup';
313323
badge.addEventListener('click', (e) => {
324+
console.log('clicked', badge.href);
314325
window.open(badge.href, 'popup', 'width=600,height=600,scrollbars=no,resizable=no');
315326
e.preventDefault();
316327
return false;

src/demo/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
</p>
4343
<h3>Scrollable container</h3>
4444
<div class="scroll">
45-
<div class="item" v-for="i in 5">{{ t('LTPlugin.ListItemN', [i]) }}</div>
45+
<div class="item translated" v-for="i in 5">{{ t('LTPlugin.ListItemN', [i]) }}</div>
4646
</div>
4747
<h3>Attribute</h3>
4848
<img class="image" src="https://source.unsplash.com/random/500x500" :alt="t('LTPlugin.Attrs.ImageAlt')"

src/demo/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ app.use(LiveTranslatorPlugin, {
1212
return `?meta=${encodeURIComponent(JSON.stringify(meta))}`
1313
},
1414
persist: true,
15+
root: document.getElementById('app'),
16+
refreshRate: 200,
1517
})
1618

1719
app.mount('#app')

src/index.ts

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,13 @@ const css = `
2626
border-radius: 100%;
2727
background-color: red;
2828
}
29-
.live-translator-badge-wrapper {
30-
position: relative !important;
31-
width: 0px;
32-
height: 0px;
33-
}
3429
.live-translator-badge-container {
3530
position: absolute !important;
31+
background: rgba(255,255,255,0.5);
32+
outline: solid 2px rgba(255,255,255,0.5);
33+
border-radius: 5px;
3634
display: flex;
35+
gap: 2px;
3736
z-index: 10000;
3837
}
3938
.live-translator-badge {
@@ -61,17 +60,16 @@ const css = `
6160
box-shadow: 0px 0px 5px #00c0ff !important;
6261
}
6362
.live-translator-box {
64-
outline: solid 2px green;
65-
background: green;
66-
opacity: 0.1;
63+
outline: solid 2px lightgreen !important;
64+
box-shadow: 0px 0px 5px lightgreen !important;
6765
position: absolute;
68-
border-radius: 4px;
66+
border-radius: 7px;
6967
z-index: 9999;
7068
display: none;
7169
}
7270
.live-translator-box.attribute {
73-
outline: solid 2px blue;
74-
background: blue;
71+
outline: solid 2px #00c0ff !important;
72+
box-shadow: 0px 0px 5px #00c0ff !important;
7573
}
7674
`
7775
export type TranslationMeta = {
@@ -85,6 +83,8 @@ export type TranslationMeta = {
8583
type LiveTranslatorPluginOptions = {
8684
translationLink: (meta: TranslationMeta) => string
8785
persist?: boolean
86+
root?: HTMLElement
87+
refreshRate?: number
8888
}
8989

9090
function deepForIn(object: Object, fn: (value: string, key: string) => void) {
@@ -179,6 +179,7 @@ class LiveTranslatorManager {
179179
_indicator: HTMLSpanElement
180180

181181
_box: HTMLDivElement
182+
_wrapper: HTMLDivElement
182183

183184
constructor (options: LiveTranslatorPluginOptions) {
184185
this._enabled = false
@@ -212,15 +213,19 @@ class LiveTranslatorManager {
212213
})
213214
document.body.appendChild(this._enableButton)
214215

216+
this._wrapper = document.createElement('div')
217+
this._wrapper.classList.add('live-translator-wrapper')
218+
document.body.prepend(this._wrapper)
219+
215220
this._box = document.createElement('div')
216221
this._box.classList.add('live-translator-box')
217-
document.body.appendChild(this._box)
222+
this._wrapper.appendChild(this._box)
218223

219224
// initialize encode
220225
// encode is moved to i18n.ts file
221226

222227
// initialize decode & render
223-
const throttler = throttle(() => this.render(), 800)
228+
const throttler = throttle(() => this.render(), this._options.refreshRate || 50)
224229
const observer = new MutationObserver(throttler)
225230
observer.observe(document.documentElement,
226231
{
@@ -231,12 +236,16 @@ class LiveTranslatorManager {
231236
},
232237
)
233238
document.documentElement.addEventListener('mousemove', throttler)
234-
window.setInterval(throttler, 1000)
239+
window.setInterval(throttler, (this._options.refreshRate || 50) * 2)
235240

236241
// render for the first time
237242
this.render()
238243
}
239244

245+
get root (): HTMLElement {
246+
return this._options.root || document.documentElement
247+
}
248+
240249
toggle (enable?: boolean) {
241250
if (enable !== undefined) {
242251
this._enabled = enable
@@ -250,11 +259,12 @@ class LiveTranslatorManager {
250259
}
251260

252261
render () {
253-
const badgeWrappers = document.querySelectorAll('.live-translator-badge-wrapper')
254262
this._box.style.display = 'none'
255-
badgeWrappers.forEach((wrapper) => {
256-
wrapper.remove()
257-
})
263+
document.
264+
querySelectorAll('.live-translator-badge-container').
265+
forEach((elem) => {
266+
elem.remove()
267+
})
258268

259269
this._indicator.style.background = this._enabled ? 'lightgreen' : 'red'
260270

@@ -264,14 +274,12 @@ class LiveTranslatorManager {
264274

265275
const re = new RegExp(ZeroWidthEncoder.PATTERN, 'gm')
266276

267-
const queue = [document.documentElement] as Node[]
277+
const queue = [this.root] as Node[]
268278
while (queue.length > 0) {
269279
const node = queue.pop() as HTMLElement
270280

271281
const badges = [] as HTMLElement[]
272-
const parent = node.parentElement as HTMLElement
273282

274-
const rect = getBoundingClientRect(node)
275283
if (node instanceof Text) {
276284
const matches = (node.textContent as string).match(re)
277285
for (const match of matches ?? []) {
@@ -295,20 +303,26 @@ class LiveTranslatorManager {
295303
}
296304

297305
if (badges.length) {
298-
let container: HTMLElement
299-
if (node.previousElementSibling && node.previousElementSibling.classList.contains('live-translator-badge-container')) {
300-
container = node.previousElementSibling as HTMLElement
301-
} else {
302-
const parentRect = getBoundingClientRect(node instanceof Text ? parent : node);
303-
container = document.createElement('span')
304-
container.classList.add('live-translator-badge-container')
305-
container.style.top = rect.top - parentRect.top + 'px'
306-
container.style.left = rect.left - parentRect.left + 'px'
307-
const relativeWrapper = document.createElement('span')
308-
relativeWrapper.classList.add('live-translator-badge-wrapper')
309-
relativeWrapper.appendChild(container)
310-
parent.insertBefore(relativeWrapper, node)
306+
let position = { top: 0, left: 0}
307+
try {
308+
if (node instanceof Text) {
309+
const clientRect = getBoundingClientRect(node)
310+
position.top = clientRect.top + window.scrollY
311+
position.left = clientRect.left + window.screenX
312+
} else {
313+
const clientRect = node.getClientRects()[0]
314+
position.top = clientRect.top + clientRect.height - 10 + window.scrollY
315+
position.left = clientRect.left + window.screenX
316+
}
317+
} catch (error) {
318+
// console.warn('Could not get bounding box for', node);
311319
}
320+
const container = document.createElement('span')
321+
container.classList.add('live-translator-badge-container')
322+
container.style.top = position.top + 'px'
323+
container.style.left = position.left + 'px'
324+
this._wrapper.appendChild(container)
325+
312326
for (const badge of badges) {
313327
container.appendChild(badge)
314328
}
@@ -353,6 +367,8 @@ const createBadge = (meta: TranslationMeta, options: LiveTranslatorPluginOptions
353367
badge.href = options.translationLink(meta)
354368
badge.target = 'popup'
355369
badge.addEventListener('click', (e: Event) => {
370+
console.log('clicked', badge.href);
371+
356372
window.open(badge.href, 'popup', 'width=600,height=600,scrollbars=no,resizable=no')
357373
e.preventDefault()
358374
return false

0 commit comments

Comments
 (0)