copy_tuner_client 1.5.0 → 2.1.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitattributes +2 -2
  3. data/.github/workflows/rspec.yml +1 -1
  4. data/CHANGELOG.md +27 -0
  5. data/CLAUDE.md +6 -4
  6. data/README.md +6 -27
  7. data/UPGRADING.md +201 -0
  8. data/app/assets/javascripts/copytuner.js +162 -250
  9. data/biome.json +39 -0
  10. data/index.html +9 -11
  11. data/lib/copy_tuner_client/cache.rb +0 -2
  12. data/lib/copy_tuner_client/configuration.rb +23 -37
  13. data/lib/copy_tuner_client/copyray/marker.rb +24 -0
  14. data/lib/copy_tuner_client/copyray/rewriter.rb +113 -0
  15. data/lib/copy_tuner_client/copyray.rb +11 -4
  16. data/lib/copy_tuner_client/copyray_middleware.rb +10 -8
  17. data/lib/copy_tuner_client/engine.rb +1 -1
  18. data/lib/copy_tuner_client/helper_extension.rb +0 -3
  19. data/lib/copy_tuner_client/i18n_backend.rb +5 -12
  20. data/lib/copy_tuner_client/version.rb +1 -1
  21. data/mise.toml +3 -0
  22. data/package.json +9 -13
  23. data/pnpm-lock.yaml +600 -0
  24. data/skills/copy-tuner/SKILL.md +37 -6
  25. data/skills/copy-tuner-to-locales-cleanup/SKILL.md +4 -4
  26. data/skills/copy-tuner-to-locales-migrate-prefix/references/local-first-regexp.md +4 -9
  27. data/skills/copy-tuner-to-t-migrate/SKILL.md +131 -0
  28. data/skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb +189 -0
  29. data/spec/copy_tuner_client/cache_spec.rb +2 -10
  30. data/spec/copy_tuner_client/client_spec.rb +1 -0
  31. data/spec/copy_tuner_client/configuration_spec.rb +16 -16
  32. data/spec/copy_tuner_client/copyray/marker_spec.rb +41 -0
  33. data/spec/copy_tuner_client/copyray/rewriter_spec.rb +216 -0
  34. data/spec/copy_tuner_client/copyray_middleware_spec.rb +88 -0
  35. data/spec/copy_tuner_client/copyray_spec.rb +22 -39
  36. data/spec/copy_tuner_client/helper_extension_spec.rb +18 -5
  37. data/spec/copy_tuner_client/i18n_backend_spec.rb +8 -15
  38. data/spec/support/client_spec_helpers.rb +0 -1
  39. data/src/copyray-overlay.ts +125 -0
  40. data/src/copytuner-bar.ts +153 -0
  41. data/src/main.ts +33 -26
  42. data/src/{copyray.css → styles.ts} +112 -136
  43. data/src/util.ts +9 -1
  44. data/tsconfig.json +5 -10
  45. data/vite.config.ts +0 -1
  46. metadata +15 -8
  47. data/.eslintrc.js +0 -12
  48. data/app/assets/stylesheets/copytuner.css +0 -1
  49. data/src/copyray.ts +0 -130
  50. data/src/copytuner_bar.ts +0 -115
  51. data/src/specimen.ts +0 -84
  52. data/yarn.lock +0 -2540
@@ -0,0 +1,153 @@
1
+ import { BAR_STYLES } from './styles'
2
+ import { debounce } from './util'
3
+
4
+ type OpenCallback = (key: string) => void
5
+
6
+ type InitOptions = {
7
+ url: string
8
+ data: Record<string, string>
9
+ keysSkipped: boolean
10
+ onOpen: OpenCallback
11
+ }
12
+
13
+ // 画面下部のツールバー。CopyTuner / Sync ボタン、ページ内翻訳の検索・ログメニューを Shadow DOM 内に描画する。
14
+ export class CopytunerBar extends HTMLElement {
15
+ #onOpen: OpenCallback = () => {}
16
+ #searchBox!: HTMLInputElement
17
+ #logMenu!: HTMLDivElement
18
+
19
+ constructor() {
20
+ super()
21
+ this.attachShadow({ mode: 'open' })
22
+ }
23
+
24
+ // custom element の constructor 内では属性・プロパティを変更できない(createElement が弾く)ため、
25
+ // hidden の初期化は DOM 挿入後に呼ばれる connectedCallback で行う。
26
+ connectedCallback() {
27
+ this.hidden = true
28
+ }
29
+
30
+ // url/data/keysSkipped/onOpen はオブジェクトや関数を含むため属性ではなくメソッドで受け渡す。
31
+ init({ url, data, keysSkipped, onOpen }: InitOptions) {
32
+ this.#onOpen = onOpen
33
+ const shadow = this.shadowRoot as ShadowRoot
34
+
35
+ const style = document.createElement('style')
36
+ style.textContent = BAR_STYLES
37
+ shadow.append(style)
38
+
39
+ // 元々 Rails から出力されていたマークアップに合わせたボタン群。
40
+ // url は設定値だが innerHTML に直接埋めず setAttribute で渡す(XSS 面で安全側に倒す)。
41
+ const copyTunerButton = this.makeButton('CopyTuner', url, '_blank')
42
+ const syncButton = this.makeButton('Sync', '/copytuner', '_blank')
43
+ const openLogButton = this.makeButton('Translations in this page', 'javascript:void(0)')
44
+
45
+ this.#searchBox = document.createElement('input')
46
+ this.#searchBox.type = 'text'
47
+ this.#searchBox.classList.add('search')
48
+ this.#searchBox.placeholder = 'search'
49
+
50
+ shadow.append(copyTunerButton, syncButton, openLogButton, this.#searchBox)
51
+
52
+ this.#logMenu = this.makeLogMenu(data)
53
+ shadow.append(this.#logMenu)
54
+
55
+ // 巨大DOM/Nokogiri例外でキー付与がスキップされた場合は、オーバーレイが使えないので
56
+ // ツールバー(Translations in this page)から編集する旨を案内する。
57
+ if (keysSkipped) {
58
+ this.appendSkippedNotice()
59
+ }
60
+
61
+ openLogButton.addEventListener('click', (event) => {
62
+ event.preventDefault()
63
+ this.toggleLogMenu()
64
+ })
65
+ this.#searchBox.addEventListener('input', debounce(this.onSearch.bind(this), 250))
66
+ }
67
+
68
+ show() {
69
+ this.hidden = false
70
+ this.#searchBox.focus()
71
+ }
72
+
73
+ hide() {
74
+ this.hidden = true
75
+ }
76
+
77
+ private makeButton(label: string, href: string, target?: string): HTMLAnchorElement {
78
+ const button = document.createElement('a')
79
+ button.classList.add('button')
80
+ button.textContent = label
81
+ button.href = href
82
+ if (target) {
83
+ button.target = target
84
+ }
85
+ return button
86
+ }
87
+
88
+ private appendSkippedNotice() {
89
+ const notice = document.createElement('span')
90
+ notice.classList.add('notice')
91
+ notice.textContent = '⚠ This page is too large for the overlay. Use "Translations in this page" to edit.'
92
+ ;(this.shadowRoot as ShadowRoot).append(notice)
93
+ }
94
+
95
+ private showLogMenu() {
96
+ this.#logMenu.hidden = false
97
+ }
98
+
99
+ private toggleLogMenu() {
100
+ this.#logMenu.hidden = !this.#logMenu.hidden
101
+ }
102
+
103
+ private makeLogMenu(data: Record<string, string>): HTMLDivElement {
104
+ const div = document.createElement('div')
105
+ div.classList.add('log-menu')
106
+ div.hidden = true
107
+
108
+ const table = document.createElement('table')
109
+ const tbody = document.createElement('tbody')
110
+
111
+ for (const key of Object.keys(data).sort()) {
112
+ const value = data[key]
113
+ if (value === '') {
114
+ continue
115
+ }
116
+
117
+ const td1 = document.createElement('td')
118
+ td1.textContent = key
119
+ const td2 = document.createElement('td')
120
+ td2.textContent = value
121
+ const tr = document.createElement('tr')
122
+ tr.dataset.key = key
123
+
124
+ tr.addEventListener('click', ({ currentTarget }) => {
125
+ const row = currentTarget as HTMLTableRowElement
126
+ if (row.dataset.key) {
127
+ this.#onOpen(row.dataset.key)
128
+ }
129
+ })
130
+
131
+ tr.append(td1, td2)
132
+ tbody.append(tr)
133
+ }
134
+
135
+ table.append(tbody)
136
+ div.append(table)
137
+
138
+ return div
139
+ }
140
+
141
+ private onSearch() {
142
+ // debounce 経由で遅延実行されると Event.target は null 化されるため、検索ボックスを直接参照する
143
+ const keyword = this.#searchBox.value.trim()
144
+ this.showLogMenu()
145
+
146
+ const rows = [...this.#logMenu.querySelectorAll('tr')]
147
+ for (const row of rows) {
148
+ const isShow =
149
+ keyword === '' || [...row.querySelectorAll('td')].some((td) => (td.textContent ?? '').includes(keyword))
150
+ row.hidden = !isShow
151
+ }
152
+ }
153
+ }
data/src/main.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-console */
2
- import Copyray from './copyray'
2
+ import { CopyrayOverlay } from './copyray-overlay'
3
+ import { CopytunerBar } from './copytuner-bar'
3
4
  import { isMac } from './util'
4
5
 
5
6
  declare global {
@@ -7,44 +8,50 @@ declare global {
7
8
  CopyTuner: {
8
9
  url: string
9
10
  toggle?: () => void
10
- // TODO: type
11
- data: object
11
+ data: Record<string, string>
12
+ // 巨大DOM/Nokogiri例外で data-copyray-key 付与をスキップしたか。
13
+ // true のときオーバーレイは使えないのでツールバーから編集する旨を案内する。
14
+ keysSkipped?: boolean
12
15
  }
13
16
  }
14
17
  }
15
18
 
16
- import './copyray.css'
17
-
18
- // NOTE: 元々railsから出力されいてたマークアップに合わせてひとまず、、
19
- const appendCopyTunerBar = (url: string) => {
20
- const bar = document.createElement('div')
21
- bar.id = 'copy-tuner-bar'
22
- bar.classList.add('copy-tuner-hidden')
23
- bar.innerHTML = `
24
- <a class="copy-tuner-bar-button" target="_blank" href="${url}">CopyTuner</a>
25
- <a href="/copytuner" target="_blank" class="copy-tuner-bar-button">Sync</a>
26
- <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>
27
- <input type="text" class="copy-tuner-bar__search js-copy-tuner-bar-search" placeholder="search">
28
- `
29
- document.body.append(bar)
30
- }
19
+ customElements.define('copytuner-bar', CopytunerBar)
20
+ customElements.define('copyray-overlay', CopyrayOverlay)
31
21
 
32
22
  const start = () => {
33
- const { url, data } = window.CopyTuner
23
+ const { url, data, keysSkipped } = window.CopyTuner
24
+ const onOpen = (key: string) => window.open(`${url}/blurbs/${key}/edit`)
25
+
26
+ const bar = document.createElement('copytuner-bar') as CopytunerBar
27
+ document.body.append(bar)
28
+ bar.init({ url, data, keysSkipped: Boolean(keysSkipped), onOpen })
29
+
30
+ const overlay = document.createElement('copyray-overlay') as CopyrayOverlay
31
+ overlay.onOpen = onOpen
32
+ document.body.append(overlay)
33
+
34
+ const show = () => {
35
+ overlay.show()
36
+ bar.show()
37
+ }
38
+ const hide = () => {
39
+ overlay.hide()
40
+ bar.hide()
41
+ }
42
+ const toggle = () => (overlay.isShowing ? hide() : show())
34
43
 
35
- appendCopyTunerBar(url)
36
- const copyray = new Copyray(url, data)
37
- window.CopyTuner.toggle = () => copyray.toggle()
44
+ overlay.onToggle = toggle
45
+ window.CopyTuner.toggle = toggle
38
46
 
39
47
  document.addEventListener('keydown', (event) => {
40
- // @ts-expect-error TS2339
41
- if (copyray.isShowing && ['Escape', 'Esc'].includes(event.key)) {
42
- copyray.hide()
48
+ if (overlay.isShowing && ['Escape', 'Esc'].includes(event.key)) {
49
+ hide()
43
50
  return
44
51
  }
45
52
 
46
53
  if (((isMac && event.metaKey) || (!isMac && event.ctrlKey)) && event.shiftKey && event.key.toLowerCase() === 'k') {
47
- copyray.toggle()
54
+ toggle()
48
55
  }
49
56
  })
50
57
 
@@ -1,116 +1,9 @@
1
- @charset "UTF-8";
2
-
3
- /* selector for element and children */
4
- #copyray-overlay,
5
- #copyray-overlay *,
6
- #copyray-overlay a:hover,
7
- #copyray-overlay a:visited,
8
- #copyray-overlay a:active {
9
- background: none;
10
- border: none;
11
- bottom: auto;
12
- clear: none;
13
- cursor: default;
14
- float: none;
15
- font-family: Arial, Helvetica, sans-serif;
16
- font-size: medium;
17
- font-style: normal;
18
- font-weight: normal;
19
- height: auto;
20
- left: auto;
21
- letter-spacing: normal;
22
- line-height: normal;
23
- max-height: none;
24
- max-width: none;
25
- min-height: 0;
26
- min-width: 0;
27
- overflow: visible;
28
- position: static;
29
- right: auto;
30
- text-align: left;
31
- text-decoration: none;
32
- text-indent: 0;
33
- text-transform: none;
34
- top: auto;
35
- visibility: visible;
36
- white-space: normal;
37
- width: auto;
38
- z-index: auto;
39
- }
40
-
41
- #copyray-overlay {
42
- position: fixed;
43
- left: 0;
44
- top: 0;
45
- bottom: 0;
46
- right: 0;
47
- background-image: radial-gradient(
48
- ellipse farthest-corner at center,
49
- rgba(0, 0, 0, 0.4) 10%,
50
- rgba(0, 0, 0, 0.8) 100%
51
- );
52
- z-index: 9000;
53
- }
54
-
55
- .copyray-specimen {
56
- position: absolute;
57
- background: rgba(255, 255, 255, 0.15);
58
- outline: 1px solid rgba(255, 255, 255, 0.8);
59
- outline-offset: -1px;
60
- color: #666;
61
- font-family: 'Helvetica Neue', sans-serif;
62
- font-size: 13px;
63
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
64
- }
65
-
66
- .copyray-specimen:hover {
67
- cursor: pointer;
68
- background: rgba(255, 255, 255, 0.4);
69
- }
70
-
71
- .copyray-specimen.Specimen {
72
- outline: 1px solid rgba(255, 50, 50, 0.8);
73
- background: rgba(255, 50, 50, 0.1);
74
- }
75
-
76
- .copyray-specimen.Specimen:hover {
77
- background: rgba(255, 50, 50, 0.4);
78
- }
79
-
80
- .copyray-specimen-handle {
81
- float: left;
82
- background: #fff;
83
- padding: 0 3px;
84
- color: #333;
85
- font-size: 10px;
86
- }
87
-
88
- .copyray-specimen-handle.Specimen {
89
- background: rgba(255, 50, 50, 0.8);
90
- color: #fff;
91
- }
92
-
93
- a.copyray-toggle-button {
94
- display: block;
95
- position: fixed;
96
- left: 0;
97
- bottom: 0;
98
- color: white;
99
- background: black;
100
- padding: 12px 16px;
101
- border-radius: 0 10px 0 0;
102
- opacity: 0;
103
- transition: opacity 0.6s ease-in-out;
104
- z-index: 10000;
105
- font-size: 12px;
106
- cursor: pointer;
107
- }
1
+ // 各 custom element の Shadow root に <style> として注入する CSS。
2
+ // Shadow DOM のスタイル隔離が効くため、旧 copyray.css にあった #copyray-overlay * のグローバルリセットは不要。
108
3
 
109
- a.copyray-toggle-button:hover {
110
- opacity: 1;
111
- }
112
-
113
- #copy-tuner-bar {
4
+ // ツールバー(<copytuner-bar>)のスタイル。:host にバー本体のレイアウトを定義する。
5
+ export const BAR_STYLES = `
6
+ :host {
114
7
  position: fixed;
115
8
  left: 0;
116
9
  right: 0;
@@ -124,44 +17,45 @@ a.copyray-toggle-button:hover {
124
17
  z-index: 2147483647;
125
18
  box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.1), inset 0 2px 6px rgba(0, 0, 0, 0.8);
126
19
  background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3));
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ :host([hidden]) {
24
+ display: none;
127
25
  }
128
26
 
129
- #copy-tuner-bar-log-menu {
27
+ .log-menu {
130
28
  position: fixed;
131
29
  left: 0;
132
30
  right: 0;
133
31
  bottom: 40px;
134
32
  max-height: calc(100vh - 40px);
135
33
  background: #222;
136
- font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
137
34
  color: #fff;
138
- z-index: 2147483647;
139
35
  overflow-y: auto;
140
36
  }
141
37
 
142
- #copy-tuner-bar-log-menu tbody td {
38
+ .log-menu[hidden] {
39
+ display: none;
40
+ }
41
+
42
+ .log-menu tbody td {
143
43
  padding: 2px 8px;
144
44
  }
145
45
 
146
- #copy-tuner-bar-log-menu tbody tr {
46
+ .log-menu tbody tr {
147
47
  cursor: pointer;
148
48
  }
149
49
 
150
- #copy-tuner-bar-log-menu tbody tr:hover {
50
+ .log-menu tbody tr:hover {
151
51
  background: #444;
152
52
  }
153
53
 
154
- #copy-tuner-bar-log-menu tbody a {
155
- color: #fff;
156
- }
157
-
158
- #copy-tuner-bar-log-menu tbody a:hover,
159
- #copy-tuner-bar-log-menu tbody a:focus {
160
- color: #fff;
161
- text-decoration: underline;
54
+ .log-menu tbody tr[hidden] {
55
+ display: none;
162
56
  }
163
57
 
164
- .copy-tuner-bar-button {
58
+ .button {
165
59
  position: relative;
166
60
  display: inline-block;
167
61
  color: #fff;
@@ -178,18 +72,26 @@ a.copyray-toggle-button:hover {
178
72
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.2),
179
73
  inset 0 0 2px rgba(255, 255, 255, 0.2);
180
74
  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.4);
75
+ text-decoration: none;
181
76
  }
182
77
 
183
- .copy-tuner-bar-button:hover,
184
- .copy-tuner-bar-button:focus {
78
+ .button:hover,
79
+ .button:focus {
185
80
  color: #fff;
186
81
  text-decoration: none;
187
82
  background-color: #555;
188
83
  }
189
84
 
190
- input[type='text'].copy-tuner-bar__search {
191
- -webkit-appearance: none;
192
- -moz-appearance: none;
85
+ .notice {
86
+ display: inline-block;
87
+ margin: 8px;
88
+ font-size: 13px;
89
+ line-height: 24px;
90
+ vertical-align: middle;
91
+ color: #ffd24d;
92
+ }
93
+
94
+ .search {
193
95
  appearance: none;
194
96
  border: none;
195
97
  border-radius: 2px;
@@ -204,13 +106,87 @@ input[type='text'].copy-tuner-bar__search {
204
106
  height: auto;
205
107
  font-size: 14px;
206
108
  }
109
+ `
110
+
111
+ // オーバーレイ(<copyray-overlay>)のスタイル。
112
+ // :host はドキュメント原点基準(position: absolute; top/left: 0)にして、
113
+ // 子の specimen を computeBoundingBox のページ座標で absolute 配置できるようにする。
114
+ // 背景の暗転(.backdrop)だけは viewport 固定(fixed)にする。
115
+ export const OVERLAY_STYLES = `
116
+ :host {
117
+ position: absolute;
118
+ top: 0;
119
+ left: 0;
120
+ width: 0;
121
+ height: 0;
122
+ }
207
123
 
208
- .copy-tuner-hidden {
209
- display: none !important;
124
+ :host([hidden]) {
125
+ display: none;
126
+ }
127
+
128
+ .backdrop {
129
+ position: fixed;
130
+ inset: 0;
131
+ background-image: radial-gradient(
132
+ ellipse farthest-corner at center,
133
+ rgba(0, 0, 0, 0.4) 10%,
134
+ rgba(0, 0, 0, 0.8) 100%
135
+ );
136
+ z-index: 9000;
137
+ }
138
+
139
+ .specimen {
140
+ position: absolute;
141
+ background: rgba(255, 50, 50, 0.1);
142
+ outline: 1px solid rgba(255, 50, 50, 0.8);
143
+ outline-offset: -1px;
144
+ color: #666;
145
+ font-family: 'Helvetica Neue', sans-serif;
146
+ font-size: 13px;
147
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
148
+ z-index: 2000000000;
149
+ }
150
+
151
+ .specimen:hover {
152
+ cursor: pointer;
153
+ background: rgba(255, 50, 50, 0.4);
154
+ }
155
+
156
+ .specimen-handle {
157
+ float: left;
158
+ margin: 0 2px 2px 0;
159
+ background: rgba(255, 50, 50, 0.8);
160
+ padding: 0 3px;
161
+ color: #fff;
162
+ font-size: 10px;
163
+ cursor: pointer;
164
+ }
165
+
166
+ .toggle-button {
167
+ display: block;
168
+ position: fixed;
169
+ left: 0;
170
+ bottom: 0;
171
+ color: white;
172
+ background: black;
173
+ padding: 12px 16px;
174
+ border-radius: 0 10px 0 0;
175
+ opacity: 0;
176
+ transition: opacity 0.6s ease-in-out;
177
+ z-index: 10000;
178
+ font-size: 12px;
179
+ cursor: pointer;
180
+ text-decoration: none;
181
+ }
182
+
183
+ .toggle-button:hover {
184
+ opacity: 1;
210
185
  }
211
186
 
212
187
  @media screen and (max-width: 480px) {
213
- .hidden-on-mobile {
214
- display: none !important;
188
+ .toggle-button {
189
+ display: none;
215
190
  }
216
191
  }
192
+ `
data/src/util.ts CHANGED
@@ -35,4 +35,12 @@ const computeBoundingBox = (element) => {
35
35
  }
36
36
  }
37
37
 
38
- export { isMac, isVisible, getOffset, computeBoundingBox }
38
+ const debounce = <A extends unknown[]>(fn: (...args: A) => void, wait: number) => {
39
+ let timer: ReturnType<typeof setTimeout> | undefined
40
+ return (...args: A) => {
41
+ clearTimeout(timer)
42
+ timer = setTimeout(() => fn(...args), wait)
43
+ }
44
+ }
45
+
46
+ export { computeBoundingBox, debounce, getOffset, isMac, isVisible }
data/tsconfig.json CHANGED
@@ -3,11 +3,9 @@
3
3
  "target": "ESNext",
4
4
  "useDefineForClassFields": true,
5
5
  "module": "ESNext",
6
- "lib": [
7
- "ESNext",
8
- "DOM"
9
- ],
10
- "moduleResolution": "Node",
6
+ "lib": ["ESNext", "DOM"],
7
+ "moduleResolution": "bundler",
8
+ "verbatimModuleSyntax": true,
11
9
  "strict": true,
12
10
  "sourceMap": true,
13
11
  "resolveJsonModule": true,
@@ -17,10 +15,7 @@
17
15
  "noUnusedLocals": true,
18
16
  "noUnusedParameters": true,
19
17
  "noImplicitReturns": true,
20
- "skipLibCheck": true,
18
+ "skipLibCheck": true
21
19
  },
22
- "include": [
23
- "client",
24
- "src",
25
- ]
20
+ "include": ["src"]
26
21
  }
data/vite.config.ts CHANGED
@@ -11,7 +11,6 @@ export default defineConfig({
11
11
  rollupOptions: {
12
12
  output: {
13
13
  entryFileNames: `javascripts/copytuner.js`,
14
- assetFileNames: `stylesheets/copytuner.[ext]`,
15
14
  },
16
15
  },
17
16
  },
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: copy_tuner_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SonicGarden
@@ -183,7 +183,6 @@ extensions: []
183
183
  extra_rdoc_files: []
184
184
  files:
185
185
  - ".babelrc"
186
- - ".eslintrc.js"
187
186
  - ".gitattributes"
188
187
  - ".github/dependabot.yml"
189
188
  - ".github/workflows/rspec.yml"
@@ -198,8 +197,9 @@ files:
198
197
  - LICENSE.txt
199
198
  - README.md
200
199
  - Rakefile
200
+ - UPGRADING.md
201
201
  - app/assets/javascripts/copytuner.js
202
- - app/assets/stylesheets/copytuner.css
202
+ - biome.json
203
203
  - copy_tuner_client.gemspec
204
204
  - gemfiles/8.0.gemfile
205
205
  - gemfiles/8.1.gemfile
@@ -210,6 +210,8 @@ files:
210
210
  - lib/copy_tuner_client/client.rb
211
211
  - lib/copy_tuner_client/configuration.rb
212
212
  - lib/copy_tuner_client/copyray.rb
213
+ - lib/copy_tuner_client/copyray/marker.rb
214
+ - lib/copy_tuner_client/copyray/rewriter.rb
213
215
  - lib/copy_tuner_client/copyray_middleware.rb
214
216
  - lib/copy_tuner_client/dotted_hash.rb
215
217
  - lib/copy_tuner_client/engine.rb
@@ -227,7 +229,9 @@ files:
227
229
  - lib/copy_tuner_client/translation_log.rb
228
230
  - lib/copy_tuner_client/version.rb
229
231
  - lib/tasks/copy_tuner_client_tasks.rake
232
+ - mise.toml
230
233
  - package.json
234
+ - pnpm-lock.yaml
231
235
  - skills/copy-tuner-to-locales-cleanup/SKILL.md
232
236
  - skills/copy-tuner-to-locales-cleanup/references/example-touchpoints.md
233
237
  - skills/copy-tuner-to-locales-cleanup/references/verification-final.md
@@ -237,10 +241,15 @@ files:
237
241
  - skills/copy-tuner-to-locales-migrate-prefix/references/local-first-regexp.md
238
242
  - skills/copy-tuner-to-locales-migrate-prefix/references/verification-per-prefix.md
239
243
  - skills/copy-tuner-to-locales-migrate-prefix/scripts/migrate_prefix.rb
244
+ - skills/copy-tuner-to-t-migrate/SKILL.md
245
+ - skills/copy-tuner-to-t-migrate/scripts/migrate_tt.rb
240
246
  - skills/copy-tuner/SKILL.md
241
247
  - spec/copy_tuner_client/cache_spec.rb
242
248
  - spec/copy_tuner_client/client_spec.rb
243
249
  - spec/copy_tuner_client/configuration_spec.rb
250
+ - spec/copy_tuner_client/copyray/marker_spec.rb
251
+ - spec/copy_tuner_client/copyray/rewriter_spec.rb
252
+ - spec/copy_tuner_client/copyray_middleware_spec.rb
244
253
  - spec/copy_tuner_client/copyray_spec.rb
245
254
  - spec/copy_tuner_client/dotted_hash_spec.rb
246
255
  - spec/copy_tuner_client/helper_extension_spec.rb
@@ -262,18 +271,16 @@ files:
262
271
  - spec/support/fake_unicorn.rb
263
272
  - spec/support/middleware_stack.rb
264
273
  - spec/support/writing_cache.rb
265
- - src/copyray.css
266
- - src/copyray.ts
267
- - src/copytuner_bar.ts
274
+ - src/copyray-overlay.ts
275
+ - src/copytuner-bar.ts
268
276
  - src/main.ts
269
- - src/specimen.ts
277
+ - src/styles.ts
270
278
  - src/util.ts
271
279
  - src/vite-env.d.ts
272
280
  - tsconfig.json
273
281
  - ui/views/copytuner/index.html.erb
274
282
  - ui/views/layouts/copytuner_default.html.erb
275
283
  - vite.config.ts
276
- - yarn.lock
277
284
  homepage: https://github.com/SonicGarden/copy-tuner-ruby-client
278
285
  licenses: []
279
286
  metadata:
data/.eslintrc.js DELETED
@@ -1,12 +0,0 @@
1
- module.exports = {
2
- plugins: ['@sonicgarden'],
3
- extends: [
4
- 'plugin:@sonicgarden/browser',
5
- 'plugin:@sonicgarden/recommended',
6
- 'plugin:@sonicgarden/typescript',
7
- 'plugin:@sonicgarden/prettier',
8
- ],
9
- settings: {
10
- 'import/internal-regex': '^@/',
11
- },
12
- }
@@ -1 +0,0 @@
1
- @charset "UTF-8";#copyray-overlay,#copyray-overlay *,#copyray-overlay a:hover,#copyray-overlay a:visited,#copyray-overlay a:active{background:none;border:none;bottom:auto;clear:none;cursor:default;float:none;font-family:Arial,Helvetica,sans-serif;font-size:medium;font-style:normal;font-weight:400;height:auto;left:auto;letter-spacing:normal;line-height:normal;max-height:none;max-width:none;min-height:0;min-width:0;overflow:visible;position:static;right:auto;text-align:left;text-decoration:none;text-indent:0;text-transform:none;top:auto;visibility:visible;white-space:normal;width:auto;z-index:auto}#copyray-overlay{position:fixed;left:0;top:0;bottom:0;right:0;background-image:radial-gradient(ellipse farthest-corner at center,rgba(0,0,0,.4) 10%,rgba(0,0,0,.8) 100%);z-index:9000}.copyray-specimen{position:absolute;background:rgba(255,255,255,.15);outline:1px solid rgba(255,255,255,.8);outline-offset:-1px;color:#666;font-family:Helvetica Neue,sans-serif;font-size:13px;box-shadow:0 1px 3px #000000b3}.copyray-specimen:hover{cursor:pointer;background:rgba(255,255,255,.4)}.copyray-specimen.Specimen{outline:1px solid rgba(255,50,50,.8);background:rgba(255,50,50,.1)}.copyray-specimen.Specimen:hover{background:rgba(255,50,50,.4)}.copyray-specimen-handle{float:left;background:#fff;padding:0 3px;color:#333;font-size:10px}.copyray-specimen-handle.Specimen{background:rgba(255,50,50,.8);color:#fff}a.copyray-toggle-button{display:block;position:fixed;left:0;bottom:0;color:#fff;background:black;padding:12px 16px;border-radius:0 10px 0 0;opacity:0;transition:opacity .6s ease-in-out;z-index:10000;font-size:12px;cursor:pointer}a.copyray-toggle-button:hover{opacity:1}#copy-tuner-bar{position:fixed;left:0;right:0;bottom:0;height:40px;padding:0 8px;background:#222;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:200;color:#fff;z-index:2147483647;box-shadow:0 -1px #ffffff1a,inset 0 2px 6px #000c;background-image:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.3))}#copy-tuner-bar-log-menu{position:fixed;left:0;right:0;bottom:40px;max-height:calc(100vh - 40px);background:#222;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;color:#fff;z-index:2147483647;overflow-y:auto}#copy-tuner-bar-log-menu tbody td{padding:2px 8px}#copy-tuner-bar-log-menu tbody tr{cursor:pointer}#copy-tuner-bar-log-menu tbody tr:hover{background:#444}#copy-tuner-bar-log-menu tbody a{color:#fff}#copy-tuner-bar-log-menu tbody a:hover,#copy-tuner-bar-log-menu tbody a:focus{color:#fff;text-decoration:underline}.copy-tuner-bar-button{position:relative;display:inline-block;color:#fff;margin:8px 1px;height:24px;line-height:24px;padding:0 8px;font-size:14px;cursor:pointer;vertical-align:middle;background-color:#444;background-image:linear-gradient(rgba(0,0,0,0),rgba(0,0,0,.2));border-radius:2px;box-shadow:1px 1px 1px #00000080,inset 0 1px #fff3,inset 0 0 2px #fff3;text-shadow:0 -1px 0 rgba(0,0,0,.4)}.copy-tuner-bar-button:hover,.copy-tuner-bar-button:focus{color:#fff;text-decoration:none;background-color:#555}input[type=text].copy-tuner-bar__search{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;border-radius:2px;background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,0));box-shadow:inset 0 1px #0003,inset 0 0 2px #0003;padding:2px 8px;margin:0;line-height:20px;vertical-align:middle;color:#000;width:auto;height:auto;font-size:14px}.copy-tuner-hidden{display:none!important}@media screen and (max-width: 480px){.hidden-on-mobile{display:none!important}}