copy_tuner_client 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ })