diff options
| -rw-r--r-- | app/controllers/admin_controller.rb | 1 | ||||
| -rw-r--r-- | app/views/admin/index.html.erb | 25 | ||||
| -rw-r--r-- | lib/tasks/node_actions.rake | 61 | ||||
| -rw-r--r-- | public/javascripts/admin_search.js | 30 | ||||
| -rw-r--r-- | public/javascripts/related_assets.js | 9 | ||||
| -rw-r--r-- | public/stylesheets/admin.css | 13 |
6 files changed, 115 insertions, 24 deletions
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 | |||
| 7 | def index | 7 | def index |
| 8 | @drafts = Node.drafts_and_autosaves(current_user_id: current_user.id).limit(5) | 8 | @drafts = Node.drafts_and_autosaves(current_user_id: current_user.id).limit(5) |
| 9 | @recent_changes = Node.recently_changed.limit(5) | 9 | @recent_changes = Node.recently_changed.limit(5) |
| 10 | @actions = NodeAction.order(:occurred_at => :desc, :id => :desc).limit(5) | ||
| 10 | end | 11 | end |
| 11 | 12 | ||
| 12 | def conventions | 13 | 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 @@ | |||
| 40 | <%= link_to "See all drafts →", drafts_nodes_path %> | 40 | <%= link_to "See all drafts →", drafts_nodes_path %> |
| 41 | </div> | 41 | </div> |
| 42 | 42 | ||
| 43 | <div class="dashboard_widget"> | 43 | <!--div class="dashboard_widget"> |
| 44 | <h3>Recent changes</h3> | 44 | <h3>Recent changes</h3> |
| 45 | <ul> | 45 | <ul> |
| 46 | <%= render partial: "nodes/recent_change_item", collection: @recent_changes, as: :node %> | 46 | <%= render partial: "nodes/recent_change_item", collection: @recent_changes, as: :node %> |
| 47 | </ul> | 47 | </ul> |
| 48 | <%= link_to "See all recent changes →", recent_nodes_path %> | 48 | <%= link_to "See all recent changes →", recent_nodes_path %> |
| 49 | </div--> | ||
| 50 | |||
| 51 | <div class="dashboard_widget"> | ||
| 52 | <h3>Recent changes</h3> | ||
| 53 | <table id="node_action_list"> | ||
| 54 | <% @actions.each do |action| %> | ||
| 55 | <tr class="node_action node_action--<%= action.action %>"> | ||
| 56 | <td class="node_action_time"><%= raw(action.occurred_at.strftime("%Y-%m-%d %H:%M").gsub(" ", "<br/>")) %></td> | ||
| 57 | <td class="node_action_body"> | ||
| 58 | <%= action_summary(action) %> | ||
| 59 | <% if action.node_id && params[:node_id].blank? %> | ||
| 60 | <%= link_to t("node_actions.node_history"), admin_log_path(:node_id => action.node_id), :class => "node_action_zoom" %> | ||
| 61 | <% end %> | ||
| 62 | <% if action.inferred_from %> | ||
| 63 | <span class="node_action_inferred" title="<%= action.inferred_from %>"><%= t("node_actions.backfilled") %></span> | ||
| 64 | <% end %> | ||
| 65 | <%= render "node_actions/change_details", :action_entry => action if action_details?(action) %> | ||
| 66 | </td> | ||
| 67 | </tr> | ||
| 68 | <% end %> | ||
| 69 | </table> | ||
| 70 | <%= link_to "See all recent changes →", admin_log_path %> | ||
| 49 | </div> | 71 | </div> |
| 72 | |||
| 50 | </div> | 73 | </div> |
| 51 | 74 | ||
| 52 | <div id="dashboard_housekeeping"> | 75 | <div id="dashboard_housekeeping"> |
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 @@ | |||
| 1 | namespace :node_actions do | ||
| 2 | desc "Rebuild inferred NodeAction entries from existing revisions and " \ | ||
| 3 | "node timestamps. Witnessed entries (inferred_from IS NULL) are " \ | ||
| 4 | "never touched; previously inferred ones are deleted and " \ | ||
| 5 | "regenerated, so the task is idempotent. Scope with NODE_ID=n." | ||
| 6 | task :backfill => :environment do | ||
| 7 | scope = Node.where.not(:parent_id => nil) | ||
| 8 | scope = scope.where(:id => ENV["NODE_ID"]) if ENV["NODE_ID"] | ||
| 9 | |||
| 10 | created = 0 | ||
| 11 | |||
| 12 | ActiveRecord::Base.transaction do | ||
| 13 | stale = NodeAction.where.not(:inferred_from => nil) | ||
| 14 | stale = stale.where(:node_id => ENV["NODE_ID"]) if ENV["NODE_ID"] | ||
| 15 | puts "Removing #{stale.count} previously inferred entries" | ||
| 16 | stale.delete_all | ||
| 17 | |||
| 18 | witnessed_creates = NodeAction.where(:action => "create", :inferred_from => nil).pluck(:node_id).to_set | ||
| 19 | witnessed_promotes = NodeAction.where(:action => "publish", :inferred_from => nil).pluck(:page_id).to_set | ||
| 20 | |||
| 21 | scope.find_each do |node| | ||
| 22 | # Every page row except the current draft was once promoted to | ||
| 23 | # head. Autosaves never carry node_id, so they cannot appear. | ||
| 24 | revisions = node.pages.to_a.reject { |page| page.id == node.draft_id } | ||
| 25 | first_page = node.pages.first | ||
| 26 | |||
| 27 | unless witnessed_creates.include?(node.id) | ||
| 28 | NodeAction.record!( | ||
| 29 | :node => node, :page => first_page, :user => first_page&.editor, | ||
| 30 | :action => "create", | ||
| 31 | :occurred_at => node.created_at, | ||
| 32 | :inferred_from => "from_node_created_at", | ||
| 33 | :title => first_page&.translations&.find_by(:locale => I18n.default_locale)&.title, | ||
| 34 | :path => node.unique_name, | ||
| 35 | :human_readable_node_name => | ||
| 36 | first_page&.translations&.find_by(:locale => I18n.default_locale)&.title) | ||
| 37 | created += 1 | ||
| 38 | end | ||
| 39 | |||
| 40 | previous = nil | ||
| 41 | revisions.each do |page| | ||
| 42 | unless witnessed_promotes.include?(page.id) | ||
| 43 | diff = NodeAction.head_diff(previous, page) | ||
| 44 | NodeAction.record!( | ||
| 45 | :node => node, :page => page, :user => page.editor, | ||
| 46 | :action => "publish", | ||
| 47 | :occurred_at => page.updated_at, | ||
| 48 | :inferred_from => "from_page_revision", | ||
| 49 | :via => "draft", | ||
| 50 | :human_readable_node_name => diff[:title]["to"], | ||
| 51 | **diff) | ||
| 52 | created += 1 | ||
| 53 | end | ||
| 54 | previous = page | ||
| 55 | end | ||
| 56 | end | ||
| 57 | end | ||
| 58 | |||
| 59 | puts "Created #{created} inferred entries" | ||
| 60 | end | ||
| 61 | end | ||
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) { | |||
| 72 | found = false; | 72 | found = false; |
| 73 | for (var i = 0; i < data.length; i++) { | 73 | for (var i = 0; i < data.length; i++) { |
| 74 | (function(node) { | 74 | (function(node) { |
| 75 | var link; | 75 | var anchor = $("<a>").attr("href", onSelect ? "#" : node.node_path); |
| 76 | anchor.append(document.createTextNode(node.title || "")); | ||
| 77 | anchor.append($("<span>", { "class": "result_path" }).text(node.unique_name || "")); | ||
| 78 | var link = $("<p>").append(anchor); | ||
| 79 | |||
| 76 | if (onSelect) { | 80 | if (onSelect) { |
| 77 | link = $( | 81 | anchor.bind("click", function() { |
| 78 | "<p><a href='#'>" + node.title + | ||
| 79 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | ||
| 80 | ); | ||
| 81 | link.find("a").bind("click", function() { | ||
| 82 | onSelect(node); | 82 | onSelect(node); |
| 83 | results.slideUp(); | 83 | results.slideUp(); |
| 84 | results.empty(); | 84 | results.empty(); |
| 85 | return false; | 85 | return false; |
| 86 | }); | 86 | }); |
| 87 | } else { | ||
| 88 | link = $( | ||
| 89 | "<p><a href='" + node.node_path + "'>" + node.title + | ||
| 90 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | ||
| 91 | ); | ||
| 92 | } | 87 | } |
| 88 | |||
| 93 | results.append(link); | 89 | results.append(link); |
| 94 | found = true; | 90 | found = true; |
| 95 | })(data[i]); | 91 | })(data[i]); |
| @@ -150,7 +146,9 @@ dashboard_search = { | |||
| 150 | results.append("<p class='search_group_label'>Tags</p>"); | 146 | results.append("<p class='search_group_label'>Tags</p>"); |
| 151 | var tag_row = $("<div class='search_tag_row'></div>"); | 147 | var tag_row = $("<div class='search_tag_row'></div>"); |
| 152 | data.tags.forEach(function(tag) { | 148 | data.tags.forEach(function(tag) { |
| 153 | tag_row.append("<a class='search_tag_pill' href='" + tag.tag_path + "'>" + tag.name + "</a>"); | 149 | tag_row.append( |
| 150 | $("<a>", { "class": "search_tag_pill" }).attr("href", tag.tag_path).text(tag.name || "") | ||
| 151 | ); | ||
| 154 | found = true; | 152 | found = true; |
| 155 | }); | 153 | }); |
| 156 | results.append(tag_row); | 154 | results.append(tag_row); |
| @@ -159,10 +157,10 @@ dashboard_search = { | |||
| 159 | if (data.nodes.length) { | 157 | if (data.nodes.length) { |
| 160 | results.append("<p class='search_group_label'>Pages</p>"); | 158 | results.append("<p class='search_group_label'>Pages</p>"); |
| 161 | data.nodes.forEach(function(node) { | 159 | data.nodes.forEach(function(node) { |
| 162 | results.append( | 160 | var anchor = $("<a>").attr("href", node.node_path); |
| 163 | "<p><a href='" + node.node_path + "'>" + node.title + | 161 | anchor.append(document.createTextNode(node.title || "")); |
| 164 | "<span class='result_path'>" + node.unique_name + "</span></a></p>" | 162 | anchor.append($("<span>", { "class": "result_path" }).text(node.unique_name || "")); |
| 165 | ); | 163 | results.append($("<p>").append(anchor)); |
| 166 | found = true; | 164 | found = true; |
| 167 | }); | 165 | }); |
| 168 | } | 166 | } |
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 = { | |||
| 15 | renderResults: function(data, results) { | 15 | renderResults: function(data, results) { |
| 16 | var found = false; | 16 | var found = false; |
| 17 | data.forEach(function(asset) { | 17 | data.forEach(function(asset) { |
| 18 | var item = $( | 18 | var item = $("<a>", { href: "#", "class": "related_asset_result" }); |
| 19 | "<a href='#' class='related_asset_result'>" + | 19 | item.append($("<img>", { src: asset.thumb_url, alt: "" })); |
| 20 | "<img src='" + asset.thumb_url + "' alt=''>" + | 20 | item.append($("<span>").text(asset.name || "")); |
| 21 | "<span>" + asset.name + "</span>" + | ||
| 22 | "</a>" | ||
| 23 | ); | ||
| 24 | item.bind("click", function() { | 21 | item.bind("click", function() { |
| 25 | related_assets.attach(asset.id, createUrl, list); | 22 | related_assets.attach(asset.id, createUrl, list); |
| 26 | return false; | 23 | return false; |
diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css index c1273c6..128933b 100644 --- a/public/stylesheets/admin.css +++ b/public/stylesheets/admin.css | |||
| @@ -37,10 +37,13 @@ td { | |||
| 37 | depend on this rule for vertical padding. table.node_table td resets | 37 | depend on this rule for vertical padding. table.node_table td resets |
| 38 | padding-top/bottom to 0 explicitly and does not depend on it. */ | 38 | padding-top/bottom to 0 explicitly and does not depend on it. */ |
| 39 | 39 | ||
| 40 | input[type=text], textarea { | 40 | input[type=text], |
| 41 | input[type=password], | ||
| 42 | textarea { | ||
| 41 | font-size: 1rem; | 43 | font-size: 1rem; |
| 42 | font-family: Helvetica; | 44 | font-family: Helvetica; |
| 43 | border: 1px solid #989898; | 45 | border: 1px solid #989898; |
| 46 | border-radius: 2px; | ||
| 44 | } | 47 | } |
| 45 | 48 | ||
| 46 | select { | 49 | select { |
| @@ -1499,6 +1502,14 @@ div#image_browser ul li { | |||
| 1499 | Action log | 1502 | Action log |
| 1500 | ============================================================ */ | 1503 | ============================================================ */ |
| 1501 | 1504 | ||
| 1505 | #dashboard_widget td.node_action_time { | ||
| 1506 | white-space: wrap; | ||
| 1507 | } | ||
| 1508 | |||
| 1509 | #node_action_list td.node_action_body { | ||
| 1510 | border-bottom: 1px solid #f1f1f1; | ||
| 1511 | } | ||
| 1512 | |||
| 1502 | #node_action_list td.node_action_time { | 1513 | #node_action_list td.node_action_time { |
| 1503 | white-space: nowrap; | 1514 | white-space: nowrap; |
| 1504 | vertical-align: top; | 1515 | vertical-align: top; |
