alchemy_cms 4.4.3 → 4.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of alchemy_cms might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +23 -17
- data/.rubocop.yml +7 -15
- data/CHANGELOG.md +35 -1
- data/alchemy_cms.gemspec +1 -0
- data/app/assets/javascripts/alchemy/admin.js +3 -0
- data/app/assets/javascripts/alchemy/alchemy.link_dialog.js.coffee +5 -5
- data/app/assets/javascripts/alchemy/alchemy.node_tree.js +66 -0
- data/app/assets/javascripts/alchemy/alchemy.utils.js +45 -0
- data/app/assets/javascripts/alchemy/templates/index.js +1 -0
- data/app/assets/javascripts/alchemy/templates/node_folder.hbs +3 -0
- data/app/assets/javascripts/alchemy/templates/page.hbs +1 -1
- data/app/assets/stylesheets/alchemy/_mixins.scss +2 -3
- data/app/assets/stylesheets/alchemy/_variables.scss +2 -2
- data/app/assets/stylesheets/alchemy/lists.scss +0 -8
- data/app/assets/stylesheets/alchemy/nodes.scss +6 -1
- data/app/assets/stylesheets/alchemy/sitemap.scss +59 -21
- data/app/controllers/alchemy/admin/dashboard_controller.rb +1 -1
- data/app/controllers/alchemy/admin/nodes_controller.rb +0 -10
- data/app/controllers/alchemy/admin/pages_controller.rb +0 -1
- data/app/controllers/alchemy/api/nodes_controller.rb +29 -0
- data/app/controllers/alchemy/api/pages_controller.rb +2 -0
- data/app/decorators/alchemy/content_editor.rb +55 -0
- data/app/helpers/alchemy/admin/pages_helper.rb +16 -16
- data/app/helpers/alchemy/pages_helper.rb +1 -1
- data/app/models/alchemy/content.rb +8 -22
- data/app/models/alchemy/node.rb +29 -6
- data/app/models/alchemy/page.rb +11 -0
- data/app/models/alchemy/page/page_naming.rb +1 -1
- data/app/models/alchemy/page/url_path.rb +66 -0
- data/app/serializers/alchemy/node_serializer.rb +12 -0
- data/app/serializers/alchemy/page_serializer.rb +2 -1
- data/app/serializers/alchemy/page_tree_serializer.rb +4 -3
- data/app/views/alchemy/admin/layoutpages/index.html.erb +5 -1
- data/app/views/alchemy/admin/nodes/_form.html.erb +13 -8
- data/app/views/alchemy/admin/nodes/_node.html.erb +10 -20
- data/app/views/alchemy/admin/nodes/index.html.erb +7 -17
- data/app/views/alchemy/admin/pages/_form.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_menu_fields.html.erb +33 -29
- data/app/views/alchemy/admin/pages/_page.html.erb +3 -6
- data/app/views/alchemy/admin/pages/_sitemap.html.erb +6 -0
- data/app/views/alchemy/admin/pages/info.html.erb +1 -1
- data/app/views/alchemy/admin/partials/_routes.html.erb +8 -0
- data/config/alchemy/config.yml +0 -6
- data/config/locales/alchemy.en.yml +14 -6
- data/config/routes.rb +8 -5
- data/lib/alchemy/config.rb +30 -2
- data/lib/alchemy/ssl_protection.rb +3 -1
- data/lib/alchemy/upgrader/four_point_six.rb +50 -0
- data/lib/alchemy/version.rb +1 -1
- data/lib/rails/generators/alchemy/install/install_generator.rb +1 -1
- data/lib/rails/generators/alchemy/install/templates/menus.yml.tt +8 -0
- data/lib/rails/generators/alchemy/menus/menus_generator.rb +3 -3
- data/lib/rails/generators/alchemy/menus/templates/wrapper.html.haml +1 -1
- data/lib/rails/generators/alchemy/menus/templates/wrapper.html.slim +1 -1
- data/lib/tasks/alchemy/convert.rake +2 -0
- data/lib/tasks/alchemy/upgrade.rake +67 -46
- data/vendor/assets/javascripts/sortable/Sortable.min.js +2 -0
- metadata +29 -4
@@ -1,3 +1,6 @@
|
|
1
|
+
$sitemap-url-large-width: 250px;
|
2
|
+
$sitemap-url-xlarge-width: 350px;
|
3
|
+
|
1
4
|
#sort_panel {
|
2
5
|
background: $light-gray;
|
3
6
|
padding: 47px 0 8px 0;
|
@@ -20,7 +23,7 @@
|
|
20
23
|
|
21
24
|
#sitemap-wrapper {
|
22
25
|
position: relative;
|
23
|
-
min-height:
|
26
|
+
min-height: calc(100vh - 96px);
|
24
27
|
}
|
25
28
|
|
26
29
|
.sitemap_pagename_link {
|
@@ -28,23 +31,35 @@
|
|
28
31
|
padding: 0 10px;
|
29
32
|
margin: 2px;
|
30
33
|
text-decoration: none;
|
34
|
+
white-space: nowrap;
|
35
|
+
text-overflow: ellipsis;
|
36
|
+
overflow: hidden;
|
31
37
|
|
32
38
|
&.inactive {
|
33
39
|
color: #656565;
|
34
40
|
}
|
35
41
|
}
|
36
42
|
|
37
|
-
.
|
43
|
+
.sitemap_url {
|
44
|
+
display: none;
|
38
45
|
float: right;
|
39
|
-
text-align: right;
|
40
46
|
background-color: $sitemap-info-background-color;
|
41
|
-
line-height: $sitemap-line-height;
|
47
|
+
line-height: $sitemap-line-height - 2px;
|
42
48
|
font-size: $small-font-size;
|
43
|
-
padding: 0 2
|
44
|
-
max-width: 45%;
|
49
|
+
padding: 0 2 * $default-padding;
|
45
50
|
white-space: nowrap;
|
46
51
|
overflow: hidden;
|
47
52
|
text-overflow: ellipsis;
|
53
|
+
border: 1px solid $sitemap-page-background-color;
|
54
|
+
|
55
|
+
@media screen and (min-width: $large-screen-break-point) {
|
56
|
+
display: block;
|
57
|
+
width: $sitemap-url-large-width;
|
58
|
+
}
|
59
|
+
|
60
|
+
@media screen and (min-width: 1440px) {
|
61
|
+
width: $sitemap-url-xlarge-width;
|
62
|
+
}
|
48
63
|
}
|
49
64
|
|
50
65
|
.sitemap_line_spacer {
|
@@ -55,7 +70,7 @@
|
|
55
70
|
|
56
71
|
.sitemap_page {
|
57
72
|
height: $sitemap-line-height;
|
58
|
-
margin: 3
|
73
|
+
margin: 3 * $default-margin 0;
|
59
74
|
position: relative;
|
60
75
|
transition: background-color $transition-duration;
|
61
76
|
|
@@ -89,13 +104,13 @@
|
|
89
104
|
width: 32px;
|
90
105
|
line-height: $sitemap-line-height;
|
91
106
|
float: left;
|
92
|
-
padding: 0 2
|
107
|
+
padding: 0 2 * $default-padding;
|
93
108
|
text-align: center;
|
94
109
|
}
|
95
110
|
|
96
111
|
.sitemap_right_tools {
|
97
112
|
height: $sitemap-line-height;
|
98
|
-
padding: 0 2
|
113
|
+
padding: 0 2 * $default-padding;
|
99
114
|
float: right;
|
100
115
|
|
101
116
|
.sitemap_tool {
|
@@ -130,7 +145,9 @@
|
|
130
145
|
&.sorting {
|
131
146
|
padding-top: 100px;
|
132
147
|
|
133
|
-
.page_icon {
|
148
|
+
.page_icon {
|
149
|
+
cursor: move;
|
150
|
+
}
|
134
151
|
}
|
135
152
|
|
136
153
|
.page_folder {
|
@@ -183,25 +200,44 @@
|
|
183
200
|
}
|
184
201
|
|
185
202
|
#sitemap_heading {
|
203
|
+
display: flex;
|
186
204
|
padding: 0;
|
187
|
-
|
188
|
-
.page_infos {
|
189
|
-
margin-right: 210px;
|
190
|
-
text-align: left;
|
191
|
-
float: right;
|
192
|
-
line-height: 28px;
|
193
|
-
background: transparent;
|
194
|
-
}
|
205
|
+
line-height: 28px;
|
195
206
|
|
196
207
|
.page_name {
|
197
|
-
line-height: 28px;
|
198
208
|
margin-left: 43px;
|
199
209
|
}
|
210
|
+
|
211
|
+
.page_urlname {
|
212
|
+
display: none;
|
213
|
+
margin-left: auto;
|
214
|
+
padding-left: 2 * $default-padding;
|
215
|
+
padding-right: 2 * $default-padding;
|
216
|
+
|
217
|
+
@media screen and (min-width: $large-screen-break-point) {
|
218
|
+
display: block;
|
219
|
+
width: $sitemap-url-large-width;
|
220
|
+
}
|
221
|
+
|
222
|
+
@media screen and (min-width: 1440px) {
|
223
|
+
width: $sitemap-url-xlarge-width;
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
.page_status {
|
228
|
+
padding-left: 2 * $default-padding;
|
229
|
+
margin-right: 214px;
|
230
|
+
margin-left: auto;
|
231
|
+
|
232
|
+
@media screen and (min-width: $large-screen-break-point) {
|
233
|
+
margin-left: initial;
|
234
|
+
}
|
235
|
+
}
|
200
236
|
}
|
201
237
|
|
202
238
|
#page_filter_result {
|
203
239
|
display: none;
|
204
|
-
margin-left: 2
|
240
|
+
margin-left: 2 * $default-margin;
|
205
241
|
}
|
206
242
|
|
207
243
|
.alchemy-dialog {
|
@@ -213,6 +249,8 @@
|
|
213
249
|
margin: 0;
|
214
250
|
padding: 0 24px 8px 8px;
|
215
251
|
|
216
|
-
.page_icon {
|
252
|
+
.page_icon {
|
253
|
+
cursor: default;
|
254
|
+
}
|
217
255
|
}
|
218
256
|
}
|
@@ -60,7 +60,7 @@ module Alchemy
|
|
60
60
|
response = query_github
|
61
61
|
if response.code == "200"
|
62
62
|
alchemy_tags = JSON.parse(response.body)
|
63
|
-
alchemy_tags.collect { |h| h['name'] }.sort
|
63
|
+
alchemy_tags.collect { |h| h['name'].tr('v', '') }.sort
|
64
64
|
else
|
65
65
|
# no luck at all?
|
66
66
|
raise UpdateServiceUnavailable
|
@@ -15,16 +15,6 @@ module Alchemy
|
|
15
15
|
)
|
16
16
|
end
|
17
17
|
|
18
|
-
def toggle
|
19
|
-
node = Node.find(params[:id])
|
20
|
-
node.update(folded: !node.folded)
|
21
|
-
if node.folded?
|
22
|
-
head :ok
|
23
|
-
else
|
24
|
-
render partial: 'node', collection: node.children.includes(:page, :children)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
18
|
private
|
29
19
|
|
30
20
|
def resource_params
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
class Api::NodesController < Api::BaseController
|
5
|
+
before_action :load_node
|
6
|
+
before_action :authorize_access, only: [:move, :toggle_folded]
|
7
|
+
|
8
|
+
def move
|
9
|
+
target_parent_node = Node.find(params[:target_parent_id])
|
10
|
+
@node.move_to_child_with_index(target_parent_node, params[:new_position])
|
11
|
+
render json: @node, serializer: NodeSerializer
|
12
|
+
end
|
13
|
+
|
14
|
+
def toggle_folded
|
15
|
+
@node.update(folded: !@node.folded)
|
16
|
+
render json: @node, serializer: NodeSerializer
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def load_node
|
22
|
+
@node = Node.find(params[:id])
|
23
|
+
end
|
24
|
+
|
25
|
+
def authorize_access
|
26
|
+
authorize! :update, @node
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -13,6 +13,7 @@ module Alchemy
|
|
13
13
|
else
|
14
14
|
@pages = Page.accessible_by(current_ability, :index)
|
15
15
|
end
|
16
|
+
@pages = @pages.where.not(parent_id: nil)
|
16
17
|
@pages = @pages.includes(*page_includes)
|
17
18
|
if params[:page_layout].present?
|
18
19
|
Alchemy::Deprecation.warn <<~WARN
|
@@ -104,6 +105,7 @@ module Alchemy
|
|
104
105
|
[
|
105
106
|
:tags,
|
106
107
|
{
|
108
|
+
language: :site,
|
107
109
|
elements: [
|
108
110
|
{
|
109
111
|
nested_elements: [
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Alchemy
|
4
|
+
class ContentEditor < SimpleDelegator
|
5
|
+
alias_method :content, :__getobj__
|
6
|
+
|
7
|
+
def to_partial_path
|
8
|
+
"alchemy/essences/#{essence_partial_name}_editor"
|
9
|
+
end
|
10
|
+
|
11
|
+
def css_classes
|
12
|
+
[
|
13
|
+
"content_editor",
|
14
|
+
essence_partial_name,
|
15
|
+
].compact
|
16
|
+
end
|
17
|
+
|
18
|
+
def data_attributes
|
19
|
+
{
|
20
|
+
content_id: id,
|
21
|
+
content_name: name,
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a string to be passed to Rails form field tags to ensure we have same params layout everywhere.
|
26
|
+
#
|
27
|
+
# === Example:
|
28
|
+
#
|
29
|
+
# <%= text_field_tag content_editor.form_field_name, content_editor.ingredient %>
|
30
|
+
#
|
31
|
+
# === Options:
|
32
|
+
#
|
33
|
+
# You can pass an Essence column_name. Default is 'ingredient'
|
34
|
+
#
|
35
|
+
# ==== Example:
|
36
|
+
#
|
37
|
+
# <%= text_field_tag content_editor.form_field_name(:link), content_editor.ingredient %>
|
38
|
+
#
|
39
|
+
def form_field_name(essence_column = "ingredient")
|
40
|
+
"contents[#{id}][#{essence_column}]"
|
41
|
+
end
|
42
|
+
|
43
|
+
def form_field_id(essence_column = "ingredient")
|
44
|
+
"contents_#{id}_#{essence_column}"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Fixes Rails partial renderer calling to_model on the object
|
48
|
+
# which reveals the delegated content instead of this decorator.
|
49
|
+
def respond_to?(method_name)
|
50
|
+
return false if method_name == :to_model
|
51
|
+
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -9,13 +9,13 @@ module Alchemy
|
|
9
9
|
#
|
10
10
|
def preview_sizes_for_select
|
11
11
|
options_for_select([
|
12
|
-
|
13
|
-
[Alchemy.t(
|
14
|
-
[Alchemy.t(
|
15
|
-
[Alchemy.t(
|
16
|
-
[Alchemy.t(
|
17
|
-
[Alchemy.t(
|
18
|
-
[Alchemy.t(
|
12
|
+
"auto",
|
13
|
+
[Alchemy.t("240", scope: "preview_sizes"), 240],
|
14
|
+
[Alchemy.t("320", scope: "preview_sizes"), 320],
|
15
|
+
[Alchemy.t("480", scope: "preview_sizes"), 480],
|
16
|
+
[Alchemy.t("768", scope: "preview_sizes"), 768],
|
17
|
+
[Alchemy.t("1024", scope: "preview_sizes"), 1024],
|
18
|
+
[Alchemy.t("1280", scope: "preview_sizes"), 1280],
|
19
19
|
])
|
20
20
|
end
|
21
21
|
|
@@ -27,30 +27,30 @@ module Alchemy
|
|
27
27
|
if page.persisted? && page.definition.blank?
|
28
28
|
[
|
29
29
|
page_layout_missing_warning,
|
30
|
-
Alchemy.t(:page_type)
|
31
|
-
].join(
|
30
|
+
Alchemy.t(:page_type),
|
31
|
+
].join(" ").html_safe
|
32
32
|
else
|
33
33
|
Alchemy.t(:page_type)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
def page_status_checkbox(page, attribute)
|
38
|
-
|
37
|
+
def page_status_checkbox(page, attribute, label: nil)
|
38
|
+
label_text = label || page.class.human_attribute_name(attribute)
|
39
39
|
|
40
40
|
if page.attribute_fixed?(attribute)
|
41
41
|
checkbox = check_box(:page, attribute, disabled: true)
|
42
|
-
hint = content_tag(:span, class:
|
42
|
+
hint = content_tag(:span, class: "hint-bubble") do
|
43
43
|
Alchemy.t(:attribute_fixed, attribute: attribute)
|
44
44
|
end
|
45
|
-
content = content_tag(:span, class:
|
46
|
-
"#{checkbox}\n#{
|
45
|
+
content = content_tag(:span, class: "with-hint") do
|
46
|
+
"#{checkbox}\n#{label_text}\n#{hint}".html_safe
|
47
47
|
end
|
48
48
|
else
|
49
49
|
checkbox = check_box(:page, attribute)
|
50
|
-
content = "#{checkbox}\n#{
|
50
|
+
content = "#{checkbox}\n#{label_text}".html_safe
|
51
51
|
end
|
52
52
|
|
53
|
-
content_tag(:label, class:
|
53
|
+
content_tag(:label, class: "checkbox") { content }
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
@@ -198,7 +198,7 @@ module Alchemy
|
|
198
198
|
node_partial_name: "#{root_node.view_folder_name}/node"
|
199
199
|
}.merge(options)
|
200
200
|
|
201
|
-
render(root_node, menu: root_node, node: root_node, options: options)
|
201
|
+
render(root_node.to_partial_path, menu: root_node, node: root_node, options: options)
|
202
202
|
rescue ActionView::MissingTemplate => e
|
203
203
|
warning <<~WARN
|
204
204
|
Menu partial not found for #{name}.
|
@@ -172,28 +172,6 @@ module Alchemy
|
|
172
172
|
definition['validate'].present?
|
173
173
|
end
|
174
174
|
|
175
|
-
# Returns a string to be passed to Rails form field tags to ensure we have same params layout everywhere.
|
176
|
-
#
|
177
|
-
# === Example:
|
178
|
-
#
|
179
|
-
# <%= text_field_tag content.form_field_name, content.ingredient %>
|
180
|
-
#
|
181
|
-
# === Options:
|
182
|
-
#
|
183
|
-
# You can pass an Essence column_name. Default is 'ingredient'
|
184
|
-
#
|
185
|
-
# ==== Example:
|
186
|
-
#
|
187
|
-
# <%= text_field_tag content.form_field_name(:link), content.ingredient %>
|
188
|
-
#
|
189
|
-
def form_field_name(essence_column = 'ingredient')
|
190
|
-
"contents[#{id}][#{essence_column}]"
|
191
|
-
end
|
192
|
-
|
193
|
-
def form_field_id(essence_column = 'ingredient')
|
194
|
-
"contents_#{id}_#{essence_column}"
|
195
|
-
end
|
196
|
-
|
197
175
|
# Returns a string used as dom id on html elements.
|
198
176
|
def dom_id
|
199
177
|
return '' if essence.nil?
|
@@ -245,6 +223,14 @@ module Alchemy
|
|
245
223
|
"has_tinymce" + (has_custom_tinymce_config? ? " #{element.name}_#{name}" : "")
|
246
224
|
end
|
247
225
|
|
226
|
+
def editor
|
227
|
+
@_editor ||= ContentEditor.new(self)
|
228
|
+
end
|
229
|
+
delegate :form_field_name, to: :editor
|
230
|
+
deprecate form_field_name: "use Alchemy::ContentEditor#form_field_name instead", deprecator: Alchemy::Deprecation
|
231
|
+
delegate :form_field_id, to: :editor
|
232
|
+
deprecate form_field_id: "use Alchemy::ContentEditor#form_field_id instead", deprecator: Alchemy::Deprecation
|
233
|
+
|
248
234
|
# Returns the default value from content definition
|
249
235
|
#
|
250
236
|
# If the value is a symbol it gets passed through i18n
|
data/app/models/alchemy/node.rb
CHANGED
@@ -4,12 +4,12 @@ module Alchemy
|
|
4
4
|
class Node < BaseRecord
|
5
5
|
VALID_URL_REGEX = /\A(\/|\D[a-z\+\d\.\-]+:)/
|
6
6
|
|
7
|
-
acts_as_nested_set scope:
|
7
|
+
acts_as_nested_set scope: "language_id", touch: true
|
8
8
|
stampable stamper_class_name: Alchemy.user_class_name
|
9
9
|
|
10
|
-
belongs_to :site, class_name:
|
11
|
-
belongs_to :language, class_name:
|
12
|
-
belongs_to :page, class_name:
|
10
|
+
belongs_to :site, class_name: "Alchemy::Site"
|
11
|
+
belongs_to :language, class_name: "Alchemy::Language"
|
12
|
+
belongs_to :page, class_name: "Alchemy::Page", optional: true, inverse_of: :nodes
|
13
13
|
|
14
14
|
validates :name, presence: true, if: -> { page.nil? }
|
15
15
|
validates :url, format: { with: VALID_URL_REGEX }, unless: -> { url.nil? }
|
@@ -25,9 +25,32 @@ module Alchemy
|
|
25
25
|
class << self
|
26
26
|
# Returns all root nodes for current language
|
27
27
|
def language_root_nodes
|
28
|
-
raise
|
28
|
+
raise "No language found" if Language.current.nil?
|
29
|
+
|
29
30
|
roots.where(language_id: Language.current.id)
|
30
31
|
end
|
32
|
+
|
33
|
+
def available_menu_names
|
34
|
+
read_definitions_file
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Reads the element definitions file named +menus.yml+ from +config/alchemy/+ folder.
|
40
|
+
#
|
41
|
+
def read_definitions_file
|
42
|
+
if ::File.exist?(definitions_file_path)
|
43
|
+
::YAML.safe_load(File.read(definitions_file_path)) || []
|
44
|
+
else
|
45
|
+
raise LoadError, "Could not find menus.yml file! Please run `rails generate alchemy:install`"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the +menus.yml+ file path
|
50
|
+
#
|
51
|
+
def definitions_file_path
|
52
|
+
Rails.root.join "config/alchemy/menus.yml"
|
53
|
+
end
|
31
54
|
end
|
32
55
|
|
33
56
|
# Returns the url
|
@@ -35,7 +58,7 @@ module Alchemy
|
|
35
58
|
# Either the value is stored in the database, aka. an external url.
|
36
59
|
# Or, if attached, the values comes from a page.
|
37
60
|
def url
|
38
|
-
page
|
61
|
+
page&.url_path || read_attribute(:url).presence
|
39
62
|
end
|
40
63
|
|
41
64
|
def to_partial_path
|