summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-08-09 15:45:56 +0200
committererdgeist <erdgeist@erdgeist.org>2026-08-09 15:45:56 +0200
commit9d32458491d79f8e06a706375030c78282f4427b (patch)
tree5eee208b844df39dfd66d9f0d1618fb5fa437fdb
parented3905b5409190c1e11c2c49c2163ac967c6d8b9 (diff)
Report every editorial attribute in the revision diff
diff_against gained external_url, published_at and user. The view renders each metadata section only when that attribute changed, and says so once when none did. Metadata now precedes the locale pointer, which speaks only of translations: above the metadata it read as "nothing changed" on a revision that had moved the page.
-rw-r--r--app/models/page.rb6
-rw-r--r--app/views/revisions/diff.html.erb133
-rw-r--r--config/locales/de.yml5
-rw-r--r--config/locales/en.yml5
-rw-r--r--public/stylesheets/admin.css6
-rw-r--r--test/controllers/revisions_controller_test.rb22
-rw-r--r--test/models/page_test.rb28
7 files changed, 148 insertions, 57 deletions
diff --git a/app/models/page.rb b/app/models/page.rb
index 3240057f..c2dc227b 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -277,6 +277,12 @@ class Page < ApplicationRecord
277 277
278 text_diffs.merge( 278 text_diffs.merge(
279 address: address_diff_against(other), 279 address: address_diff_against(other),
280 external_url: { from: other.external_url, to: external_url,
281 changed: external_url.presence != other.external_url.presence },
282 published_at: { from: other.published_at, to: published_at,
283 changed: published_at != other.published_at },
284 user: { from: other.user, to: user,
285 changed: user_id != other.user_id },
280 tags: { added: tag_list.to_a - other.tag_list.to_a, removed: other.tag_list.to_a - tag_list.to_a }, 286 tags: { added: tag_list.to_a - other.tag_list.to_a, removed: other.tag_list.to_a - tag_list.to_a },
281 template_name: { from: other.template_name, to: template_name, changed: template_name != other.template_name }, 287 template_name: { from: other.template_name, to: template_name, changed: template_name != other.template_name },
282 assets: { added: assets.to_a - other.assets.to_a, removed: other.assets.to_a - assets.to_a } 288 assets: { added: assets.to_a - other.assets.to_a, removed: other.assets.to_a - assets.to_a }
diff --git a/app/views/revisions/diff.html.erb b/app/views/revisions/diff.html.erb
index 0fd9ccb9..25f5ad04 100644
--- a/app/views/revisions/diff.html.erb
+++ b/app/views/revisions/diff.html.erb
@@ -48,7 +48,6 @@
48 </p> 48 </p>
49<% end %> 49<% end %>
50 50
51
52<% if @available_layer_pairs.present? %> 51<% if @available_layer_pairs.present? %>
53 <div class="node_action_bar standalone_action_bar"> 52 <div class="node_action_bar standalone_action_bar">
54 <% @available_layer_pairs.each do |pair| %> 53 <% @available_layer_pairs.each do |pair| %>
@@ -71,21 +70,88 @@
71<% end %> 70<% end %>
72 71
73<div id="diffview"> 72<div id="diffview">
74 <% current_summary = @locale_summary.find { |s| s[:locale] == @translation_locale } %> 73 <% meta_changed = @diff[:address][:changed] || @diff[:external_url][:changed] ||
75 <% if current_summary && !current_summary[:changed] %> 74 @diff[:template_name][:changed] ||
76 <% elsewhere = @locale_summary.select { |s| s[:changed] }.map { |s| 75 @diff[:published_at][:changed] || @diff[:user][:changed] ||
77 link_to s[:locale].to_s.upcase, diff_node_revisions_path(@node, 76 @diff[:tags][:added].any? || @diff[:tags][:removed].any? ||
78 start_revision: params[:start_revision], end_revision: params[:end_revision], 77 @diff[:assets][:added].any? || @diff[:assets][:removed].any? %>
79 view: @diff_view, translation_locale: s[:locale]) } %> 78
80 <p class="diff_unchanged diff_locale_pointer"> 79 <div class="diff_preamble">
81 <% if elsewhere.any? %> 80 <div class="diff_meta">
82 <%= t(".unchanged_here_html", :lang => @translation_locale.to_s.upcase, 81 <% unless meta_changed %>
83 :others => safe_join(elsewhere, ", ")) %> 82 <p class="diff_unchanged"><%= t(".no_metadata_change") %></p>
84 <% else %> 83 <% end %>
85 <%= t(".unchanged_anywhere") %> 84
85 <% if @diff[:address][:changed] %>
86 <h3><%= t(".address") %></h3>
87 <p>
88 <del><%= @diff[:address][:from] || t(".none_marker") %></del>
89 <ins><%= @diff[:address][:to] || t(".none_marker") %></ins>
90 </p>
91 <% end %>
92
93 <% if @diff[:external_url][:changed] %>
94 <h3><%= Page.human_attribute_name(:external_url) %></h3>
95 <p>
96 <del><%= @diff[:external_url][:from].presence || t(".none_marker") %></del>
97 <ins><%= @diff[:external_url][:to].presence || t(".none_marker") %></ins>
98 </p>
99 <% end %>
100
101 <% if @diff[:published_at][:changed] %>
102 <h3><%= Page.human_attribute_name(:published_at) %></h3>
103 <p>
104 <del><%= @diff[:published_at][:from] ? admin_datetime(@diff[:published_at][:from]) : t(".none_marker") %></del>
105 <ins><%= @diff[:published_at][:to] ? admin_datetime(@diff[:published_at][:to]) : t(".none_marker") %></ins>
106 </p>
86 <% end %> 107 <% end %>
87 </p> 108
88 <% end %> 109 <% if @diff[:user][:changed] %>
110 <h3><%= Page.human_attribute_name(:user) %></h3>
111 <p>
112 <del><%= @diff[:user][:from]&.login || t(".none_marker") %></del>
113 <ins><%= @diff[:user][:to]&.login || t(".none_marker") %></ins>
114 </p>
115 <% end %>
116
117 <% if @diff[:tags][:added].any? || @diff[:tags][:removed].any? %>
118 <h3><%= Page.human_attribute_name(:tag_list) %></h3>
119 <ul class="diff_set_list">
120 <% @diff[:tags][:added].each do |tag| %><li><ins><%= tag %></ins></li><% end %>
121 <% @diff[:tags][:removed].each do |tag| %><li><del><%= tag %></del></li><% end %>
122 </ul>
123 <% end %>
124
125 <% if @diff[:template_name][:changed] %>
126 <h3><%= Page.human_attribute_name(:template_name) %></h3>
127 <p><del><%= @diff[:template_name][:from] || t(".none_marker") %></del> <ins><%= @diff[:template_name][:to] || t(".none_marker") %></ins></p>
128 <% end %>
129
130 <% if @diff[:assets][:added].any? || @diff[:assets][:removed].any? %>
131 <h3><%= Page.human_attribute_name(:assets) %></h3>
132 <ul class="diff_set_list">
133 <% @diff[:assets][:added].each do |asset| %><li><ins><%= asset.upload_file_name %></ins></li><% end %>
134 <% @diff[:assets][:removed].each do |asset| %><li><del><%= asset.upload_file_name %></del></li><% end %>
135 </ul>
136 <% end %>
137 </div>
138
139 <% current_summary = @locale_summary.find { |s| s[:locale] == @translation_locale } %>
140 <% if current_summary && !current_summary[:changed] %>
141 <% elsewhere = @locale_summary.select { |s| s[:changed] }.map { |s|
142 link_to s[:locale].to_s.upcase, diff_node_revisions_path(@node,
143 start_revision: params[:start_revision], end_revision: params[:end_revision],
144 view: @diff_view, translation_locale: s[:locale]) } %>
145 <p class="diff_unchanged diff_locale_pointer">
146 <% if elsewhere.any? %>
147 <%= t(".unchanged_here_html", :lang => @translation_locale.to_s.upcase,
148 :others => safe_join(elsewhere, ", ")) %>
149 <% else %>
150 <%= t(".unchanged_anywhere") %>
151 <% end %>
152 </p>
153 <% end %>
154 </div>
89 155
90 <% if @diff_view == :side_by_side %> 156 <% if @diff_view == :side_by_side %>
91 <div class="diff_side_by_side"> 157 <div class="diff_side_by_side">
@@ -114,41 +180,4 @@
114 <h3><%= Page.human_attribute_name(:body) %></h3> 180 <h3><%= Page.human_attribute_name(:body) %></h3>
115 <%= raw @diff[:body] %> 181 <%= raw @diff[:body] %>
116 <% end %> 182 <% end %>
117
118 <h3><%= t(".address") %></h3>
119 <% if @diff[:address][:changed] %>
120 <p>
121 <del><%= @diff[:address][:from] || t(".none_marker") %></del>
122 <ins><%= @diff[:address][:to] || t(".none_marker") %></ins>
123 </p>
124 <% else %>
125 <p class="diff_unchanged"><%= t(".no_change") %></p>
126 <% end %>
127
128 <h3><%= Page.human_attribute_name(:tag_list) %></h3>
129 <% if @diff[:tags][:added].empty? && @diff[:tags][:removed].empty? %>
130 <p class="diff_unchanged"><%= t(".no_change") %></p>
131 <% else %>
132 <ul class="diff_set_list">
133 <% @diff[:tags][:added].each do |tag| %><li><ins><%= tag %></ins></li><% end %>
134 <% @diff[:tags][:removed].each do |tag| %><li><del><%= tag %></del></li><% end %>
135 </ul>
136 <% end %>
137
138 <h3><%= Page.human_attribute_name(:template_name) %></h3>
139 <% if @diff[:template_name][:changed] %>
140 <p><del><%= @diff[:template_name][:from] || t(".none_marker") %></del> <ins><%= @diff[:template_name][:to] || t(".none_marker") %></ins></p>
141 <% else %>
142 <p class="diff_unchanged"><%= t(".no_change") %></p>
143 <% end %>
144
145 <h3><%= Page.human_attribute_name(:assets) %></h3>
146 <% if @diff[:assets][:added].empty? && @diff[:assets][:removed].empty? %>
147 <p class="diff_unchanged"><%= t(".no_change") %></p>
148 <% else %>
149 <ul class="diff_set_list">
150 <% @diff[:assets][:added].each do |asset| %><li><ins><%= asset.upload_file_name %></ins></li><% end %>
151 <% @diff[:assets][:removed].each do |asset| %><li><del><%= asset.upload_file_name %></del></li><% end %>
152 </ul>
153 <% end %>
154</div> 183</div>
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 2aec7bfa..3f2eb0ab 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -106,6 +106,9 @@ de:
106 tag_list: "Tags" 106 tag_list: "Tags"
107 template_name: "Template" 107 template_name: "Template"
108 assets: "Anhänge" 108 assets: "Anhänge"
109 external_url: "Externe Homepage"
110 published_at: "Veröffentlichungsdatum"
111 user: "Autor"
109 menu_item: 112 menu_item:
110 node_id: "Node-ID" 113 node_id: "Node-ID"
111 path: "Pfad" 114 path: "Pfad"
@@ -744,7 +747,7 @@ de:
744 revisions_link: "Alle Revisionen" 747 revisions_link: "Alle Revisionen"
745 compare_numbered: "Stattdessen zwei nummerierte Revisionen vergleichen" 748 compare_numbered: "Stattdessen zwei nummerierte Revisionen vergleichen"
746 none_marker: "(keins)" 749 none_marker: "(keins)"
747 no_change: "Keine Änderung." 750 no_metadata_change: "Keine Änderungen an den Metadaten."
748 unchanged_here_html: "Keine Änderung in der Übersetzung %{lang} zwischen diesen Revisionen — geändert wurde %{others}." 751 unchanged_here_html: "Keine Änderung in der Übersetzung %{lang} zwischen diesen Revisionen — geändert wurde %{others}."
749 unchanged_anywhere: "Zwischen diesen Revisionen wurde keine Übersetzung geändert." 752 unchanged_anywhere: "Zwischen diesen Revisionen wurde keine Übersetzung geändert."
750 address: "Adresse" 753 address: "Adresse"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 965906df..e8428f69 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -57,6 +57,9 @@ en:
57 tag_list: "Tags" 57 tag_list: "Tags"
58 template_name: "Template" 58 template_name: "Template"
59 assets: "Assets" 59 assets: "Assets"
60 external_url: "External homepage"
61 published_at: "Publication date"
62 user: "Author"
60 menu_item: 63 menu_item:
61 node_id: "Node Id" 64 node_id: "Node Id"
62 path: "Path" 65 path: "Path"
@@ -712,7 +715,7 @@ en:
712 revisions_link: "All revisions" 715 revisions_link: "All revisions"
713 compare_numbered: "Compare two numbered revisions instead" 716 compare_numbered: "Compare two numbered revisions instead"
714 none_marker: "(none)" 717 none_marker: "(none)"
715 no_change: "No change." 718 no_metadata_change: "No metadata changes."
716 unchanged_here_html: "No change in the %{lang} translation between these revisions — %{others} changed." 719 unchanged_here_html: "No change in the %{lang} translation between these revisions — %{others} changed."
717 unchanged_anywhere: "No translation changed between these revisions." 720 unchanged_anywhere: "No translation changed between these revisions."
718 address: "Address" 721 address: "Address"
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css
index 7951f667..b8a01112 100644
--- a/public/stylesheets/admin.css
+++ b/public/stylesheets/admin.css
@@ -799,6 +799,12 @@ table.revisions_table tr:hover {
799 margin: 0; 799 margin: 0;
800} 800}
801 801
802.diff_preamble {
803 border-bottom: 1px solid var(--hairline);
804 margin-bottom: 1.5rem;
805 padding-bottom: 0.5rem;
806}
807
802.user_table { 808.user_table {
803 width: 100%; 809 width: 100%;
804 max-width: 44rem; 810 max-width: 44rem;
diff --git a/test/controllers/revisions_controller_test.rb b/test/controllers/revisions_controller_test.rb
index 34f00a4f..d9f49e58 100644
--- a/test/controllers/revisions_controller_test.rb
+++ b/test/controllers/revisions_controller_test.rb
@@ -151,19 +151,35 @@ class RevisionsControllerTest < ActionController::TestCase
151 assert_select "input[type='hidden'][name='end_revision'][value='draft']" 151 assert_select "input[type='hidden'][name='end_revision'][value='draft']"
152 end 152 end
153 153
154 test "diffing two revisions also shows tag, template, and asset changes" do 154 test "diffing shows tag, template, external URL and asset changes" do
155 login_as :quentin 155 login_as :quentin
156 find_or_create_draft(@node, @user) 156 find_or_create_draft(@node, @user)
157 @node.draft.tag_list = "update" 157 draft = @node.draft
158 @node.draft.save! 158 draft.tag_list = "update"
159 draft.template_name = "title_only"
160 draft.external_url = "https://example.org/"
161 draft.save!
162 draft.related_assets.create!(:asset => Asset.create!(:name => "diffed",
163 :upload_file_name => "diffed.png",
164 :upload_content_type => "image/png"))
159 165
160 post(:diff, params: { :node_id => @node.id, :start_revision => @node.pages.first.revision, :end_revision => @node.pages.last.revision }) 166 post(:diff, params: { :node_id => @node.id, :start_revision => @node.pages.first.revision, :end_revision => @node.pages.last.revision })
161 assert_response :success 167 assert_response :success
162 assert_select "h3", Page.human_attribute_name(:tag_list) 168 assert_select "h3", Page.human_attribute_name(:tag_list)
163 assert_select "h3", Page.human_attribute_name(:template_name) 169 assert_select "h3", Page.human_attribute_name(:template_name)
170 assert_select "h3", Page.human_attribute_name(:external_url)
164 assert_select "h3", Page.human_attribute_name(:assets) 171 assert_select "h3", Page.human_attribute_name(:assets)
165 end 172 end
166 173
174 test "a diff with no metadata changes says so once" do
175 login_as :quentin
176
177 post(:diff, params: { :node_id => @node.id, :start_revision => @node.pages.first.revision, :end_revision => @node.pages.last.revision })
178 assert_response :success
179 assert_select ".diff_meta .diff_unchanged"
180 assert_select ".diff_meta h3", false
181 end
182
167 test "revisions#index links back to the node" do 183 test "revisions#index links back to the node" do
168 login_as :quentin 184 login_as :quentin
169 get :index, params: { :node_id => @node.id } 185 get :index, params: { :node_id => @node.id }
diff --git a/test/models/page_test.rb b/test/models/page_test.rb
index b737e8b0..f095a7e1 100644
--- a/test/models/page_test.rb
+++ b/test/models/page_test.rb
@@ -302,6 +302,34 @@ class PageTest < ActiveSupport::TestCase
302 assert_equal "title_only", diff[:template_name][:to] 302 assert_equal "title_only", diff[:template_name][:to]
303 end 303 end
304 304
305 test "diff_against reports external URL, publication date and author changes" do
306 n = Node.root.children.create! :slug => "meta_diff_test"
307 d = find_or_create_draft(n, @user1)
308 d.external_url = "https://old.example.org/"
309 d.published_at = Time.utc(2026, 1, 1, 12, 0, 0)
310 d.save!
311 n.publish_draft!
312
313 new_author = User.where.not(:id => n.head.user_id).first
314 d2 = find_or_create_draft(n, @user1)
315 d2.external_url = "https://new.example.org/"
316 d2.published_at = Time.utc(2026, 3, 1, 12, 0, 0)
317 d2.user = new_author
318 d2.save!
319
320 diff = d2.diff_against(n.head)
321
322 assert diff[:external_url][:changed]
323 assert_equal "https://old.example.org/", diff[:external_url][:from]
324 assert_equal "https://new.example.org/", diff[:external_url][:to]
325
326 assert diff[:published_at][:changed]
327 assert_equal Time.utc(2026, 3, 1, 12, 0, 0).to_i, diff[:published_at][:to].to_i
328
329 assert diff[:user][:changed]
330 assert_equal new_author.login, diff[:user][:to].login
331 end
332
305 test "diff_against reports added and removed assets by filename" do 333 test "diff_against reports added and removed assets by filename" do
306 n = Node.root.children.create! :slug => "asset_diff_test" 334 n = Node.root.children.create! :slug => "asset_diff_test"
307 d = find_or_create_draft(n, @user1) 335 d = find_or_create_draft(n, @user1)