Skip to content

Commit 2f4f817

Browse files
authored
Merge pull request #2513 from tf/improve-duplicate-entry
Improve duplicate entry
2 parents fd74106 + a966fb1 commit 2f4f817

28 files changed

Lines changed: 965 additions & 95 deletions

File tree

‎admins/pageflow/entry.rb‎

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,10 @@ module Pageflow
147147

148148
action_item(:duplicate, only: :show, priority: 5) do
149149
if authorized?(:duplicate, entry)
150-
button_to(I18n.t('pageflow.admin.entries.duplicate'),
151-
duplicate_admin_entry_path(entry),
152-
method: :post,
153-
form_class: 'action_item duplicate',
154-
data: {
155-
rel: 'duplicate',
156-
confirm: I18n.t('pageflow.admin.entries.confirm_duplicate')
157-
})
150+
link_to(I18n.t('pageflow.admin.entries.duplicate'),
151+
duplicate_admin_entry_path(entry),
152+
class: 'duplicate',
153+
data: {rel: 'duplicate'})
158154
end
159155
end
160156

@@ -184,11 +180,23 @@ module Pageflow
184180
end
185181
end
186182

187-
member_action :duplicate, method: :post do
188-
entry = Entry.find(params[:id])
189-
authorize!(:duplicate, entry)
190-
new_entry = entry.duplicate
191-
redirect_to(edit_admin_entry_path(new_entry))
183+
member_action :duplicate, method: [:get, :post] do
184+
original_entry = Entry.find(params[:id])
185+
authorize!(:duplicate, original_entry)
186+
187+
duplicate = EntryDuplicate.of(
188+
original_entry,
189+
attributes: permitted_duplicate_attributes(original_entry),
190+
copy_memberships: params.dig(:duplicate, :copy_memberships) != '0'
191+
)
192+
193+
if request.post? && duplicate.save
194+
redirect_to(admin_entry_path(duplicate.new_entry))
195+
else
196+
@page_title = I18n.t('pageflow.admin.entries.duplicate_form.title',
197+
title: original_entry.title)
198+
render(:duplicate, locals: {original_entry:, entry: duplicate.new_entry, duplicate:})
199+
end
192200
end
193201

194202
member_action :snapshot, method: :post do
@@ -364,6 +372,33 @@ def permitted_attributes # rubocop:todo Metrics/AbcSize, Metrics/CyclomaticCompl
364372
end
365373
# rubocop:enable Metrics/PerceivedComplexity
366374

375+
def permitted_duplicate_attributes(original_entry)
376+
attributes = [:title, {permalink_attributes: [:slug, :directory_id]}]
377+
attributes << :account_id if moving_duplicate_to_other_account_allowed?(original_entry)
378+
attributes << :folder_id if authorized?(:configure_folder_for, original_entry)
379+
380+
if authorized?(:update_site_on, original_entry) && site_params_present? &&
381+
site_in_allowed_sites_for?(duplicate_target_account(original_entry))
382+
attributes << :site_id
383+
end
384+
385+
params.fetch(:entry, {}).permit(attributes)
386+
end
387+
388+
def moving_duplicate_to_other_account_allowed?(original_entry)
389+
account_params_present? &&
390+
authorized?(:update_account_on, original_entry) &&
391+
account_policy_scope.entry_movable.include?(Account.find(params[:entry][:account_id]))
392+
end
393+
394+
def duplicate_target_account(original_entry)
395+
if moving_duplicate_to_other_account_allowed?(original_entry)
396+
Account.find(params[:entry][:account_id])
397+
else
398+
original_entry.account
399+
end
400+
end
401+
367402
def permit_feature_states(attributes)
368403
return unless params[:id] && authorized?(:update_feature_configuration_on, resource)
369404

‎app/assets/javascripts/pageflow/admin/entries.js‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ jQuery(function($) {
9191
var siteSelect = $('#entry_site_id', this);
9292
var titleInput = $('#entry_title', this);
9393
var permalinkDirectorySelect = $('#entry_permalink_attributes_directory_id', this);
94+
var permalinkIdInput = $('#entry_permalink_attributes_id', this);
9495

9596
var isRootPermalink = $(this).attr('data-root-permalink') === 'true';
9697

@@ -115,16 +116,20 @@ jQuery(function($) {
115116
}
116117

117118
function updatePermalinkInput() {
118-
fetchPermalinkInput(function(response) {
119+
fetchPermalinkInput('', function(response) {
119120
$('#entry_permalink_attributes_permalink_input').replaceWith(response);
120121

121122
permalinkDirectorySelect = $('#entry_permalink_attributes_directory_id');
122-
permalinkDirectorySelect.on('change', updateSlugPlaceholder);
123+
permalinkDirectorySelect.on('change', updateSlugPlaceholderData);
123124
});
124125
}
125126

126127
function updateSlugPlaceholderData() {
127-
fetchPermalinkInput(function(response) {
128+
var directoryParam = permalinkDirectorySelect.val() ?
129+
'&entry[permalink_attributes][directory_id]=' + permalinkDirectorySelect.val() :
130+
'';
131+
132+
fetchPermalinkInput(directoryParam, function(response) {
128133
$('#entry_permalink_attributes_slug')
129134
.attr('data-placeholder',
130135
$(response).find('input[data-placeholder]').attr('data-placeholder'));
@@ -147,10 +152,12 @@ jQuery(function($) {
147152
);
148153
}
149154

150-
function fetchPermalinkInput(callback) {
155+
function fetchPermalinkInput(extraParams, callback) {
151156
$.get('/admin/entries/permalink_inputs' +
152157
'?entry[account_id]=' + accountSelect.val() +
153158
(siteSelect.val() ? '&entry[site_id]=' + siteSelect.val() : '') +
159+
extraParams +
160+
(permalinkIdInput.val() ? '&permalink_id=' + permalinkIdInput.val() : '') +
154161
'&entry[title]=' + encodeURIComponent(titleInput.val()))
155162
.done(callback);
156163
}
@@ -161,7 +168,7 @@ jQuery(function($) {
161168
siteSelect.on('change', updatePermalinkInput);
162169

163170
titleInput.on('change keyup', debounce(updateSlugPlaceholderData, 300));
164-
permalinkDirectorySelect.on('change', updateSlugPlaceholder);
171+
permalinkDirectorySelect.on('change', updateSlugPlaceholderData);
165172

166173
updateSlugPlaceholder();
167174
});

‎app/assets/stylesheets/pageflow/admin/forms.scss‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@
99
padding: 5px 10px;
1010
}
1111

12+
.choice_title {
13+
font-weight: bold;
14+
15+
&::after {
16+
content: " –";
17+
font-weight: normal;
18+
}
19+
}
20+
1221
fieldset.actions .link_action {
1322
padding: 5px;
1423

‎app/helpers/pageflow/admin/entries_helper.rb‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,26 @@ def entry_comments_tooltip(summary)
7878
t("#{scope}.tooltip", summary: parts.join(', '))
7979
end
8080

81+
def account_input_shown_for?(entry)
82+
authorized?(:update_account_on, entry) &&
83+
account_policy_scope.entry_creatable.many?
84+
end
85+
8186
def entry_type_collection(entry_types = Pageflow.config.entry_types)
8287
entry_types.map(&:name).index_by do |type|
8388
I18n.t(type, scope: 'activerecord.values.pageflow/entry.type_names')
8489
end
8590
end
91+
92+
def permalink_slug_placeholder(entry, permalink, except:)
93+
directory = permalink.directory
94+
95+
unless directory&.site_id == entry.site_id
96+
directory = entry.site.permalink_directories.first
97+
end
98+
99+
Permalink.default_slug_for(entry, directory:, except:)
100+
end
86101
end
87102
end
88103
end

‎app/helpers/pageflow/admin/form_helper.rb‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ module FormHelper # rubocop:todo Style/Documentation
99
def admin_form_for(resource, options = {}, &)
1010
semantic_form_for(resource, options.merge(builder: ActiveAdmin::FormBuilder), &)
1111
end
12+
13+
def choice_label_with_description(title, description)
14+
safe_join([content_tag(:span, title, class: 'choice_title'), ' ', description])
15+
end
1216
end
1317
end
1418
end

‎app/models/pageflow/entry_duplicate.rb‎

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,62 @@
11
module Pageflow
2-
EntryDuplicate = Struct.new(:original_entry) do
3-
def create!
4-
ActiveRecord::Base.transaction do
5-
create_entry
2+
# @api private
3+
class EntryDuplicate
4+
attr_reader :original_entry, :new_entry
5+
6+
def self.of(entry, attributes: {}, copy_memberships: true)
7+
new(entry, attributes:, copy_memberships:)
8+
end
9+
10+
def initialize(original_entry, attributes: {}, copy_memberships: true)
11+
@original_entry = original_entry
12+
@copy_memberships = copy_memberships
13+
@new_entry = build_entry(attributes)
14+
end
15+
16+
def copy_memberships?
17+
@copy_memberships
18+
end
19+
20+
def save
21+
passed_slug = new_entry.permalink&.slug
22+
23+
saved = ActiveRecord::Base.transaction do
24+
next false unless new_entry.save
625

7-
copy_permalink
826
copy_draft
9-
copy_memberships
27+
copy_memberships if copy_memberships?
28+
29+
true
1030
end
1131

12-
new_entry
32+
new_entry.permalink.slug = passed_slug if !saved && new_entry.permalink
33+
saved
1334
end
1435

15-
def self.of(entry)
16-
new(entry)
36+
def create!
37+
raise ActiveRecord::RecordInvalid, new_entry unless save
38+
39+
new_entry
1740
end
1841

1942
private
2043

21-
attr_reader :new_entry
44+
def build_entry(attributes)
45+
Entry.new(default_attributes).tap do |entry|
46+
entry.assign_attributes(attributes)
2247

23-
def create_entry
24-
@new_entry = Entry.create!(new_attributes)
48+
entry.site = entry.account.default_site if entry.site.account_id != entry.account_id
49+
entry.folder = nil if entry.folder && entry.folder.account_id != entry.account_id
50+
51+
build_permalink_in_original_directory(entry) unless entry.permalink
52+
end
2553
end
2654

27-
def copy_permalink
28-
return if original_entry.permalink.blank?
55+
def build_permalink_in_original_directory(entry)
56+
directory = original_entry.permalink&.directory
57+
return unless directory && directory.site_id == entry.site_id
2958

30-
new_entry.create_permalink!(directory: original_entry.permalink.directory)
59+
entry.build_permalink(directory:, slug: '')
3160
end
3261

3362
def copy_draft
@@ -37,25 +66,32 @@ def copy_draft
3766
end
3867

3968
def copy_memberships
40-
original_entry.memberships.each do |membership|
41-
Membership.create(user: membership.user, role: membership.role.to_sym, entity: new_entry)
69+
memberships_of_target_account_members.each do |membership|
70+
Membership.create!(user: membership.user, role: membership.role.to_sym, entity: new_entry)
4271
end
4372
end
4473

45-
def new_attributes
74+
def memberships_of_target_account_members
75+
original_entry
76+
.memberships
77+
.where(user_id: new_entry.account.memberships.select(:user_id))
78+
end
79+
80+
def default_attributes
4681
{
4782
type_name: original_entry.type_name,
48-
title: new_title,
83+
title: default_title,
4984
account: original_entry.account,
5085
site: original_entry.site,
86+
folder: original_entry.folder,
5187
features_configuration: original_entry.features_configuration,
5288
perma_id_counter: original_entry.perma_id_counter,
5389

5490
skip_draft_creation: true
5591
}
5692
end
5793

58-
def new_title
94+
def default_title
5995
I18n.t('pageflow.entry.duplicated_title', title: original_entry.title)
6096
end
6197
end

‎app/models/pageflow/permalink.rb‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,20 @@ class Permalink < ApplicationRecord
2424

2525
attr_accessor :allow_root_path
2626

27+
def self.default_slug_for(entry, directory:, except: nil)
28+
base_slug = entry.default_permalink_slug
29+
return base_slug if base_slug.blank? || !directory
30+
31+
directory.first_available_slug(base_slug, except:)
32+
end
33+
2734
private
2835

2936
def set_default_slug
3037
return unless slug == ''
3138
return if allow_emtpy_slug?
3239

33-
self.slug = entry.default_permalink_slug
40+
self.slug = Permalink.default_slug_for(entry, directory:, except: id)
3441
end
3542

3643
def allow_emtpy_slug?

‎app/models/pageflow/permalink_directory.rb‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,19 @@ class PermalinkDirectory < ApplicationRecord
1313
has_many(:redirects,
1414
class_name: 'PermalinkRedirect',
1515
foreign_key: :directory_id)
16+
17+
def first_available_slug(base_slug, except: nil)
18+
taken_slugs = permalinks
19+
.where.not(id: except)
20+
.where('slug = :base_slug OR slug LIKE :suffixed',
21+
base_slug:,
22+
suffixed: "#{self.class.sanitize_sql_like(base_slug)}-%")
23+
.pluck(:slug)
24+
25+
(1..)
26+
.lazy
27+
.map { |index| index == 1 ? base_slug : "#{base_slug}-#{index}" }
28+
.find { |slug| !taken_slugs.include?(slug) }
29+
end
1630
end
1731
end

‎app/policies/pageflow/entry_policy.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def create?
105105
end
106106

107107
def duplicate?
108-
query.has_at_least_role?(:publisher)
108+
query.has_at_least_account_role?(:publisher)
109109
end
110110

111111
def manage_translations?
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<%= form.input(:title,
2+
hint: entry.site.permalink_directories.blank? &&
3+
I18n.t('pageflow.admin.entries.title_hint')) %>
4+
5+
<% if account_input_shown_for?(entry) %>
6+
<%= form.input(:account,
7+
as: :searchable_select,
8+
include_blank: false,
9+
ajax: {
10+
resource: Pageflow::Entry,
11+
collection_name: :eligible_accounts
12+
},
13+
input_html: {class: 'entry_account_input'}) %>
14+
<%end %>
15+
16+
<%= render('admin/entries/site_input',
17+
entry:,
18+
form:) %>
19+
20+
<%= form.fields_for(:permalink, permalink) do |permalink_form| %>
21+
<%= render('admin/entries/permalink_inputs',
22+
entry:,
23+
form: permalink_form) %>
24+
<% end %>

0 commit comments

Comments
 (0)