i18n_feedback 0.1.0 → 0.2.1

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: db55035ed25835495f5e4840e9b3e6663a7e745d52600803207e8e38b7f92dcf
4
- data.tar.gz: 82cd102f8766cbab2de69c3c69fe56b6fefd8e27efb4f1e897b2556e9ef76510
3
+ metadata.gz: fb6db4ee347586bcb037eaeedb37564d969e9d1395db2a8b6ca450710d943796
4
+ data.tar.gz: 4d9aeaa1a8ece352719e3019ae08049b7ad9103dc1ce61447e317149269451d3
5
5
  SHA512:
6
- metadata.gz: 886de0912affd3a3d0e9ed7a20bca10605b6d2da21cb8446028049e0568cefe74ea2c57ee28f083cb29010220eabb3790cd0eec0e0af478ded46f45761a66ef2
7
- data.tar.gz: 94e65e65440bf08316bf2b55eacdc8af421dfad8072ac62b1172b93bb8b93ed4974f3b31d2051ba11bb9c4e708299a7a97a4a7bfa60d0fe4606312a888439844
6
+ metadata.gz: 48c4b5ac039436c560d5e667d7ed36f1474a784b1330e8efaea55376e306eecd0865d6ddb81fa8001cc20d1c790bfdb0668b8435322f3a8f13aca95b02a5a99e
7
+ data.tar.gz: cde3e82b0c34baf896c2d54c887bf581f48c8d00e65a74fe34d11e531b5264ed85c2267d69ab94c1715bd2abf5ba9ad102c22b746ec6f6d36721a61bf4b202d4
data/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.1]
6
+
7
+ - Keep the suggest pill and active-mode highlighting working across Turbo Drive
8
+ navigations. Turbo replaces `<body>` on each visit, which removed the pill; the
9
+ widget now re-runs its per-page setup on `turbo:load` instead of only on the
10
+ initial load, so it no longer requires a hard reload.
11
+
12
+ ## [0.2.0]
13
+
14
+ - Stamp the injected widget scripts with the request's Content-Security-Policy
15
+ nonce when one is present, so the tool works under a nonce-based CSP (including
16
+ `strict-dynamic`). No-op for apps without a CSP nonce.
17
+
5
18
  ## [0.1.0]
6
19
 
7
20
  - Initial release.
data/README.md CHANGED
@@ -35,11 +35,11 @@ It is meant for development and staging, never production.
35
35
 
36
36
  ## Installation
37
37
 
38
- Add the gem (from source until it is published on RubyGems):
38
+ Add the gem:
39
39
 
40
40
  ```ruby
41
41
  # Gemfile
42
- gem "i18n_feedback", github: "yshmarov/i18n-feedback"
42
+ gem "i18n_feedback"
43
43
  ```
44
44
 
45
45
  ```bash
@@ -42,18 +42,27 @@
42
42
  }
43
43
 
44
44
  ready(function () {
45
- injectStyles();
46
- if (config.showPill !== false) buildPill();
45
+ // Document-level listeners survive Turbo navigations, so register them once.
47
46
  document.addEventListener("keydown", handleKeydown);
47
+ if (config.active) document.addEventListener("click", handleClick, true);
48
+
49
+ // Everything else lives in <body>, which Turbo replaces on every visit —
50
+ // taking the pill and the active-mode highlighting with it. Re-run the
51
+ // per-page setup on each visit so the widget keeps working without a hard
52
+ // reload. render() also runs now for the initial (or non-Turbo) load.
53
+ render();
54
+ document.addEventListener("turbo:load", render);
55
+ document.addEventListener("turbo:frame-load", strip);
56
+ });
48
57
 
58
+ function render() {
59
+ injectStyles();
60
+ if (config.showPill !== false) buildPill();
49
61
  if (config.active) {
50
62
  document.documentElement.classList.add("i18nf-active");
51
- document.addEventListener("click", handleClick, true);
52
- document.addEventListener("turbo:load", strip);
53
- document.addEventListener("turbo:frame-load", strip);
54
63
  strip();
55
64
  }
56
- });
65
+ }
57
66
 
58
67
  // --- suggest-mode toggle --------------------------------------------------
59
68
 
@@ -123,6 +132,9 @@
123
132
  // --- pill -----------------------------------------------------------------
124
133
 
125
134
  function buildPill() {
135
+ var existing = document.querySelector(".i18nf-pill");
136
+ if (existing) existing.remove();
137
+
126
138
  var pill = document.createElement("button");
127
139
  pill.type = "button";
128
140
  pill.className = "i18nf-pill" + (config.active ? " i18nf-pill-on" : "");
@@ -11,7 +11,8 @@ module I18nFeedback
11
11
  Widget.snippet(
12
12
  endpoint: I18nFeedback.config.suggestions_endpoint,
13
13
  locale: I18n.locale,
14
- active: I18nFeedback::Marking.enabled?
14
+ active: I18nFeedback::Marking.enabled?,
15
+ nonce: (content_security_policy_nonce if respond_to?(:content_security_policy_nonce))
15
16
  ).html_safe
16
17
  end
17
18
  end
@@ -30,7 +30,7 @@ module I18nFeedback
30
30
  headers = persist_choice(headers, override) if available && !override.nil?
31
31
 
32
32
  if available && I18nFeedback.config.auto_inject && html?(headers) && !request.xhr?
33
- status, headers, body = inject(status, headers, body, marking)
33
+ status, headers, body = inject(status, headers, body, marking, csp_nonce(env))
34
34
  end
35
35
 
36
36
  [status, headers, body]
@@ -44,6 +44,17 @@ module I18nFeedback
44
44
  !request.cookies[COOKIE].to_s.empty?
45
45
  end
46
46
 
47
+ # The request's CSP nonce, so the injected scripts run under a nonce-based
48
+ # Content-Security-Policy. Reads the value Rails memoizes on the env, which is
49
+ # the same nonce the CSP header uses. nil when the app sets no nonce.
50
+ def csp_nonce(env)
51
+ return nil unless defined?(ActionDispatch::Request)
52
+
53
+ ActionDispatch::Request.new(env).content_security_policy_nonce
54
+ rescue StandardError
55
+ nil
56
+ end
57
+
47
58
  def toggle_override(request)
48
59
  param = I18nFeedback.config.toggle_param
49
60
  return nil unless request.params.key?(param)
@@ -80,7 +91,7 @@ module I18nFeedback
80
91
  headers['Content-Type'] || headers['content-type']
81
92
  end
82
93
 
83
- def inject(status, headers, body, marking)
94
+ def inject(status, headers, body, marking, nonce)
84
95
  html = +''
85
96
  body.each { |part| html << part.to_s }
86
97
  body.close if body.respond_to?(:close)
@@ -88,7 +99,8 @@ module I18nFeedback
88
99
  snippet = Widget.snippet(
89
100
  endpoint: I18nFeedback.config.suggestions_endpoint,
90
101
  locale: I18n.locale,
91
- active: marking
102
+ active: marking,
103
+ nonce: nonce
92
104
  )
93
105
  html = html.include?('</body>') ? html.sub('</body>', "#{snippet}</body>") : html + snippet
94
106
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18nFeedback
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
@@ -17,8 +17,10 @@ module I18nFeedback
17
17
  end
18
18
 
19
19
  # The two <script> tags to place before </body>: one carrying the runtime
20
- # config, one carrying the widget itself.
21
- def snippet(endpoint:, locale:, active:)
20
+ # config, one carrying the widget itself. `nonce:` stamps both tags so the
21
+ # widget runs under a nonce-based Content-Security-Policy (e.g. one using
22
+ # 'strict-dynamic'); pass nil when the app has no CSP nonce.
23
+ def snippet(endpoint:, locale:, active:, nonce: nil)
22
24
  config = {
23
25
  endpoint: endpoint,
24
26
  locale: locale.to_s,
@@ -26,9 +28,10 @@ module I18nFeedback
26
28
  showPill: I18nFeedback.config.show_pill ? true : false,
27
29
  pillLabel: I18nFeedback.config.pill_label
28
30
  }
31
+ nonce_attr = nonce ? %( nonce="#{nonce}") : ''
29
32
 
30
- %(<script data-i18n-feedback>window.__i18nFeedback=#{config.to_json};</script>) +
31
- %(<script data-i18n-feedback-widget>#{javascript}</script>)
33
+ %(<script data-i18n-feedback#{nonce_attr}>window.__i18nFeedback=#{config.to_json};</script>) +
34
+ %(<script data-i18n-feedback-widget#{nonce_attr}>#{javascript}</script>)
32
35
  end
33
36
  end
34
37
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n_feedback
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Shmarov
@@ -76,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
78
  requirements: []
79
- rubygems_version: 4.0.10
79
+ rubygems_version: 4.0.6
80
80
  specification_version: 4
81
81
  summary: 'In-context i18n proofreading for Rails: click any translated string and
82
82
  suggest a fix.'