alchemy_cms 6.0.0.pre.rc6 → 6.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +23 -8
- data/.github/workflows/stale.yml +21 -7
- data/.gitignore +0 -1
- data/.rspec +1 -0
- data/CHANGELOG.md +102 -0
- data/Gemfile +20 -5
- data/README.md +4 -3
- data/Rakefile +5 -1
- data/alchemy_cms.gemspec +3 -3
- data/app/assets/javascripts/alchemy/admin.js +0 -1
- data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +2 -0
- data/app/assets/javascripts/alchemy/page_select.js +13 -8
- data/app/assets/javascripts/alchemy/templates/index.js +1 -0
- data/app/assets/javascripts/alchemy/templates/page.hbs +17 -7
- data/app/assets/javascripts/alchemy/templates/page_folder.hbs +3 -0
- data/app/assets/stylesheets/alchemy/archive.scss +9 -0
- data/app/assets/stylesheets/alchemy/elements.scss +4 -0
- data/app/assets/stylesheets/alchemy/page-select.scss +29 -4
- data/app/assets/stylesheets/alchemy/sitemap.scss +2 -6
- data/app/controllers/alchemy/admin/elements_controller.rb +20 -17
- data/app/controllers/alchemy/admin/pages_controller.rb +24 -19
- data/app/controllers/alchemy/api/base_controller.rb +4 -3
- data/app/controllers/alchemy/api/contents_controller.rb +1 -5
- data/app/controllers/alchemy/api/elements_controller.rb +2 -6
- data/app/controllers/alchemy/api/nodes_controller.rb +1 -0
- data/app/controllers/alchemy/api/pages_controller.rb +16 -10
- data/app/controllers/alchemy/base_controller.rb +7 -0
- data/app/controllers/alchemy/messages_controller.rb +0 -3
- data/app/controllers/alchemy/pages_controller.rb +0 -7
- data/app/helpers/alchemy/elements_helper.rb +17 -12
- data/app/models/alchemy/element.rb +13 -6
- data/app/models/alchemy/ingredient.rb +10 -1
- data/app/models/alchemy/ingredient_validator.rb +1 -1
- data/app/models/alchemy/language.rb +1 -1
- data/app/models/alchemy/page/page_elements.rb +2 -2
- data/app/models/alchemy/page/page_naming.rb +1 -1
- data/app/models/alchemy/page.rb +1 -1
- data/app/models/alchemy/picture/transformations.rb +2 -2
- data/app/models/alchemy/picture.rb +1 -1
- data/app/models/alchemy/picture_variant.rb +3 -1
- data/app/models/alchemy/site.rb +1 -1
- data/app/serializers/alchemy/page_serializer.rb +7 -1
- data/app/serializers/alchemy/page_tree_serializer.rb +3 -3
- data/app/views/alchemy/admin/clipboard/insert.js.erb +13 -0
- data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +27 -0
- data/app/views/alchemy/admin/elements/_element.html.erb +1 -23
- data/app/views/alchemy/admin/elements/_form.html.erb +5 -1
- data/app/views/alchemy/admin/pages/_form.html.erb +19 -0
- data/app/views/alchemy/admin/pages/_new_page_form.html.erb +16 -5
- data/app/views/alchemy/admin/pages/_page.html.erb +111 -133
- data/app/views/alchemy/admin/pages/_sitemap.html.erb +2 -8
- data/app/views/alchemy/admin/pages/_toolbar.html.erb +0 -12
- data/app/views/alchemy/admin/pages/index.html.erb +1 -1
- data/app/views/alchemy/admin/pages/update.js.erb +7 -0
- data/app/views/alchemy/admin/partials/_routes.html.erb +12 -1
- data/app/views/alchemy/admin/resources/_form.html.erb +5 -0
- data/app/views/alchemy/essences/_essence_node_editor.html.erb +1 -1
- data/app/views/alchemy/essences/_essence_page_editor.html.erb +1 -1
- data/config/alchemy/config.yml +1 -0
- data/config/initializers/dragonfly.rb +2 -0
- data/config/locales/alchemy.en.yml +0 -3
- data/config/routes.rb +4 -2
- data/lib/alchemy/config.rb +5 -1
- data/lib/alchemy/controller_actions.rb +2 -1
- data/lib/alchemy/dragonfly/processors/thumbnail.rb +27 -0
- data/lib/alchemy/element_definition.rb +2 -3
- data/lib/alchemy/elements_finder.rb +1 -2
- data/lib/alchemy/engine.rb +12 -1
- data/lib/alchemy/essence.rb +1 -27
- data/lib/alchemy/page_layout.rb +5 -1
- data/lib/alchemy/permissions.rb +2 -3
- data/lib/alchemy/resource.rb +16 -1
- data/lib/alchemy/test_support/essence_shared_examples.rb +0 -12
- data/lib/alchemy/test_support/shared_ingredient_examples.rb +4 -2
- data/lib/alchemy/upgrader/tasks/ingredients_migrator.rb +1 -1
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +2 -4
- data/lib/generators/alchemy/base.rb +7 -3
- data/lib/generators/alchemy/install/install_generator.rb +10 -2
- data/package/src/image_loader.js +4 -2
- data/package/src/node_tree.js +13 -6
- data/package/src/page_publication_fields.js +15 -14
- data/package/src/page_sorter.js +62 -0
- data/package/src/picture_editors.js +8 -8
- data/package/src/sitemap.js +51 -36
- data/package/src/utils/__tests__/ajax.spec.js +52 -16
- data/package/src/utils/ajax.js +12 -0
- data/package.json +1 -1
- metadata +43 -42
- data/app/assets/javascripts/alchemy/alchemy.page_sorter.js +0 -24
- data/app/views/alchemy/admin/pages/fold.js.erb +0 -2
- data/app/views/alchemy/admin/pages/sort.html.erb +0 -19
- data/vendor/assets/javascripts/jquery_plugins/jquery.ui.nestedSortable.js +0 -434
@@ -7,7 +7,7 @@ module Alchemy
|
|
7
7
|
|
8
8
|
helper "alchemy/pages"
|
9
9
|
|
10
|
-
before_action :load_resource, except: [:index, :flush, :new, :order, :create, :copy_language_tree, :link
|
10
|
+
before_action :load_resource, except: [:index, :flush, :new, :order, :create, :copy_language_tree, :link]
|
11
11
|
|
12
12
|
authorize_resource class: Alchemy::Page, except: [:index, :tree]
|
13
13
|
|
@@ -21,11 +21,9 @@ module Alchemy
|
|
21
21
|
except: [:show]
|
22
22
|
|
23
23
|
before_action :set_root_page,
|
24
|
-
only: [:index, :show, :
|
24
|
+
only: [:index, :show, :order]
|
25
25
|
|
26
|
-
before_action :
|
27
|
-
if: :run_on_page_layout_callbacks?,
|
28
|
-
only: [:show]
|
26
|
+
before_action :set_preview_mode, only: [:show]
|
29
27
|
|
30
28
|
before_action :load_languages_and_layouts,
|
31
29
|
unless: -> { @page_root },
|
@@ -35,6 +33,10 @@ module Alchemy
|
|
35
33
|
|
36
34
|
before_action :set_page_version, only: [:show, :edit]
|
37
35
|
|
36
|
+
before_action :run_on_page_layout_callbacks,
|
37
|
+
if: :run_on_page_layout_callbacks?,
|
38
|
+
only: [:show]
|
39
|
+
|
38
40
|
def index
|
39
41
|
@query = @current_language.pages.contentpages.ransack(search_filter_params[:q])
|
40
42
|
|
@@ -64,7 +66,6 @@ module Alchemy
|
|
64
66
|
# Used by page preview iframe in Page#edit view.
|
65
67
|
#
|
66
68
|
def show
|
67
|
-
@preview_mode = true
|
68
69
|
Page.current_preview = @page
|
69
70
|
# Setting the locale to pages language, so the page content has it's correct translations.
|
70
71
|
::I18n.locale = @page.language.locale
|
@@ -123,6 +124,7 @@ module Alchemy
|
|
123
124
|
# * fetches page via before filter
|
124
125
|
#
|
125
126
|
def update
|
127
|
+
@old_parent_id = @page.parent_id
|
126
128
|
if @page.update(page_params)
|
127
129
|
@notice = Alchemy.t("Page saved", name: @page.name)
|
128
130
|
@while_page_edit = request.referer.include?("edit")
|
@@ -167,9 +169,7 @@ module Alchemy
|
|
167
169
|
def fold
|
168
170
|
# @page is fetched via before filter
|
169
171
|
@page.fold!(current_alchemy_user.id, !@page.folded?(current_alchemy_user.id))
|
170
|
-
|
171
|
-
format.js
|
172
|
-
end
|
172
|
+
render json: serialized_page_tree
|
173
173
|
end
|
174
174
|
|
175
175
|
# Leaves the page editing mode and unlocks the page for other users
|
@@ -180,9 +180,12 @@ module Alchemy
|
|
180
180
|
@pages_locked_by_user = Page.from_current_site.locked_by(current_alchemy_user)
|
181
181
|
respond_to do |format|
|
182
182
|
format.js
|
183
|
-
format.html
|
184
|
-
redirect_to
|
185
|
-
|
183
|
+
format.html do
|
184
|
+
redirect_to(
|
185
|
+
params[:redirect_to].presence || admin_pages_path,
|
186
|
+
allow_other_host: true,
|
187
|
+
)
|
188
|
+
end
|
186
189
|
end
|
187
190
|
end
|
188
191
|
|
@@ -202,10 +205,6 @@ module Alchemy
|
|
202
205
|
redirect_to admin_pages_path
|
203
206
|
end
|
204
207
|
|
205
|
-
def sort
|
206
|
-
@sorting = true
|
207
|
-
end
|
208
|
-
|
209
208
|
# Receives a JSON object representing a language tree to be ordered
|
210
209
|
# and updates all pages in that language structure to their correct indexes
|
211
210
|
def order
|
@@ -387,9 +386,11 @@ module Alchemy
|
|
387
386
|
end
|
388
387
|
|
389
388
|
def serialized_page_tree
|
390
|
-
PageTreeSerializer.new(
|
391
|
-
|
392
|
-
|
389
|
+
PageTreeSerializer.new(
|
390
|
+
@page,
|
391
|
+
ability: current_ability,
|
392
|
+
user: current_alchemy_user,
|
393
|
+
)
|
393
394
|
end
|
394
395
|
|
395
396
|
def load_languages_and_layouts
|
@@ -397,6 +398,10 @@ module Alchemy
|
|
397
398
|
@languages_with_page_tree = Language.on_current_site.with_root_page
|
398
399
|
@page_layouts = PageLayout.layouts_for_select(@language.id)
|
399
400
|
end
|
401
|
+
|
402
|
+
def set_preview_mode
|
403
|
+
@preview_mode = true
|
404
|
+
end
|
400
405
|
end
|
401
406
|
end
|
402
407
|
end
|
@@ -5,17 +5,18 @@ module Alchemy
|
|
5
5
|
layout false
|
6
6
|
respond_to :json
|
7
7
|
|
8
|
-
rescue_from CanCan::AccessDenied,
|
8
|
+
rescue_from CanCan::AccessDenied, with: :render_not_authorized
|
9
9
|
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
|
10
|
+
rescue_from ActionController::RoutingError, with: :render_not_found
|
10
11
|
|
11
12
|
private
|
12
13
|
|
13
14
|
def render_not_authorized
|
14
|
-
render json: {error: "Not authorized"}, status: 403
|
15
|
+
render json: { error: "Not authorized" }, status: 403
|
15
16
|
end
|
16
17
|
|
17
18
|
def render_not_found
|
18
|
-
render json: {error: "Record not found"}, status: 404
|
19
|
+
render json: { error: "Record not found" }, status: 404
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
@@ -47,18 +47,14 @@ module Alchemy
|
|
47
47
|
{
|
48
48
|
nested_elements: [
|
49
49
|
{
|
50
|
-
contents:
|
51
|
-
essence: :ingredient_association,
|
52
|
-
},
|
50
|
+
contents: :essence,
|
53
51
|
ingredients: :related_object,
|
54
52
|
},
|
55
53
|
:tags,
|
56
54
|
],
|
57
55
|
},
|
58
56
|
{
|
59
|
-
contents:
|
60
|
-
essence: :ingredient_association,
|
61
|
-
},
|
57
|
+
contents: :essence,
|
62
58
|
ingredients: :related_object,
|
63
59
|
},
|
64
60
|
:tags,
|
@@ -2,17 +2,19 @@
|
|
2
2
|
|
3
3
|
module Alchemy
|
4
4
|
class Api::PagesController < Api::BaseController
|
5
|
+
serialization_scope :current_ability
|
5
6
|
before_action :load_page, only: [:show]
|
6
7
|
|
7
8
|
# Returns all pages as json object
|
8
9
|
#
|
9
10
|
def index
|
10
11
|
# Fix for cancancan not able to merge multiple AR scopes for logged in users
|
11
|
-
if
|
12
|
-
@pages = Alchemy::Page.
|
13
|
-
@pages = @pages.where(language: Language.current)
|
12
|
+
if can? :edit_content, Alchemy::Page
|
13
|
+
@pages = Alchemy::Page.all
|
14
14
|
else
|
15
|
-
|
15
|
+
language = Alchemy::Language.find_by(id: params[:language_id]) || Alchemy::Language.current
|
16
|
+
@pages = Alchemy::Page.accessible_by(current_ability, :index)
|
17
|
+
@pages = @pages.where(language: language)
|
16
18
|
end
|
17
19
|
@pages = @pages.includes(*page_includes)
|
18
20
|
@pages = @pages.ransack(params[:q]).result
|
@@ -49,6 +51,14 @@ module Alchemy
|
|
49
51
|
respond_with @page
|
50
52
|
end
|
51
53
|
|
54
|
+
def move
|
55
|
+
@page = Page.find(params[:id])
|
56
|
+
authorize! :update, @page
|
57
|
+
target_parent_page = Page.find(params[:target_parent_id])
|
58
|
+
@page.move_to_child_with_index(target_parent_page, params[:new_position])
|
59
|
+
render json: @page, serializer: PageSerializer
|
60
|
+
end
|
61
|
+
|
52
62
|
private
|
53
63
|
|
54
64
|
def load_page
|
@@ -102,17 +112,13 @@ module Alchemy
|
|
102
112
|
{
|
103
113
|
nested_elements: [
|
104
114
|
{
|
105
|
-
contents:
|
106
|
-
essence: :ingredient_association,
|
107
|
-
},
|
115
|
+
contents: :essence,
|
108
116
|
},
|
109
117
|
:tags,
|
110
118
|
],
|
111
119
|
},
|
112
120
|
{
|
113
|
-
contents:
|
114
|
-
essence: :ingredient_association,
|
115
|
-
},
|
121
|
+
contents: :essence,
|
116
122
|
},
|
117
123
|
:tags,
|
118
124
|
],
|
@@ -9,6 +9,13 @@ module Alchemy
|
|
9
9
|
include Alchemy::ControllerActions
|
10
10
|
include Alchemy::Modules
|
11
11
|
|
12
|
+
# Include Turbolinks explicitly in case Alchemy is embedded into a
|
13
|
+
# larger application that doesn't work with Turbolinks. The app
|
14
|
+
# can then set config.turbolinks.auto_include = false so that
|
15
|
+
# Turbolinks is not included in the app controllers.
|
16
|
+
include Turbolinks::Controller
|
17
|
+
::ActionDispatch::Assertions.include ::Turbolinks::Assertions
|
18
|
+
|
12
19
|
protect_from_forgery
|
13
20
|
|
14
21
|
before_action :mailer_set_url_options
|
@@ -62,7 +62,6 @@ module Alchemy
|
|
62
62
|
end
|
63
63
|
|
64
64
|
@page = @element.page
|
65
|
-
@root_page = @page.get_language_root
|
66
65
|
if @message.valid?
|
67
66
|
MessagesMailer.contact_form_mail(@message, mail_to, mail_from, subject).deliver
|
68
67
|
redirect_to_success_page
|
@@ -122,8 +121,6 @@ module Alchemy
|
|
122
121
|
if @page.blank?
|
123
122
|
raise "Page for page_layout #{mailer_config["page_layout_name"]} not found"
|
124
123
|
end
|
125
|
-
|
126
|
-
@root_page = @page.get_language_root
|
127
124
|
end
|
128
125
|
|
129
126
|
def message_params
|
@@ -32,9 +32,6 @@ module Alchemy
|
|
32
32
|
if: :locale_prefix_missing?,
|
33
33
|
only: [:index, :show]
|
34
34
|
|
35
|
-
# We only need to set the +@root_page+ if we are sure that no more redirects happen.
|
36
|
-
before_action :set_root_page, only: [:index, :show]
|
37
|
-
|
38
35
|
# Page layout callbacks need to run after all other callbacks
|
39
36
|
before_action :run_on_page_layout_callbacks,
|
40
37
|
if: :run_on_page_layout_callbacks?,
|
@@ -199,10 +196,6 @@ module Alchemy
|
|
199
196
|
end
|
200
197
|
end
|
201
198
|
|
202
|
-
def set_root_page
|
203
|
-
@root_page ||= Language.current_root_page
|
204
|
-
end
|
205
|
-
|
206
199
|
def signup_required?
|
207
200
|
if Alchemy.user_class.respond_to?(:admins)
|
208
201
|
Alchemy.user_class.admins.empty? && @page.nil?
|
@@ -70,7 +70,7 @@ module Alchemy
|
|
70
70
|
# A class instance that will return elements that get rendered.
|
71
71
|
# Use this for your custom element loading logic in views.
|
72
72
|
#
|
73
|
-
def render_elements(options = {})
|
73
|
+
def render_elements(options = {}, &blk)
|
74
74
|
options = {
|
75
75
|
from_page: @page,
|
76
76
|
render_format: "html",
|
@@ -86,11 +86,12 @@ module Alchemy
|
|
86
86
|
|
87
87
|
elements = finder.elements(page_version: page_version)
|
88
88
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
89
|
+
default_rendering = ->(element, i) { render_element(element, options, i + 1) }
|
90
|
+
if block_given?
|
91
|
+
elements.map.with_index(&blk)
|
92
|
+
else
|
93
|
+
elements.map.with_index(&default_rendering)
|
94
|
+
end.join(options[:separator]).html_safe
|
94
95
|
end
|
95
96
|
|
96
97
|
# This helper renders a {Alchemy::Element} view partial.
|
@@ -125,7 +126,7 @@ module Alchemy
|
|
125
126
|
#
|
126
127
|
# == Usage
|
127
128
|
#
|
128
|
-
# <%= render_element(Alchemy::Element.
|
129
|
+
# <%= render_element(Alchemy::Element.published.named(:headline).first) %>
|
129
130
|
#
|
130
131
|
# @param [Alchemy::Element] element
|
131
132
|
# The element you want to render the view for
|
@@ -146,11 +147,15 @@ module Alchemy
|
|
146
147
|
|
147
148
|
element.store_page(@page)
|
148
149
|
|
149
|
-
render
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
150
|
+
render(
|
151
|
+
partial: options[:partial] || element.to_partial_path,
|
152
|
+
object: element,
|
153
|
+
locals: {
|
154
|
+
element: element,
|
155
|
+
counter: counter,
|
156
|
+
options: options.except(:locals, :partial),
|
157
|
+
}.merge(options[:locals] || {}),
|
158
|
+
)
|
154
159
|
rescue ActionView::MissingTemplate => e
|
155
160
|
warning(%(
|
156
161
|
Element view partial not found for #{element.name}.\n
|
@@ -70,7 +70,7 @@ module Alchemy
|
|
70
70
|
dependent: :destroy
|
71
71
|
|
72
72
|
has_many :nested_elements,
|
73
|
-
-> { order(:position).
|
73
|
+
-> { order(:position).published },
|
74
74
|
class_name: "Alchemy::Element",
|
75
75
|
foreign_key: :parent_element_id,
|
76
76
|
dependent: :destroy,
|
@@ -159,7 +159,7 @@ module Alchemy
|
|
159
159
|
end
|
160
160
|
|
161
161
|
def all_from_clipboard(clipboard)
|
162
|
-
return
|
162
|
+
return none if clipboard.nil?
|
163
163
|
|
164
164
|
where(id: clipboard.collect { |e| e["id"] })
|
165
165
|
end
|
@@ -167,12 +167,19 @@ module Alchemy
|
|
167
167
|
# All elements in clipboard that could be placed on page
|
168
168
|
#
|
169
169
|
def all_from_clipboard_for_page(clipboard, page)
|
170
|
-
return
|
170
|
+
return none if clipboard.nil? || page.nil?
|
171
171
|
|
172
|
-
all_from_clipboard(clipboard).
|
173
|
-
page.available_element_names.include?(ce.name)
|
174
|
-
}
|
172
|
+
all_from_clipboard(clipboard).where(name: page.available_element_names)
|
175
173
|
end
|
174
|
+
|
175
|
+
# All elements in clipboard that could be placed as a child of `parent_element`
|
176
|
+
def all_from_clipboard_for_parent_element(clipboard, parent_element)
|
177
|
+
return none if clipboard.nil? || parent_element.nil?
|
178
|
+
|
179
|
+
all_from_clipboard(clipboard).where(name: parent_element.definition["nestable_elements"])
|
180
|
+
end
|
181
|
+
|
182
|
+
deprecate available: :published, deprecator: Alchemy::Deprecation
|
176
183
|
end
|
177
184
|
|
178
185
|
# Returns next public element from same page.
|
@@ -6,12 +6,17 @@ module Alchemy
|
|
6
6
|
|
7
7
|
include Hints
|
8
8
|
|
9
|
+
# MariaDB needs to be told explicitly to use `data` as a JSON store. All other databases
|
10
|
+
# can do this natively.
|
11
|
+
store :data, coder: JSON
|
12
|
+
|
9
13
|
self.table_name = "alchemy_ingredients"
|
10
14
|
|
11
15
|
belongs_to :element, touch: true, class_name: "Alchemy::Element", inverse_of: :ingredients
|
12
16
|
belongs_to :related_object, polymorphic: true, optional: true
|
13
17
|
|
14
|
-
|
18
|
+
after_initialize :set_default_value,
|
19
|
+
if: -> { definition.key?(:default) && value.nil? }
|
15
20
|
|
16
21
|
validates :type, presence: true
|
17
22
|
validates :role, presence: true
|
@@ -161,6 +166,10 @@ module Alchemy
|
|
161
166
|
role
|
162
167
|
end
|
163
168
|
|
169
|
+
def set_default_value
|
170
|
+
self.value = default_value
|
171
|
+
end
|
172
|
+
|
164
173
|
# Returns the default value from ingredient definition
|
165
174
|
#
|
166
175
|
# If the value is a symbol it gets passed through i18n
|
@@ -88,7 +88,7 @@ module Alchemy
|
|
88
88
|
|
89
89
|
def duplicates
|
90
90
|
ingredient.class
|
91
|
-
.joins(:element).merge(Alchemy::Element.
|
91
|
+
.joins(:element).merge(Alchemy::Element.published)
|
92
92
|
.where(Alchemy::Element.table_name => { name: ingredient.element.name })
|
93
93
|
.where(value: ingredient.value)
|
94
94
|
.where.not(id: ingredient.id)
|
@@ -38,7 +38,7 @@ module Alchemy
|
|
38
38
|
|
39
39
|
validates :language_code,
|
40
40
|
presence: true,
|
41
|
-
uniqueness: { scope: [:site_id, :country_code] },
|
41
|
+
uniqueness: { scope: [:site_id, :country_code], case_sensitive: false },
|
42
42
|
format: { with: /\A[a-z]{2}\z/, if: -> { language_code.present? } }
|
43
43
|
|
44
44
|
validates :country_code,
|
@@ -15,8 +15,8 @@ module Alchemy
|
|
15
15
|
source: :elements,
|
16
16
|
) do
|
17
17
|
has_many :all_elements
|
18
|
-
has_many :elements, -> { not_nested.unfixed.
|
19
|
-
has_many :fixed_elements, -> { fixed.
|
18
|
+
has_many :elements, -> { not_nested.unfixed.published }
|
19
|
+
has_many :fixed_elements, -> { fixed.published }
|
20
20
|
end
|
21
21
|
|
22
22
|
has_many :contents, through: :elements
|
@@ -15,7 +15,7 @@ module Alchemy
|
|
15
15
|
validates :name,
|
16
16
|
presence: true
|
17
17
|
validates :urlname,
|
18
|
-
uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? } },
|
18
|
+
uniqueness: { scope: [:language_id, :layoutpage], if: -> { urlname.present? }, case_sensitive: false },
|
19
19
|
exclusion: { in: RESERVED_URLNAMES },
|
20
20
|
length: { minimum: 3, if: -> { urlname.present? } }
|
21
21
|
|
data/app/models/alchemy/page.rb
CHANGED
@@ -117,7 +117,7 @@ module Alchemy
|
|
117
117
|
has_many :nodes, class_name: "Alchemy::Node", inverse_of: :page
|
118
118
|
has_many :versions, class_name: "Alchemy::PageVersion", inverse_of: :page, dependent: :destroy
|
119
119
|
has_one :draft_version, -> { drafts }, class_name: "Alchemy::PageVersion"
|
120
|
-
has_one :public_version, -> { published }, class_name: "Alchemy::PageVersion"
|
120
|
+
has_one :public_version, -> { published }, class_name: "Alchemy::PageVersion", autosave: -> { persisted? }
|
121
121
|
|
122
122
|
before_validation :set_language,
|
123
123
|
if: -> { language.nil? }
|
@@ -30,7 +30,7 @@ module Alchemy
|
|
30
30
|
# Returns the rendered resized image using imagemagick directly.
|
31
31
|
#
|
32
32
|
def resize(size, upsample = false)
|
33
|
-
image_file.
|
33
|
+
image_file.thumbnail(upsample ? size : "#{size}>")
|
34
34
|
end
|
35
35
|
|
36
36
|
# Returns true if picture's width is greater than it's height
|
@@ -119,7 +119,7 @@ module Alchemy
|
|
119
119
|
if is_smaller_than?(dimensions) && upsample == false
|
120
120
|
dimensions = reduce_to_image(dimensions)
|
121
121
|
end
|
122
|
-
image_file.
|
122
|
+
image_file.thumbnail("#{dimensions_to_string(dimensions)}#")
|
123
123
|
end
|
124
124
|
|
125
125
|
# Use imagemagick to custom crop an image. Uses -thumbnail for better performance when resizing.
|
@@ -13,6 +13,8 @@ module Alchemy
|
|
13
13
|
include Alchemy::Logger
|
14
14
|
include Alchemy::Picture::Transformations
|
15
15
|
|
16
|
+
ANIMATED_IMAGE_FORMATS = %w[gif webp]
|
17
|
+
|
16
18
|
attr_reader :picture, :render_format
|
17
19
|
|
18
20
|
def_delegators :@picture,
|
@@ -86,7 +88,7 @@ module Alchemy
|
|
86
88
|
end
|
87
89
|
|
88
90
|
options = {
|
89
|
-
flatten: render_format
|
91
|
+
flatten: !render_format.in?(ANIMATED_IMAGE_FORMATS) && picture.image_file_format == "gif",
|
90
92
|
}.with_indifferent_access.merge(options)
|
91
93
|
|
92
94
|
encoding_options = []
|
data/app/models/alchemy/site.rb
CHANGED
@@ -14,8 +14,14 @@ module Alchemy
|
|
14
14
|
:created_at,
|
15
15
|
:updated_at,
|
16
16
|
:status,
|
17
|
-
:url_path
|
17
|
+
:url_path,
|
18
|
+
:parent_id
|
18
19
|
|
19
20
|
has_many :elements
|
21
|
+
|
22
|
+
with_options if: -> { scope.can?(:edit_content, object) } do
|
23
|
+
belongs_to :site
|
24
|
+
belongs_to :language
|
25
|
+
end
|
20
26
|
end
|
21
27
|
end
|
@@ -3,13 +3,13 @@
|
|
3
3
|
module Alchemy
|
4
4
|
class PageTreeSerializer < BaseSerializer
|
5
5
|
def attributes
|
6
|
-
{"pages" => nil}
|
6
|
+
{ "pages" => nil }
|
7
7
|
end
|
8
8
|
|
9
9
|
def pages
|
10
10
|
tree = []
|
11
|
-
path = [{id: object.parent_id, children: tree}]
|
12
|
-
page_list = object.self_and_descendants
|
11
|
+
path = [{ id: object.parent_id, children: tree }]
|
12
|
+
page_list = object.self_and_descendants.includes(:public_version, { language: :site })
|
13
13
|
base_level = object.level - 1
|
14
14
|
# Load folded pages in advance
|
15
15
|
folded_user_pages = FoldedPage.folded_for_user(opts[:user]).pluck(:page_id)
|
@@ -14,3 +14,16 @@
|
|
14
14
|
Alchemy.growl('<%= j Alchemy.t("item copied to clipboard", name: @item.class.name == "Alchemy::Element" ? @item.display_name_with_preview_text : @item.name) %>')
|
15
15
|
<% end -%>
|
16
16
|
$('#clipboard_button .icon').removeClass('fa-clipboard').addClass('fa-paste');
|
17
|
+
|
18
|
+
<%# Update add nested element forms for any elements that accept ONLY this as a nested element %>
|
19
|
+
<% if @item.class == Alchemy::Element %>
|
20
|
+
if (window.location.pathname == "<%= edit_admin_page_path(@item.page.id) %>") {
|
21
|
+
<%
|
22
|
+
@item.page.draft_version.elements.expanded.select do |element|
|
23
|
+
element.definition["nestable_elements"] == [@item.name]
|
24
|
+
end.each do |element|
|
25
|
+
%>
|
26
|
+
$(".add-nested-element[data-element-id='<%= element.id %>']").replaceWith('<%= j render "alchemy/admin/elements/add_nested_element_form", element: element %>')
|
27
|
+
<% end %>
|
28
|
+
}
|
29
|
+
<% end %>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
<%= content_tag :div, class: 'add-nested-element', data: { element_id: element.id } do %>
|
2
|
+
<% if element.expanded? || element.fixed? %>
|
3
|
+
<% if element.nestable_elements.length == 1 &&
|
4
|
+
(nestable_element = element.nestable_elements.first) &&
|
5
|
+
Alchemy::Element.all_from_clipboard_for_parent_element(get_clipboard("elements"), element).none?
|
6
|
+
%>
|
7
|
+
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
|
8
|
+
remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
|
9
|
+
<%= f.hidden_field :name %>
|
10
|
+
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
|
11
|
+
<%= f.hidden_field :parent_element_id, value: element.id %>
|
12
|
+
<button class="button add-nestable-element-button" data-alchemy-button>
|
13
|
+
<%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element, scope: 'element_names')) %>
|
14
|
+
</button>
|
15
|
+
<% end %>
|
16
|
+
<% else %>
|
17
|
+
<%= link_to_dialog (nestable_element ? Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element, scope: 'element_names')) : Alchemy.t("New Element")),
|
18
|
+
alchemy.new_admin_element_path(
|
19
|
+
parent_element_id: element.id,
|
20
|
+
page_version_id: element.page_version_id
|
21
|
+
), {
|
22
|
+
size: "320x125",
|
23
|
+
title: Alchemy.t("New Element")
|
24
|
+
}, class: "button add-nestable-element-button" %>
|
25
|
+
<% end %>
|
26
|
+
<% end %>
|
27
|
+
<% end %>
|
@@ -55,29 +55,7 @@
|
|
55
55
|
} %>
|
56
56
|
<% end %>
|
57
57
|
|
58
|
-
|
59
|
-
<% if element.nestable_elements.length == 1 %>
|
60
|
-
<% nestable_element = element.nestable_elements.first %>
|
61
|
-
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
|
62
|
-
remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
|
63
|
-
<%= f.hidden_field :name %>
|
64
|
-
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
|
65
|
-
<%= f.hidden_field :parent_element_id, value: element.id %>
|
66
|
-
<button class="button add-nestable-element-button" data-alchemy-button>
|
67
|
-
<%= Alchemy.t(:add_nested_element) % { name: Alchemy.t(nestable_element, scope: 'element_names') } %>
|
68
|
-
</button>
|
69
|
-
<% end %>
|
70
|
-
<% else %>
|
71
|
-
<%= link_to_dialog Alchemy.t("New Element"),
|
72
|
-
alchemy.new_admin_element_path(
|
73
|
-
parent_element_id: element.id,
|
74
|
-
page_version_id: element.page_version_id
|
75
|
-
), {
|
76
|
-
size: "320x125",
|
77
|
-
title: Alchemy.t("New Element")
|
78
|
-
}, class: "button add-nestable-element-button" %>
|
79
|
-
<% end %>
|
80
|
-
<% end %>
|
58
|
+
<%= render "alchemy/admin/elements/add_nested_element_form", element: element %>
|
81
59
|
</div>
|
82
60
|
<% end %>
|
83
61
|
<% end %>
|
@@ -9,7 +9,11 @@
|
|
9
9
|
label: Alchemy.t(:element_of_type),
|
10
10
|
collection: elements_for_select(@elements),
|
11
11
|
prompt: Alchemy.t(:select_element),
|
12
|
-
|
12
|
+
selected: (@elements.first if @elements.count == 1),
|
13
|
+
input_html: {class: 'alchemy_selectbox', autofocus: true, disabled: @elements.count == 1} %>
|
14
|
+
<% if @elements.count == 1 %>
|
15
|
+
<%= form.hidden_field :name, value: @elements.first[:name] %>
|
16
|
+
<% end %>
|
13
17
|
<%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
|
14
18
|
<%= form.submit Alchemy.t(:add) %>
|
15
19
|
<%- end -%>
|