alchemy_cms 8.3.6 → 8.3.8
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/assets/builds/alchemy/admin.css +1 -1
- data/app/assets/builds/alchemy/alchemy_admin.min.js +1 -1
- data/app/assets/builds/alchemy/alchemy_admin.min.js.map +1 -1
- 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/controllers/alchemy/admin/elements_controller.rb +3 -1
- data/app/javascript/alchemy_admin/components/attachment_select.js +5 -2
- data/app/javascript/alchemy_admin/components/node_select.js +6 -3
- data/app/javascript/alchemy_admin/components/page_select.js +8 -5
- data/app/javascript/alchemy_admin/components/remote_select.js +35 -1
- data/app/stylesheets/alchemy/admin/_tom-select.scss +1 -0
- data/app/stylesheets/alchemy/admin/forms.scss +2 -1
- data/app/views/alchemy/_menubar.html.erb +3 -2
- data/app/views/alchemy/admin/nodes/_page_nodes.html.erb +4 -2
- data/lib/alchemy/seeder.rb +0 -1
- data/lib/alchemy/version.rb +1 -1
- metadata +1 -1
|
@@ -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
|
|
@@ -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
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
RemoteSelect,
|
|
3
|
+
escapeHtml
|
|
4
|
+
} from "alchemy_admin/components/remote_select"
|
|
2
5
|
|
|
3
6
|
export class AttachmentSelect extends RemoteSelect {
|
|
4
7
|
_renderResult(item) {
|
|
@@ -15,7 +18,7 @@ export class AttachmentSelect extends RemoteSelect {
|
|
|
15
18
|
_renderListEntry(attachment, term) {
|
|
16
19
|
return `
|
|
17
20
|
<div class="attachment-select--attachment">
|
|
18
|
-
<alchemy-icon name="${attachment.icon_css_class}"></alchemy-icon>
|
|
21
|
+
<alchemy-icon name="${escapeHtml(attachment.icon_css_class)}"></alchemy-icon>
|
|
19
22
|
<span class="attachment-select--attachment-name">${this._hightlightTerm(attachment.name, term)}</span>
|
|
20
23
|
</div>
|
|
21
24
|
`
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
RemoteSelect,
|
|
3
|
+
escapeHtml
|
|
4
|
+
} from "alchemy_admin/components/remote_select"
|
|
2
5
|
|
|
3
6
|
class NodeSelect extends RemoteSelect {
|
|
4
7
|
_searchQuery(term, page) {
|
|
@@ -23,7 +26,7 @@ class NodeSelect extends RemoteSelect {
|
|
|
23
26
|
* @private
|
|
24
27
|
*/
|
|
25
28
|
_renderListEntry(node, term) {
|
|
26
|
-
const ancestors = node.ancestors.map((a) => a.name)
|
|
29
|
+
const ancestors = node.ancestors.map((a) => escapeHtml(a.name))
|
|
27
30
|
const seperator = `<alchemy-icon name="arrow-right-s"></alchemy-icon>`
|
|
28
31
|
|
|
29
32
|
return `
|
|
@@ -38,7 +41,7 @@ class NodeSelect extends RemoteSelect {
|
|
|
38
41
|
</span>
|
|
39
42
|
</div>
|
|
40
43
|
<div class="node-select--node-url">
|
|
41
|
-
${node.url || ""}
|
|
44
|
+
${escapeHtml(node.url || "")}
|
|
42
45
|
</div>
|
|
43
46
|
</div>
|
|
44
47
|
`
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
RemoteSelect,
|
|
3
|
+
escapeHtml
|
|
4
|
+
} from "alchemy_admin/components/remote_select"
|
|
2
5
|
|
|
3
6
|
export class PageSelect extends RemoteSelect {
|
|
4
7
|
get pageId() {
|
|
@@ -30,7 +33,7 @@ export class PageSelect extends RemoteSelect {
|
|
|
30
33
|
* @private
|
|
31
34
|
*/
|
|
32
35
|
_renderResult(page) {
|
|
33
|
-
return page.text || page.name
|
|
36
|
+
return escapeHtml(page.text || page.name)
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
@@ -46,11 +49,11 @@ export class PageSelect extends RemoteSelect {
|
|
|
46
49
|
<div class="page-select--top">
|
|
47
50
|
<alchemy-icon name="file-3"></alchemy-icon>
|
|
48
51
|
<span class="page-select--page-name">${this._hightlightTerm(page.name, term)}</span>
|
|
49
|
-
<span class="page-select--site-name">${page.site.name}</span>
|
|
52
|
+
<span class="page-select--site-name">${escapeHtml(page.site.name)}</span>
|
|
50
53
|
</div>
|
|
51
54
|
<div class="page-select--bottom">
|
|
52
|
-
<span class="page-select--page-urlname">${page.url_path}</span>
|
|
53
|
-
<span class="page-select--language-code">${page.language_code}</span>
|
|
55
|
+
<span class="page-select--page-urlname">${escapeHtml(page.url_path)}</span>
|
|
56
|
+
<span class="page-select--language-code">${escapeHtml(page.language_code)}</span>
|
|
54
57
|
</div>
|
|
55
58
|
</div>
|
|
56
59
|
`
|
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
import { setupSelectLocale } from "alchemy_admin/i18n"
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Escapes a string for use within HTML. Select2 hands the return value of
|
|
5
|
+
* `formatResult`/`formatSelection` straight to jQuery, so everything woven into
|
|
6
|
+
* those templates has to be escaped here.
|
|
7
|
+
* @param {string} value
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
export function escapeHtml(value) {
|
|
11
|
+
return `${value}`
|
|
12
|
+
.replace(/&/g, "&")
|
|
13
|
+
.replace(/</g, "<")
|
|
14
|
+
.replace(/>/g, ">")
|
|
15
|
+
.replace(/"/g, """)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Escapes a string for literal use inside a `RegExp`, so a term with
|
|
20
|
+
* metacharacters (e.g. `(`) matches itself instead of being compiled as a
|
|
21
|
+
* pattern, which could match the wrong text or throw a SyntaxError.
|
|
22
|
+
* @param {string} value
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function escapeRegExp(value) {
|
|
26
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
27
|
+
}
|
|
28
|
+
|
|
3
29
|
export function hightlightTerm(name, term) {
|
|
4
|
-
|
|
30
|
+
// The result is rendered raw as the entry's name, so the name must be escaped
|
|
31
|
+
// before the term markup is woven in — otherwise a crafted name (e.g. a menu
|
|
32
|
+
// node title) is a stored XSS sink.
|
|
33
|
+
const escapedName = escapeHtml(name)
|
|
34
|
+
if (!term) return escapedName
|
|
35
|
+
return escapedName.replace(
|
|
36
|
+
new RegExp(escapeRegExp(term), "gi"),
|
|
37
|
+
(match) => `<em>${match}</em>`
|
|
38
|
+
)
|
|
5
39
|
}
|
|
6
40
|
|
|
7
41
|
export class RemoteSelect extends HTMLElement {
|
|
@@ -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>
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
<th class="tools"></th>
|
|
8
8
|
</tr>
|
|
9
9
|
<% nodes = @page.nodes.select(&:persisted?) %>
|
|
10
|
-
<% seperator = '<alchemy-icon name="arrow-right-s" style="vertical-align: text-bottom"></alchemy-icon>' %>
|
|
10
|
+
<% seperator = '<alchemy-icon name="arrow-right-s" style="vertical-align: text-bottom"></alchemy-icon>'.html_safe %>
|
|
11
11
|
<% if nodes.length > 0 %>
|
|
12
12
|
<% nodes.each do |node| %>
|
|
13
13
|
<tr class="even">
|
|
14
|
-
<td
|
|
14
|
+
<td>
|
|
15
|
+
<%= safe_join(node.ancestors.map(&:name), seperator) %><%= seperator %><strong><%= node.name %></strong>
|
|
16
|
+
</td>
|
|
15
17
|
<td class="tools">
|
|
16
18
|
<sl-tooltip content="<%= Alchemy.t("delete_node") %>">
|
|
17
19
|
<%= link_to render_icon(:minus),
|
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