copy_tuner_client 0.10.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.js +12 -0
  3. data/.gitattributes +2 -0
  4. data/.github/workflows/rspec.yml +22 -30
  5. data/.ruby-version +1 -1
  6. data/.vscode/settings.json +7 -0
  7. data/CHANGELOG.md +13 -0
  8. data/Gemfile +1 -1
  9. data/Gemfile.lock +168 -145
  10. data/README.md +8 -10
  11. data/app/assets/javascripts/main.js +388 -0
  12. data/app/assets/stylesheets/style.css +1 -0
  13. data/copy_tuner_client.gemspec +3 -3
  14. data/gemfiles/6.1.gemfile +5 -0
  15. data/gemfiles/7.0.gemfile +5 -0
  16. data/gemfiles/main.gemfile +5 -0
  17. data/index.html +34 -0
  18. data/lib/copy_tuner_client/configuration.rb +9 -1
  19. data/lib/copy_tuner_client/copyray_middleware.rb +15 -29
  20. data/lib/copy_tuner_client/engine.rb +6 -31
  21. data/lib/copy_tuner_client/errors.rb +3 -0
  22. data/lib/copy_tuner_client/helper_extension.rb +34 -0
  23. data/lib/copy_tuner_client/i18n_backend.rb +6 -0
  24. data/lib/copy_tuner_client/version.rb +1 -1
  25. data/package.json +14 -15
  26. data/spec/copy_tuner_client/helper_extension_spec.rb +40 -0
  27. data/spec/copy_tuner_client/i18n_backend_spec.rb +2 -0
  28. data/{app/assets/stylesheets → src}/copyray.css +0 -0
  29. data/src/copyray.ts +130 -0
  30. data/src/copytuner_bar.ts +115 -0
  31. data/src/main.ts +58 -0
  32. data/src/specimen.ts +84 -0
  33. data/src/util.ts +38 -0
  34. data/src/vite-env.d.ts +1 -0
  35. data/tsconfig.json +26 -0
  36. data/vite.config.ts +18 -0
  37. data/yarn.lock +2251 -0
  38. metadata +36 -27
  39. data/.eslintrc +0 -4
  40. data/app/assets/javascripts/copyray.js +0 -1069
  41. data/app/views/_copy_tuner_bar.html.erb +0 -6
  42. data/gemfiles/5.2.gemfile +0 -7
  43. data/gemfiles/6.0.gemfile +0 -7
  44. data/package-lock.json +0 -4341
  45. data/rollup.config.js +0 -16
  46. data/src/copyray.js +0 -112
  47. data/src/copytuner_bar.js +0 -96
  48. data/src/main.js +0 -42
  49. data/src/specimen.js +0 -63
  50. data/src/util.js +0 -32
@@ -1,6 +1,6 @@
1
1
  module CopyTunerClient
2
2
  # Client version
3
- VERSION = '0.10.0'.freeze
3
+ VERSION = '0.13.0'.freeze
4
4
 
5
5
  # API version being used to communicate with the server
6
6
  API_VERSION = '2.0'.freeze
data/package.json CHANGED
@@ -5,26 +5,25 @@
5
5
  "author": "SonicGarden",
6
6
  "license": "MIT",
7
7
  "engines": {
8
- "node": "8.x"
8
+ "node": "16.x"
9
9
  },
10
10
  "scripts": {
11
- "build": "rollup -c",
12
- "watch": "rollup -c -w"
11
+ "dev": "vite",
12
+ "build": "tsc && vite build",
13
+ "preview": "vite preview"
13
14
  },
14
15
  "dependencies": {},
15
16
  "devDependencies": {
16
- "babel-core": "^6.26.3",
17
- "babel-plugin-external-helpers": "^6.22.0",
18
- "babel-preset-env": "^1.7.0",
19
- "eslint": "^4.19.1",
20
- "eslint-config-airbnb-base": "^13.0.0",
21
- "eslint-plugin-import": "^2.13.0",
22
- "keycode-js": "^1.0.0",
17
+ "@sonicgarden/eslint-plugin": "^0.4.11",
18
+ "@sonicgarden/prettier-config": "^0.0.1",
19
+ "eslint": "^8.16.0",
23
20
  "lodash.debounce": "^4.0.8",
24
- "rollup": "^0.62.0",
25
- "rollup-plugin-babel": "^3.0.7",
26
- "rollup-plugin-commonjs": "^9.1.3",
27
- "rollup-plugin-node-resolve": "^3.3.0",
28
- "rollup-watch": "^4.3.1"
21
+ "typescript": "^4.7.2",
22
+ "vite": "^2.9.9"
23
+ },
24
+ "prettier": "@sonicgarden/prettier-config",
25
+ "volta": {
26
+ "node": "16.15.0",
27
+ "yarn": "1.22.18"
29
28
  }
30
29
  }
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'copy_tuner_client/helper_extension'
3
+ require 'copy_tuner_client/copyray'
4
+
5
+ describe CopyTunerClient::HelperExtension do
6
+ # rails <= 6.0.x
7
+ module HashArgumentHelper
8
+ def translate(key, options = {})
9
+ "Hello, #{options[:name]}"
10
+ end
11
+ end
12
+
13
+ # rails >= 6.1.x
14
+ module KeywordArgumentsHelper
15
+ def translate(key, **options)
16
+ "Hello, #{options[:name]}"
17
+ end
18
+ end
19
+
20
+ class HashArgumentView
21
+ include HashArgumentHelper
22
+ end
23
+
24
+ class KeywordArgumentsView
25
+ include KeywordArgumentsHelper
26
+ end
27
+
28
+ CopyTunerClient::HelperExtension.hook_translation_helper(HashArgumentHelper, middleware_enabled: true)
29
+ CopyTunerClient::HelperExtension.hook_translation_helper(KeywordArgumentsHelper, middleware_enabled: true)
30
+
31
+ it 'works with hash argument method' do
32
+ view = HashArgumentView.new
33
+ expect(view.translate('some.key', name: 'World')).to eq '<!--COPYRAY some.key-->Hello, World'
34
+ end
35
+
36
+ it 'works with keyword argument method' do
37
+ view = KeywordArgumentsView.new
38
+ expect(view.translate('some.key', name: 'World')).to eq '<!--COPYRAY some.key-->Hello, World'
39
+ end
40
+ end
@@ -39,7 +39,9 @@ describe CopyTunerClient::I18nBackend do
39
39
  end
40
40
 
41
41
  it "finds available locales from locale files and cache" do
42
+ # TODO: ruby@2.7サポート終わったらこっちは不要
42
43
  allow(YAML).to receive(:load_file).and_return({ 'es' => { 'key' => 'value' } })
44
+ allow(YAML).to receive(:unsafe_load_file).and_return({ 'es' => { 'key' => 'value' } })
43
45
  allow(I18n).to receive(:load_path).and_return(["test.yml"])
44
46
 
45
47
  cache['en.key'] = ''
File without changes
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
+ })