Skip to content
This repository was archived by the owner on Aug 16, 2024. It is now read-only.

Commit 942d35f

Browse files
committed
feat: select to translate
1 parent c8a80f4 commit 942d35f

File tree

10 files changed

+119
-37
lines changed

10 files changed

+119
-37
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@types/lodash-es": "^4.17.4",
3333
"@types/node": "^14",
3434
"@types/pino": "^6.3.4",
35+
"@types/rangy": "^0.0.33",
3536
"@types/react": "^17.0.0",
3637
"@types/react-dom": "^17.0.0",
3738
"@types/uuid": "^8.3.0",
@@ -65,11 +66,13 @@
6566
"postcss-import": "^14.0.0",
6667
"postcss-loader": "^4.1.0",
6768
"prettier": "^2.2.1",
69+
"rangy": "^1.3.0",
6870
"react": "^17.0.1",
6971
"react-dom": "^17.0.1",
7072
"react-draggable": "^4.4.3",
7173
"react-hot-loader": "^4.13.0",
7274
"sass-loader": "^10.1.0",
75+
"smoothscroll-polyfill": "^0.4.4",
7376
"source-map-loader": "^1.1.3",
7477
"style-loader": "^2.0.0",
7578
"tailwindcss": "^2.0.2",

src/pages/Content/common/types.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { SupportLanguages } from '../../../common/types'
22

33
export interface TextSelection {
4-
anchor?: Node
5-
anchorOffset?: number
6-
rangeCount?: number
4+
parentElement?: HTMLElement
75
sourceLang?: SupportLanguages
86
text: string
97
}

src/pages/Content/common/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const getFirstRange = (sel: RangySelection): RangyRange | undefined => {
2+
return sel.rangeCount ? sel.getRangeAt(0) : undefined
3+
}

src/pages/Content/components/TranslationItem.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React, { useEffect, useState } from 'react'
22

3-
import Client from '../../../common/api'
43
import logger from '../../../common/logger'
54
import { TranslateResult } from '../../../common/types'
65
import client from '../common/client'
@@ -14,11 +13,24 @@ const TranslationItem: React.FC<{
1413
const [loading, setLoading] = useState(true)
1514
const [result, setResult] = useState<string>()
1615

16+
const findOriginal = () => {
17+
const { parentElement } = job
18+
19+
if (!parentElement) {
20+
return
21+
}
22+
23+
window.scrollTo(0, parentElement.offsetTop - 20)
24+
}
25+
1726
useEffect(() => {
27+
if (!config) return
28+
1829
const res = client.send(
1930
'translate',
2031
{
21-
...job,
32+
text: job.text,
33+
id: job.id,
2234
targetLang: config.targetLang,
2335
},
2436
true,
@@ -46,7 +58,7 @@ const TranslationItem: React.FC<{
4658
}, [job, config])
4759

4860
return (
49-
<div className="ate_TranslationItem">
61+
<div className="ate_TranslationItem" onClick={() => findOriginal()}>
5062
<div className="ate_TranslationItem__upper">
5163
<div>
5264
{job.text.length > 100 ? (

src/pages/Content/index.tsx

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
import React from 'react'
22
import { render } from 'react-dom'
3+
// @ts-ignore
4+
import smoothScrollPolyfill from 'smoothscroll-polyfill'
5+
import * as rangy from 'rangy'
6+
// @ts-ignore
7+
import 'rangy/lib/rangy-classapplier'
8+
import 'rangy/lib/rangy-highlighter'
39

410
import logger from '../../common/logger'
511
import { SupportLanguages } from '../../common/types'
612
import translationStack from './common/translation-stack'
713
import { TextSelection } from './common/types'
14+
import { getFirstRange } from './common/utils'
815
import App from './components/App'
916
import './styles/index.scss'
1017
import { TranslateJobsProvider } from './providers/translate-jobs'
1118

1219
let isAppAttached = false
13-
let lastSelection: TextSelection | undefined
20+
let lastSelection: (TextSelection & { selection: RangySelection }) | undefined
21+
let highlighter: any
22+
1423
const main = async () => {
1524
const container = document.createElement('div')
1625
container.id = 'ate-container'
@@ -28,26 +37,43 @@ const main = async () => {
2837

2938
window.addEventListener('load', () => {
3039
try {
31-
document.querySelector('body')?.append(iconContainer)
32-
document.querySelector('body')?.append(container)
40+
// @ts-ignore
41+
rangy.init()
42+
// @ts-ignore
43+
highlighter = rangy.createHighlighter()
44+
// @ts-ignore
45+
highlighter.addClassApplier(
46+
// @ts-ignore
47+
rangy.createClassApplier('ate-highlight', {
48+
ignoreWhiteSpace: true,
49+
tagNames: ['span', 'a'],
50+
}),
51+
)
52+
53+
document.querySelector<HTMLBodyElement>('body')?.append(iconContainer)
54+
document.querySelector<HTMLBodyElement>('body')?.append(container)
3355

3456
attachListeners()
3557

3658
// TODO: remove before deploying
37-
initApp()
59+
// initApp()
3860
} catch (err) {
3961
logger.error({
4062
err,
4163
})
4264
}
4365
})
66+
67+
if (!('scrollBehavior' in document.documentElement.style)) {
68+
smoothScrollPolyfill.polyfill()
69+
}
4470
}
4571

4672
const onMouseUp = (e: MouseEvent) => {
47-
const selection = window.getSelection()
73+
const selection = rangy.getSelection()
4874
const iconElement = document.querySelector<HTMLSpanElement>('#ate-icon')
4975

50-
if (selection?.toString().trim() && iconElement) {
76+
if (selection.toString().trim() && iconElement) {
5177
lastSelection = getTextSelection(selection)
5278
iconElement.style.top = e.pageY + 20 + 'px'
5379
iconElement.style.left = e.pageX + 'px'
@@ -73,24 +99,49 @@ const onClickTranslate = (selection: TextSelection) => {
7399

74100
const attachListeners = () => {
75101
document.addEventListener('mouseup', onMouseUp, false)
76-
document.querySelector('#ate-icon')?.addEventListener('click', (e) => {
77-
logger.debug({
78-
msg: 'lastSelection',
79-
lastSelection,
102+
document
103+
.querySelector<HTMLSpanElement>('#ate-icon')
104+
?.addEventListener('click', function () {
105+
logger.debug({
106+
msg: 'lastSelection',
107+
lastSelection,
108+
})
109+
110+
if (lastSelection) {
111+
highlightSelection(lastSelection.selection)
112+
113+
onClickTranslate({
114+
text: lastSelection.text,
115+
parentElement: lastSelection.parentElement,
116+
sourceLang: lastSelection.sourceLang,
117+
})
118+
119+
setTimeout(() => {
120+
lastSelection?.selection.removeAllRanges()
121+
this.classList.remove('active')
122+
}, 0)
123+
}
80124
})
81-
if (lastSelection) {
82-
onClickTranslate(lastSelection)
83-
}
84-
})
85125
}
86126

87-
const getTextSelection = (selection: Selection): TextSelection => {
127+
const highlightSelection = (selection: RangySelection) => {
128+
const range = getFirstRange(selection)
129+
130+
if (!range || !highlighter) {
131+
return
132+
}
133+
134+
highlighter.highlightSelection('ate-highlight')
135+
}
136+
137+
const getTextSelection = (
138+
selection: RangySelection,
139+
): TextSelection & { selection: RangySelection } => {
88140
const text = selection.toString().trim()
89141

90142
return {
91-
anchor: selection.anchorNode?.parentElement ?? undefined,
92-
anchorOffset: selection.anchorOffset,
93-
rangeCount: selection.rangeCount,
143+
selection,
144+
parentElement: selection.anchorNode?.parentElement ?? undefined,
94145
sourceLang: getSourceLang(),
95146
text,
96147
}

src/pages/Content/providers/config.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@ export type ConfigState = {
88

99
export const ConfigContext = createContext<ConfigState | undefined>(undefined)
1010

11-
export const useConfig = (): ConfigState => {
12-
const context = useContext(ConfigContext)
13-
14-
if (context === undefined) {
15-
throw new Error('useConfig must be used within a ConfigContext.Provider')
16-
}
17-
18-
return context
11+
export const useConfig = (): ConfigState | undefined => {
12+
return useContext(ConfigContext)
1913
}

src/pages/Content/styles/components/TranslationItem.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.ate_TranslationItem {
2-
@apply text-gray-800 space-y-3 select-auto;
2+
@apply p-3 text-gray-800 space-y-3 select-text cursor-pointer;
33

44
&__upper {
55
@apply space-y-3;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.ate_TranslationList {
2-
@apply space-y-2 divide-y divide-gray-200 divide-solid;
2+
@apply divide-y divide-gray-200 divide-solid;
33

44
&__item {
5-
@apply p-3 border-l-0 border-r-0;
5+
@apply border-l-0 border-r-0;
66
}
77
}

src/pages/Content/styles/index.scss

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@
1414
left: 0;
1515
margin: 0;
1616
padding: 0;
17-
18-
@apply select-none;
1917
}
2018

2119
#ate-container {
2220
position: fixed;
21+
22+
@apply select-none;
2323
}
2424

2525
#ate-icon-container {
2626
position: absolute;
27+
28+
@apply select-none;
2729
}
2830

2931
#ate-icon {
@@ -45,3 +47,7 @@
4547
#ate-icon:hover {
4648
cursor: pointer;
4749
}
50+
51+
.ate-highlight {
52+
@apply bg-purple-200;
53+
}

yarn.lock

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,11 @@
14161416
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
14171417
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
14181418

1419+
"@types/rangy@^0.0.33":
1420+
version "0.0.33"
1421+
resolved "https://registry.yarnpkg.com/@types/rangy/-/rangy-0.0.33.tgz#9f1afff01b4ba840360713a2c2ef3a36e7d49576"
1422+
integrity sha512-j2lhwVgbxZORhiB7yP+cly6+ow75MYmyHIkkSBwDl7XzrO5TMUECKkXqCf0pZVDjW5TgulIvhKg7SyEWSSgzmA==
1423+
14191424
"@types/react-dom@^17.0.0":
14201425
version "17.0.0"
14211426
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.0.tgz#b3b691eb956c4b3401777ee67b900cb28415d95a"
@@ -6000,6 +6005,11 @@ range-parser@^1.2.1, range-parser@~1.2.1:
60006005
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
60016006
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
60026007

6008+
rangy@^1.3.0:
6009+
version "1.3.0"
6010+
resolved "https://registry.yarnpkg.com/rangy/-/rangy-1.3.0.tgz#b7c9a70aea05e5d8cdc74a8d4c8273d697271904"
6011+
integrity sha1-t8mnCuoF5djNx0qNTIJz1pcnGQQ=
6012+
60036013
raw-body@2.4.0:
60046014
version "2.4.0"
60056015
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
@@ -6634,6 +6644,11 @@ slice-ansi@^4.0.0:
66346644
astral-regex "^2.0.0"
66356645
is-fullwidth-code-point "^3.0.0"
66366646

6647+
smoothscroll-polyfill@^0.4.4:
6648+
version "0.4.4"
6649+
resolved "https://registry.yarnpkg.com/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz#3a259131dc6930e6ca80003e1cb03b603b69abf8"
6650+
integrity sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==
6651+
66376652
snapdragon-node@^2.0.1:
66386653
version "2.1.1"
66396654
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"

0 commit comments

Comments
 (0)