i18n_proofreading 0.9.1 → 0.9.3
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 +4 -4
- data/CHANGELOG.md +24 -0
- data/app/controllers/i18n_proofreading/widgets_controller.rb +26 -0
- data/config/routes.rb +4 -0
- data/lib/i18n_proofreading/version.rb +1 -1
- data/lib/i18n_proofreading/widget.js +16 -0
- data/lib/i18n_proofreading/widget.rb +19 -6
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c009ca04acda8614a83cfa656bc807a01765319ba2b7da5dcf80fb76f4980c94
|
|
4
|
+
data.tar.gz: dbad6b7f76f62b577ffe8dc05448ad0b35d67f621f8a820884bc8a48527ec007
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8f1064cb1de9690b57823662ca4f479da538b6bd973880fd23e6781af99f723b5803322f13d76e66f74312dfd2287445ebff20b4c96880572951ad5db5345575
|
|
7
|
+
data.tar.gz: 4e7dda45dc4809cfe4393987be38f273e2dbf98f04162bc9d4af175c3b150bb5e9d00cabd9bb0d6d0c468e0f1356315b4c2f735e7d7019a4042f496c86d663e7
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.3]
|
|
6
|
+
|
|
7
|
+
- Fix the widget going dead after a Turbo Drive visit under a nonce-based
|
|
8
|
+
Content-Security-Policy. The widget code was inlined as a nonce'd `<script>`;
|
|
9
|
+
Turbo body swaps re-run body scripts against the *original* page's CSP
|
|
10
|
+
header, which refuses the freshly minted inline nonce — so after any soft
|
|
11
|
+
visit the widget never booted until a hard reload. The code is now served as
|
|
12
|
+
a **same-origin, content-fingerprinted script**
|
|
13
|
+
(`GET {mount_path}/widget.js?v=<md5>`), which `script-src 'self'` (or the
|
|
14
|
+
nonce the tag still carries) covers on every visit. The fingerprinted URL is
|
|
15
|
+
immutable and cached for a year — new code means a new URL — while any other
|
|
16
|
+
URL only ETag-revalidates. The inline JSON config block is unchanged: it is
|
|
17
|
+
data, not code, and needs no nonce.
|
|
18
|
+
|
|
19
|
+
## [0.9.2]
|
|
20
|
+
|
|
21
|
+
- Make the suggestion popover **full-screen on mobile** (≤ 480px wide): edge to
|
|
22
|
+
edge with no rounded corners, instead of a floating dialog — no bottom sheet
|
|
23
|
+
and no animation. The widget's inputs render at 16px on small screens so iOS
|
|
24
|
+
Safari no longer zooms the page when one gains focus, the action row pads
|
|
25
|
+
itself past the iPhone home indicator (`env(safe-area-inset-bottom)`), and the
|
|
26
|
+
panel's scrolling is contained (`overscroll-behavior`) so reaching its end
|
|
27
|
+
never scrolls the page behind it. CSS only; desktop is unchanged.
|
|
28
|
+
|
|
5
29
|
## [0.9.1]
|
|
6
30
|
|
|
7
31
|
- Add Minitest **system tests** that drive the widget in headless Chrome
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module I18nProofreading
|
|
4
|
+
# Serves the widget JavaScript as a plain same-origin script. Same-origin
|
|
5
|
+
# matters: under a `script-src 'self'` (or nonce-based) CSP, an external
|
|
6
|
+
# script from the app's own host is always allowed — including when Turbo
|
|
7
|
+
# Drive swaps the <body> and re-runs body scripts under the *original*
|
|
8
|
+
# page's CSP header, where a freshly minted inline nonce would be refused.
|
|
9
|
+
class WidgetsController < ApplicationController
|
|
10
|
+
# The script is static and carries no user data; without this, Rails'
|
|
11
|
+
# cross-origin JavaScript guard refuses to serve it to a plain
|
|
12
|
+
# <script src> request.
|
|
13
|
+
skip_forgery_protection
|
|
14
|
+
|
|
15
|
+
# The script URL carries a content fingerprint (?v=<md5>), so stale code
|
|
16
|
+
# is structurally impossible: new code means a new URL. The canonical
|
|
17
|
+
# fingerprinted URL is immutable and gets long-lived caching; anything
|
|
18
|
+
# else only ETag-revalidates.
|
|
19
|
+
def show
|
|
20
|
+
expires_in 1.year, public: true if params[:v] == Widget.fingerprint
|
|
21
|
+
return unless stale?(etag: [I18nProofreading::VERSION, Widget.fingerprint])
|
|
22
|
+
|
|
23
|
+
render plain: Widget.javascript, content_type: 'text/javascript'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/config/routes.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
I18nProofreading::Engine.routes.draw do
|
|
4
|
+
# The widget code, served same-origin so it runs under a `'self'`/nonce-based
|
|
5
|
+
# CSP even across Turbo body swaps (see WidgetsController).
|
|
6
|
+
get 'widget.js', to: 'widgets#show', as: :widget
|
|
7
|
+
|
|
4
8
|
# index is the read-only admin dashboard; create + the `context` collection
|
|
5
9
|
# route are the widget's public API (see SuggestionsController). There is
|
|
6
10
|
# deliberately no update/destroy — the tool never mutates suggestions from the
|
|
@@ -394,6 +394,7 @@
|
|
|
394
394
|
" align-items: center; justify-content: center; padding: 16px;",
|
|
395
395
|
" background: rgba(0,0,0,.45); cursor: default; }",
|
|
396
396
|
".i18np-panel { width: 28rem; max-width: 100%; max-height: 90vh; overflow: auto;",
|
|
397
|
+
" overscroll-behavior: contain;",
|
|
397
398
|
" background: #fff; color: #111; border-radius: 12px; padding: 20px;",
|
|
398
399
|
" box-shadow: 0 20px 60px rgba(0,0,0,.35); font: 14px/1.5 system-ui, sans-serif; }",
|
|
399
400
|
".i18np-panel > * + * { margin-top: 14px; }",
|
|
@@ -437,6 +438,21 @@
|
|
|
437
438
|
" .i18np-error { color: #f87171; }",
|
|
438
439
|
" .i18np-btn-ghost { color: #f4f4f5; }",
|
|
439
440
|
"}",
|
|
441
|
+
|
|
442
|
+
// Full-screen popover on small screens — no bottom sheet, no animation.
|
|
443
|
+
// Selectors are at least as specific as the base rules and this block
|
|
444
|
+
// comes last, so the mobile values win.
|
|
445
|
+
"@media (max-width: 480px) {",
|
|
446
|
+
" .i18np-overlay { padding: 0; }",
|
|
447
|
+
" .i18np-overlay .i18np-panel { position: fixed; top: 0; right: 0; bottom: 0; left: 0;",
|
|
448
|
+
" width: 100%; height: 100dvh; max-height: 100dvh; border-radius: 0; margin: 0; }",
|
|
449
|
+
// 16px inputs stop iOS Safari from zooming the page on focus.
|
|
450
|
+
" .i18np-panel input.i18np-input, .i18np-panel textarea.i18np-input,",
|
|
451
|
+
" .i18np-panel select { font-size: 16px; }",
|
|
452
|
+
// Keep the action row clear of the iPhone home indicator (its own
|
|
453
|
+
// padding-bottom is 0; the panel padding sits outside the row).
|
|
454
|
+
" .i18np-panel .i18np-actions { padding-bottom: calc(0px + env(safe-area-inset-bottom)); }",
|
|
455
|
+
"}",
|
|
440
456
|
].join("\n");
|
|
441
457
|
document.head.appendChild(style);
|
|
442
458
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'digest'
|
|
4
5
|
|
|
5
6
|
module I18nProofreading
|
|
6
7
|
# Serves the self-contained browser widget. The JavaScript is plain ES (no
|
|
7
8
|
# framework, no build step) and styles itself inline, so it drops into any Rails
|
|
8
|
-
# app regardless of its CSS or JS setup. It is
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# app regardless of its CSS or JS setup. It is served by the engine's own
|
|
10
|
+
# controller (see WidgetsController) rather than through an asset pipeline, to
|
|
11
|
+
# avoid any dependency on the host's asset setup.
|
|
11
12
|
module Widget
|
|
12
13
|
# Lives under lib/ (not app/assets/), so a host that runs an asset
|
|
13
14
|
# pipeline never ingests it: Rails auto-registers every engine's
|
|
@@ -24,6 +25,13 @@ module I18nProofreading
|
|
|
24
25
|
@javascript ||= File.read(SOURCE)
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# Content fingerprint for the cache-busting script URL: a changed file is
|
|
29
|
+
# a changed URL, so no browser can ever run stale widget code — Safari
|
|
30
|
+
# has been caught ignoring must-revalidate on same-URL scripts.
|
|
31
|
+
def fingerprint
|
|
32
|
+
@fingerprint ||= Digest::MD5.hexdigest(javascript)
|
|
33
|
+
end
|
|
34
|
+
|
|
27
35
|
# The two <script> tags to place before </body>.
|
|
28
36
|
#
|
|
29
37
|
# The config rides in a `type="application/json"` block: it is *data*, not
|
|
@@ -32,8 +40,12 @@ module I18nProofreading
|
|
|
32
40
|
# widget can re-read the *current* page's config on every `turbo:load`
|
|
33
41
|
# instead of being stuck with whatever the last full load evaluated.
|
|
34
42
|
#
|
|
35
|
-
#
|
|
36
|
-
# nonce-based
|
|
43
|
+
# The code is a same-origin `src` script served by the engine — NOT
|
|
44
|
+
# inlined. Under a nonce-based CSP, Turbo Drive body swaps re-run body
|
|
45
|
+
# scripts against the *original* page's CSP header, so a fresh inline
|
|
46
|
+
# nonce gets refused; a same-origin src is covered by `'self'` on every
|
|
47
|
+
# visit. `nonce:` is still stamped for hosts whose script-src has no
|
|
48
|
+
# 'self'; pass nil when the app has no nonce.
|
|
37
49
|
def snippet(endpoint:, locale:, active:, nonce: nil)
|
|
38
50
|
config = {
|
|
39
51
|
endpoint: endpoint,
|
|
@@ -48,9 +60,10 @@ module I18nProofreading
|
|
|
48
60
|
# Escape "</" so a value can't close the <script> block early.
|
|
49
61
|
json = config.to_json.gsub('</', '<\/')
|
|
50
62
|
nonce_attr = nonce ? %( nonce="#{nonce}") : ''
|
|
63
|
+
src = "#{I18nProofreading.config.mount_path.chomp('/')}/widget.js?v=#{fingerprint}"
|
|
51
64
|
|
|
52
65
|
%(<script type="application/json" data-i18n-proofreading-config>#{json}</script>) +
|
|
53
|
-
%(<script data-i18n-proofreading-widget
|
|
66
|
+
%(<script src="#{src}" defer#{nonce_attr} data-i18n-proofreading-widget></script>)
|
|
54
67
|
end
|
|
55
68
|
|
|
56
69
|
private
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: i18n_proofreading
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -40,6 +40,7 @@ files:
|
|
|
40
40
|
- Rakefile
|
|
41
41
|
- app/controllers/i18n_proofreading/application_controller.rb
|
|
42
42
|
- app/controllers/i18n_proofreading/suggestions_controller.rb
|
|
43
|
+
- app/controllers/i18n_proofreading/widgets_controller.rb
|
|
43
44
|
- app/helpers/i18n_proofreading/tag_helper.rb
|
|
44
45
|
- app/models/i18n_proofreading/application_record.rb
|
|
45
46
|
- app/models/i18n_proofreading/suggestion.rb
|