copy_tuner_client 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
data/src/copyray.ts ADDED
@@ -0,0 +1,130 @@
1
+ import CopyTunerBar from './copytuner_bar'
2
+ import Specimen from './specimen'
3
+
4
+ const findBlurbs = () => {
5
+ const filterNone = () => NodeFilter.FILTER_ACCEPT
6
+
7
+ // @ts-expect-error TS2554
8
+ const iterator = document.createNodeIterator(document.body, NodeFilter.SHOW_COMMENT, filterNone, false)
9
+
10
+ const comments = []
11
+ let curNode
12
+
13
+ while ((curNode = iterator.nextNode())) {
14
+ comments.push(curNode)
15
+ }
16
+
17
+ return (
18
+ comments
19
+ // @ts-expect-error TS2531
20
+ .filter((comment) => comment.nodeValue.startsWith('COPYRAY'))
21
+ .map((comment) => {
22
+ // @ts-expect-error TS2488
23
+ const [, key] = comment.nodeValue.match(/^COPYRAY (\S*)$/)
24
+ const element = comment.parentNode
25
+ return { key, element }
26
+ })
27
+ )
28
+ }
29
+
30
+ export default class Copyray {
31
+ // @ts-expect-error TS7006
32
+ constructor(baseUrl, data) {
33
+ // @ts-expect-error TS2339
34
+ this.baseUrl = baseUrl
35
+ // @ts-expect-error TS2339
36
+ this.data = data
37
+ // @ts-expect-error TS2339
38
+ this.isShowing = false
39
+ // @ts-expect-error TS2339
40
+ this.specimens = []
41
+ // @ts-expect-error TS2339
42
+ this.overlay = this.makeOverlay()
43
+ // @ts-expect-error TS2339
44
+ this.toggleButton = this.makeToggleButton()
45
+ // @ts-expect-error TS2339
46
+ this.boundOpen = this.open.bind(this)
47
+
48
+ // @ts-expect-error TS2339
49
+ this.copyTunerBar = new CopyTunerBar(document.querySelector('#copy-tuner-bar'), this.data, this.boundOpen)
50
+ }
51
+
52
+ show() {
53
+ this.reset()
54
+
55
+ // @ts-expect-error TS2339
56
+ document.body.append(this.overlay)
57
+ this.makeSpecimens()
58
+
59
+ // @ts-expect-error TS2339
60
+ for (const specimen of this.specimens) {
61
+ specimen.show()
62
+ }
63
+
64
+ // @ts-expect-error TS2339
65
+ this.copyTunerBar.show()
66
+ // @ts-expect-error TS2339
67
+ this.isShowing = true
68
+ }
69
+
70
+ hide() {
71
+ // @ts-expect-error TS2339
72
+ this.overlay.remove()
73
+ this.reset()
74
+ // @ts-expect-error TS2339
75
+ this.copyTunerBar.hide()
76
+ // @ts-expect-error TS2339
77
+ this.isShowing = false
78
+ }
79
+
80
+ toggle() {
81
+ // @ts-expect-error TS2339
82
+ if (this.isShowing) {
83
+ this.hide()
84
+ } else {
85
+ this.show()
86
+ }
87
+ }
88
+
89
+ // @ts-expect-error TS7006
90
+ open(key) {
91
+ // @ts-expect-error TS2339
92
+ window.open(`${this.baseUrl}/blurbs/${key}/edit`)
93
+ }
94
+
95
+ makeSpecimens() {
96
+ for (const { element, key } of findBlurbs()) {
97
+ // @ts-expect-error TS2339
98
+ this.specimens.push(new Specimen(element, key, this.boundOpen))
99
+ }
100
+ }
101
+
102
+ makeToggleButton() {
103
+ const element = document.createElement('a')
104
+
105
+ element.addEventListener('click', () => {
106
+ this.show()
107
+ })
108
+
109
+ element.classList.add('copyray-toggle-button')
110
+ element.classList.add('hidden-on-mobile')
111
+ element.textContent = 'Open CopyTuner'
112
+ document.body.append(element)
113
+
114
+ return element
115
+ }
116
+
117
+ makeOverlay() {
118
+ const div = document.createElement('div')
119
+ div.setAttribute('id', 'copyray-overlay')
120
+ div.addEventListener('click', () => this.hide())
121
+ return div
122
+ }
123
+
124
+ reset() {
125
+ // @ts-expect-error TS2339
126
+ for (const specimen of this.specimens) {
127
+ specimen.remove()
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,115 @@
1
+ // @ts-expect-error TS7016
2
+ import debounce from 'lodash.debounce'
3
+
4
+ const HIDDEN_CLASS = 'copy-tuner-hidden'
5
+
6
+ export default class CopytunerBar {
7
+ // @ts-expect-error TS7006
8
+ constructor(element, data, callback) {
9
+ // @ts-expect-error TS2339
10
+ this.element = element
11
+ // @ts-expect-error TS2339
12
+ this.data = data
13
+ // @ts-expect-error TS2339
14
+ this.callback = callback
15
+ // @ts-expect-error TS2339
16
+ this.searchBoxElement = element.querySelector('.js-copy-tuner-bar-search')
17
+ // @ts-expect-error TS2339
18
+ this.logMenuElement = this.makeLogMenu()
19
+ // @ts-expect-error TS2339
20
+ this.element.append(this.logMenuElement)
21
+
22
+ this.addHandler()
23
+ }
24
+
25
+ addHandler() {
26
+ // @ts-expect-error TS2339
27
+ const openLogButton = this.element.querySelector('.js-copy-tuner-bar-open-log')
28
+ // @ts-expect-error TS7006
29
+ openLogButton.addEventListener('click', (event) => {
30
+ event.preventDefault()
31
+ this.toggleLogMenu()
32
+ })
33
+
34
+ // @ts-expect-error TS2339
35
+ this.searchBoxElement.addEventListener('input', debounce(this.onKeyup.bind(this), 250))
36
+ }
37
+
38
+ show() {
39
+ // @ts-expect-error TS2339
40
+ this.element.classList.remove(HIDDEN_CLASS)
41
+ // @ts-expect-error TS2339
42
+ this.searchBoxElement.focus()
43
+ }
44
+
45
+ hide() {
46
+ // @ts-expect-error TS2339
47
+ this.element.classList.add(HIDDEN_CLASS)
48
+ }
49
+
50
+ showLogMenu() {
51
+ // @ts-expect-error TS2339
52
+ this.logMenuElement.classList.remove(HIDDEN_CLASS)
53
+ }
54
+
55
+ toggleLogMenu() {
56
+ // @ts-expect-error TS2339
57
+ this.logMenuElement.classList.toggle(HIDDEN_CLASS)
58
+ }
59
+
60
+ makeLogMenu() {
61
+ const div = document.createElement('div')
62
+ div.setAttribute('id', 'copy-tuner-bar-log-menu')
63
+ div.classList.add(HIDDEN_CLASS)
64
+
65
+ const table = document.createElement('table')
66
+ const tbody = document.createElement('tbody')
67
+ tbody.classList.remove('is-not-initialized')
68
+
69
+ // @ts-expect-error TS2339
70
+ for (const key of Object.keys(this.data).sort()) {
71
+ // @ts-expect-error TS2339
72
+ const value = this.data[key]
73
+
74
+ if (value === '') {
75
+ continue
76
+ }
77
+
78
+ const td1 = document.createElement('td')
79
+ td1.textContent = key
80
+ const td2 = document.createElement('td')
81
+ td2.textContent = value
82
+ const tr = document.createElement('tr')
83
+ tr.classList.add('copy-tuner-bar-log-menu__row')
84
+ tr.dataset.key = key
85
+
86
+ tr.addEventListener('click', ({ currentTarget }) => {
87
+ // @ts-expect-error TS2339
88
+ this.callback(currentTarget.dataset.key)
89
+ })
90
+
91
+ tr.append(td1)
92
+ tr.append(td2)
93
+ tbody.append(tr)
94
+ }
95
+
96
+ table.append(tbody)
97
+ div.append(table)
98
+
99
+ return div
100
+ }
101
+
102
+ // @ts-expect-error TS7031
103
+ onKeyup({ target }) {
104
+ const keyword = target.value.trim()
105
+ this.showLogMenu()
106
+
107
+ // @ts-expect-error TS2339
108
+ const rows = [...this.logMenuElement.querySelectorAll('tr')]
109
+
110
+ for (const row of rows) {
111
+ const isShow = keyword === '' || [...row.querySelectorAll('td')].some((td) => td.textContent.includes(keyword))
112
+ row.classList.toggle(HIDDEN_CLASS, !isShow)
113
+ }
114
+ }
115
+ }
data/src/main.ts ADDED
@@ -0,0 +1,58 @@
1
+ /* eslint-disable no-console */
2
+ import Copyray from './copyray'
3
+ import { isMac } from './util'
4
+
5
+ declare global {
6
+ interface Window {
7
+ CopyTuner: {
8
+ url: string
9
+ // TODO: type
10
+ data: object
11
+ }
12
+ }
13
+ }
14
+
15
+ import './copyray.css'
16
+
17
+ // NOTE: 元々railsから出力されいてたマークアップに合わせてひとまず、、
18
+ const appendCopyTunerBar = (url: string) => {
19
+ const bar = document.createElement('div')
20
+ bar.id = 'copy-tuner-bar'
21
+ bar.classList.add('copy-tuner-hidden')
22
+ bar.innerHTML = `
23
+ <a class="copy-tuner-bar-button" target="_blank" href="${url}">CopyTuner</a>
24
+ <a href="/copytuner" target="_blank" class="copy-tuner-bar-button">Sync</a>
25
+ <a href="javascript:void(0)" class="copy-tuner-bar-open-log copy-tuner-bar-button js-copy-tuner-bar-open-log">Translations in this page</a>
26
+ <input type="text" class="copy-tuner-bar__search js-copy-tuner-bar-search" placeholder="search">
27
+ `
28
+ document.body.append(bar)
29
+ }
30
+
31
+ const start = () => {
32
+ const { url, data } = window.CopyTuner
33
+
34
+ appendCopyTunerBar(url)
35
+ const copyray = new Copyray(url, data)
36
+
37
+ document.addEventListener('keydown', (event) => {
38
+ // @ts-expect-error TS2339
39
+ if (copyray.isShowing && ['Escape', 'Esc'].includes(event.key)) {
40
+ copyray.hide()
41
+ return
42
+ }
43
+
44
+ if (((isMac && event.metaKey) || (!isMac && event.ctrlKey)) && event.shiftKey && event.key === 'k') {
45
+ copyray.toggle()
46
+ }
47
+ })
48
+
49
+ if (console) {
50
+ console.log(`Ready to Copyray. Press ${isMac ? 'cmd+shift+k' : 'ctrl+shift+k'} to scan your UI.`)
51
+ }
52
+ }
53
+
54
+ if (document.readyState === 'complete' || document.readyState !== 'loading') {
55
+ start()
56
+ } else {
57
+ document.addEventListener('DOMContentLoaded', () => start())
58
+ }
data/src/specimen.ts ADDED
@@ -0,0 +1,84 @@
1
+ import { computeBoundingBox } from './util'
2
+
3
+ const ZINDEX = 2_000_000_000
4
+
5
+ export default class Specimen {
6
+ // @ts-expect-error TS7006
7
+ constructor(element, key, callback) {
8
+ // @ts-expect-error TS2339
9
+ this.element = element
10
+ // @ts-expect-error TS2339
11
+ this.key = key
12
+ // @ts-expect-error TS2339
13
+ this.callback = callback
14
+ }
15
+
16
+ show() {
17
+ // @ts-expect-error TS2339
18
+ this.box = this.makeBox()
19
+ // @ts-expect-error TS2339
20
+ if (this.box === null) return
21
+
22
+ // @ts-expect-error TS2339
23
+ this.box.addEventListener('click', () => {
24
+ // @ts-expect-error TS2339
25
+ this.callback(this.key)
26
+ })
27
+
28
+ // @ts-expect-error TS2339
29
+ document.body.append(this.box)
30
+ }
31
+
32
+ remove() {
33
+ // @ts-expect-error TS2339
34
+ if (!this.box) {
35
+ return
36
+ }
37
+ // @ts-expect-error TS2339
38
+ this.box.remove()
39
+ // @ts-expect-error TS2339
40
+ this.box = null
41
+ }
42
+
43
+ makeBox() {
44
+ const box = document.createElement('div')
45
+ box.classList.add('copyray-specimen')
46
+ box.classList.add('Specimen')
47
+
48
+ // @ts-expect-error TS2339
49
+ const bounds = computeBoundingBox(this.element)
50
+ if (bounds === null) return null
51
+
52
+ for (const key of Object.keys(bounds)) {
53
+ // @ts-expect-error TS7053
54
+ const value = bounds[key]
55
+ // @ts-expect-error TS7015
56
+ box.style[key] = `${value}px`
57
+ }
58
+ // @ts-expect-error TS2322
59
+ box.style.zIndex = ZINDEX
60
+
61
+ // @ts-expect-error TS2339
62
+ const { position, top, left } = getComputedStyle(this.element)
63
+ if (position === 'fixed') {
64
+ // @ts-expect-error TS2339
65
+ this.box.style.position = 'fixed'
66
+ // @ts-expect-error TS2339
67
+ this.box.style.top = `${top}px`
68
+ // @ts-expect-error TS2339
69
+ this.box.style.left = `${left}px`
70
+ }
71
+
72
+ box.append(this.makeLabel())
73
+ return box
74
+ }
75
+
76
+ makeLabel() {
77
+ const div = document.createElement('div')
78
+ div.classList.add('copyray-specimen-handle')
79
+ div.classList.add('Specimen')
80
+ // @ts-expect-error TS2339
81
+ div.textContent = this.key
82
+ return div
83
+ }
84
+ }
data/src/util.ts ADDED
@@ -0,0 +1,38 @@
1
+ const isMac = navigator.platform.toUpperCase().includes('MAC')
2
+
3
+ // @ts-expect-error TS7006
4
+ const isVisible = (element) => !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length > 0)
5
+
6
+ // @ts-expect-error TS7006
7
+ const getOffset = (elment) => {
8
+ const box = elment.getBoundingClientRect()
9
+
10
+ return {
11
+ top: box.top + (window.pageYOffset - document.documentElement.clientTop),
12
+ left: box.left + (window.pageXOffset - document.documentElement.clientLeft),
13
+ }
14
+ }
15
+
16
+ // @ts-expect-error TS7006
17
+ const computeBoundingBox = (element) => {
18
+ if (!isVisible(element)) {
19
+ return null
20
+ }
21
+
22
+ const boxFrame = getOffset(element)
23
+ // @ts-expect-error TS2339
24
+ boxFrame.right = boxFrame.left + element.offsetWidth
25
+ // @ts-expect-error TS2339
26
+ boxFrame.bottom = boxFrame.top + element.offsetHeight
27
+
28
+ return {
29
+ left: boxFrame.left,
30
+ top: boxFrame.top,
31
+ // @ts-expect-error TS2339
32
+ width: boxFrame.right - boxFrame.left,
33
+ // @ts-expect-error TS2339
34
+ height: boxFrame.bottom - boxFrame.top,
35
+ }
36
+ }
37
+
38
+ export { isMac, isVisible, getOffset, computeBoundingBox }
data/src/vite-env.d.ts ADDED
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
data/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": [
7
+ "ESNext",
8
+ "DOM"
9
+ ],
10
+ "moduleResolution": "Node",
11
+ "strict": true,
12
+ "sourceMap": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "esModuleInterop": true,
16
+ "noEmit": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noImplicitReturns": true,
20
+ "skipLibCheck": true,
21
+ },
22
+ "include": [
23
+ "client",
24
+ "src",
25
+ ]
26
+ }
data/vite.config.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'vite'
2
+
3
+ // https://vitejs.dev/config/
4
+ export default defineConfig({
5
+ build: {
6
+ outDir: './app/assets',
7
+ lib: {
8
+ entry: 'src/main.ts',
9
+ formats: ['es'],
10
+ },
11
+ rollupOptions: {
12
+ output: {
13
+ entryFileNames: `javascripts/[name].js`,
14
+ assetFileNames: `stylesheets/[name].[ext]`,
15
+ }
16
+ }
17
+ },
18
+ })