diff options
| -rw-r--r-- | app/models/page.rb | 6 | ||||
| -rw-r--r-- | app/views/revisions/diff.html.erb | 133 | ||||
| -rw-r--r-- | config/locales/de.yml | 5 | ||||
| -rw-r--r-- | config/locales/en.yml | 5 | ||||
| -rw-r--r-- | public/stylesheets/admin.css | 6 | ||||
| -rw-r--r-- | test/controllers/revisions_controller_test.rb | 22 | ||||
| -rw-r--r-- | test/models/page_test.rb | 28 |
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) |
