summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorerdgeist <erdgeist@erdgeist.org>2026-07-11 23:43:02 +0200
committererdgeist <erdgeist@erdgeist.org>2026-07-11 23:43:02 +0200
commit92c394b43a0603743b914c6298aab986805ca990 (patch)
tree39308770fbae5558d98a7def3d25802ed8083d81 /app
parent47beb3fe6ed6fdb75c2dd3a55362ad1aba3bbc3b (diff)
Add drafts/recent/mine/chapters admin node views
Four NodesController actions -- drafts, recent, mine, chapters -- each building its own base scope, sharing one private method (index_matching) for search narrowing and pagination. Wizard rewrite to link into these instead of rendering its own tables is a separate, later step. Node.editor_search backs the shared "q" narrowing: an ILIKE substring match against title/abstract on whichever of head or draft is present, splitting the term on whitespace and requiring every word to match somewhere independently, not as one phrase, since real words can end up separated by markup in the underlying HTML. Deliberately separate from Node.search, the public content search, which stays tsvector-based and head-only. chapters generalizes into /admin/nodes/tags/:tags for an arbitrary tag list (OR'd, not AND'd), sharing the controller action but rendering its own template rather than branching inside one view.
Diffstat (limited to 'app')
-rw-r--r--app/controllers/nodes_controller.rb45
-rw-r--r--app/models/node.rb22
-rw-r--r--app/views/nodes/_node_list.html.erb39
-rw-r--r--app/views/nodes/chapters.html.erb9
-rw-r--r--app/views/nodes/drafts.html.erb3
-rw-r--r--app/views/nodes/mine.html.erb3
-rw-r--r--app/views/nodes/recent.html.erb3
-rw-r--r--app/views/nodes/tags.html.erb3
8 files changed, 127 insertions, 0 deletions
diff --git a/app/controllers/nodes_controller.rb b/app/controllers/nodes_controller.rb
index 6fcd930..ede91ad 100644
--- a/app/controllers/nodes_controller.rb
+++ b/app/controllers/nodes_controller.rb
@@ -183,6 +183,39 @@ class NodesController < ApplicationController
183 render plain: slug_for(params[:title]) 183 render plain: slug_for(params[:title])
184 end 184 end
185 185
186 # Filter functions for admin views
187 def drafts
188 base = Node.where("draft_id IS NOT NULL OR autosave_id IS NOT NULL")
189 @nodes = index_matching(base)
190 end
191
192 def recent
193 base = Node.where(
194 "nodes.updated_at < ? AND nodes.updated_at > ? AND nodes.parent_id IS NOT NULL",
195 Time.now, Time.now - 14.days
196 )
197 @nodes = index_matching(base)
198 end
199
200 def mine
201 base = Node.joins(:pages)
202 .where("pages.user_id = ? or pages.editor_id = ?", current_user, current_user)
203 .distinct
204 @nodes = index_matching(base)
205 end
206
207 def chapters
208 @kind_keys = Array(params[:kinds]) & %w[erfa chaostreff]
209 @kind_keys = %w[erfa chaostreff] if @kind_keys.empty?
210 tags = @kind_keys.flat_map { |key| CccConventions::NODE_KINDS[key][:tags] }
211 @nodes = nodes_matching_tags(tags)
212 end
213
214 def tags
215 tags = params[:tags].to_s.split(',').map(&:strip).reject(&:blank?)
216 @nodes = nodes_matching_tags(tags)
217 end
218
186 private 219 private
187 220
188 def slug_for(title) 221 def slug_for(title)
@@ -214,4 +247,16 @@ class NodesController < ApplicationController
214 config && config[:parent] ? config[:parent].call.id : nil 247 config && config[:parent] ? config[:parent].call.id : nil
215 end 248 end
216 end 249 end
250
251 def nodes_matching_tags(tags)
252 matching_pages = Page.tagged_with(tags, any: true).reselect(:id)
253 base = Node.where(head_id: matching_pages).or(Node.where(draft_id: matching_pages))
254 index_matching(base)
255 end
256
257 def index_matching(base_scope)
258 scope = base_scope.includes(:head, :draft)
259 scope = scope.merge(Node.editor_search(params[:q])) if params[:q].present?
260 scope.order("nodes.updated_at desc").paginate(page: params[:page], per_page: 25)
261 end
217end 262end
diff --git a/app/models/node.rb b/app/models/node.rb
index 9cb4840..24f3cd0 100644
--- a/app/models/node.rb
+++ b/app/models/node.rb
@@ -345,6 +345,28 @@ class Node < ApplicationRecord
345 .distinct 345 .distinct
346 end 346 end
347 347
348 # This one is for admin-only views, where finding a draft is the point.
349 # Substring match on whichever of head/draft is present.
350 def self.editor_search(term)
351 words = term.to_s.split(/\s+/).reject(&:blank?)
352 return none if words.empty?
353
354 conditions = []
355 binds = {}
356
357 words.each_with_index do |word, i|
358 key = "term#{i}"
359 binds[key.to_sym] = "%#{sanitize_sql_like(word)}%"
360 conditions << "(head_translations.title ILIKE :#{key} OR head_translations.abstract ILIKE :#{key} " \
361 "OR draft_translations.title ILIKE :#{key} OR draft_translations.abstract ILIKE :#{key})"
362 end
363
364 joins("LEFT JOIN page_translations head_translations ON head_translations.page_id = nodes.head_id")
365 .joins("LEFT JOIN page_translations draft_translations ON draft_translations.page_id = nodes.draft_id")
366 .where(conditions.join(" AND "), binds)
367 .distinct
368 end
369
348 protected 370 protected
349 def lock_for! current_user 371 def lock_for! current_user
350 self.lock_owner = current_user 372 self.lock_owner = current_user
diff --git a/app/views/nodes/_node_list.html.erb b/app/views/nodes/_node_list.html.erb
new file mode 100644
index 0000000..03f38b4
--- /dev/null
+++ b/app/views/nodes/_node_list.html.erb
@@ -0,0 +1,39 @@
1<%= form_tag url_for(controller: params[:controller], action: params[:action]), method: :get, class: "node_search_form" do %>
2 <% Array(params[:kinds]).each do |kind| %>
3 <%= hidden_field_tag "kinds[]", kind %>
4 <% end %>
5 <%= hidden_field_tag :tags, params[:tags] if params[:tags].present? %>
6 <%= text_field_tag :q, params[:q], placeholder: "Search title, abstract, body…" %>
7 <%= submit_tag "Search", class: "action_button" %>
8 <% if params[:q].present? || params[:kinds].present? || params[:tags].present? %>
9 <%= link_to "Reset", url_for(controller: params[:controller], action: params[:action]) %>
10 <% end %>
11<% end %>
12
13<%= will_paginate @nodes %>
14<table class="node_table">
15 <tr class="header">
16 <th class="node_id">ID</th>
17 <th class="title">Title</th>
18 <th class="actions">Actions</th>
19 <th class="editor">Locked by</th>
20 <th class="revision">Rev.</th>
21 </tr>
22 <% @nodes.each do |node| %>
23 <tr class="<%= cycle("even", "odd") %>">
24 <td class="node_id"><%= node.id %></td>
25 <td class="title">
26 <h4><%= link_to title_for_node(node), node_path(node) %></h4>
27 <p><%= link_to_path(node.unique_name, node.unique_name) %></p>
28 </td>
29 <td class="actions">
30 <%= link_to 'show', node_path(node) %>
31 <%= link_to 'edit', edit_node_path(node) %>
32 <%= link_to 'revisions', node_revisions_path(node) %>
33 </td>
34 <td><%= node.lock_owner.login if node.lock_owner %></td>
35 <td><%= node.draft ? node.draft.revision : (node.head ? node.head.revision : "EMPTY") %></td>
36 </tr>
37 <% end %>
38</table>
39<%= will_paginate @nodes %>
diff --git a/app/views/nodes/chapters.html.erb b/app/views/nodes/chapters.html.erb
new file mode 100644
index 0000000..939f19c
--- /dev/null
+++ b/app/views/nodes/chapters.html.erb
@@ -0,0 +1,9 @@
1<h1>Chapters</h1>
2
3<%= form_tag chapters_nodes_path, method: :get do %>
4 <label><%= check_box_tag 'kinds[]', 'erfa', @kind_keys.include?('erfa') %> Erfa</label>
5 <label><%= check_box_tag 'kinds[]', 'chaostreff', @kind_keys.include?('chaostreff') %> Chaostreff</label>
6 <%= submit_tag "Filter", class: "action_button" %>
7<% end %>
8
9<%= render 'node_list' %>
diff --git a/app/views/nodes/drafts.html.erb b/app/views/nodes/drafts.html.erb
new file mode 100644
index 0000000..6d0830c
--- /dev/null
+++ b/app/views/nodes/drafts.html.erb
@@ -0,0 +1,3 @@
1<h1>Nodes with drafts or autosaves</h1>
2
3<%= render 'node_list' %>
diff --git a/app/views/nodes/mine.html.erb b/app/views/nodes/mine.html.erb
new file mode 100644
index 0000000..fc5680a
--- /dev/null
+++ b/app/views/nodes/mine.html.erb
@@ -0,0 +1,3 @@
1<h1>My work</h1>
2
3<%= render 'node_list' %>
diff --git a/app/views/nodes/recent.html.erb b/app/views/nodes/recent.html.erb
new file mode 100644
index 0000000..3602775
--- /dev/null
+++ b/app/views/nodes/recent.html.erb
@@ -0,0 +1,3 @@
1<h1>Recently changed</h1>
2
3<%= render 'node_list' %>
diff --git a/app/views/nodes/tags.html.erb b/app/views/nodes/tags.html.erb
new file mode 100644
index 0000000..0ebc6ce
--- /dev/null
+++ b/app/views/nodes/tags.html.erb
@@ -0,0 +1,3 @@
1<h1>Nodes tagged: <%= params[:tags] %></h1>
2
3<%= render 'node_list' %>