Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Original text popup #65

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/content/InPageTranslation-popup.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
:root {
color-scheme: light dark;
}

.popup {
position: absolute;
z-index: 10000;
background: canvas;
color: canvastext;
font-size: 14px;
padding: 14px;
border: 1px solid rgba(0, 0, 0, 0.25);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";

/* Shadow from https://getcssscan.com/css-box-shadow-examples */
box-shadow: rgba(0, 0, 0, 0.16) 0px 3px 6px, rgba(0, 0, 0, 0.23) 0px 3px 6px;

box-sizing: border-box;
min-height: calc(40px + 14px);
min-width: 80px;
width: fit-content;

overflow: auto;
}

.popup p {
margin: 0 0 .2em 0;
}

.popup span + span {
margin-left: 1ch;
}
115 changes: 114 additions & 1 deletion src/content/InPageTranslation.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import "./InPageTranslation.css";
import { isElementVisible, isElementInViewport } from '../shared/common.js';
import compat from "../shared/compat.js";
import { isElementVisible, isElementInViewport } from "../shared/common.js";

function computePath(node, root) {
if (root === undefined)
Expand Down Expand Up @@ -31,6 +32,21 @@ function removeTextNodes(node) {
});
}

function once(target, name, callback, options) {
const wrapper = (evt) => {
try {
callback(evt);
} finally {
remove()
}
};
var remove = () => { /* `var` because hoisted so `wrapper` can use it */
target.removeEventListener(name, wrapper, options);
};
target.addEventListener(name, wrapper, options);
return remove;
}

/**
* Similar to createTreeWalker's functionality, but the filter function gets
* context when descending down a branch. This is used to dig down excluded
Expand Down Expand Up @@ -273,6 +289,10 @@ export default class InPageTranslation {
});

this.isParentQueuedCache = new Map();

// Bound mouse listener so multiple addEventListener calls don't
// register multiple copies, and removeEventListener can be used.
this.onMouseEnterCallback = this.onMouseEnter.bind(this);
}

/**
Expand Down Expand Up @@ -411,6 +431,9 @@ export default class InPageTranslation {
}

restoreElement(node) {
// Remove the [mouseenter] event listener
node.removeEventListener('mouseenter', this.onMouseEnterCallback);

const original = this.originalContent.get(node);

// We start tracking a node in enqueueTranslation. If it isn't tracked
Expand Down Expand Up @@ -1060,6 +1083,8 @@ export default class InPageTranslation {
};

merge(node, scratch.body);

node.addEventListener('mouseenter', this.onMouseEnterCallback);
};

const updateTextNode = ({id, translated}, node) => {
Expand Down Expand Up @@ -1148,4 +1173,92 @@ export default class InPageTranslation {
// specific models are not used for translating broad language codes.
return false;
}

cloneOriginal(node) {
const mapping = {
'em': 'em',
'strong': 'strong',
'i': 'em',
'b': 'strong',
'u': 'u',
'small': 'small',
'mark': 'span',
'time': 'span',
'var': 'var',
'wbr': 'wbr',
'sup': 'sup',
'sub': 'sub',
'ins': 'ins',
'del': 'del',
'p': 'p',
'h1': 'p',
'h2': 'p',
'h3': 'p',
'h4': 'p',
'h5': 'p',
'span': 'span',
'div': 'div',
'th': 'strong',
'td': 'span',
'li': 'span',
'a': 'span',
};

switch (node.nodeType) {
case Node.TEXT_NODE: {
const original = this.originalContent.get(node);
return document.createTextNode(original !== undefined ? original : node.data);
}
case Node.ELEMENT_NODE: {
const original = this.originalContent.get(node);
if (original === undefined || mapping[node.tagName.toLowerCase()] === undefined)
return document.createDocumentFragment(); // empty

const content = document.createElement(mapping[node.tagName.toLowerCase()]);
original.forEach(node => {
const restored = this.cloneOriginal(node);
content.appendChild(restored);
});
return content;
}
}
}

onMouseEnter(evt) {
const render = () => {
const original = this.cloneOriginal(evt.target);

const popup = document.createElement('div');
popup.classList.add('popup');
popup.appendChild(original);

const rect = evt.target.getBoundingClientRect();
Object.assign(popup.style, {
top: `${rect.bottom + (document.documentElement.scrollTop || document.body.scrollTop)}px`,
left: `${rect.left + (document.documentElement.scrollLeft || document.body.scrollLeft)}px`,
maxWidth: `${rect.width}px`,
// height: `${rect.height}px`
});

const root = document.createElement('div');
root.translate = false;

const stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.href = compat.runtime.getURL('InPageTranslation-popup.css')

const dom = root.attachShadow({mode: 'closed'});
dom.appendChild(stylesheet);
dom.appendChild(popup);

document.body.appendChild(root);

// Remove popup if you leave the element
once(evt.target, 'mouseleave', () => document.body.removeChild(root));
};

// Only trigger the popup after 1s
const timer = setTimeout(render, 1000);
once(evt.target, 'mouseleave', () => clearTimeout(timer));
}
}
3 changes: 3 additions & 0 deletions webpack.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ export default {
{
from: 'src/content/OutboundTranslation.css'
},
{
from: 'src/content/InPageTranslation-popup.css'
},
{
from: 'node_modules/@browsermt/bergamot-translator/worker/bergamot-translator-worker.js'
},
Expand Down