summaryrefslogtreecommitdiff
path: root/app/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'app/controllers')
-rw-r--r--app/controllers/admin_controller.rb82
-rw-r--r--app/controllers/application_controller.rb12
-rw-r--r--app/controllers/events_controller.rb23
-rw-r--r--app/controllers/menu_items_controller.rb2
-rw-r--r--app/controllers/nodes_controller.rb248
-rw-r--r--app/controllers/page_translations_controller.rb109
-rw-r--r--app/controllers/related_assets_controller.rb49
-rw-r--r--app/controllers/revisions_controller.rb25
-rw-r--r--app/controllers/shared_previews_controller.rb22
-rw-r--r--app/controllers/users_controller.rb2
10 files changed, 471 insertions, 103 deletions
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index d671384..37fd78b 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -1,74 +1,74 @@
1class AdminController < ApplicationController 1class AdminController < ApplicationController
2 2
3 # Private 3 # Private
4 4
5 before_action :login_required 5 before_action :login_required
6 6
7 def index 7 def index
8 @drafts = Node.where("draft_id IS NOT NULL") 8 @drafts = Node.drafts_and_autosaves(current_user_id: current_user.id).limit(5)
9 .limit(50).order("updated_at desc") 9 @recent_changes = Node.recently_changed.limit(5)
10 10 end
11 @drafts_count = Node.where("draft_id IS NOT NULL").count
12
13 @recent_changes = Node.where(
14 "updated_at < ? AND updated_at > ? AND parent_id IS NOT NULL",
15 Time.now, Time.now - 14.days
16 ).limit(50).order("updated_at desc")
17
18 all_nodes = Node.root.self_and_descendants
19 @sitemap_depth = {}
20 Node.each_with_level(all_nodes) do |node, level|
21 @sitemap_depth[node.id] = level
22 end
23 @sitemap = all_nodes.to_a.sort! { |node1,node2| node1.lft <=> node2.lft }.delete_if { |node| node.update? }
24
25 @mypages = Page.where("user_id = ? or editor_id = ?", @current_user, @current_user)
26 11
27 @mynodes = Node.joins(:pages) 12 def conventions
28 .where("pages.user_id = ? or pages.editor_id = ?", @current_user, @current_user) 13 @node_kinds = CccConventions::NODE_KINDS
29 .order("updated_at desc")
30 .uniq.first(50)
31 end 14 end
32 15
33 def search 16 def search
34 @results = Node.search params[:search_term], :per_page => 1000 17 @results = Node.editor_search(params[:search_term])
35 18
36 respond_to do |format| 19 respond_to do |format|
37 format.html do 20 format.html do
38 render :template => 'admin/search_results' 21 render :template => 'admin/search_results'
39 end 22 end
40 format.js do 23 format.js do
41 render( :json => @results.map do |node| 24 render( :json => @results.map do |node|
42 if node 25 if node
43 { :id => node.id, :title => node.title, :unique_name => node.unique_name, :node_path => node_path(node) } 26 { :id => node.id, :title => node.title, :unique_name => node.unique_name, :node_path => node_path(node) }
44 end 27 end
45 end 28 end
46 ) 29 )
47 30
48 end 31 end
32 end
33 end
34
35 def dashboard_search
36 term = params[:search_term]
37
38 if term.blank?
39 render json: { tags: [], nodes: [] }
40 return
49 end 41 end
42
43 render json: {
44 tags: ActsAsTaggableOn::Tag.named_like(term).limit(5).map { |tag|
45 { name: tag.name, tag_path: tags_nodes_path(tags: tag.name) }
46 },
47 nodes: Node.editor_search(term).limit(10).map { |node|
48 { node_id: node.id, title: node.title, unique_name: node.unique_name, node_path: node_path(node) }
49 }
50 }
50 end 51 end
51 52
52 def menu_search 53 def menu_search
53 if params[:search_term] == "Root" 54 if params[:search_term] == "Root"
54 @results = [Node.root] 55 @results = [Node.root]
55 else 56 else
56 @results = Node.search params[:search_term] 57 @results = Node.editor_search(params[:search_term])
57 end 58 end
58 59
59 respond_to do |format| 60 respond_to do |format|
60 format.html do 61 format.html do
61 render :partial => 'admin/menu_search_results' 62 render :partial => 'admin/menu_search_results'
62 end 63 end
63 64
64 format.js do 65 format.js do
65 render( :json => @results.map do |node| 66 render( :json => @results.map do |node|
66 {:node_id => node.id, :title => node.title, :unique_name => node.unique_name} 67 {:node_id => node.id, :title => node.title, :unique_name => node.unique_name, :node_path => node_path(node)}
67 end 68 end
68 ) 69 )
69 70
70 end 71 end
71 end 72 end
72 end 73 end
73
74end 74end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 75f92c3..cbeb40d 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -5,6 +5,8 @@ class ApplicationController < ActionController::Base
5 5
6 before_action :set_locale 6 before_action :set_locale
7 7
8 helper_method :safe_return_to
9
8 protected 10 protected
9 11
10 def set_locale 12 def set_locale
@@ -18,4 +20,14 @@ class ApplicationController < ActionController::Base
18 def default_url_options 20 def default_url_options
19 { locale: I18n.locale == I18n.default_locale ? nil : I18n.locale } 21 { locale: I18n.locale == I18n.default_locale ? nil : I18n.locale }
20 end 22 end
23
24 def safe_return_to(url)
25 return events_path if url.blank?
26 uri = URI.parse(url)
27 return events_path if uri.host.present?
28 return events_path unless url.start_with?('/')
29 url
30 rescue URI::InvalidURIError
31 events_path
32 end
21end 33end
diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb
index f50da3e..be3f547 100644
--- a/app/controllers/events_controller.rb
+++ b/app/controllers/events_controller.rb
@@ -9,18 +9,24 @@ class EventsController < ApplicationController
9 # GET /events 9 # GET /events
10 # GET /events.xml 10 # GET /events.xml
11 def index 11 def index
12 @events = Event.all 12 @events = Event.order(:id)
13 13
14 respond_to do |format| 14 respond_to do |format|
15 format.html # index.html.erb 15 format.html { @events = @events.paginate(page: params[:page], per_page: 25) }
16 format.xml { render :xml => @events } 16 format.xml { render :xml => @events }
17 end 17 end
18 end 18 end
19 19
20 # GET /events/without_node
21 def without_node
22 @events = Event.where(node_id: nil).order(:start_time).paginate(page: params[:page], per_page: 25)
23 end
24
20 # GET /events/1 25 # GET /events/1
21 # GET /events/1.xml 26 # GET /events/1.xml
22 def show 27 def show
23 @event = Event.find(params[:id]) 28 @event = Event.find(params[:id])
29 @return_to = params[:return_to] || events_path
24 30
25 respond_to do |format| 31 respond_to do |format|
26 format.html # show.html.erb 32 format.html # show.html.erb
@@ -31,7 +37,14 @@ class EventsController < ApplicationController
31 # GET /events/new 37 # GET /events/new
32 # GET /events/new.xml 38 # GET /events/new.xml
33 def new 39 def new
34 @event = Event.new(:node_id => params[:node_id]) 40 @event = Event.new(
41 node_id: params[:node_id],
42 tag_list: params[:tag_list]
43 )
44
45 if params[:tag_list].present? && params[:auto_tag_source].present?
46 flash.now[:notice] = "Tag '#{params[:tag_list]}' was pre-filled because this page is tagged '#{params[:auto_tag_source]}'. You can remove it below."
47 end
35 48
36 respond_to do |format| 49 respond_to do |format|
37 format.html # new.html.erb 50 format.html # new.html.erb
@@ -53,7 +66,7 @@ class EventsController < ApplicationController
53 respond_to do |format| 66 respond_to do |format|
54 if @event.save 67 if @event.save
55 flash[:notice] = 'Event was successfully created.' 68 flash[:notice] = 'Event was successfully created.'
56 format.html { redirect_to(@event.node ? edit_node_path(@event.node) : edit_event_path(@event)) } 69 format.html { redirect_to(safe_return_to(params[:return_to] || (@event.node ? edit_node_path(@event.node) : edit_event_path(@event)))) }
57 format.xml { render :xml => @event, :status => :created, :location => @event } 70 format.xml { render :xml => @event, :status => :created, :location => @event }
58 else 71 else
59 format.html { render :action => "new" } 72 format.html { render :action => "new" }
@@ -94,6 +107,6 @@ class EventsController < ApplicationController
94 private 107 private
95 108
96 def event_params 109 def event_params
97 params.require(:event).permit(:start_time, :end_time, :rrule, :custom_rrule, :allday, :url, :latitude, :longitude, :node_id, :location) 110 params.require(:event).permit(:title, :description, :start_time, :end_time, :rrule, :allday, :url, :latitude, :longitude, :node_id, :location, :tag_list)
98 end 111 end
99end 112end
diff --git a/app/controllers/menu_items_controller.rb b/app/controllers/menu_items_controller.rb
index 1b1eb59..314d1ea 100644
--- a/app/controllers/menu_items_controller.rb
+++ b/app/controllers/menu_items_controller.rb
@@ -14,7 +14,7 @@ class MenuItemsController < ApplicationController
14 end 14 end
15 15
16 def new 16 def new
17 @menu_item = MenuItem.new menu_item_params 17 @menu_item = MenuItem.new
18 end 18 end
19 19
20 def create 20 def create
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 @@
1class NodesController < ApplicationController 1class 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
137end 285end
diff --git a/app/controllers/page_translations_controller.rb b/app/controllers/page_translations_controller.rb
new file mode 100644
index 0000000..9446cd2
--- /dev/null
+++ b/app/controllers/page_translations_controller.rb
@@ -0,0 +1,109 @@
1class PageTranslationsController < ApplicationController
2 layout 'admin'
3
4 before_action :login_required
5 before_action :find_node
6 before_action :find_locale, :only => [:show, :edit, :update, :autosave, :destroy]
7
8 def index
9 page = @node.draft || @node.head
10 @translations = page ? page.translation_summary : []
11 end
12
13 def show
14 @page = @node.draft || @node.head
15 @translation = @page.translations.find_by(:locale => @locale)
16 @default_translation = @page.translations.find_by(:locale => I18n.default_locale)
17 end
18
19 def edit
20 @node.lock_for_editing!(current_user)
21 @page = @node.draft || @node.head
22 @translation = @page.translations.find_by(:locale => @locale)
23 rescue LockedByAnotherUser => e
24 flash[:error] = e.message
25 redirect_to node_path(@node)
26 end
27
28 def update
29 Globalize.with_locale(@locale) { @node.autosave!(translation_params, current_user) }
30 @node.save_draft!(current_user)
31 flash[:notice] = "#{@locale.upcase} translation saved. Publish the draft to make it live."
32
33 if params[:commit] == "Save + Unlock + Exit"
34 @node.unlock!
35 redirect_to node_path(@node)
36 else
37 redirect_to edit_node_translation_path(@node, @locale)
38 end
39 rescue LockedByAnotherUser => e
40 flash[:error] = e.message
41 redirect_to node_path(@node)
42 end
43
44 def autosave
45 Globalize.with_locale(@locale) { @node.autosave!(translation_params, current_user) }
46 head :ok
47 rescue LockedByAnotherUser => e
48 render plain: e.message, status: :locked
49 rescue ActiveRecord::RecordInvalid => e
50 render plain: e.message, status: :unprocessable_entity
51 rescue StandardError => e
52 render plain: "Autosave failed", status: :internal_server_error
53 end
54
55 def destroy
56 base = @node.draft || @node.head
57 unless base && base.translated_locales.include?(@locale)
58 flash[:error] = "No #{@locale.to_s.upcase} translation exists to remove."
59 return redirect_to node_path(@node)
60 end
61
62 if (base.translated_locales - [@locale]).empty?
63 flash[:error] = "Can't remove the only remaining translation."
64 return redirect_to node_path(@node)
65 end
66
67 draft = ensure_editable_draft
68 draft.translations.where(:locale => @locale).delete_all
69 draft.reload
70
71 flash[:notice] = "#{@locale.upcase} translation removed from the draft. Publish to make this permanent."
72 redirect_to node_path(@node)
73 rescue LockedByAnotherUser => e
74 flash[:error] = e.message
75 redirect_to node_path(@node)
76 end
77
78 private
79
80 def find_node
81 @node = Node.find(params[:node_id])
82 end
83
84 # Every remaining action's :translation_locale is already
85 # router-constrained to non-default locales, so this should never
86 # actually fire in practice -- it's a deliberate second check, kept
87 # as a drift detector in case the route constraint and this list
88 # (both driven by Page.non_default_locales) ever disagree.
89 def find_locale
90 candidate = params[:translation_locale].to_s.to_sym
91 unless Page.non_default_locales.include?(candidate)
92 raise ActionController::RoutingError, "Unknown or default locale"
93 end
94 @locale = candidate
95 end
96
97 # Never mutates head directly. Locks first (same guard as the
98 # primary editor), then reuses an existing draft or creates one --
99 # deliberately not going through the autosave buffer: this is a
100 # plain, explicit Save, not a live-typing surface.
101 def ensure_editable_draft
102 @node.lock_for_editing!(current_user)
103 @node.draft || @node.create_new_draft(current_user)
104 end
105
106 def translation_params
107 params.fetch(:page, {}).permit(:title, :abstract, :body)
108 end
109end
diff --git a/app/controllers/related_assets_controller.rb b/app/controllers/related_assets_controller.rb
new file mode 100644
index 0000000..479ebec
--- /dev/null
+++ b/app/controllers/related_assets_controller.rb
@@ -0,0 +1,49 @@
1class RelatedAssetsController < ApplicationController
2 before_action :login_required
3 before_action :find_node
4
5 def search
6 term = params[:search_term].to_s.strip
7 attached_ids = @node.editable_page.related_assets.pluck(:asset_id)
8 scope = Asset.images.where.not(id: attached_ids)
9
10 results = if term.present?
11 scope.where("name ILIKE ?", "%#{term}%").limit(10)
12 else
13 scope.order(created_at: :desc).limit(5)
14 end
15
16 render json: results.map { |a|
17 { id: a.id, name: a.name, thumb_url: a.upload.url(:thumb) }
18 }
19 end
20
21 def create
22 asset = Asset.find(params[:asset_id])
23 related = @node.editable_page.related_assets.find_or_create_by!(asset: asset)
24
25 render json: {
26 id: related.id,
27 asset_id: asset.id,
28 name: asset.name,
29 thumb_url: asset.upload.url(:thumb),
30 url: node_related_asset_path(@node, related)
31 }
32 end
33
34 def destroy
35 @node.editable_page.related_assets.find(params[:id]).destroy
36 head :ok
37 end
38
39 def update
40 @node.editable_page.related_assets.find(params[:id]).insert_at(params[:position].to_i)
41 head :ok
42 end
43
44 private
45
46 def find_node
47 @node = Node.find(params[:node_id])
48 end
49end
diff --git a/app/controllers/revisions_controller.rb b/app/controllers/revisions_controller.rb
index 42d667e..0aed243 100644
--- a/app/controllers/revisions_controller.rb
+++ b/app/controllers/revisions_controller.rb
@@ -13,16 +13,31 @@ class RevisionsController < ApplicationController
13 13
14 def diff 14 def diff
15 @node = Node.find(params[:node_id]) 15 @node = Node.find(params[:node_id])
16 16
17 if @node.pages.length > 1 17 if @node.pages.length > 1
18 params[:start_revision] ||= @node.pages.all[-2].revision 18 params[:start_revision] ||= @node.pages.all[-2].revision
19 params[:end_revision] ||= @node.pages.all[-1].revision 19 params[:end_revision] ||= @node.pages.all[-1].revision
20 else 20 else
21 params[:start], params[:end] = 1, 1 21 params[:start_revision], params[:end_revision] = 1, 1
22 end
23
24 @start = @node.resolve_page_reference(params[:start_revision])
25 @end = @node.resolve_page_reference(params[:end_revision])
26
27 if @start.nil? || @end.nil?
28 flash[:error] = "That comparison is no longer available."
29 redirect_to(node_path(@node)) and return
22 end 30 end
23 31
24 @start = @node.pages.find_by_revision( params[:start_revision] ) 32 @locale_summary = @end.locale_diff_summary(@start)
25 @end = @node.pages.find_by_revision( params[:end_revision] ) 33 requested_locale = params[:locale].presence&.to_sym
34 default_locale = @locale_summary.find { |s| s[:changed] }&.dig(:locale) || I18n.default_locale
35 @locale = @locale_summary.any? { |s| s[:locale] == requested_locale } ? requested_locale : default_locale
36
37 @diff_view = params[:view] == "side_by_side" ? :side_by_side : :inline
38 @diff = @end.diff_against(@start, view: @diff_view, locale: @locale)
39 @available_layer_pairs = @node.available_layer_pairs
40 @locked_by_other = @node.locked? && @node.lock_owner != current_user
26 end 41 end
27 42
28 def show 43 def show
diff --git a/app/controllers/shared_previews_controller.rb b/app/controllers/shared_previews_controller.rb
new file mode 100644
index 0000000..f6fb45a
--- /dev/null
+++ b/app/controllers/shared_previews_controller.rb
@@ -0,0 +1,22 @@
1class SharedPreviewsController < ApplicationController
2 def show
3 @page = Page.find_by!(preview_token: params[:token])
4 node = @page.node
5
6 if node
7 is_head = node.head_id == @page.id
8 is_draft = node.draft_id == @page.id
9
10 currently_public = is_head && @page.public?
11 superseded = !is_head && !is_draft
12
13 if superseded || currently_public
14 redirect_to @page.public_link
15 return
16 end
17 end
18
19 response.headers['X-Robots-Tag'] = 'noindex'
20 render template: @page.valid_template, layout: "application"
21 end
22end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index f01691f..6572b7a 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -15,7 +15,7 @@ class UsersController < ApplicationController
15 end 15 end
16 16
17 def new 17 def new
18 @user = User.new 18 @user = User.new(admin: params[:admin].present?)
19 end 19 end
20 20
21 def create 21 def create