From 3b4e1423635fd9f33ae760312e438cc1e484662e Mon Sep 17 00:00:00 2001 From: erdgeist Date: Fri, 31 Jul 2026 20:41:05 +0200 Subject: Show the gate before it refuses, and check the destination nodes#show renders publish and trash as disabled_action spans with a hint naming what an editor can still do, matching how locked_by_other already reads. Editing and reverting stay live: drafting is free everywhere. --- app/models/node.rb | 23 ++++++++++++++++------- app/models/user.rb | 6 ++++++ app/views/nodes/show.html.erb | 26 ++++++++++++++++++++------ 3 files changed, 42 insertions(+), 13 deletions(-) (limited to 'app') diff --git a/app/models/node.rb b/app/models/node.rb index ac3a6160..188b6c17 100644 --- a/app/models/node.rb +++ b/app/models/node.rb @@ -236,7 +236,7 @@ class Node < ApplicationRecord # Return nil if nothing to publish and no staged changes return nil unless self.draft || staged_slug || staged_parent_id - guard_live_change!(current_user) + guard_live_change!(current_user, :target_path => prospective_unique_name) if in_trash? || trash_node? errors.add(:base, :publish_in_trash) @@ -574,10 +574,8 @@ class Node < ApplicationRecord false end - def restricted? - return true if root? - - name = unique_name.to_s + def self.restricted_path? name + name = name.to_s return false if name.empty? CccConventions::RESTRICTED_SUBTREES.any? do |prefix| @@ -585,6 +583,17 @@ class Node < ApplicationRecord end end + def restricted? + root? || self.class.restricted_path?(unique_name) + end + + def prospective_unique_name + target_parent = staged_parent_id ? Node.find_by(:id => staged_parent_id) : parent + return nil unless target_parent + + [target_parent.unique_name.presence, staged_slug.presence || slug].compact.join("/") + end + # Returns immutable node id for all new nodes so that the atom feed entry ids # stay the same eventhough the slug or positions changes. # Can be removed after a year or so ;) @@ -697,9 +706,9 @@ class Node < ApplicationRecord private - def guard_live_change! user + def guard_live_change! user, target_path: nil return if user.nil? - return if user.may_change_live?(self) + return if user.may_change_live?(self) && user.may_change_live_at?(target_path) errors.add(:base, :not_permitted) raise ActiveRecord::RecordInvalid.new(self) diff --git a/app/models/user.rb b/app/models/user.rb index e8c3b9bb..bf0f40ee 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -110,6 +110,12 @@ class User < ApplicationRecord redaktion? end + def may_change_live_at?(path) + return true if path.nil? + return true unless Node.restricted_path?(path) + redaktion? + end + def deactivate!(actor:) return false if alumni? transaction do diff --git a/app/views/nodes/show.html.erb b/app/views/nodes/show.html.erb index 8b12e2eb..a6167c9e 100644 --- a/app/views/nodes/show.html.erb +++ b/app/views/nodes/show.html.erb @@ -1,4 +1,5 @@ <% locked_by_other = @node.locked? && @node.lock_owner != current_user %> +<% may_change_live = current_user.may_change_live?(@node) %>

<%= title_for_node(@node) %> (<%= I18n.default_locale.to_s.upcase %>)

@@ -48,8 +49,12 @@ <% unless locked_by_other %> <% if @node.draft && !@node.autosave && !@node.in_trash? && !@node.trash_node? %>
- <%= button_to t(".publish"), publish_node_path(@node), method: :put, - form: { data: { confirm: t(".confirm_publish") }, class: 'button_to state_changing' } %> + <% if may_change_live %> + <%= button_to t(".publish"), publish_node_path(@node), method: :put, + form: { data: { confirm: t(".confirm_publish") }, class: 'button_to state_changing' } %> + <% else %> + <%= t(".publish") %> + <% end %>
<% end %> <% if @node.autosave || (@node.draft && @node.head) %> @@ -64,10 +69,17 @@ <% end %> <% unless @node.trash_node? || @node.in_trash? || @node.root? %>
- <%= button_to trash_node_path(@node), method: :put, - form: { data: { confirm: t(".confirm_trash") }, class: 'button_to destructive' } do %> - <%= icon("trash", library: "tabler", "aria-hidden": true) %> - <%= t(".move_to_trash") %> + <% if may_change_live %> + <%= button_to trash_node_path(@node), method: :put, + form: { data: { confirm: t(".confirm_trash") }, class: 'button_to destructive' } do %> + <%= icon("trash", library: "tabler", "aria-hidden": true) %> + <%= t(".move_to_trash") %> + <% end %> + <% else %> + + <%= icon("trash", library: "tabler", "aria-hidden": true) %> + <%= t(".move_to_trash") %> + <% end %>
<% end %> @@ -76,6 +88,8 @@ <% if locked_by_other %> <%= t(".locked_hint") %> + <% elsif !may_change_live %> + <%= t(".restricted_hint") %> <% end %>
-- cgit v1.3