alchemy_cms 8.3.5 → 8.3.7
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.
- checksums.yaml +4 -4
- data/app/components/alchemy/admin/resource/action.rb +4 -6
- data/app/components/alchemy/admin/resource/cell.rb +4 -6
- data/app/components/alchemy/admin/resource/table.rb +14 -2
- data/app/components/alchemy/ingredients/richtext_view.rb +4 -0
- data/app/controllers/alchemy/admin/elements_controller.rb +3 -1
- data/app/controllers/alchemy/api/nodes_controller.rb +1 -1
- data/app/models/alchemy/ingredients/richtext.rb +4 -4
- data/app/models/alchemy/node.rb +21 -0
- data/app/models/alchemy/permissions.rb +10 -0
- data/app/models/alchemy/storage_adapter/active_storage.rb +22 -0
- data/app/views/alchemy/_menubar.html.erb +3 -2
- data/lib/alchemy/engine.rb +6 -7
- data/lib/alchemy/seeder.rb +0 -1
- data/lib/alchemy/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8f639a1d9089eea972ddfa6d299a072337dfe342441fddd6150cd2dfae8e47f
|
|
4
|
+
data.tar.gz: dcc66f199295ff7bffbe07e5e2c28f98e3867675066faa87a6142d745425bf5a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65edc33be6840411cefa89b7a01e99f9d63d556b80c7e3b51f8efe40eac5d75fb6fc9ed7622c9af74161ac80719834e1128dfb7e312e85db5cc0a1254a0f5bd1
|
|
7
|
+
data.tar.gz: 00254aaacad56b9fd1cd0ac085d676bb3907f4c69f73a0590ab6c0e1b43ae0f7ae0b59c83e84b953131e65a1937da917cbccecd14ec6447af3080921bd1a54fa
|
|
@@ -10,6 +10,8 @@ module Alchemy
|
|
|
10
10
|
# name of an action to evaluate if the user can perform these action on the given object
|
|
11
11
|
# @param [String, nil] :tooltip
|
|
12
12
|
# show a tooltip around the button
|
|
13
|
+
# @param [Object, nil] :resource
|
|
14
|
+
# the resource of the row the action belongs to
|
|
13
15
|
# @param [Lambda] :block
|
|
14
16
|
# a block to include a button or a link
|
|
15
17
|
#
|
|
@@ -30,15 +32,11 @@ module Alchemy
|
|
|
30
32
|
<% end %>
|
|
31
33
|
ERB
|
|
32
34
|
|
|
33
|
-
def initialize(name = nil, tooltip = nil, &block)
|
|
35
|
+
def initialize(name = nil, tooltip = nil, resource = nil, &block)
|
|
34
36
|
@name = name
|
|
35
37
|
@tooltip = tooltip
|
|
36
|
-
@block = block
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def with_resource(resource)
|
|
40
38
|
@resource = resource
|
|
41
|
-
|
|
39
|
+
@block = block
|
|
42
40
|
end
|
|
43
41
|
end
|
|
44
42
|
end
|
|
@@ -7,6 +7,8 @@ module Alchemy
|
|
|
7
7
|
#
|
|
8
8
|
# @param [String, nil] :css_classes
|
|
9
9
|
# css classes that are show at the table cell
|
|
10
|
+
# @param [Object, nil] :resource
|
|
11
|
+
# the resource of the row the cell belongs to
|
|
10
12
|
# @param [Lambda] :block
|
|
11
13
|
# a block to include a button or a link
|
|
12
14
|
#
|
|
@@ -19,14 +21,10 @@ module Alchemy
|
|
|
19
21
|
</td>
|
|
20
22
|
ERB
|
|
21
23
|
|
|
22
|
-
def initialize(css_classes, &block)
|
|
24
|
+
def initialize(css_classes, resource = nil, &block)
|
|
23
25
|
@css_classes = css_classes
|
|
24
|
-
@block = block
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def with_resource(resource)
|
|
28
26
|
@resource = resource
|
|
29
|
-
|
|
27
|
+
@block = block
|
|
30
28
|
end
|
|
31
29
|
end
|
|
32
30
|
end
|
|
@@ -69,12 +69,12 @@ module Alchemy
|
|
|
69
69
|
<% collection.each do |resource| %>
|
|
70
70
|
<tr class="<%= cycle('even', 'odd') %>">
|
|
71
71
|
<% cells.each do |cell| %>
|
|
72
|
-
<%= render cell
|
|
72
|
+
<%= render cell_for(cell, resource) %>
|
|
73
73
|
<% end %>
|
|
74
74
|
<% if actions? %>
|
|
75
75
|
<td class="tools">
|
|
76
76
|
<% actions.each do |action| %>
|
|
77
|
-
<%= render action
|
|
77
|
+
<%= render action_for(action, resource) %>
|
|
78
78
|
<% end %>
|
|
79
79
|
</td>
|
|
80
80
|
<% end %>
|
|
@@ -134,6 +134,18 @@ module Alchemy
|
|
|
134
134
|
|
|
135
135
|
private
|
|
136
136
|
|
|
137
|
+
# A fresh cell instance is built for every resource because ViewComponent
|
|
138
|
+
# instances are single-use and may not be rendered more than once.
|
|
139
|
+
def cell_for(cell, resource)
|
|
140
|
+
Cell.new(cell.css_classes, resource, &cell.block)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# A fresh action instance is built for every resource because ViewComponent
|
|
144
|
+
# instances are single-use and may not be rendered more than once.
|
|
145
|
+
def action_for(action, resource)
|
|
146
|
+
Action.new(action.name, action.tooltip, resource, &action.block)
|
|
147
|
+
end
|
|
148
|
+
|
|
137
149
|
##
|
|
138
150
|
# if no cells are available the resource_helper will be used, to generate the
|
|
139
151
|
# default attributes of the given resource
|
|
@@ -3,6 +3,8 @@ module Alchemy
|
|
|
3
3
|
class RichtextView < BaseView
|
|
4
4
|
attr_reader :plain_text
|
|
5
5
|
|
|
6
|
+
delegate :sanitizer_settings, to: :ingredient
|
|
7
|
+
|
|
6
8
|
# @param ingredient [Alchemy::Ingredient]
|
|
7
9
|
# @param plain_text [Boolean] (false) Whether to show as plain text or with markup
|
|
8
10
|
def initialize(ingredient, plain_text: nil, html_options: {})
|
|
@@ -13,6 +15,8 @@ module Alchemy
|
|
|
13
15
|
def call
|
|
14
16
|
if plain_text
|
|
15
17
|
ingredient.stripped_body
|
|
18
|
+
elsif sanitizer_settings.present?
|
|
19
|
+
sanitize(value.to_s, **sanitizer_settings)
|
|
16
20
|
else
|
|
17
21
|
value.to_s.html_safe
|
|
18
22
|
end.html_safe
|
|
@@ -10,9 +10,11 @@ module Alchemy
|
|
|
10
10
|
include Alchemy::Admin::PreviewTime
|
|
11
11
|
|
|
12
12
|
before_action :load_element, only: [:update, :destroy, :collapse, :expand, :publish]
|
|
13
|
-
authorize_resource class: Alchemy::Element
|
|
13
|
+
authorize_resource class: Alchemy::Element, except: [:index]
|
|
14
14
|
|
|
15
15
|
def index
|
|
16
|
+
authorize! :edit_content, @page
|
|
17
|
+
|
|
16
18
|
preloaded = Alchemy::ElementPreloader
|
|
17
19
|
.new(page_version: @page_version)
|
|
18
20
|
.call
|
|
@@ -6,7 +6,7 @@ module Alchemy
|
|
|
6
6
|
before_action :authorize_access, only: [:move, :toggle_folded]
|
|
7
7
|
|
|
8
8
|
def index
|
|
9
|
-
@nodes = Node.
|
|
9
|
+
@nodes = Node.accessible_by(current_ability, :index)
|
|
10
10
|
@nodes = @nodes.includes(:parent)
|
|
11
11
|
@nodes = @nodes.where(language_id: params[:language_id]) if params[:language_id]
|
|
12
12
|
@nodes = @nodes.ransack(params[:filter]).result.order(:lft)
|
|
@@ -36,6 +36,10 @@ module Alchemy
|
|
|
36
36
|
settings[:tinymce] || {}
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
def sanitizer_settings
|
|
40
|
+
settings[:sanitizer] || {}
|
|
41
|
+
end
|
|
42
|
+
|
|
39
43
|
private
|
|
40
44
|
|
|
41
45
|
def strip_content
|
|
@@ -48,10 +52,6 @@ module Alchemy
|
|
|
48
52
|
sanitizer_settings
|
|
49
53
|
)
|
|
50
54
|
end
|
|
51
|
-
|
|
52
|
-
def sanitizer_settings
|
|
53
|
-
settings[:sanitizer] || {}
|
|
54
|
-
end
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
end
|
data/app/models/alchemy/node.rb
CHANGED
|
@@ -20,6 +20,27 @@ module Alchemy
|
|
|
20
20
|
foreign_key: :related_object_id,
|
|
21
21
|
inverse_of: :related_object
|
|
22
22
|
|
|
23
|
+
# All nodes of the current site whose language is published.
|
|
24
|
+
scope :from_current_public_site, -> {
|
|
25
|
+
joins(:language)
|
|
26
|
+
.where(Alchemy::Language.table_name => {site_id: Alchemy::Current.site})
|
|
27
|
+
.merge(Alchemy::Language.published)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Nodes an anonymous visitor may see: external links or nodes attached to a
|
|
31
|
+
# published, unrestricted page, scoped to the current site.
|
|
32
|
+
scope :available_to_guests, -> {
|
|
33
|
+
from_current_public_site.where(page: nil)
|
|
34
|
+
.or(from_current_public_site.where(page: Alchemy::Page.published.not_restricted))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Like #available_to_guests but including nodes attached to restricted pages,
|
|
38
|
+
# which logged in members are allowed to see.
|
|
39
|
+
scope :available_to_members, -> {
|
|
40
|
+
from_current_public_site.where(page: nil)
|
|
41
|
+
.or(from_current_public_site.where(page: Alchemy::Page.published))
|
|
42
|
+
}
|
|
43
|
+
|
|
23
44
|
before_validation :translate_root_menu_name, if: -> { root? }
|
|
24
45
|
before_validation :set_menu_type_from_root, unless: -> { root? }
|
|
25
46
|
|
|
@@ -44,6 +44,11 @@ module Alchemy
|
|
|
44
44
|
can :read, Alchemy::Page, Alchemy::Page.published.not_restricted.from_current_site do |p|
|
|
45
45
|
p.public? && !p.restricted? && p.site == Alchemy::Current.site
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
can :read, Alchemy::Node, Alchemy::Node.available_to_guests do |node|
|
|
49
|
+
node.language.public? && node.language.site_id == Alchemy::Current.site&.id &&
|
|
50
|
+
(node.page.nil? || (node.page.public? && !node.page.restricted?))
|
|
51
|
+
end
|
|
47
52
|
end
|
|
48
53
|
end
|
|
49
54
|
|
|
@@ -67,6 +72,11 @@ module Alchemy
|
|
|
67
72
|
can :read, Alchemy::Page, Alchemy::Page.published.from_current_site do |p|
|
|
68
73
|
p.public? && p.site == Alchemy::Current.site
|
|
69
74
|
end
|
|
75
|
+
|
|
76
|
+
can :read, Alchemy::Node, Alchemy::Node.available_to_members do |node|
|
|
77
|
+
node.language.public? && node.language.site_id == Alchemy::Current.site&.id &&
|
|
78
|
+
(node.page.nil? || node.page.public?)
|
|
79
|
+
end
|
|
70
80
|
end
|
|
71
81
|
end
|
|
72
82
|
|
|
@@ -11,6 +11,17 @@ module Alchemy
|
|
|
11
11
|
base.after_create_commit if: :svg? do
|
|
12
12
|
SanitizeSvgJob.perform_later(self, file_accessor: :image_file)
|
|
13
13
|
end
|
|
14
|
+
|
|
15
|
+
# The image file's dirty state is cleared by the time
|
|
16
|
+
# +after_update_commit+ runs, so capture it here to know whether the
|
|
17
|
+
# blob was actually replaced (rather than only its metadata updated).
|
|
18
|
+
base.before_update do
|
|
19
|
+
@image_file_replaced = image_file.changed?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
base.after_update_commit if: -> { @image_file_replaced && svg? } do
|
|
23
|
+
SanitizeSvgJob.perform_later(self, file_accessor: :image_file)
|
|
24
|
+
end
|
|
14
25
|
end
|
|
15
26
|
end
|
|
16
27
|
|
|
@@ -20,6 +31,17 @@ module Alchemy
|
|
|
20
31
|
base.after_create_commit if: :svg? do
|
|
21
32
|
SanitizeSvgJob.perform_later(self, file_accessor: :file)
|
|
22
33
|
end
|
|
34
|
+
|
|
35
|
+
# The file's dirty state is cleared by the time
|
|
36
|
+
# +after_update_commit+ runs, so capture it here to know whether the
|
|
37
|
+
# blob was actually replaced (rather than only its metadata updated).
|
|
38
|
+
base.before_update do
|
|
39
|
+
@file_replaced = file.changed?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
base.after_update_commit if: -> { @file_replaced && svg? } do
|
|
43
|
+
SanitizeSvgJob.perform_later(self, file_accessor: :file)
|
|
44
|
+
end
|
|
23
45
|
end
|
|
24
46
|
end
|
|
25
47
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
box-shadow: 0 0 0 1px #fff;
|
|
18
18
|
box-sizing: border-box;
|
|
19
19
|
border-bottom-right-radius: var(--border-radius);
|
|
20
|
-
padding: 12px 16px 12px;
|
|
20
|
+
padding: 12px 16px 12px 12px;
|
|
21
21
|
gap: 12px;
|
|
22
22
|
justify-content: space-between;
|
|
23
23
|
align-items: center;
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
font-family: "Open Sans", "Lucida Grande", "Lucida Sans Unicode",
|
|
27
27
|
"Lucida Sans", Verdana, Tahoma, sans-serif;
|
|
28
28
|
font-size: 13px;
|
|
29
|
+
line-height: normal;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
.menubar * {
|
|
@@ -107,7 +108,7 @@
|
|
|
107
108
|
<% end %>
|
|
108
109
|
<% end %>
|
|
109
110
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
|
110
|
-
<path fill="#fff" d="M15.7 7.9L9.9 2.2 2.1 4.3 0 12.1l5.7 5.8 7.8-2.1 2.2-7.9zm-5.3 10.3l-1.2 4.4 3.2 3.2 4.4-1.2 1.2-4.4-3.2-3.2-4.4 1.2zM23.5 7.3L17.2 9l-1.7 6.2 4.5 4.6 6.2-1.7 1.7-6.2-4.4-4.6z"/>
|
|
111
|
+
<path fill="#fff" d="M15.7 7.9L9.9 2.2 2.1 4.3 0 12.1l5.7 5.8 7.8-2.1 2.2-7.9zm-5.3 10.3l-1.2 4.4 3.2 3.2 4.4-1.2 1.2-4.4-3.2-3.2-4.4 1.2zM23.5 7.3L17.2 9l-1.7 6.2 4.5 4.6 6.2-1.7 1.7-6.2-4.4-4.6z" />
|
|
111
112
|
</svg>
|
|
112
113
|
</div>
|
|
113
114
|
</template>
|
data/lib/alchemy/engine.rb
CHANGED
|
@@ -178,13 +178,12 @@ module Alchemy
|
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
if app.config.active_storage
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
#
|
|
187
|
-
# We sanitize SVGs on upload and therefore can serve them as image.
|
|
181
|
+
# Serve SVGs with their real image/svg+xml content type so they render
|
|
182
|
+
# in <img> tags. We deliberately do NOT add svg to
|
|
183
|
+
# +content_types_allowed_inline+: an SVG opened as a top-level document
|
|
184
|
+
# executes its embedded scripts, so it must be served with an
|
|
185
|
+
# attachment (download) disposition rather than inline. <img> ignores
|
|
186
|
+
# Content-Disposition, so image display is unaffected.
|
|
188
187
|
if app.config.active_storage.content_types_to_serve_as_binary.include? svg
|
|
189
188
|
app.config.active_storage.content_types_to_serve_as_binary.delete(svg)
|
|
190
189
|
end
|
data/lib/alchemy/seeder.rb
CHANGED
|
@@ -111,7 +111,6 @@ module Alchemy
|
|
|
111
111
|
def create_page(draft, attributes = {})
|
|
112
112
|
children = draft.delete("children") || []
|
|
113
113
|
page = Alchemy::Page.new(draft.merge(attributes))
|
|
114
|
-
page.versions.build
|
|
115
114
|
page.save!
|
|
116
115
|
log "Created page: #{page.name}"
|
|
117
116
|
children.each do |child|
|
data/lib/alchemy/version.rb
CHANGED