copy_tuner_client 2.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cabf3cddda7ee14a30e13b8a1bd1d03e4948118651db8be26f97f7f3068adafe
4
- data.tar.gz: 3a33b80e2c676e8bd16e6e1f390b4f9676d5f14b3e55e36deb172571afd537c7
3
+ metadata.gz: a3c816e1f12e5715861194284864c00ab5d18428d3c56b6a3140cfe9db9cd360
4
+ data.tar.gz: d9a8cd76caf12860c0a981a840655254a5aab64665745d16f4524ded77821771
5
5
  SHA512:
6
- metadata.gz: 921e075005f137ba95632548e254391bc74a915a415f29a0b09170d31b558d01a67037f0c60a58210f844e28a6c42483e281b792de37ef190c2237dd5a5086dc
7
- data.tar.gz: 4ca2e0fe3fbced96692f05ca438dc50d7bb284617951525e6f8ec17aa2e6b0d610031d16a81217066ca8ff925800a2e00675e1ded84e3fc32c6ef6533f6209df
6
+ metadata.gz: 1538da853220ee8f4bfc4942023e1afcfd55dc4f719a89b3c965d8719acb57ec5bd418d8821e72eee8adbb5b59ca2f23b462d8894b9239734ff68a30b8a0a074
7
+ data.tar.gz: '091eee7aae91c30f88fb294716bd9a139d55d7ac90d982602e14305d2c43203360dc81b98092e17dd931dcce1885940af27a47f0031e374e9724eaf4898934d5'
data/CLAUDE.md CHANGED
@@ -6,9 +6,12 @@ CopyTuner の Ruby クライアント gem。Rails アプリの I18n を CopyTune
6
6
  - テスト: `bundle exec rspec`(単一ファイル: `bundle exec rspec spec/copy_tuner_client/cache_spec.rb`)
7
7
  - 特定の Rails バージョンでテスト: `BUNDLE_GEMFILE=gemfiles/8.0.gemfile bundle exec rspec`
8
8
  - Lint: `bundle exec rubocop`(`sgcop` を継承)
9
- - フロントエンドビルド: `yarn build`(開発: `yarn dev`)
9
+ - フロントエンドビルド: `pnpm build`(開発: `pnpm dev`)
10
+ - フロントエンド Lint/Format: `pnpm check`(Biome)
10
11
  - gem リリース: `bundle exec rake build|install|release`
11
12
 
13
+ Node.js / pnpm は mise(mise.toml)で管理。フロントエンドのツールチェーンは pnpm + Biome + Vite + TypeScript。
14
+
12
15
  ## アーキテクチャ
13
16
  `Configuration#apply`(lib/copy_tuner_client/configuration.rb)が全コンポーネントを組み立てる起点:
14
17
  - `Client` — CopyTuner サーバ / S3 との HTTP 通信
data/README.md CHANGED
@@ -84,9 +84,13 @@ Development
84
84
  `src`以下を編集してください。
85
85
  `app/assets/*`を直接編集したらダメよ!
86
86
 
87
+ Node.js と pnpm は [mise](https://mise.jdx.dev/) で管理しています(`mise install` でセットアップ)。
88
+
87
89
  ```
88
- $ yarn dev # 開発時
89
- $ yarn build # ビルド
90
+ $ pnpm install # 依存インストール
91
+ $ pnpm dev # 開発時
92
+ $ pnpm build # ビルド
93
+ $ pnpm check # Lint + Format(Biome)
90
94
  ```
91
95
 
92
96
 
@@ -1,257 +1,164 @@
1
- var y = typeof globalThis < "u" ? globalThis : typeof window < "u" ? window : typeof global < "u" ? global : typeof self < "u" ? self : {}, _ = "Expected a function", C = 0 / 0, $ = "[object Symbol]", N = /^\s+|\s+$/g, H = /^[-+]0x[0-9a-f]+$/i, q = /^0b[01]+$/i, D = /^0o[0-7]+$/i, R = parseInt, U = typeof y == "object" && y && y.Object === Object && y, W = typeof self == "object" && self && self.Object === Object && self, K = U || W || Function("return this")(), F = Object.prototype, P = F.toString, X = Math.max, z = Math.min, v = function() {
2
- return K.Date.now();
1
+ //#region src/styles.ts
2
+ var e = navigator.platform.toUpperCase().includes("MAC"), t = (e) => !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length > 0), n = (e) => {
3
+ let t = e.getBoundingClientRect();
4
+ return {
5
+ top: t.top + (window.pageYOffset - document.documentElement.clientTop),
6
+ left: t.left + (window.pageXOffset - document.documentElement.clientLeft)
7
+ };
8
+ }, r = (e) => {
9
+ if (!t(e)) return null;
10
+ let r = n(e);
11
+ return r.right = r.left + e.offsetWidth, r.bottom = r.top + e.offsetHeight, {
12
+ left: r.left,
13
+ top: r.top,
14
+ width: r.right - r.left,
15
+ height: r.bottom - r.top
16
+ };
17
+ }, i = (e, t) => {
18
+ let n;
19
+ return (...r) => {
20
+ clearTimeout(n), n = setTimeout(() => e(...r), t);
21
+ };
22
+ }, a = () => Array.from(document.querySelectorAll("[data-copyray-key]")).map((e) => ({
23
+ keys: (e.getAttribute("data-copyray-key") ?? "").split(",").filter(Boolean),
24
+ element: e
25
+ })), o = class extends HTMLElement {
26
+ #e = () => {};
27
+ #t = () => {};
28
+ #n;
29
+ #r;
30
+ #i;
31
+ constructor() {
32
+ super();
33
+ let e = this.attachShadow({ mode: "open" }), t = document.createElement("style");
34
+ t.textContent = "\n:host {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.backdrop {\n position: fixed;\n inset: 0;\n background-image: radial-gradient(\n ellipse farthest-corner at center,\n rgba(0, 0, 0, 0.4) 10%,\n rgba(0, 0, 0, 0.8) 100%\n );\n z-index: 9000;\n}\n\n.specimen {\n position: absolute;\n background: rgba(255, 50, 50, 0.1);\n outline: 1px solid rgba(255, 50, 50, 0.8);\n outline-offset: -1px;\n color: #666;\n font-family: 'Helvetica Neue', sans-serif;\n font-size: 13px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);\n z-index: 2000000000;\n}\n\n.specimen:hover {\n cursor: pointer;\n background: rgba(255, 50, 50, 0.4);\n}\n\n.specimen-handle {\n float: left;\n margin: 0 2px 2px 0;\n background: rgba(255, 50, 50, 0.8);\n padding: 0 3px;\n color: #fff;\n font-size: 10px;\n cursor: pointer;\n}\n\n.toggle-button {\n display: block;\n position: fixed;\n left: 0;\n bottom: 0;\n color: white;\n background: black;\n padding: 12px 16px;\n border-radius: 0 10px 0 0;\n opacity: 0;\n transition: opacity 0.6s ease-in-out;\n z-index: 10000;\n font-size: 12px;\n cursor: pointer;\n text-decoration: none;\n}\n\n.toggle-button:hover {\n opacity: 1;\n}\n\n@media screen and (max-width: 480px) {\n .toggle-button {\n display: none;\n }\n}\n", e.append(t), this.#n = document.createElement("div"), this.#n.classList.add("backdrop"), this.#n.addEventListener("click", () => this.hide()), this.#r = document.createElement("div"), this.#r.classList.add("specimens"), this.#i = document.createElement("a"), this.#i.classList.add("toggle-button"), this.#i.textContent = "Open CopyTuner", this.#i.addEventListener("click", () => this.#t()), e.append(this.#n, this.#r, this.#i), this.hide();
35
+ }
36
+ set onOpen(e) {
37
+ this.#e = e;
38
+ }
39
+ set onToggle(e) {
40
+ this.#t = e;
41
+ }
42
+ get isShowing() {
43
+ return !this.#n.hidden;
44
+ }
45
+ show() {
46
+ this.reset(), this.#n.hidden = !1;
47
+ for (let { element: e, keys: t } of a()) {
48
+ let n = this.makeBox(e, t);
49
+ n && this.#r.append(n);
50
+ }
51
+ }
52
+ hide() {
53
+ this.reset(), this.#n.hidden = !0;
54
+ }
55
+ reset() {
56
+ this.#r.replaceChildren();
57
+ }
58
+ makeBox(e, t) {
59
+ let n = r(e);
60
+ if (n === null) return null;
61
+ let i = document.createElement("div");
62
+ i.classList.add("specimen"), i.style.left = `${n.left}px`, i.style.top = `${n.top}px`, i.style.width = `${n.width}px`, i.style.height = `${n.height}px`;
63
+ let { position: a, top: o, left: s } = getComputedStyle(e);
64
+ a === "fixed" && (i.style.position = "fixed", i.style.top = o, i.style.left = s), i.addEventListener("click", () => this.#e(t[0]));
65
+ for (let e of t) i.append(this.makeLabel(e));
66
+ return i;
67
+ }
68
+ makeLabel(e) {
69
+ let t = document.createElement("div");
70
+ return t.classList.add("specimen-handle"), t.textContent = e, t.addEventListener("click", (t) => {
71
+ t.stopPropagation(), this.#e(e);
72
+ }), t;
73
+ }
74
+ }, s = class extends HTMLElement {
75
+ #e = () => {};
76
+ #t;
77
+ #n;
78
+ constructor() {
79
+ super(), this.attachShadow({ mode: "open" });
80
+ }
81
+ connectedCallback() {
82
+ this.hidden = !0;
83
+ }
84
+ init({ url: e, data: t, keysSkipped: n, onOpen: r }) {
85
+ this.#e = r;
86
+ let a = this.shadowRoot, o = document.createElement("style");
87
+ o.textContent = "\n:host {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n height: 40px;\n padding: 0 8px;\n background: #222;\n font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n font-weight: 200;\n color: #fff;\n z-index: 2147483647;\n box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.1), inset 0 2px 6px rgba(0, 0, 0, 0.8);\n background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.3));\n box-sizing: border-box;\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.log-menu {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 40px;\n max-height: calc(100vh - 40px);\n background: #222;\n color: #fff;\n overflow-y: auto;\n}\n\n.log-menu[hidden] {\n display: none;\n}\n\n.log-menu tbody td {\n padding: 2px 8px;\n}\n\n.log-menu tbody tr {\n cursor: pointer;\n}\n\n.log-menu tbody tr:hover {\n background: #444;\n}\n\n.log-menu tbody tr[hidden] {\n display: none;\n}\n\n.button {\n position: relative;\n display: inline-block;\n color: #fff;\n margin: 8px 1px;\n height: 24px;\n line-height: 24px;\n padding: 0 8px;\n font-size: 14px;\n cursor: pointer;\n vertical-align: middle;\n background-color: #444;\n background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.2));\n border-radius: 2px;\n box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.2),\n inset 0 0 2px rgba(255, 255, 255, 0.2);\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.4);\n text-decoration: none;\n}\n\n.button:hover,\n.button:focus {\n color: #fff;\n text-decoration: none;\n background-color: #555;\n}\n\n.notice {\n display: inline-block;\n margin: 8px;\n font-size: 13px;\n line-height: 24px;\n vertical-align: middle;\n color: #ffd24d;\n}\n\n.search {\n appearance: none;\n border: none;\n border-radius: 2px;\n background-image: linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0));\n box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.2), inset 0 0 2px rgba(0, 0, 0, 0.2);\n padding: 2px 8px;\n margin: 0;\n line-height: 20px;\n vertical-align: middle;\n color: black;\n width: auto;\n height: auto;\n font-size: 14px;\n}\n", a.append(o);
88
+ let s = this.makeButton("CopyTuner", e, "_blank"), c = this.makeButton("Sync", "/copytuner", "_blank"), l = this.makeButton("Translations in this page", "javascript:void(0)");
89
+ this.#t = document.createElement("input"), this.#t.type = "text", this.#t.classList.add("search"), this.#t.placeholder = "search", a.append(s, c, l, this.#t), this.#n = this.makeLogMenu(t), a.append(this.#n), n && this.appendSkippedNotice(), l.addEventListener("click", (e) => {
90
+ e.preventDefault(), this.toggleLogMenu();
91
+ }), this.#t.addEventListener("input", i(this.onSearch.bind(this), 250));
92
+ }
93
+ show() {
94
+ this.hidden = !1, this.#t.focus();
95
+ }
96
+ hide() {
97
+ this.hidden = !0;
98
+ }
99
+ makeButton(e, t, n) {
100
+ let r = document.createElement("a");
101
+ return r.classList.add("button"), r.textContent = e, r.href = t, n && (r.target = n), r;
102
+ }
103
+ appendSkippedNotice() {
104
+ let e = document.createElement("span");
105
+ e.classList.add("notice"), e.textContent = "⚠ This page is too large for the overlay. Use \"Translations in this page\" to edit.", this.shadowRoot.append(e);
106
+ }
107
+ showLogMenu() {
108
+ this.#n.hidden = !1;
109
+ }
110
+ toggleLogMenu() {
111
+ this.#n.hidden = !this.#n.hidden;
112
+ }
113
+ makeLogMenu(e) {
114
+ let t = document.createElement("div");
115
+ t.classList.add("log-menu"), t.hidden = !0;
116
+ let n = document.createElement("table"), r = document.createElement("tbody");
117
+ for (let t of Object.keys(e).sort()) {
118
+ let n = e[t];
119
+ if (n === "") continue;
120
+ let i = document.createElement("td");
121
+ i.textContent = t;
122
+ let a = document.createElement("td");
123
+ a.textContent = n;
124
+ let o = document.createElement("tr");
125
+ o.dataset.key = t, o.addEventListener("click", ({ currentTarget: e }) => {
126
+ let t = e;
127
+ t.dataset.key && this.#e(t.dataset.key);
128
+ }), o.append(i, a), r.append(o);
129
+ }
130
+ return n.append(r), t.append(n), t;
131
+ }
132
+ onSearch() {
133
+ let e = this.#t.value.trim();
134
+ this.showLogMenu();
135
+ let t = [...this.#n.querySelectorAll("tr")];
136
+ for (let n of t) n.hidden = !(e === "" || [...n.querySelectorAll("td")].some((t) => (t.textContent ?? "").includes(e)));
137
+ }
3
138
  };
4
- function G(t, e, n) {
5
- var o, s, l, r, a, c, u = 0, E = !1, h = !1, b = !0;
6
- if (typeof t != "function")
7
- throw new TypeError(_);
8
- e = B(e) || 0, x(n) && (E = !!n.leading, h = "maxWait" in n, l = h ? X(B(n.maxWait) || 0, e) : l, b = "trailing" in n ? !!n.trailing : b);
9
- function g(i) {
10
- var d = o, f = s;
11
- return o = s = void 0, u = i, r = t.apply(f, d), r;
12
- }
13
- function j(i) {
14
- return u = i, a = setTimeout(m, e), E ? g(i) : r;
15
- }
16
- function M(i) {
17
- var d = i - c, f = i - u, T = e - d;
18
- return h ? z(T, l - f) : T;
19
- }
20
- function w(i) {
21
- var d = i - c, f = i - u;
22
- return c === void 0 || d >= e || d < 0 || h && f >= l;
23
- }
24
- function m() {
25
- var i = v();
26
- if (w(i))
27
- return S(i);
28
- a = setTimeout(m, M(i));
29
- }
30
- function S(i) {
31
- return a = void 0, b && o ? g(i) : (o = s = void 0, r);
32
- }
33
- function I() {
34
- a !== void 0 && clearTimeout(a), u = 0, o = c = s = a = void 0;
35
- }
36
- function A() {
37
- return a === void 0 ? r : S(v());
38
- }
39
- function k() {
40
- var i = v(), d = w(i);
41
- if (o = arguments, s = this, c = i, d) {
42
- if (a === void 0)
43
- return j(c);
44
- if (h)
45
- return a = setTimeout(m, e), g(c);
46
- }
47
- return a === void 0 && (a = setTimeout(m, e)), r;
48
- }
49
- return k.cancel = I, k.flush = A, k;
50
- }
51
- function x(t) {
52
- var e = typeof t;
53
- return !!t && (e == "object" || e == "function");
54
- }
55
- function V(t) {
56
- return !!t && typeof t == "object";
57
- }
58
- function Y(t) {
59
- return typeof t == "symbol" || V(t) && P.call(t) == $;
60
- }
61
- function B(t) {
62
- if (typeof t == "number")
63
- return t;
64
- if (Y(t))
65
- return C;
66
- if (x(t)) {
67
- var e = typeof t.valueOf == "function" ? t.valueOf() : t;
68
- t = x(e) ? e + "" : e;
69
- }
70
- if (typeof t != "string")
71
- return t === 0 ? t : +t;
72
- t = t.replace(N, "");
73
- var n = q.test(t);
74
- return n || D.test(t) ? R(t.slice(2), n ? 2 : 8) : H.test(t) ? C : +t;
75
- }
76
- var Z = G;
77
- const p = "copy-tuner-hidden";
78
- class J {
79
- // @ts-expect-error TS7006
80
- constructor(e, n, o, s = !1) {
81
- this.element = e, this.data = n, this.callback = o, this.searchBoxElement = e.querySelector(".js-copy-tuner-bar-search"), this.logMenuElement = this.makeLogMenu(), this.element.append(this.logMenuElement), s && this.appendSkippedNotice(), this.addHandler();
82
- }
83
- appendSkippedNotice() {
84
- const e = document.createElement("span");
85
- e.classList.add("copy-tuner-bar__notice"), e.textContent = '⚠ This page is too large for the overlay. Use "Translations in this page" to edit.', this.element.append(e);
86
- }
87
- addHandler() {
88
- this.element.querySelector(".js-copy-tuner-bar-open-log").addEventListener("click", (n) => {
89
- n.preventDefault(), this.toggleLogMenu();
90
- }), this.searchBoxElement.addEventListener("input", Z(this.onKeyup.bind(this), 250));
91
- }
92
- show() {
93
- this.element.classList.remove(p), this.searchBoxElement.focus();
94
- }
95
- hide() {
96
- this.element.classList.add(p);
97
- }
98
- showLogMenu() {
99
- this.logMenuElement.classList.remove(p);
100
- }
101
- toggleLogMenu() {
102
- this.logMenuElement.classList.toggle(p);
103
- }
104
- makeLogMenu() {
105
- const e = document.createElement("div");
106
- e.setAttribute("id", "copy-tuner-bar-log-menu"), e.classList.add(p);
107
- const n = document.createElement("table"), o = document.createElement("tbody");
108
- o.classList.remove("is-not-initialized");
109
- for (const s of Object.keys(this.data).sort()) {
110
- const l = this.data[s];
111
- if (l === "")
112
- continue;
113
- const r = document.createElement("td");
114
- r.textContent = s;
115
- const a = document.createElement("td");
116
- a.textContent = l;
117
- const c = document.createElement("tr");
118
- c.classList.add("copy-tuner-bar-log-menu__row"), c.dataset.key = s, c.addEventListener("click", ({ currentTarget: u }) => {
119
- this.callback(u.dataset.key);
120
- }), c.append(r), c.append(a), o.append(c);
121
- }
122
- return n.append(o), e.append(n), e;
123
- }
124
- // @ts-expect-error TS7031
125
- onKeyup({ target: e }) {
126
- const n = e.value.trim();
127
- this.showLogMenu();
128
- const o = [...this.logMenuElement.querySelectorAll("tr")];
129
- for (const s of o) {
130
- const l = n === "" || [...s.querySelectorAll("td")].some((r) => r.textContent.includes(n));
131
- s.classList.toggle(p, !l);
132
- }
133
- }
134
- }
135
- const L = navigator.platform.toUpperCase().includes("MAC"), Q = (t) => !!(t.offsetWidth || t.offsetHeight || t.getClientRects().length > 0), ee = (t) => {
136
- const e = t.getBoundingClientRect();
137
- return {
138
- top: e.top + (window.pageYOffset - document.documentElement.clientTop),
139
- left: e.left + (window.pageXOffset - document.documentElement.clientLeft)
140
- };
141
- }, te = (t) => {
142
- if (!Q(t))
143
- return null;
144
- const e = ee(t);
145
- return e.right = e.left + t.offsetWidth, e.bottom = e.top + t.offsetHeight, {
146
- left: e.left,
147
- top: e.top,
148
- // @ts-expect-error TS2339
149
- width: e.right - e.left,
150
- // @ts-expect-error TS2339
151
- height: e.bottom - e.top
152
- };
153
- }, ne = 2e9;
154
- class oe {
155
- // @ts-expect-error TS7006
156
- constructor(e, n, o) {
157
- this.element = e, this.keys = n, this.callback = o;
158
- }
159
- show() {
160
- this.box = this.makeBox(), this.box !== null && (this.box.addEventListener("click", () => {
161
- this.callback(this.keys[0]);
162
- }), document.body.append(this.box));
163
- }
164
- remove() {
165
- this.box && (this.box.remove(), this.box = null);
166
- }
167
- makeBox() {
168
- const e = document.createElement("div");
169
- e.classList.add("copyray-specimen"), e.classList.add("Specimen");
170
- const n = te(this.element);
171
- if (n === null)
172
- return null;
173
- for (const r of Object.keys(n)) {
174
- const a = n[r];
175
- e.style[r] = `${a}px`;
176
- }
177
- e.style.zIndex = ne;
178
- const { position: o, top: s, left: l } = getComputedStyle(this.element);
179
- o === "fixed" && (this.box.style.position = "fixed", this.box.style.top = `${s}px`, this.box.style.left = `${l}px`);
180
- for (const r of this.keys)
181
- e.append(this.makeLabel(r));
182
- return e;
183
- }
184
- // @ts-expect-error TS7006
185
- makeLabel(e) {
186
- const n = document.createElement("div");
187
- return n.classList.add("copyray-specimen-handle"), n.classList.add("Specimen"), n.textContent = e, n.addEventListener("click", (o) => {
188
- o.stopPropagation(), this.callback(e);
189
- }), n;
190
- }
191
- }
192
- const se = () => Array.from(document.querySelectorAll("[data-copyray-key]")).map((t) => ({
193
- // 1 要素に複数キーがカンマ区切りで入りうる(同一テキストノードに複数訳文が連結された場合)
194
- keys: (t.getAttribute("data-copyray-key") ?? "").split(",").filter(Boolean),
195
- element: t
196
- }));
197
- class ie {
198
- // @ts-expect-error TS7006
199
- constructor(e, n, o = !1) {
200
- this.baseUrl = e, this.data = n, this.isShowing = !1, this.specimens = [], this.overlay = this.makeOverlay(), this.toggleButton = this.makeToggleButton(), this.boundOpen = this.open.bind(this), this.copyTunerBar = new J(document.querySelector("#copy-tuner-bar"), this.data, this.boundOpen, o);
201
- }
202
- show() {
203
- this.reset(), document.body.append(this.overlay), this.makeSpecimens();
204
- for (const e of this.specimens)
205
- e.show();
206
- this.copyTunerBar.show(), this.isShowing = !0;
207
- }
208
- hide() {
209
- this.overlay.remove(), this.reset(), this.copyTunerBar.hide(), this.isShowing = !1;
210
- }
211
- toggle() {
212
- this.isShowing ? this.hide() : this.show();
213
- }
214
- // @ts-expect-error TS7006
215
- open(e) {
216
- window.open(`${this.baseUrl}/blurbs/${e}/edit`);
217
- }
218
- makeSpecimens() {
219
- for (const { element: e, keys: n } of se())
220
- this.specimens.push(new oe(e, n, this.boundOpen));
221
- }
222
- makeToggleButton() {
223
- const e = document.createElement("a");
224
- return e.addEventListener("click", () => {
225
- this.show();
226
- }), e.classList.add("copyray-toggle-button"), e.classList.add("hidden-on-mobile"), e.textContent = "Open CopyTuner", document.body.append(e), e;
227
- }
228
- makeOverlay() {
229
- const e = document.createElement("div");
230
- return e.setAttribute("id", "copyray-overlay"), e.addEventListener("click", () => this.hide()), e;
231
- }
232
- reset() {
233
- for (const e of this.specimens)
234
- e.remove();
235
- }
236
- }
237
- const re = (t) => {
238
- const e = document.createElement("div");
239
- e.id = "copy-tuner-bar", e.classList.add("copy-tuner-hidden"), e.innerHTML = `
240
- <a class="copy-tuner-bar-button" target="_blank" href="${t}">CopyTuner</a>
241
- <a href="/copytuner" target="_blank" class="copy-tuner-bar-button">Sync</a>
242
- <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>
243
- <input type="text" class="copy-tuner-bar__search js-copy-tuner-bar-search" placeholder="search">
244
- `, document.body.append(e);
245
- }, O = () => {
246
- const { url: t, data: e, keysSkipped: n } = window.CopyTuner;
247
- re(t);
248
- const o = new ie(t, e, !!n);
249
- window.CopyTuner.toggle = () => o.toggle(), document.addEventListener("keydown", (s) => {
250
- if (o.isShowing && ["Escape", "Esc"].includes(s.key)) {
251
- o.hide();
252
- return;
253
- }
254
- (L && s.metaKey || !L && s.ctrlKey) && s.shiftKey && s.key.toLowerCase() === "k" && o.toggle();
255
- }), console && console.log(`Ready to Copyray. Press ${L ? "cmd+shift+k" : "ctrl+shift+k"} to scan your UI.`);
139
+ customElements.define("copytuner-bar", s), customElements.define("copyray-overlay", o);
140
+ var c = () => {
141
+ let { url: t, data: n, keysSkipped: r } = window.CopyTuner, i = (e) => window.open(`${t}/blurbs/${e}/edit`), a = document.createElement("copytuner-bar");
142
+ document.body.append(a), a.init({
143
+ url: t,
144
+ data: n,
145
+ keysSkipped: !!r,
146
+ onOpen: i
147
+ });
148
+ let o = document.createElement("copyray-overlay");
149
+ o.onOpen = i, document.body.append(o);
150
+ let s = () => {
151
+ o.show(), a.show();
152
+ }, c = () => {
153
+ o.hide(), a.hide();
154
+ }, l = () => o.isShowing ? c() : s();
155
+ o.onToggle = l, window.CopyTuner.toggle = l, document.addEventListener("keydown", (t) => {
156
+ if (o.isShowing && ["Escape", "Esc"].includes(t.key)) {
157
+ c();
158
+ return;
159
+ }
160
+ (e && t.metaKey || !e && t.ctrlKey) && t.shiftKey && t.key.toLowerCase() === "k" && l();
161
+ }), console && console.log(`Ready to Copyray. Press ${e ? "cmd+shift+k" : "ctrl+shift+k"} to scan your UI.`);
256
162
  };
257
- document.readyState === "complete" || document.readyState !== "loading" ? O() : document.addEventListener("DOMContentLoaded", () => O());
163
+ document.readyState === "complete" || document.readyState !== "loading" ? c() : document.addEventListener("DOMContentLoaded", () => c());
164
+ //#endregion
data/biome.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.5.0/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false,
10
+ "includes": ["src/**", "*.ts", "*.json"]
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "space",
15
+ "indentWidth": 2,
16
+ "lineWidth": 120
17
+ },
18
+ "linter": {
19
+ "enabled": true,
20
+ "rules": {
21
+ "preset": "recommended"
22
+ }
23
+ },
24
+ "javascript": {
25
+ "formatter": {
26
+ "quoteStyle": "single",
27
+ "semicolons": "asNeeded",
28
+ "trailingCommas": "all"
29
+ }
30
+ },
31
+ "assist": {
32
+ "enabled": true,
33
+ "actions": {
34
+ "source": {
35
+ "organizeImports": "on"
36
+ }
37
+ }
38
+ }
39
+ }
data/index.html CHANGED
@@ -5,27 +5,25 @@
5
5
  <meta charset="UTF-8" />
6
6
  <link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
- <title>Vite App</title>
8
+ <title>CopyTuner Dev</title>
9
9
  </head>
10
10
 
11
11
  <body>
12
12
  <div id="app">
13
13
  <ul>
14
- <li>
15
- <!--COPYRAY projects.index.locales-->言語
16
- </li>
17
- <li>
18
- <!--COPYRAY projects.index.blurbs-->翻訳キー
19
- </li>
20
- <li>
21
- <!--COPYRAY projects.index.members-->メンバー数
22
- </li>
14
+ <li data-copyray-key="projects.index.locales">言語</li>
15
+ <li data-copyray-key="projects.index.blurbs">翻訳キー</li>
16
+ <li data-copyray-key="projects.index.members">メンバー数</li>
23
17
  </ul>
24
18
  </div>
25
19
  <script>
26
20
  window.CopyTuner = {
27
21
  url: 'https://copy-tuner.herokuapp.com/projects/xxx',
28
- data: {},
22
+ data: {
23
+ 'projects.index.locales': '言語',
24
+ 'projects.index.blurbs': '翻訳キー',
25
+ 'projects.index.members': 'メンバー数',
26
+ },
29
27
  }
30
28
  </script>
31
29
  <script type="module" src="/src/main.ts"></script>
@@ -18,7 +18,6 @@ module CopyTunerClient
18
18
  # NOTE: skipped は data-copyray-key を付与できなかったこと(巨大DOM/Nokogiri例外)を表す。
19
19
  # JS にこれを伝え、オーバーレイ非対応である旨をツールバーで案内させる。
20
20
  body, skipped = CopyTunerClient::Copyray::Rewriter.rewrite(body)
21
- body = append_css(body, csp_nonce)
22
21
  body = append_js(body, csp_nonce, skipped: skipped)
23
22
  content_length = body.bytesize.to_s
24
23
  headers['Content-Length'] = content_length
@@ -39,11 +38,6 @@ module CopyTunerClient
39
38
  ActionController::Base.helpers
40
39
  end
41
40
 
42
- def append_css(html, csp_nonce)
43
- css_tag = helpers.stylesheet_link_tag 'copytuner', media: :all, nonce: csp_nonce
44
- append_to_html_body(html, css_tag)
45
- end
46
-
47
41
  def append_js(html, csp_nonce, skipped: false)
48
42
  json =
49
43
  if CopyTunerClient::TranslationLog.initialized?
@@ -25,7 +25,7 @@ module CopyTunerClient
25
25
  end
26
26
 
27
27
  initializer "copy_tuner.assets.precompile", group: :all do |app|
28
- app.config.assets.precompile += ['copytuner.js', 'copytuner.css']
28
+ app.config.assets.precompile += ['copytuner.js']
29
29
  end
30
30
  end
31
31
  end
@@ -1,6 +1,6 @@
1
1
  module CopyTunerClient
2
2
  # Client version
3
- VERSION = '2.0.0'.freeze
3
+ VERSION = '2.1.0'.freeze
4
4
 
5
5
  # API version being used to communicate with the server
6
6
  API_VERSION = '2.0'.freeze
data/mise.toml ADDED
@@ -0,0 +1,3 @@
1
+ [tools]
2
+ node = "24"
3
+ pnpm = "11.9.0"
data/package.json CHANGED
@@ -4,26 +4,22 @@
4
4
  "repository": "https://github.com/SonicGarden/copy-tuner-ruby-client",
5
5
  "author": "SonicGarden",
6
6
  "license": "MIT",
7
+ "packageManager": "pnpm@11.9.0",
7
8
  "engines": {
8
- "node": "16.x"
9
+ "node": ">=24"
9
10
  },
10
11
  "scripts": {
11
12
  "dev": "vite",
12
13
  "build": "tsc && vite build",
13
- "preview": "vite preview"
14
+ "preview": "vite preview",
15
+ "lint": "biome lint .",
16
+ "format": "biome format --write .",
17
+ "check": "biome check --write ."
14
18
  },
15
19
  "dependencies": {},
16
20
  "devDependencies": {
17
- "@sonicgarden/eslint-plugin": "^0.5.2",
18
- "@sonicgarden/prettier-config": "^0.0.1",
19
- "eslint": "^8.16.0",
20
- "lodash.debounce": "^4.0.8",
21
- "typescript": "^5.0.2",
22
- "vite": "^4.2.1"
23
- },
24
- "prettier": "@sonicgarden/prettier-config",
25
- "volta": {
26
- "node": "16.15.0",
27
- "yarn": "1.22.18"
21
+ "@biomejs/biome": "2.5.0",
22
+ "typescript": "^6.0.0",
23
+ "vite": "^8.0.0"
28
24
  }
29
25
  }