From fc4c03cdc3a7c68fe764581287b6b13d92b440fc Mon Sep 17 00:00:00 2001 From: erdgeist Date: Thu, 16 Jul 2026 22:34:51 +0200 Subject: Add a script to backfill action log from existing records --- lib/tasks/node_actions.rake | 61 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 lib/tasks/node_actions.rake diff --git a/lib/tasks/node_actions.rake b/lib/tasks/node_actions.rake new file mode 100644 index 0000000..fdd286c --- /dev/null +++ b/lib/tasks/node_actions.rake @@ -0,0 +1,61 @@ +namespace :node_actions do + desc "Rebuild inferred NodeAction entries from existing revisions and " \ + "node timestamps. Witnessed entries (inferred_from IS NULL) are " \ + "never touched; previously inferred ones are deleted and " \ + "regenerated, so the task is idempotent. Scope with NODE_ID=n." + task :backfill => :environment do + scope = Node.where.not(:parent_id => nil) + scope = scope.where(:id => ENV["NODE_ID"]) if ENV["NODE_ID"] + + created = 0 + + ActiveRecord::Base.transaction do + stale = NodeAction.where.not(:inferred_from => nil) + stale = stale.where(:node_id => ENV["NODE_ID"]) if ENV["NODE_ID"] + puts "Removing #{stale.count} previously inferred entries" + stale.delete_all + + witnessed_creates = NodeAction.where(:action => "create", :inferred_from => nil).pluck(:node_id).to_set + witnessed_promotes = NodeAction.where(:action => "publish", :inferred_from => nil).pluck(:page_id).to_set + + scope.find_each do |node| + # Every page row except the current draft was once promoted to + # head. Autosaves never carry node_id, so they cannot appear. + revisions = node.pages.to_a.reject { |page| page.id == node.draft_id } + first_page = node.pages.first + + unless witnessed_creates.include?(node.id) + NodeAction.record!( + :node => node, :page => first_page, :user => first_page&.editor, + :action => "create", + :occurred_at => node.created_at, + :inferred_from => "from_node_created_at", + :title => first_page&.translations&.find_by(:locale => I18n.default_locale)&.title, + :path => node.unique_name, + :human_readable_node_name => + first_page&.translations&.find_by(:locale => I18n.default_locale)&.title) + created += 1 + end + + previous = nil + revisions.each do |page| + unless witnessed_promotes.include?(page.id) + diff = NodeAction.head_diff(previous, page) + NodeAction.record!( + :node => node, :page => page, :user => page.editor, + :action => "publish", + :occurred_at => page.updated_at, + :inferred_from => "from_page_revision", + :via => "draft", + :human_readable_node_name => diff[:title]["to"], + **diff) + created += 1 + end + previous = page + end + end + end + + puts "Created #{created} inferred entries" + end +end -- cgit v1.3 From 855fc9c9c85720b5e0c6b9086083165492e2a864 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Thu, 16 Jul 2026 17:15:54 +0200 Subject: Construct DOM elements using escaping helpers to prevent XSS --- public/javascripts/admin_search.js | 30 ++++++++++++++---------------- public/javascripts/related_assets.js | 9 +++------ 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/public/javascripts/admin_search.js b/public/javascripts/admin_search.js index 7604bb7..d135503 100644 --- a/public/javascripts/admin_search.js +++ b/public/javascripts/admin_search.js @@ -72,24 +72,20 @@ function initSearchPicker(options) { found = false; for (var i = 0; i < data.length; i++) { (function(node) { - var link; + var anchor = $("").attr("href", onSelect ? "#" : node.node_path); + anchor.append(document.createTextNode(node.title || "")); + anchor.append($("", { "class": "result_path" }).text(node.unique_name || "")); + var link = $("

").append(anchor); + if (onSelect) { - link = $( - "

" + node.title + - "" + node.unique_name + "

" - ); - link.find("a").bind("click", function() { + anchor.bind("click", function() { onSelect(node); results.slideUp(); results.empty(); return false; }); - } else { - link = $( - "

" + node.title + - "" + node.unique_name + "

" - ); } + results.append(link); found = true; })(data[i]); @@ -150,7 +146,9 @@ dashboard_search = { results.append("

Tags

"); var tag_row = $("
"); data.tags.forEach(function(tag) { - tag_row.append("" + tag.name + ""); + tag_row.append( + $("", { "class": "search_tag_pill" }).attr("href", tag.tag_path).text(tag.name || "") + ); found = true; }); results.append(tag_row); @@ -159,10 +157,10 @@ dashboard_search = { if (data.nodes.length) { results.append("

Pages

"); data.nodes.forEach(function(node) { - results.append( - "

" + node.title + - "" + node.unique_name + "

" - ); + var anchor = $("").attr("href", node.node_path); + anchor.append(document.createTextNode(node.title || "")); + anchor.append($("", { "class": "result_path" }).text(node.unique_name || "")); + results.append($("

").append(anchor)); found = true; }); } diff --git a/public/javascripts/related_assets.js b/public/javascripts/related_assets.js index 803332f..3340fa7 100644 --- a/public/javascripts/related_assets.js +++ b/public/javascripts/related_assets.js @@ -15,12 +15,9 @@ related_assets = { renderResults: function(data, results) { var found = false; data.forEach(function(asset) { - var item = $( - "" + - "" + - "" + asset.name + "" + - "" - ); + var item = $("", { href: "#", "class": "related_asset_result" }); + item.append($("", { src: asset.thumb_url, alt: "" })); + item.append($("").text(asset.name || "")); item.bind("click", function() { related_assets.attach(asset.id, createUrl, list); return false; -- cgit v1.3 From c810d1aa93a267dd8fcf8984c5d0b125629efa31 Mon Sep 17 00:00:00 2001 From: erdgeist Date: Fri, 17 Jul 2026 00:42:54 +0200 Subject: Cutover to new Recent Changes widget --- app/controllers/admin_controller.rb | 1 + app/views/admin/index.html.erb | 25 ++++++++++++++++++++++++- public/stylesheets/admin.css | 8 ++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 37fd78b..40cfbdc 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -7,6 +7,7 @@ class AdminController < ApplicationController def index @drafts = Node.drafts_and_autosaves(current_user_id: current_user.id).limit(5) @recent_changes = Node.recently_changed.limit(5) + @actions = NodeAction.order(:occurred_at => :desc, :id => :desc).limit(5) end def conventions diff --git a/app/views/admin/index.html.erb b/app/views/admin/index.html.erb index a9c0512..1b4a0a0 100644 --- a/app/views/admin/index.html.erb +++ b/app/views/admin/index.html.erb @@ -40,13 +40,36 @@ <%= link_to "See all drafts →", drafts_nodes_path %> -

diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css index c1273c6..0aa22f8 100644 --- a/public/stylesheets/admin.css +++ b/public/stylesheets/admin.css @@ -1499,6 +1499,14 @@ div#image_browser ul li { Action log ============================================================ */ +#dashboard_widget td.node_action_time { + white-space: wrap; +} + +#node_action_list td.node_action_body { + border-bottom: 1px solid #f1f1f1; +} + #node_action_list td.node_action_time { white-space: nowrap; vertical-align: top; -- cgit v1.3