diff options
Diffstat (limited to 'app/controllers/nodes_controller.rb')
| -rw-r--r-- | app/controllers/nodes_controller.rb | 248 |
1 files changed, 198 insertions, 50 deletions
diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb index 494887d..bff1733 100644 --- a/app/controllers/nodes_controller.rb +++ b/app/controllers/nodes_controller.rb | |||
| @@ -1,19 +1,23 @@ | |||
| 1 | class NodesController < ApplicationController | 1 | class NodesController < ApplicationController |
| 2 | 2 | ||
| 3 | # Private | 3 | # Private |
| 4 | 4 | ||
| 5 | layout 'admin' | 5 | layout 'admin' |
| 6 | 6 | ||
| 7 | before_action :login_required | 7 | before_action :login_required |
| 8 | before_action :find_node, :only => [ | 8 | before_action :find_node, :only => [ |
| 9 | :show, | 9 | :show, |
| 10 | :edit, | 10 | :edit, |
| 11 | :update, | 11 | :update, |
| 12 | :destroy, | 12 | :destroy, |
| 13 | :publish, | 13 | :publish, |
| 14 | :unlock | 14 | :unlock, |
| 15 | :autosave, | ||
| 16 | :revert | ||
| 15 | ] | 17 | ] |
| 16 | 18 | ||
| 19 | around_action :pin_to_default_locale, :only => [:show, :edit, :update, :autosave] | ||
| 20 | |||
| 17 | def index | 21 | def index |
| 18 | @nodes = Node.root.descendants.includes(:head, :draft) | 22 | @nodes = Node.root.descendants.includes(:head, :draft) |
| 19 | .order('id DESC') | 23 | .order('id DESC') |
| @@ -22,90 +26,203 @@ class NodesController < ApplicationController | |||
| 22 | 26 | ||
| 23 | def new | 27 | def new |
| 24 | @node = Node.new node_params | 28 | @node = Node.new node_params |
| 29 | @selected_kind = CccConventions::NODE_KINDS.key?(params[:kind]) ? params[:kind] : "generic" | ||
| 25 | if params.has_key?(:parent_id) | 30 | if params.has_key?(:parent_id) |
| 26 | @parent_id = params[:parent_id] | 31 | @parent_id = params[:parent_id] |
| 27 | @parent_name = Node.find(@parent_id).title | 32 | @parent_name = Node.find(@parent_id).title |
| 28 | end | 33 | end |
| 29 | end | 34 | end |
| 30 | 35 | ||
| 31 | def create | 36 | def create |
| 32 | params[:title] ||= "" | 37 | params[:title] ||= "" |
| 33 | 38 | ||
| 34 | @node = Node.new | 39 | @node = Node.new |
| 35 | @node.parent_id = find_parent | 40 | @node.parent_id = find_parent |
| 36 | @node.slug = params[:title].parameterize.to_s | 41 | @node.slug = slug_for(params[:title]) |
| 37 | 42 | ||
| 43 | config = CccConventions::NODE_KINDS[params[:kind]] | ||
| 44 | |||
| 38 | if @node.save | 45 | if @node.save |
| 39 | @node.draft.update(:title => params[:title]) | 46 | @node.draft.update(:title => params[:title]) |
| 40 | case params[:kind] | 47 | Array(config && config[:tags]).each { |t| @node.draft.tag_list.add(t) } |
| 41 | when "update" | ||
| 42 | @node.draft.tag_list.add("update") | ||
| 43 | when "press_release" | ||
| 44 | @node.draft.tag_list.add("update", "pressemitteilung") | ||
| 45 | end | ||
| 46 | @node.draft.save! | 48 | @node.draft.save! |
| 49 | |||
| 50 | @node.update!(default_template_name: config[:template]) if config && config[:template] | ||
| 51 | |||
| 47 | redirect_to(edit_node_path(@node)) | 52 | redirect_to(edit_node_path(@node)) |
| 48 | else | 53 | else |
| 54 | @selected_kind = CccConventions::NODE_KINDS.key?(params[:kind]) ? params[:kind] : "generic" | ||
| 55 | if params[:parent_id].present? | ||
| 56 | @parent_id = params[:parent_id] | ||
| 57 | @parent_name = Node.find(@parent_id).title | ||
| 58 | end | ||
| 49 | render :new | 59 | render :new |
| 50 | end | 60 | end |
| 51 | end | 61 | end |
| 52 | 62 | ||
| 53 | def show | 63 | def show |
| 54 | node = Node.find(params[:id]) | 64 | @page = @node.draft || @node.head |
| 55 | node.wipe_draft! | 65 | @translations = @page.translation_summary |
| 56 | @page = node.draft || node.head | ||
| 57 | end | 66 | end |
| 58 | 67 | ||
| 59 | def edit | 68 | def edit |
| 60 | begin | 69 | freshly_locked = @node.lock_owner != current_user |
| 61 | @draft = @node.find_or_create_draft( current_user ) | 70 | @node.lock_for_editing!( current_user ) |
| 62 | rescue LockedByAnotherUser => e | 71 | @page = @node.autosave || @node.draft || @node.head |
| 63 | flash[:error] = e.message | 72 | |
| 64 | if request.referer | 73 | if @node.autosave |
| 65 | redirect_to request.referer || node_path(@node) | 74 | flash.now[:notice] = |
| 66 | else | 75 | "This page has unsaved changes from a previous session, shown below. " \ |
| 67 | redirect_to node_path(@node) | 76 | "Save to keep them, or use \"Discard Autosave\" below to go back to the last saved version." |
| 68 | end | 77 | elsif freshly_locked |
| 78 | flash.now[:notice] = "Node locked and ready to edit" | ||
| 69 | end | 79 | end |
| 80 | rescue LockedByAnotherUser => e | ||
| 81 | flash[:error] = e.message | ||
| 82 | redirect_to(request.referer || node_path(@node)) | ||
| 70 | end | 83 | end |
| 71 | 84 | ||
| 72 | def update | 85 | def update |
| 73 | @node.update(node_params) | 86 | @node.update(node_params) |
| 74 | @draft = @node.find_or_create_draft current_user | 87 | @node.autosave!( page_params.merge(:tag_list => params[:tag_list]), current_user ) |
| 75 | @draft.tag_list = params[:tag_list] | 88 | @node.save_draft!(current_user) |
| 76 | if @draft.update( page_params ) | 89 | |
| 77 | flash[:notice] = "Draft has been saved: #{Time.now}" | 90 | flash[:notice] = "Draft saved. Publish your changes in the Status section once you're done." |
| 78 | respond_to do |format| | 91 | flash[:status_path] = node_path(@node) |
| 79 | format.html { redirect_to edit_node_path(@node) } | 92 | |
| 80 | format.js | 93 | if @node.draft.translated_locales.size > 1 |
| 94 | stale_locale = @node.draft.translated_locales.find do |locale| | ||
| 95 | @node.draft.outdated_translations?(locale: locale) | ||
| 81 | end | 96 | end |
| 97 | if stale_locale | ||
| 98 | flash[:stale_locale] = stale_locale | ||
| 99 | flash[:stale_locale_path] = edit_node_path(@node, locale: stale_locale) | ||
| 100 | end | ||
| 101 | end | ||
| 102 | |||
| 103 | if params[:commit] == "Save + Unlock + Exit" | ||
| 104 | @node.unlock! | ||
| 105 | redirect_to node_path(@node) | ||
| 106 | else | ||
| 107 | redirect_to edit_node_path(@node) | ||
| 108 | end | ||
| 109 | rescue LockedByAnotherUser => e | ||
| 110 | flash[:error] = e.message | ||
| 111 | redirect_to node_path(@node) | ||
| 112 | rescue ActiveRecord::RecordInvalid | ||
| 113 | @page = @node.autosave || @node.draft || @node.head | ||
| 114 | render :action => :edit | ||
| 115 | end | ||
| 116 | |||
| 117 | def autosave | ||
| 118 | @node.update(node_params) | ||
| 119 | @node.autosave!( page_params.merge(:tag_list => params[:tag_list]), current_user ) | ||
| 120 | head :ok | ||
| 121 | rescue LockedByAnotherUser => e | ||
| 122 | render plain: e.message, status: :locked | ||
| 123 | rescue ActiveRecord::RecordInvalid => e | ||
| 124 | render plain: e.message, status: :unprocessable_entity | ||
| 125 | rescue StandardError => e | ||
| 126 | render plain: "Autosave failed", status: :internal_server_error | ||
| 127 | end | ||
| 128 | |||
| 129 | def revert | ||
| 130 | @node.lock_for_editing!(current_user) | ||
| 131 | @node.revert!(current_user) | ||
| 132 | |||
| 133 | if params[:return_to].present? | ||
| 134 | redirect_to safe_return_to(params[:return_to]) | ||
| 135 | elsif @node.draft | ||
| 136 | redirect_to edit_node_path(@node) | ||
| 82 | else | 137 | else |
| 83 | render :action => :edit | 138 | redirect_to node_path(@node) |
| 84 | end | 139 | end |
| 140 | rescue LockedByAnotherUser => e | ||
| 141 | flash[:error] = e.message | ||
| 142 | redirect_to node_path(@node) | ||
| 85 | end | 143 | end |
| 86 | 144 | ||
| 87 | def destroy | 145 | def destroy |
| 88 | @node.destroy | 146 | @node.destroy |
| 89 | end | 147 | end |
| 90 | 148 | ||
| 91 | def publish | 149 | def publish |
| 92 | @node.publish_draft! | 150 | @node.publish_draft! |
| 93 | flash[:notice] = "Draft has been published" | 151 | flash[:notice] = "Draft has been published" |
| 94 | redirect_to node_path(@node) | 152 | redirect_to node_path(@node) |
| 95 | end | 153 | end |
| 96 | 154 | ||
| 97 | def unlock | 155 | def unlock |
| 98 | if @node.unlock! | 156 | if @node.unlock! |
| 99 | flash[:notice] = "Node unlocked" | 157 | flash[:notice] = "Node unlocked" |
| 100 | else | 158 | else |
| 101 | flash[:notice] = "Already unlocked" | 159 | flash[:notice] = "Already unlocked" |
| 102 | end | 160 | end |
| 103 | 161 | ||
| 162 | redirect_to node_path(@node) | ||
| 163 | end | ||
| 164 | |||
| 165 | def generate_shared_preview | ||
| 166 | @node = Node.find(params[:id]) | ||
| 167 | if @node.draft | ||
| 168 | @node.draft.ensure_preview_token! | ||
| 169 | flash[:notice] = "Shareable preview link created - see below." | ||
| 170 | else | ||
| 171 | flash[:notice] = "Create or edit a draft first - shared preview links are only available for pages with an active draft." | ||
| 172 | end | ||
| 173 | redirect_to node_path(@node) | ||
| 174 | end | ||
| 175 | |||
| 176 | def revoke_shared_preview | ||
| 177 | @node = Node.find(params[:id]) | ||
| 178 | @node.draft.revoke_preview_token! if @node.draft | ||
| 179 | flash[:notice] = "Shareable preview link revoked." | ||
| 104 | redirect_to node_path(@node) | 180 | redirect_to node_path(@node) |
| 105 | end | 181 | end |
| 106 | 182 | ||
| 183 | def parameterize_preview | ||
| 184 | render plain: slug_for(params[:title]) | ||
| 185 | end | ||
| 186 | |||
| 187 | # Filter functions for admin views | ||
| 188 | def drafts | ||
| 189 | @nodes = index_matching(Node.drafts_and_autosaves) | ||
| 190 | end | ||
| 191 | |||
| 192 | def recent | ||
| 193 | @nodes = index_matching(Node.recently_changed) | ||
| 194 | end | ||
| 195 | |||
| 196 | def mine | ||
| 197 | base = Node.joins(:pages) | ||
| 198 | .where("pages.user_id = ? or pages.editor_id = ?", current_user, current_user) | ||
| 199 | .distinct | ||
| 200 | @nodes = index_matching(base) | ||
| 201 | end | ||
| 202 | |||
| 203 | def chapters | ||
| 204 | @kind_keys = Array(params[:kinds]) & %w[erfa chaostreff] | ||
| 205 | @kind_keys = %w[erfa chaostreff] if @kind_keys.empty? | ||
| 206 | tags = @kind_keys.flat_map { |key| CccConventions::NODE_KINDS[key][:tags] } | ||
| 207 | @nodes = nodes_matching_tags(tags) | ||
| 208 | end | ||
| 209 | |||
| 210 | def tags | ||
| 211 | tags = params[:tags].to_s.split(',').map(&:strip).reject(&:blank?) | ||
| 212 | @nodes = nodes_matching_tags(tags) | ||
| 213 | end | ||
| 214 | |||
| 215 | def sitemap | ||
| 216 | @sitemap = Node.root.self_and_descendants_ordered_with_level | ||
| 217 | @sitemap_descendant_counts = descendant_counts_for(@sitemap) | ||
| 218 | end | ||
| 219 | |||
| 107 | private | 220 | private |
| 108 | 221 | ||
| 222 | def slug_for(title) | ||
| 223 | title.to_s.parameterize | ||
| 224 | end | ||
| 225 | |||
| 109 | def node_params | 226 | def node_params |
| 110 | params.fetch(:node, {}).permit(:slug, :parent_id, :staged_slug, :staged_parent_id) | 227 | params.fetch(:node, {}).permit(:slug, :parent_id, :staged_slug, :staged_parent_id) |
| 111 | end | 228 | end |
| @@ -113,25 +230,56 @@ class NodesController < ApplicationController | |||
| 113 | def page_params | 230 | def page_params |
| 114 | params.fetch(:page, {}).permit(:title, :abstract, :body, :template_name, :published_at, :user_id) | 231 | params.fetch(:page, {}).permit(:title, :abstract, :body, :template_name, :published_at, :user_id) |
| 115 | end | 232 | end |
| 116 | 233 | ||
| 117 | def find_node | 234 | def find_node |
| 118 | @node = Node.find(params[:id]) | 235 | @node = Node.find(params[:id]) |
| 119 | end | 236 | end |
| 120 | 237 | ||
| 121 | def find_parent | 238 | def find_parent |
| 122 | case params[:kind] | 239 | case params[:kind] |
| 123 | when "top_level" | ||
| 124 | Node.root.id | ||
| 125 | when "update" | ||
| 126 | Update.find_or_create_parent.id | ||
| 127 | when "press_release" | ||
| 128 | Update.find_or_create_parent.id | ||
| 129 | when "generic" | 240 | when "generic" |
| 130 | if params[:parent_id] && Node.find(params[:parent_id]) | 241 | if params[:parent_id] && Node.find(params[:parent_id]) |
| 131 | params[:parent_id] | 242 | params[:parent_id] |
| 132 | else | 243 | else |
| 133 | nil | 244 | nil |
| 134 | end | 245 | end |
| 246 | else | ||
| 247 | config = CccConventions::NODE_KINDS[params[:kind]] | ||
| 248 | config && config[:parent] ? config[:parent].call.id : nil | ||
| 249 | end | ||
| 250 | end | ||
| 251 | |||
| 252 | def pin_to_default_locale | ||
| 253 | Globalize.with_locale(I18n.default_locale) { yield } | ||
| 254 | end | ||
| 255 | |||
| 256 | def descendant_counts_for(ordered_with_level) | ||
| 257 | counts = Hash.new(0) | ||
| 258 | stack = [] # [node, level, index] | ||
| 259 | ordered_with_level.each_with_index do |(node, level), index| | ||
| 260 | while stack.any? && stack.last[1] >= level | ||
| 261 | ancestor_node, _ancestor_level, ancestor_index = stack.pop | ||
| 262 | counts[ancestor_node.id] = index - ancestor_index - 1 | ||
| 263 | end | ||
| 264 | stack << [node, level, index] | ||
| 265 | end | ||
| 266 | total = ordered_with_level.length | ||
| 267 | while stack.any? | ||
| 268 | ancestor_node, _ancestor_level, ancestor_index = stack.pop | ||
| 269 | counts[ancestor_node.id] = total - ancestor_index - 1 | ||
| 135 | end | 270 | end |
| 271 | counts | ||
| 272 | end | ||
| 273 | |||
| 274 | def nodes_matching_tags(tags) | ||
| 275 | matching_pages = Page.tagged_with(tags, any: true).reselect(:id) | ||
| 276 | base = Node.where(head_id: matching_pages).or(Node.where(draft_id: matching_pages)) | ||
| 277 | index_matching(base) | ||
| 278 | end | ||
| 279 | |||
| 280 | def index_matching(base_scope) | ||
| 281 | scope = base_scope.includes(:head, :draft) | ||
| 282 | scope = scope.merge(Node.editor_search(params[:q])) if params[:q].present? | ||
| 283 | scope.order("nodes.updated_at desc").paginate(page: params[:page], per_page: 25) | ||
| 136 | end | 284 | end |
| 137 | end | 285 | end |
