i18n_proofreading 0.10.3 → 0.10.5
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 +14 -0
- data/app/controllers/i18n_proofreading/application_controller.rb +4 -0
- data/app/controllers/i18n_proofreading/suggestions_controller.rb +11 -2
- data/app/views/i18n_proofreading/suggestions/_suggestion_panel.html.erb +42 -0
- data/app/views/i18n_proofreading/suggestions/index.html.erb +89 -67
- data/app/views/i18n_proofreading/suggestions/show.html.erb +8 -0
- data/app/views/layouts/i18n_proofreading/application.html.erb +1 -1
- data/config/routes.rb +1 -1
- data/lib/generators/i18n_proofreading/install/templates/initializer.rb +5 -0
- data/lib/i18n_proofreading/configuration.rb +5 -0
- data/lib/i18n_proofreading/dashboard.css +81 -0
- data/lib/i18n_proofreading/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 92f93f3b80d87b86e0ec3b0ce33dc7c89cdd5ba9c2e7fc8b40bcb26b92a1a224
|
|
4
|
+
data.tar.gz: f994d6b60277c364a23eb299e344ab0af2f1fcdaa567966d07bd97352ac7f2fe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d99a83e338ef612aa9a519eb115cb55f55a35978c514171070848e4dca9ea470dbb9055d29f04e5b950107611181a8719d8eff9d976272d7f4c67f8694186ff3
|
|
7
|
+
data.tar.gz: 3a296e3305dcce5f55b22d01843f5a885a34d805f30f4dcfa794a08138948f06125a438d550c03e142409d1a3e60e6ebf32259dd470f930b42c945b0e892340b
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.10.5]
|
|
6
|
+
|
|
7
|
+
- Added `config.admin_layout`, letting host apps render the proofreading
|
|
8
|
+
dashboard inside their own admin layout while keeping the standalone gem
|
|
9
|
+
layout as the default.
|
|
10
|
+
|
|
11
|
+
## [0.10.4]
|
|
12
|
+
|
|
13
|
+
- Redesigned the proofreading admin dashboard into a two-column review layout
|
|
14
|
+
with status filters, a selected-suggestion pane, and refreshed before/after
|
|
15
|
+
screenshots.
|
|
16
|
+
- Added independent suggestion show pages while keeping long detail content
|
|
17
|
+
scrollable on narrow screens.
|
|
18
|
+
|
|
5
19
|
## [0.10.3]
|
|
6
20
|
|
|
7
21
|
- Added `mount_i18n_proofreading at: "/i18n_proofreading"` as the install-time
|
|
@@ -6,6 +6,10 @@ module I18nProofreading
|
|
|
6
6
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
+
def i18n_proofreading_admin_layout
|
|
10
|
+
I18nProofreading.config.admin_layout
|
|
11
|
+
end
|
|
12
|
+
|
|
9
13
|
# Gate for the widget's public API (submit a suggestion, read prior context).
|
|
10
14
|
# The client can set the cookie, but it can never reach the endpoints unless
|
|
11
15
|
# the app itself says the tool is available for this request.
|
|
@@ -6,9 +6,10 @@ module I18nProofreading
|
|
|
6
6
|
|
|
7
7
|
# Public widget API is available-gated; the read-only dashboard is admin-gated.
|
|
8
8
|
before_action :require_available, only: %i[context create]
|
|
9
|
-
before_action :require_admin, only:
|
|
9
|
+
before_action :require_admin, only: %i[index show]
|
|
10
|
+
before_action :set_suggestion, only: :show
|
|
10
11
|
|
|
11
|
-
layout
|
|
12
|
+
layout :i18n_proofreading_admin_layout, only: %i[index show]
|
|
12
13
|
|
|
13
14
|
# Throttle the public submission endpoint per IP so one user or bot can't
|
|
14
15
|
# flood the table. Uses the rate limiter built into Rails 7.2+ (backed by
|
|
@@ -37,8 +38,12 @@ module I18nProofreading
|
|
|
37
38
|
rows = scope.newest_first.offset((@page - 1) * PER_PAGE).limit(PER_PAGE + 1).to_a
|
|
38
39
|
@more = rows.size > PER_PAGE
|
|
39
40
|
@suggestions = rows.first(PER_PAGE)
|
|
41
|
+
|
|
42
|
+
@selected_suggestion = Suggestion.find_by(id: params[:suggestion_id]) if params[:suggestion_id].present?
|
|
40
43
|
end
|
|
41
44
|
|
|
45
|
+
def show; end
|
|
46
|
+
|
|
42
47
|
# --- widget API (public) -------------------------------------------------
|
|
43
48
|
|
|
44
49
|
# Pending suggestions for one key/locale, shown as read-only context when the
|
|
@@ -67,6 +72,10 @@ module I18nProofreading
|
|
|
67
72
|
|
|
68
73
|
private
|
|
69
74
|
|
|
75
|
+
def set_suggestion
|
|
76
|
+
@suggestion = Suggestion.find(params[:id])
|
|
77
|
+
end
|
|
78
|
+
|
|
70
79
|
def attribute_author(suggestion)
|
|
71
80
|
author = current_author
|
|
72
81
|
return unless author
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<section class="suggestion-panel">
|
|
2
|
+
<div class="panel-head">
|
|
3
|
+
<h1>
|
|
4
|
+
<code class="key"><%= suggestion.translation_key %></code>
|
|
5
|
+
<span class="badge locale"><%= suggestion.locale %></span>
|
|
6
|
+
<span class="badge status-<%= suggestion.status %>">
|
|
7
|
+
<%= t("i18n_proofreading.statuses.#{suggestion.status}", default: suggestion.status.humanize) %>
|
|
8
|
+
</span>
|
|
9
|
+
<span class="case-id">#<%= suggestion.id %></span>
|
|
10
|
+
</h1>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="detail-scroll">
|
|
14
|
+
<article class="card pad">
|
|
15
|
+
<dl class="meta-list">
|
|
16
|
+
<% if suggestion.author_label.present? || suggestion.author_id.present? %>
|
|
17
|
+
<dt><%= t('i18n_proofreading.dashboard.from', default: 'From') %></dt>
|
|
18
|
+
<dd><%= suggestion.author_label.presence || "##{suggestion.author_id}" %></dd>
|
|
19
|
+
<% end %>
|
|
20
|
+
<% if suggestion.page_url.present? %>
|
|
21
|
+
<dt><%= t('i18n_proofreading.dashboard.page', default: 'Page') %></dt>
|
|
22
|
+
<dd><%= link_to suggestion.page_url, suggestion.page_url, target: '_blank', rel: 'noopener' %></dd>
|
|
23
|
+
<% end %>
|
|
24
|
+
<dt><%= t('i18n_proofreading.dashboard.received', default: 'Received') %></dt>
|
|
25
|
+
<dd><%= suggestion.created_at %></dd>
|
|
26
|
+
</dl>
|
|
27
|
+
</article>
|
|
28
|
+
|
|
29
|
+
<article class="card pad">
|
|
30
|
+
<dl class="diff">
|
|
31
|
+
<dt><%= t('i18n_proofreading.dashboard.current', default: 'Current') %></dt>
|
|
32
|
+
<dd><span class="old" dir="auto"><%= suggestion.old_value.presence || '—' %></span></dd>
|
|
33
|
+
<dt><%= t('i18n_proofreading.dashboard.suggested', default: 'Suggested') %></dt>
|
|
34
|
+
<dd><span class="new" dir="auto"><%= suggestion.proposed_value %></span></dd>
|
|
35
|
+
</dl>
|
|
36
|
+
|
|
37
|
+
<% if suggestion.comment.present? %>
|
|
38
|
+
<div class="comment" dir="auto"><%= suggestion.comment %></div>
|
|
39
|
+
<% end %>
|
|
40
|
+
</article>
|
|
41
|
+
</div>
|
|
42
|
+
</section>
|
|
@@ -1,77 +1,99 @@
|
|
|
1
|
+
<% content_for :body_class, 'ip-index' %>
|
|
2
|
+
|
|
1
3
|
<header class="page">
|
|
2
4
|
<h1><%= t('i18n_proofreading.dashboard.title', default: 'I18nProofreading') %></h1>
|
|
3
5
|
<p class="lede"><%= t('i18n_proofreading.dashboard.subtitle', default: 'Review what reviewers proposed, then apply it to your locale files.') %></p>
|
|
4
6
|
</header>
|
|
5
7
|
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<% if @locales.many? %>
|
|
17
|
-
<form class="filters" method="get">
|
|
18
|
-
<input type="hidden" name="status" value="<%= @status %>">
|
|
19
|
-
<label for="i18np-locale"><%= t('i18n_proofreading.dashboard.locale', default: 'Locale') %></label>
|
|
20
|
-
<select id="i18np-locale" name="locale" onchange="this.form.submit()">
|
|
21
|
-
<option value=""><%= t('i18n_proofreading.dashboard.all_locales', default: 'All locales') %></option>
|
|
22
|
-
<% @locales.each do |locale| %>
|
|
23
|
-
<option value="<%= locale %>" <%= 'selected' if locale == @locale %>><%= locale %></option>
|
|
8
|
+
<div class="review-shell <%= 'has-selected' if @selected_suggestion %>">
|
|
9
|
+
<aside class="review-sidebar" aria-label="<%= t('i18n_proofreading.dashboard.suggestions', default: 'Suggestions') %>">
|
|
10
|
+
<nav class="tabs">
|
|
11
|
+
<% I18nProofreading::Suggestion::STATUSES.each do |status| %>
|
|
12
|
+
<%= link_to suggestions_path(status: status, locale: @locale),
|
|
13
|
+
class: ('active' if status == @status) do %>
|
|
14
|
+
<%= t("i18n_proofreading.statuses.#{status}", default: status.humanize) %>
|
|
15
|
+
<span class="count"><%= @counts.fetch(status, 0) %></span>
|
|
16
|
+
<% end %>
|
|
24
17
|
<% end %>
|
|
25
|
-
</
|
|
26
|
-
<%# Inline onchange auto-submits where allowed; this button is the reliable
|
|
27
|
-
path where a strict CSP blocks inline handlers, or with JS off. %>
|
|
28
|
-
<button><%= t('i18n_proofreading.dashboard.filter', default: 'Filter') %></button>
|
|
29
|
-
</form>
|
|
30
|
-
<% end %>
|
|
18
|
+
</nav>
|
|
31
19
|
|
|
32
|
-
<% if @
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<dl class="diff">
|
|
49
|
-
<dt><%= t('i18n_proofreading.dashboard.current', default: 'Current') %></dt>
|
|
50
|
-
<dd><span class="old" dir="auto"><%= suggestion.old_value.presence || '—' %></span></dd>
|
|
51
|
-
<dt><%= t('i18n_proofreading.dashboard.suggested', default: 'Suggested') %></dt>
|
|
52
|
-
<dd><span class="new" dir="auto"><%= suggestion.proposed_value %></span></dd>
|
|
53
|
-
</dl>
|
|
20
|
+
<% if @locales.many? %>
|
|
21
|
+
<form class="filters" method="get">
|
|
22
|
+
<input type="hidden" name="status" value="<%= @status %>">
|
|
23
|
+
<label for="i18np-locale"><%= t('i18n_proofreading.dashboard.locale', default: 'Locale') %></label>
|
|
24
|
+
<select id="i18np-locale" name="locale" onchange="this.form.submit()">
|
|
25
|
+
<option value=""><%= t('i18n_proofreading.dashboard.all_locales', default: 'All locales') %></option>
|
|
26
|
+
<% @locales.each do |locale| %>
|
|
27
|
+
<option value="<%= locale %>" <%= 'selected' if locale == @locale %>><%= locale %></option>
|
|
28
|
+
<% end %>
|
|
29
|
+
</select>
|
|
30
|
+
<%# Inline onchange auto-submits where allowed; this button is the reliable
|
|
31
|
+
path where a strict CSP blocks inline handlers, or with JS off. %>
|
|
32
|
+
<button><%= t('i18n_proofreading.dashboard.filter', default: 'Filter') %></button>
|
|
33
|
+
</form>
|
|
34
|
+
<% end %>
|
|
54
35
|
|
|
55
|
-
|
|
56
|
-
|
|
36
|
+
<div class="suggestion-list">
|
|
37
|
+
<% if @suggestions.any? %>
|
|
38
|
+
<% @suggestions.each do |suggestion| %>
|
|
39
|
+
<%= link_to suggestions_path(status: @status, locale: @locale, page: @page,
|
|
40
|
+
suggestion_id: suggestion.id),
|
|
41
|
+
class: ['suggestion-row', ('active' if @selected_suggestion&.id == suggestion.id)].compact do %>
|
|
42
|
+
<span class="suggestion-main">
|
|
43
|
+
<span class="suggestion-topline">
|
|
44
|
+
<code class="key"><%= suggestion.translation_key %></code>
|
|
45
|
+
<span class="badge locale"><%= suggestion.locale %></span>
|
|
46
|
+
<span class="muted suggestion-time" title="<%= suggestion.created_at %>">
|
|
47
|
+
<%= time_ago_in_words(suggestion.created_at) %> <%= t('i18n_proofreading.dashboard.ago', default: 'ago') %>
|
|
48
|
+
</span>
|
|
49
|
+
</span>
|
|
50
|
+
<span class="suggestion-copy" dir="auto"><%= truncate(suggestion.proposed_value, length: 110) %></span>
|
|
51
|
+
<span class="muted suggestion-meta">
|
|
52
|
+
<span>#<%= suggestion.id %></span>
|
|
53
|
+
<% if suggestion.author_label.present? %>
|
|
54
|
+
<span><%= suggestion.author_label %></span>
|
|
55
|
+
<% end %>
|
|
56
|
+
</span>
|
|
57
|
+
</span>
|
|
58
|
+
<span class="badge status-<%= suggestion.status %>">
|
|
59
|
+
<%= t("i18n_proofreading.statuses.#{suggestion.status}", default: suggestion.status.humanize) %>
|
|
60
|
+
</span>
|
|
61
|
+
<% end %>
|
|
62
|
+
<% end %>
|
|
63
|
+
<% else %>
|
|
64
|
+
<div class="empty"><%= t('i18n_proofreading.dashboard.empty', default: 'Nothing here yet.') %></div>
|
|
57
65
|
<% end %>
|
|
58
|
-
</
|
|
59
|
-
<% end %>
|
|
66
|
+
</div>
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
<nav class="pager">
|
|
69
|
+
<span>
|
|
70
|
+
<% if @page > 1 %>
|
|
71
|
+
<%= link_to t('i18n_proofreading.dashboard.newer', default: '← Newer'),
|
|
72
|
+
suggestions_path(status: @status, locale: @locale, page: @page - 1) %>
|
|
73
|
+
<% end %>
|
|
74
|
+
</span>
|
|
75
|
+
<span>
|
|
76
|
+
<% if @more %>
|
|
77
|
+
<%= link_to t('i18n_proofreading.dashboard.older', default: 'Older →'),
|
|
78
|
+
suggestions_path(status: @status, locale: @locale, page: @page + 1) %>
|
|
79
|
+
<% end %>
|
|
80
|
+
</span>
|
|
81
|
+
</nav>
|
|
82
|
+
</aside>
|
|
83
|
+
|
|
84
|
+
<main class="review-detail" aria-label="<%= t('i18n_proofreading.dashboard.current_suggestion', default: 'Current suggestion') %>">
|
|
85
|
+
<% if @selected_suggestion %>
|
|
86
|
+
<p class="mobile-back">
|
|
87
|
+
<%= link_to t('i18n_proofreading.dashboard.back', default: '← All suggestions'),
|
|
88
|
+
suggestions_path(status: @status, locale: @locale, page: @page) %>
|
|
89
|
+
</p>
|
|
90
|
+
<%= render 'suggestion_panel', suggestion: @selected_suggestion %>
|
|
91
|
+
<% else %>
|
|
92
|
+
<div class="empty-state">
|
|
93
|
+
<h2><%= t('i18n_proofreading.dashboard.select_suggestion', default: 'Select a suggestion') %></h2>
|
|
94
|
+
<p class="muted"><%= t('i18n_proofreading.dashboard.select_suggestion_hint',
|
|
95
|
+
default: 'Open a row to review the proposed wording beside the queue.') %></p>
|
|
96
|
+
</div>
|
|
97
|
+
<% end %>
|
|
98
|
+
</main>
|
|
99
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<% content_for :body_class, 'ip-show' %>
|
|
2
|
+
|
|
3
|
+
<p class="breadcrumb">
|
|
4
|
+
<%= link_to t('i18n_proofreading.dashboard.title', default: 'I18nProofreading'), suggestions_path %>
|
|
5
|
+
/ #<%= @suggestion.id %>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<%= render 'suggestion_panel', suggestion: @suggestion %>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<%= stylesheet_link_tag "#{dashboard_stylesheet_path}?v=#{I18nProofreading::Widget.dashboard_stylesheet_fingerprint}",
|
|
10
10
|
'data-turbo-track': 'reload' %>
|
|
11
11
|
</head>
|
|
12
|
-
<body>
|
|
12
|
+
<body class="<%= content_for?(:body_class) ? yield(:body_class) : '' %>">
|
|
13
13
|
<div class="container">
|
|
14
14
|
<%= yield %>
|
|
15
15
|
</div>
|
data/config/routes.rb
CHANGED
|
@@ -10,7 +10,7 @@ I18nProofreading::Engine.routes.draw do
|
|
|
10
10
|
# route are the widget's public API (see SuggestionsController). There is
|
|
11
11
|
# deliberately no update/destroy — the tool never mutates suggestions from the
|
|
12
12
|
# UI, since it can't write to the host's locale files.
|
|
13
|
-
resources :suggestions, only: %i[index create] do
|
|
13
|
+
resources :suggestions, only: %i[index show create] do
|
|
14
14
|
get :context, on: :collection
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -16,6 +16,11 @@ I18nProofreading.configure do |config|
|
|
|
16
16
|
# development only; wire it to your own admin check to open it elsewhere.
|
|
17
17
|
#
|
|
18
18
|
# config.authorize_admin = ->(request) { request.env["warden"]&.user&.admin? }
|
|
19
|
+
#
|
|
20
|
+
# Render the dashboard inside your app's admin layout. Default: the gem's
|
|
21
|
+
# standalone dashboard layout.
|
|
22
|
+
#
|
|
23
|
+
# config.admin_layout = "admin/application"
|
|
19
24
|
|
|
20
25
|
# Attribute a suggestion to a user (optional). Return an object responding to
|
|
21
26
|
# #id (ideally #email too), or nil. Receives the Rack::Request. You resolve the
|
|
@@ -21,6 +21,10 @@ module I18nProofreading
|
|
|
21
21
|
# the dashboard until you wire it to your own admin check.
|
|
22
22
|
attr_accessor :authorize_admin
|
|
23
23
|
|
|
24
|
+
# Layout used by the triage dashboard. Override this to render it inside
|
|
25
|
+
# your app's admin shell, e.g. "admin/application".
|
|
26
|
+
attr_accessor :admin_layout
|
|
27
|
+
|
|
24
28
|
# Resolve the current user for attribution (optional). Return an object
|
|
25
29
|
# responding to #id, or nil. Receives the Rack::Request.
|
|
26
30
|
attr_accessor :current_user
|
|
@@ -68,6 +72,7 @@ module I18nProofreading
|
|
|
68
72
|
@enabled_environments = %w[development staging]
|
|
69
73
|
@enabled = ->(_request) { true }
|
|
70
74
|
@authorize_admin = ->(_request) { Rails.env.development? }
|
|
75
|
+
@admin_layout = 'i18n_proofreading/application'
|
|
71
76
|
@current_user = ->(_request) {}
|
|
72
77
|
@author_label = ->(user) { user.respond_to?(:email) ? user.email : user&.to_s }
|
|
73
78
|
@available_locales = -> { I18n.available_locales.map(&:to_s) }
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
46
46
|
padding: 14px 16px; margin-bottom: 12px;
|
|
47
47
|
}
|
|
48
|
+
.card.pad { margin: 0; }
|
|
48
49
|
.row-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
|
|
49
50
|
code.key {
|
|
50
51
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
|
|
@@ -78,3 +79,83 @@
|
|
|
78
79
|
.empty { padding: 48px 16px; text-align: center; color: var(--muted); border: 1px dashed var(--border); border-radius: 12px; }
|
|
79
80
|
.pager { margin-top: 16px; display: flex; justify-content: space-between; }
|
|
80
81
|
.pager .muted { color: var(--muted); }
|
|
82
|
+
.breadcrumb { margin: 0 0 12px; font-size: 13px; }
|
|
83
|
+
.case-id { color: var(--muted); font-weight: 400; font-size: 15px; }
|
|
84
|
+
.panel-head { flex: 0 0 auto; }
|
|
85
|
+
.panel-head h1 { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
86
|
+
.detail-scroll { min-height: 0; display: flex; flex-direction: column; gap: 12px; }
|
|
87
|
+
.suggestion-panel { min-width: 0; display: flex; flex-direction: column; }
|
|
88
|
+
.meta-list { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0; }
|
|
89
|
+
.meta-list dt { color: var(--muted); font-size: 13px; }
|
|
90
|
+
.meta-list dd { margin: 0; overflow-wrap: anywhere; }
|
|
91
|
+
|
|
92
|
+
/* Desktop review: queue on the left, selected suggestion on the right. */
|
|
93
|
+
.ip-index, .ip-index .container { height: 100vh; height: 100dvh; }
|
|
94
|
+
.ip-index { overflow: hidden; }
|
|
95
|
+
.ip-index .container { max-width: 1280px; display: flex; flex-direction: column; padding-bottom: 16px; }
|
|
96
|
+
.ip-index header.page { flex: 0 0 auto; margin-bottom: 14px; }
|
|
97
|
+
.review-shell { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(330px, 430px) 1fr;
|
|
98
|
+
gap: 16px; }
|
|
99
|
+
.review-sidebar { min-width: 0; min-height: 0; display: flex; flex-direction: column;
|
|
100
|
+
background: var(--surface); border: 1px solid var(--border);
|
|
101
|
+
border-radius: 8px; overflow: hidden; }
|
|
102
|
+
.review-sidebar .tabs { flex: 0 0 auto; margin: 12px 12px 0; padding: 0; background: transparent;
|
|
103
|
+
border: 0; border-radius: 0; gap: 6px; flex-wrap: nowrap; }
|
|
104
|
+
.review-sidebar .tabs a { flex: 1 1 0; position: relative; display: flex; align-items: center;
|
|
105
|
+
justify-content: center; gap: 6px; min-height: 34px; padding: 5px 10px 8px;
|
|
106
|
+
border-radius: 8px; color: var(--muted); font-weight: 600; }
|
|
107
|
+
.review-sidebar .tabs a.active { color: var(--text); border: 0; background: transparent; box-shadow: none; margin: 0; }
|
|
108
|
+
.review-sidebar .tabs a.active::after { content: ""; position: absolute; left: 20px; right: 20px; bottom: 0;
|
|
109
|
+
height: 2px; border-radius: 999px; background: var(--accent); }
|
|
110
|
+
.review-sidebar .tabs a:not(.active):hover { text-decoration: none; color: var(--text); background: var(--bg); }
|
|
111
|
+
.review-sidebar .tabs .count { margin-left: 0; background: color-mix(in srgb, var(--muted) 14%, transparent); }
|
|
112
|
+
.review-sidebar .tabs a.active .count { background: var(--border); }
|
|
113
|
+
.review-sidebar .filters { flex: 0 0 auto; margin: 0; padding: 12px; border-bottom: 1px solid var(--border); }
|
|
114
|
+
.suggestion-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
|
|
115
|
+
.suggestion-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 12px; padding: 13px 14px;
|
|
116
|
+
color: var(--text); border-bottom: 1px solid var(--border); }
|
|
117
|
+
.suggestion-row > .badge { align-self: start; }
|
|
118
|
+
.suggestion-row:hover { text-decoration: none; background: var(--bg); }
|
|
119
|
+
.suggestion-row.active { background: color-mix(in srgb, var(--accent) 9%, var(--surface));
|
|
120
|
+
box-shadow: inset 3px 0 0 var(--accent); }
|
|
121
|
+
.suggestion-main { min-width: 0; display: flex; flex-direction: column; gap: 5px; }
|
|
122
|
+
.suggestion-topline, .suggestion-meta { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
|
123
|
+
.suggestion-topline .key { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
124
|
+
.suggestion-copy, .suggestion-meta span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
125
|
+
.suggestion-copy { display: block; font-weight: 600; }
|
|
126
|
+
.suggestion-time { margin-left: auto; white-space: nowrap; }
|
|
127
|
+
.review-sidebar .pager { flex: 0 0 auto; margin: 0; padding: 10px 12px; border-top: 1px solid var(--border); }
|
|
128
|
+
.review-detail { min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
129
|
+
.review-detail .suggestion-panel { flex: 1 1 auto; min-height: 0; }
|
|
130
|
+
.review-detail .detail-scroll { flex: 1 1 auto; overflow-y: auto; padding-right: 2px; }
|
|
131
|
+
.empty-state { margin: auto; max-width: 320px; padding: 24px; text-align: center; }
|
|
132
|
+
.empty-state h2 { margin: 0 0 6px; font-size: 18px; }
|
|
133
|
+
.empty-state p { margin: 0; }
|
|
134
|
+
.mobile-back { display: none; }
|
|
135
|
+
|
|
136
|
+
/* Standalone show pages keep normal page scrolling. */
|
|
137
|
+
.ip-show { min-height: 100vh; overflow: auto; }
|
|
138
|
+
.ip-show .detail-scroll { overflow: visible; }
|
|
139
|
+
@media (max-width: 760px) {
|
|
140
|
+
body { font-size: 14px; }
|
|
141
|
+
.container { padding: 14px 10px 24px; }
|
|
142
|
+
.ip-index, .ip-show { overflow-x: hidden; }
|
|
143
|
+
.review-detail, .suggestion-panel, .detail-scroll, .card, dl, dd { min-width: 0; max-width: 100%; }
|
|
144
|
+
.suggestion-panel .card { width: calc(100vw - 20px); }
|
|
145
|
+
.lede, dd, .comment { max-width: calc(100vw - 54px); white-space: normal; overflow-wrap: anywhere; }
|
|
146
|
+
.ip-index { overflow-y: auto; overflow-x: hidden; }
|
|
147
|
+
.ip-index, .ip-index .container { min-height: 100vh; height: auto; }
|
|
148
|
+
.ip-index .container { padding-bottom: 10px; }
|
|
149
|
+
.review-shell { display: block; min-height: calc(100vh - 76px); }
|
|
150
|
+
.review-sidebar, .review-detail { min-height: calc(100vh - 76px); }
|
|
151
|
+
.review-shell.has-selected .review-sidebar { display: none; }
|
|
152
|
+
.review-shell:not(.has-selected) .review-detail { display: none; }
|
|
153
|
+
.suggestion-list { overflow: visible; }
|
|
154
|
+
.suggestion-row { padding: 12px; }
|
|
155
|
+
.suggestion-meta { display: none; }
|
|
156
|
+
.review-detail .suggestion-panel { min-height: calc(100vh - 64px); }
|
|
157
|
+
.mobile-back { display: block; flex: 0 0 auto; margin: 10px 10px 0; font-size: 13px; }
|
|
158
|
+
.panel-head h1 { font-size: 18px; }
|
|
159
|
+
.panel-head .key { min-width: 0; white-space: normal; overflow-wrap: anywhere; }
|
|
160
|
+
dl.diff, .meta-list { grid-template-columns: 1fr; gap: 3px; }
|
|
161
|
+
}
|
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.10.
|
|
4
|
+
version: 0.10.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -44,7 +44,9 @@ files:
|
|
|
44
44
|
- app/helpers/i18n_proofreading/tag_helper.rb
|
|
45
45
|
- app/models/i18n_proofreading/application_record.rb
|
|
46
46
|
- app/models/i18n_proofreading/suggestion.rb
|
|
47
|
+
- app/views/i18n_proofreading/suggestions/_suggestion_panel.html.erb
|
|
47
48
|
- app/views/i18n_proofreading/suggestions/index.html.erb
|
|
49
|
+
- app/views/i18n_proofreading/suggestions/show.html.erb
|
|
48
50
|
- app/views/layouts/i18n_proofreading/application.html.erb
|
|
49
51
|
- config/locales/i18n_proofreading.ar.yml
|
|
50
52
|
- config/locales/i18n_proofreading.bg.yml
|