alchemy_cms 8.2.7 → 8.3.0
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/README.md +4 -1
- 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/assets/builds/alchemy/dark-theme.css +1 -1
- data/app/assets/builds/alchemy/light-theme.css +1 -1
- data/app/assets/builds/alchemy/preview.min.js +1 -1
- data/app/assets/builds/alchemy/theme.css +1 -1
- data/app/assets/builds/alchemy/welcome.css +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy-dark/content.min.css +1 -1
- data/app/assets/builds/tinymce/skins/ui/alchemy/skin.min.css +1 -1
- data/app/assets/builds/tinymce/skins/ui/alchemy-dark/skin.min.css +1 -1
- data/app/assets/images/alchemy/admin/logo.svg +27 -0
- data/app/assets/images/alchemy/icons-sprite.svg +1 -1
- data/app/components/alchemy/admin/dashboard/widget.rb +40 -0
- data/app/components/alchemy/admin/dashboard/widgets/attachment_counts.rb +17 -0
- data/app/components/alchemy/admin/dashboard/widgets/element_usage.rb +37 -0
- data/app/components/alchemy/admin/dashboard/widgets/greeting.html.erb +13 -0
- data/app/components/alchemy/admin/dashboard/widgets/greeting.rb +21 -0
- data/app/components/alchemy/admin/dashboard/widgets/locked_pages.html.erb +54 -0
- data/app/components/alchemy/admin/dashboard/widgets/locked_pages.rb +20 -0
- data/app/components/alchemy/admin/dashboard/widgets/online_users.html.erb +22 -0
- data/app/components/alchemy/admin/dashboard/widgets/online_users.rb +19 -0
- data/app/components/alchemy/admin/dashboard/widgets/page_counts.rb +23 -0
- data/app/components/alchemy/admin/dashboard/widgets/page_usage.rb +46 -0
- data/app/components/alchemy/admin/dashboard/widgets/picture_counts.rb +17 -0
- data/app/components/alchemy/admin/dashboard/widgets/recent_pages.html.erb +41 -0
- data/app/components/alchemy/admin/dashboard/widgets/recent_pages.rb +16 -0
- data/app/components/alchemy/admin/dashboard/widgets/sites.html.erb +29 -0
- data/app/components/alchemy/admin/dashboard/widgets/sites.rb +15 -0
- data/app/components/alchemy/admin/dashboard/widgets/stat_widget.html.erb +23 -0
- data/app/components/alchemy/admin/dashboard/widgets/stat_widget.rb +19 -0
- data/app/components/alchemy/admin/dashboard/widgets/system_info.html.erb +32 -0
- data/app/components/alchemy/admin/dashboard/widgets/system_info.rb +37 -0
- data/app/components/alchemy/admin/dashboard/widgets/usage_widget.html.erb +42 -0
- data/app/components/alchemy/admin/dashboard/widgets/usage_widget.rb +66 -0
- data/app/components/alchemy/admin/dashboard/widgets/user_counts.rb +25 -0
- data/app/components/alchemy/admin/element_editor.html.erb +27 -20
- data/app/components/alchemy/admin/element_schedule_timestamps.rb +33 -0
- data/app/components/alchemy/admin/element_select.rb +4 -3
- data/app/components/alchemy/admin/page_node.html.erb +1 -20
- data/app/components/alchemy/admin/page_publication_fields.html.erb +30 -0
- data/app/components/alchemy/admin/page_publication_fields.rb +18 -0
- data/app/components/alchemy/admin/page_status_indicators.html.erb +29 -0
- data/app/components/alchemy/admin/page_status_indicators.rb +9 -0
- data/app/components/alchemy/admin/publish_element_button.html.erb +12 -4
- data/app/components/alchemy/ingredients/headline_editor.rb +1 -1
- data/app/controllers/alchemy/admin/dashboard/widgets_controller.rb +21 -0
- data/app/controllers/alchemy/admin/dashboard_controller.rb +3 -12
- data/app/controllers/alchemy/pages_controller.rb +5 -4
- data/app/helpers/alchemy/elements_block_helper.rb +1 -0
- data/app/javascript/alchemy_admin/components/auto_submit.js +15 -9
- data/app/javascript/alchemy_admin/components/char_counter.js +17 -7
- data/app/javascript/alchemy_admin/components/clipboard_button.js +2 -6
- data/app/javascript/alchemy_admin/components/color_select.js +13 -4
- data/app/javascript/alchemy_admin/components/datepicker.js +11 -14
- data/app/javascript/alchemy_admin/components/dialog_link.js +5 -2
- data/app/javascript/alchemy_admin/components/element_editor/delete_element_button.js +6 -3
- data/app/javascript/alchemy_admin/components/element_editor.js +45 -28
- data/app/javascript/alchemy_admin/components/element_select.js +7 -4
- data/app/javascript/alchemy_admin/components/elements_window.js +38 -31
- data/app/javascript/alchemy_admin/components/elements_window_handle.js +7 -3
- data/app/javascript/alchemy_admin/components/file_editor.js +5 -2
- data/app/javascript/alchemy_admin/components/ingredient_group.js +6 -4
- data/app/javascript/alchemy_admin/components/link_buttons/link_button.js +1 -2
- data/app/javascript/alchemy_admin/components/link_buttons/unlink_button.js +1 -2
- data/app/javascript/alchemy_admin/components/link_buttons.js +6 -2
- data/app/javascript/alchemy_admin/components/list_filter.js +44 -29
- data/app/javascript/alchemy_admin/components/message.js +22 -15
- data/app/javascript/alchemy_admin/components/overlay.js +5 -7
- data/app/javascript/alchemy_admin/components/page_publication_fields.js +38 -25
- data/app/javascript/alchemy_admin/components/picture_description_select.js +5 -2
- data/app/javascript/alchemy_admin/components/picture_editor.js +5 -10
- data/app/javascript/alchemy_admin/components/picture_thumbnail.js +4 -5
- data/app/javascript/alchemy_admin/components/preview_window.js +5 -10
- data/app/javascript/alchemy_admin/components/publish_page_button.js +2 -5
- data/app/javascript/alchemy_admin/components/remote_select.js +53 -23
- data/app/javascript/alchemy_admin/components/select.js +169 -26
- data/app/javascript/alchemy_admin/components/sortable_elements.js +1 -1
- data/app/javascript/alchemy_admin/components/spinner.js +11 -11
- data/app/javascript/alchemy_admin/components/tags_autocomplete.js +9 -1
- data/app/javascript/alchemy_admin/components/tinymce.js +16 -22
- data/app/javascript/alchemy_admin/components/uploader/file_upload.js +48 -45
- data/app/javascript/alchemy_admin/components/uploader/progress.js +70 -84
- data/app/javascript/alchemy_admin/components/uploader.js +71 -46
- data/app/javascript/alchemy_admin/dialog.js +3 -0
- data/app/javascript/alchemy_admin/hotkeys.js +0 -18
- data/app/javascript/alchemy_admin/image_cropper.js +7 -9
- data/app/javascript/alchemy_admin/initializer.js +21 -0
- data/app/javascript/alchemy_admin/utils/dispatch_page_dirty_event.js +7 -0
- data/app/javascript/tinymce/plugins/alchemy_link/index.js +9 -0
- data/app/jobs/alchemy/base_job.rb +2 -2
- data/app/jobs/alchemy/invalidate_elements_cache_job.rb +33 -0
- data/app/models/alchemy/page/page_naming.rb +28 -5
- data/app/models/alchemy/page/page_natures.rb +7 -2
- data/app/models/alchemy/page/page_scopes.rb +2 -2
- data/app/models/alchemy/page/url_path.rb +7 -2
- data/app/models/alchemy/page.rb +2 -2
- data/app/models/alchemy/page_definition.rb +1 -0
- data/app/models/alchemy/permissions.rb +1 -1
- data/app/models/concerns/alchemy/relatable_resource.rb +8 -0
- data/app/services/alchemy/page_finder.rb +88 -0
- data/app/stylesheets/alchemy/_custom-properties.scss +6 -4
- data/app/stylesheets/alchemy/_mixins.scss +1 -7
- data/app/stylesheets/alchemy/_themes.scss +13 -1
- data/app/stylesheets/alchemy/admin/_tom-select.scss +240 -0
- data/app/stylesheets/alchemy/admin/archive.scss +0 -1
- data/app/stylesheets/alchemy/admin/base.scss +0 -19
- data/app/stylesheets/alchemy/admin/dashboard.scss +395 -28
- data/app/stylesheets/alchemy/admin/elements.scss +14 -17
- data/app/stylesheets/alchemy/admin/form_fields.scss +3 -3
- data/app/stylesheets/alchemy/admin/forms.scss +107 -93
- data/app/stylesheets/alchemy/admin/icons.scss +28 -0
- data/app/stylesheets/alchemy/admin/image_library.scss +20 -10
- data/app/stylesheets/alchemy/admin/navigation.scss +4 -1
- data/app/stylesheets/alchemy/admin/popover.scss +3 -5
- data/app/stylesheets/alchemy/admin/resource_info.scss +11 -17
- data/app/stylesheets/alchemy/admin/shoelace.scss +8 -0
- data/app/stylesheets/alchemy/admin/sitemap.scss +5 -0
- data/app/stylesheets/alchemy/admin/tables.scss +32 -3
- data/app/stylesheets/alchemy/admin/toolbar.scss +0 -1
- data/app/stylesheets/alchemy/admin.scss +1 -0
- data/app/stylesheets/tinymce/skins/ui/alchemy/skin.scss +0 -4
- data/app/stylesheets/tinymce/skins/ui/alchemy-dark/skin.scss +0 -4
- data/app/types/alchemy/wildcard_url_type.rb +48 -0
- data/app/views/alchemy/_menubar.html.erb +1 -5
- data/app/views/alchemy/admin/attachments/edit.html.erb +6 -3
- data/app/views/alchemy/admin/dashboard/_dashboard.html.erb +3 -2
- data/app/views/alchemy/admin/dashboard/_footer.html.erb +22 -0
- data/app/views/alchemy/admin/dashboard/_stats.html.erb +7 -0
- data/app/views/alchemy/admin/dashboard/_top.html.erb +4 -12
- data/app/views/alchemy/admin/dashboard/_widgets.html.erb +7 -0
- data/app/views/alchemy/admin/dashboard/index.html.erb +0 -17
- data/app/views/alchemy/admin/dashboard/info.html.erb +1 -62
- data/app/views/alchemy/admin/dashboard/widgets/show.html.erb +3 -0
- data/app/views/alchemy/admin/elements/_form.html.erb +2 -1
- data/app/views/alchemy/admin/elements/_schedule.html.erb +2 -15
- data/app/views/alchemy/admin/elements/_schedule_fields.html.erb +2 -0
- data/app/views/alchemy/admin/layoutpages/edit.html.erb +6 -3
- data/app/views/alchemy/admin/nodes/_page_nodes.html.erb +10 -8
- data/app/views/alchemy/admin/pages/_form.html.erb +25 -19
- data/app/views/alchemy/admin/pages/_publication_fields.html.erb +2 -32
- data/app/views/alchemy/admin/pages/_table.html.erb +1 -18
- data/app/views/alchemy/admin/pages/configure.html.erb +2 -2
- data/app/views/alchemy/admin/pages/info.html.erb +6 -0
- data/app/views/alchemy/admin/resources/_form.html.erb +7 -4
- data/app/views/alchemy/admin/resources/edit.html.erb +3 -1
- data/app/views/alchemy/admin/resources/new.html.erb +3 -1
- data/app/views/alchemy/admin/styleguide/index.html.erb +52 -30
- data/app/views/alchemy/admin/translations/_en.js +4 -0
- data/app/views/layouts/alchemy/admin.html.erb +3 -3
- data/config/importmap.rb +2 -0
- data/config/locales/alchemy.en.yml +15 -0
- data/config/routes.rb +1 -0
- data/lib/alchemy/configuration/class_option.rb +46 -3
- data/lib/alchemy/configuration/collection_option.rb +4 -0
- data/lib/alchemy/configurations/dashboard.rb +79 -0
- data/lib/alchemy/configurations/main.rb +15 -0
- data/lib/alchemy/engine.rb +9 -3
- data/lib/alchemy/sprockets/skip_builds_compression.rb +33 -0
- data/lib/alchemy/test_support/capybara_helpers.rb +17 -0
- data/lib/alchemy/test_support/relatable_resource_examples.rb +20 -0
- data/lib/alchemy/test_support/rspec_matchers.rb +8 -0
- data/lib/alchemy/test_support/shared_publishable_examples.rb +38 -31
- data/lib/alchemy/tinymce.rb +1 -1
- data/lib/alchemy/version.rb +17 -3
- data/vendor/javascript/cropperjs.min.js +1 -1
- data/vendor/javascript/flatpickr.min.js +1 -1
- data/vendor/javascript/floating-ui.min.js +1 -0
- data/vendor/javascript/keymaster.min.js +1 -1
- data/vendor/javascript/rails-ujs.min.js +1 -1
- data/vendor/javascript/shoelace.min.js +93 -93
- data/vendor/javascript/sortable.min.js +1 -1
- data/vendor/javascript/tinymce.min.js +5 -1
- data/vendor/javascript/tom-select.min.js +1 -0
- metadata +57 -18
- data/app/javascript/alchemy_admin/components/alchemy_html_element.js +0 -129
- data/app/views/alchemy/admin/dashboard/_left_column.html.erb +0 -4
- data/app/views/alchemy/admin/dashboard/_right_column.html.erb +0 -9
- data/app/views/alchemy/admin/dashboard/widgets/_locked_pages.html.erb +0 -52
- data/app/views/alchemy/admin/dashboard/widgets/_recent_pages.html.erb +0 -34
- data/app/views/alchemy/admin/dashboard/widgets/_sites.html.erb +0 -25
- data/app/views/alchemy/admin/dashboard/widgets/_users.html.erb +0 -21
- data/app/views/alchemy/admin/languages/edit.html.erb +0 -1
- data/app/views/alchemy/admin/languages/new.html.erb +0 -1
- data/app/views/alchemy/admin/sites/edit.html.erb +0 -1
- data/app/views/alchemy/admin/sites/new.html.erb +0 -1
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"
|
|
2
2
|
import { post } from "alchemy_admin/utils/ajax"
|
|
3
3
|
import { createHtmlElement } from "alchemy_admin/utils/dom_helpers"
|
|
4
|
+
import { dispatchPageDirtyEvent } from "alchemy_admin/utils/dispatch_page_dirty_event"
|
|
4
5
|
import { growl } from "alchemy_admin/growler"
|
|
5
6
|
|
|
6
7
|
import "alchemy_admin/components/element_editor/publish_element_button"
|
|
7
8
|
import "alchemy_admin/components/element_editor/delete_element_button"
|
|
8
9
|
|
|
9
|
-
export function dispatchPageDirtyEvent(data) {
|
|
10
|
-
document.dispatchEvent(
|
|
11
|
-
new CustomEvent("alchemy:page-dirty", {
|
|
12
|
-
detail: { tooltip: data.publishButtonTooltip }
|
|
13
|
-
})
|
|
14
|
-
)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
10
|
export class ElementEditor extends HTMLElement {
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
#form = null
|
|
12
|
+
#header = null
|
|
13
|
+
#toggleButton = null
|
|
14
|
+
|
|
15
|
+
connectedCallback() {
|
|
16
|
+
// The placeholder while be being dragged is empty.
|
|
17
|
+
if (this.classList.contains("ui-sortable-placeholder")) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
20
|
|
|
21
21
|
// Add event listeners
|
|
22
22
|
this.addEventListener("click", this)
|
|
@@ -27,24 +27,16 @@ export class ElementEditor extends HTMLElement {
|
|
|
27
27
|
|
|
28
28
|
// Dirty observer still needs to be jQuery
|
|
29
29
|
// in order to support select2.
|
|
30
|
-
|
|
30
|
+
this.#form = this.form
|
|
31
|
+
if (this.#form) {
|
|
32
|
+
$(this.#form).on("change", this.onChange)
|
|
33
|
+
}
|
|
31
34
|
|
|
32
|
-
this
|
|
33
|
-
|
|
34
|
-
})
|
|
35
|
-
this.toggleButton?.addEventListener("click", (evt) => {
|
|
36
|
-
const elementEditor = evt.target.closest("alchemy-element-editor")
|
|
37
|
-
if (elementEditor === this) {
|
|
38
|
-
this.toggle()
|
|
39
|
-
}
|
|
40
|
-
})
|
|
41
|
-
}
|
|
35
|
+
this.#header = this.header
|
|
36
|
+
this.#header?.addEventListener("dblclick", this.#onHeaderDblclick)
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (this.classList.contains("ui-sortable-placeholder")) {
|
|
46
|
-
return
|
|
47
|
-
}
|
|
38
|
+
this.#toggleButton = this.toggleButton
|
|
39
|
+
this.#toggleButton?.addEventListener("click", this.#onToggleClick)
|
|
48
40
|
|
|
49
41
|
// When newly created, focus the element and refresh the preview
|
|
50
42
|
if (this.hasAttribute("created")) {
|
|
@@ -56,6 +48,20 @@ export class ElementEditor extends HTMLElement {
|
|
|
56
48
|
}
|
|
57
49
|
}
|
|
58
50
|
|
|
51
|
+
disconnectedCallback() {
|
|
52
|
+
this.removeEventListener("click", this)
|
|
53
|
+
this.removeEventListener("alchemy:element-update-title", this)
|
|
54
|
+
this.removeEventListener("ajax:complete", this)
|
|
55
|
+
if (this.#form) {
|
|
56
|
+
$(this.#form).off("change", this.onChange)
|
|
57
|
+
this.#form = null
|
|
58
|
+
}
|
|
59
|
+
this.#header?.removeEventListener("dblclick", this.#onHeaderDblclick)
|
|
60
|
+
this.#header = null
|
|
61
|
+
this.#toggleButton?.removeEventListener("click", this.#onToggleClick)
|
|
62
|
+
this.#toggleButton = null
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
handleEvent(event) {
|
|
60
66
|
switch (event.type) {
|
|
61
67
|
case "click":
|
|
@@ -79,7 +85,7 @@ export class ElementEditor extends HTMLElement {
|
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
|
|
82
|
-
onChange(event) {
|
|
88
|
+
onChange = (event) => {
|
|
83
89
|
const target = event.target
|
|
84
90
|
// SortableJS fires a native change event :/
|
|
85
91
|
// and we do not want to set the element editor dirty
|
|
@@ -87,7 +93,7 @@ export class ElementEditor extends HTMLElement {
|
|
|
87
93
|
if (target.classList.contains("nested-elements")) {
|
|
88
94
|
return
|
|
89
95
|
}
|
|
90
|
-
this.
|
|
96
|
+
this.setDirty(target)
|
|
91
97
|
event.stopPropagation()
|
|
92
98
|
return false
|
|
93
99
|
}
|
|
@@ -579,6 +585,17 @@ export class ElementEditor extends HTMLElement {
|
|
|
579
585
|
get previewWindow() {
|
|
580
586
|
return document.getElementById("alchemy_preview_window")
|
|
581
587
|
}
|
|
588
|
+
|
|
589
|
+
#onHeaderDblclick = () => {
|
|
590
|
+
this.toggle()
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
#onToggleClick = (evt) => {
|
|
594
|
+
const elementEditor = evt.target.closest("alchemy-element-editor")
|
|
595
|
+
if (elementEditor === this) {
|
|
596
|
+
this.toggle()
|
|
597
|
+
}
|
|
598
|
+
}
|
|
582
599
|
}
|
|
583
600
|
|
|
584
601
|
customElements.define("alchemy-element-editor", ElementEditor)
|
|
@@ -21,9 +21,7 @@ const formatItem = (icon, text, hint) => {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
class ElementSelect extends HTMLElement {
|
|
24
|
-
|
|
25
|
-
super()
|
|
26
|
-
}
|
|
24
|
+
#select2 = null
|
|
27
25
|
|
|
28
26
|
connectedCallback() {
|
|
29
27
|
const results = this.options
|
|
@@ -48,7 +46,12 @@ class ElementSelect extends HTMLElement {
|
|
|
48
46
|
formatSelection,
|
|
49
47
|
placeholder: this.placeholder
|
|
50
48
|
}
|
|
51
|
-
$(this.inputField).select2(options)
|
|
49
|
+
this.#select2 = $(this.inputField).select2(options)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
disconnectedCallback() {
|
|
53
|
+
this.#select2?.select2("destroy")
|
|
54
|
+
this.#select2 = null
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
get options() {
|
|
@@ -4,22 +4,51 @@ class ElementsWindow extends HTMLElement {
|
|
|
4
4
|
#visible = true
|
|
5
5
|
#turboFrame = null
|
|
6
6
|
|
|
7
|
-
constructor() {
|
|
8
|
-
super()
|
|
9
|
-
this.#attachEvents()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
7
|
connectedCallback() {
|
|
13
|
-
this.toggleButton?.addEventListener("click",
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
this.toggleButton?.addEventListener("click", this.#onToggleClick)
|
|
9
|
+
this.collapseButton?.addEventListener("click", this.#onCollapseClick)
|
|
10
|
+
window.addEventListener("message", this.#onWindowMessage)
|
|
11
|
+
document.body.addEventListener("click", this.#onBodyClick)
|
|
17
12
|
if (window.location.hash) {
|
|
18
13
|
this.focusElementEditor(window.location.hash)
|
|
19
14
|
}
|
|
20
15
|
this.resize()
|
|
21
16
|
}
|
|
22
17
|
|
|
18
|
+
disconnectedCallback() {
|
|
19
|
+
this.toggleButton?.removeEventListener("click", this.#onToggleClick)
|
|
20
|
+
this.collapseButton?.removeEventListener("click", this.#onCollapseClick)
|
|
21
|
+
window.removeEventListener("message", this.#onWindowMessage)
|
|
22
|
+
document.body.removeEventListener("click", this.#onBodyClick)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#onToggleClick = (evt) => {
|
|
26
|
+
evt.preventDefault()
|
|
27
|
+
this.toggle()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#onCollapseClick = () => {
|
|
31
|
+
this.collapseAllElements()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#onWindowMessage = (event) => {
|
|
35
|
+
const data = event.data
|
|
36
|
+
if (data?.message == "Alchemy.focusElementEditor") {
|
|
37
|
+
const element = document.getElementById(`element_${data.element_id}`)
|
|
38
|
+
this.show()
|
|
39
|
+
element?.focusElement()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#onBodyClick = (evt) => {
|
|
44
|
+
if (!evt.target.closest("alchemy-element-editor")) {
|
|
45
|
+
this.querySelectorAll("alchemy-element-editor").forEach((editor) => {
|
|
46
|
+
editor.classList.remove("selected")
|
|
47
|
+
})
|
|
48
|
+
this.previewWindow?.postMessage({ message: "Alchemy.blurElements" })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
23
52
|
collapseAllElements() {
|
|
24
53
|
this.querySelectorAll(
|
|
25
54
|
"alchemy-element-editor:not([compact]):not([fixed])"
|
|
@@ -101,28 +130,6 @@ class ElementsWindow extends HTMLElement {
|
|
|
101
130
|
this.turboFrame.style.transitionProperty = dragged ? "none" : null
|
|
102
131
|
this.turboFrame.style.pointerEvents = dragged ? "none" : null
|
|
103
132
|
}
|
|
104
|
-
|
|
105
|
-
#attachEvents() {
|
|
106
|
-
this.collapseButton?.addEventListener("click", () => {
|
|
107
|
-
this.collapseAllElements()
|
|
108
|
-
})
|
|
109
|
-
window.addEventListener("message", (event) => {
|
|
110
|
-
const data = event.data
|
|
111
|
-
if (data?.message == "Alchemy.focusElementEditor") {
|
|
112
|
-
const element = document.getElementById(`element_${data.element_id}`)
|
|
113
|
-
this.show()
|
|
114
|
-
element?.focusElement()
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
document.body.addEventListener("click", (evt) => {
|
|
118
|
-
if (!evt.target.closest("alchemy-element-editor")) {
|
|
119
|
-
this.querySelectorAll("alchemy-element-editor").forEach((editor) => {
|
|
120
|
-
editor.classList.remove("selected")
|
|
121
|
-
})
|
|
122
|
-
this.previewWindow?.postMessage({ message: "Alchemy.blurElements" })
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
customElements.define("alchemy-elements-window", ElementsWindow)
|
|
@@ -7,14 +7,18 @@ class ElementsWindowHandle extends HTMLElement {
|
|
|
7
7
|
#minWidth = MIN_WIDTH
|
|
8
8
|
#maxWidth = MAX_WIDTH
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
super()
|
|
12
|
-
|
|
10
|
+
connectedCallback() {
|
|
13
11
|
this.addEventListener("mousedown", this)
|
|
14
12
|
window.addEventListener("mousemove", this)
|
|
15
13
|
window.addEventListener("mouseup", this)
|
|
16
14
|
}
|
|
17
15
|
|
|
16
|
+
disconnectedCallback() {
|
|
17
|
+
this.removeEventListener("mousedown", this)
|
|
18
|
+
window.removeEventListener("mousemove", this)
|
|
19
|
+
window.removeEventListener("mouseup", this)
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
handleEvent(event) {
|
|
19
23
|
switch (event.type) {
|
|
20
24
|
case "mousedown":
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
class FileEditor extends HTMLElement {
|
|
2
|
-
|
|
3
|
-
super()
|
|
2
|
+
connectedCallback() {
|
|
4
3
|
this.deleteLink = this.querySelector(".remove_file_link")
|
|
5
4
|
this.fileIcon = this.querySelector(".file_icon")
|
|
6
5
|
this.fileName = this.querySelector(".file_name")
|
|
@@ -9,6 +8,10 @@ class FileEditor extends HTMLElement {
|
|
|
9
8
|
this.deleteLink?.addEventListener("click", this)
|
|
10
9
|
}
|
|
11
10
|
|
|
11
|
+
disconnectedCallback() {
|
|
12
|
+
this.deleteLink?.removeEventListener("click", this)
|
|
13
|
+
}
|
|
14
|
+
|
|
12
15
|
handleEvent(event) {
|
|
13
16
|
if (event.type === "click") this.removeFile()
|
|
14
17
|
event.stopPropagation()
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export class IngredientGroup extends HTMLDetailsElement {
|
|
2
2
|
#localStorageKey = "Alchemy.expanded_ingredient_groups"
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
super()
|
|
6
|
-
|
|
4
|
+
connectedCallback() {
|
|
7
5
|
this.addEventListener("toggle", this)
|
|
8
6
|
|
|
9
7
|
if (this.isInLocalStorage) {
|
|
@@ -11,6 +9,10 @@ export class IngredientGroup extends HTMLDetailsElement {
|
|
|
11
9
|
}
|
|
12
10
|
}
|
|
13
11
|
|
|
12
|
+
disconnectedCallback() {
|
|
13
|
+
this.removeEventListener("toggle", this)
|
|
14
|
+
}
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* Toggle visibility of the ingredient fields in this group
|
|
16
18
|
*/
|
|
@@ -21,7 +23,7 @@ export class IngredientGroup extends HTMLDetailsElement {
|
|
|
21
23
|
this.toggleIcon.name = "arrow-down-s"
|
|
22
24
|
if (!this.isInLocalStorage) expanded_ingredient_groups.push(this.id)
|
|
23
25
|
} else {
|
|
24
|
-
this.toggleIcon.name = "arrow-
|
|
26
|
+
this.toggleIcon.name = "arrow-right-s"
|
|
25
27
|
expanded_ingredient_groups = expanded_ingredient_groups.filter(
|
|
26
28
|
(value) => value !== this.id
|
|
27
29
|
)
|
|
@@ -2,12 +2,16 @@ import "alchemy_admin/components/link_buttons/link_button"
|
|
|
2
2
|
import "alchemy_admin/components/link_buttons/unlink_button"
|
|
3
3
|
|
|
4
4
|
class LinkButtons extends HTMLElement {
|
|
5
|
-
|
|
6
|
-
super()
|
|
5
|
+
connectedCallback() {
|
|
7
6
|
this.addEventListener("alchemy:link", this)
|
|
8
7
|
this.addEventListener("alchemy:unlink", this)
|
|
9
8
|
}
|
|
10
9
|
|
|
10
|
+
disconnectedCallback() {
|
|
11
|
+
this.removeEventListener("alchemy:link", this)
|
|
12
|
+
this.removeEventListener("alchemy:unlink", this)
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
handleEvent(event) {
|
|
12
16
|
switch (event.type) {
|
|
13
17
|
case "alchemy:link":
|
|
@@ -2,41 +2,30 @@ const DEFAULT_DEBOUNCE_TIME = 150
|
|
|
2
2
|
|
|
3
3
|
class ListFilter extends HTMLElement {
|
|
4
4
|
#debounceTimer
|
|
5
|
+
#filterField = null
|
|
6
|
+
#clearButton = null
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
super()
|
|
8
|
-
this.#attachEvents()
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
#attachEvents() {
|
|
8
|
+
connectedCallback() {
|
|
12
9
|
if (this.hotkey) {
|
|
13
|
-
key(this.hotkey,
|
|
14
|
-
this.filterField.focus()
|
|
15
|
-
return false
|
|
16
|
-
})
|
|
10
|
+
key(this.hotkey, this.#onHotkey)
|
|
17
11
|
}
|
|
18
|
-
this
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.clearButton.addEventListener("click", (e) => {
|
|
27
|
-
e.preventDefault()
|
|
28
|
-
this.clear()
|
|
29
|
-
})
|
|
30
|
-
this.filterField.addEventListener("focus", () =>
|
|
31
|
-
key.setScope("list_filter")
|
|
32
|
-
)
|
|
33
|
-
key("esc", "list_filter", () => {
|
|
34
|
-
this.clear()
|
|
35
|
-
this.filterField.blur()
|
|
36
|
-
})
|
|
12
|
+
this.#filterField = this.filterField
|
|
13
|
+
this.#filterField.addEventListener("keyup", this.#onKeyup)
|
|
14
|
+
this.#filterField.addEventListener("focus", this.#onFocus)
|
|
15
|
+
|
|
16
|
+
this.#clearButton = this.clearButton
|
|
17
|
+
this.#clearButton.addEventListener("click", this.#onClearClick)
|
|
18
|
+
|
|
19
|
+
key("esc", "list_filter", this.#onEscape)
|
|
37
20
|
}
|
|
38
21
|
|
|
39
22
|
disconnectedCallback() {
|
|
23
|
+
clearTimeout(this.#debounceTimer)
|
|
24
|
+
this.#filterField?.removeEventListener("keyup", this.#onKeyup)
|
|
25
|
+
this.#filterField?.removeEventListener("focus", this.#onFocus)
|
|
26
|
+
this.#filterField = null
|
|
27
|
+
this.#clearButton?.removeEventListener("click", this.#onClearClick)
|
|
28
|
+
this.#clearButton = null
|
|
40
29
|
if (this.hotkey) {
|
|
41
30
|
key.unbind(this.hotkey)
|
|
42
31
|
}
|
|
@@ -112,6 +101,32 @@ class ListFilter extends HTMLElement {
|
|
|
112
101
|
get hotkey() {
|
|
113
102
|
return this.getAttribute("hotkey")
|
|
114
103
|
}
|
|
104
|
+
|
|
105
|
+
#onHotkey = () => {
|
|
106
|
+
this.filterField.focus()
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#onKeyup = () => {
|
|
111
|
+
clearTimeout(this.#debounceTimer)
|
|
112
|
+
this.#debounceTimer = setTimeout(() => {
|
|
113
|
+
const term = this.filterField.value
|
|
114
|
+
this.clearButton.style.visibility = term ? "visible" : "hidden"
|
|
115
|
+
this.filter(term)
|
|
116
|
+
}, this.debounceTime)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#onFocus = () => key.setScope("list_filter")
|
|
120
|
+
|
|
121
|
+
#onClearClick = (e) => {
|
|
122
|
+
e.preventDefault()
|
|
123
|
+
this.clear()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#onEscape = () => {
|
|
127
|
+
this.clear()
|
|
128
|
+
this.filterField.blur()
|
|
129
|
+
}
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
customElements.define("alchemy-list-filter", ListFilter)
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
const DISMISS_DELAY = 5000
|
|
2
2
|
|
|
3
3
|
class Message extends HTMLElement {
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
constructor() {
|
|
7
|
-
super()
|
|
8
|
-
this.#message = this.innerHTML
|
|
9
|
-
if (this.dismissable || this.type === "error") {
|
|
10
|
-
this.addEventListener("click", this)
|
|
11
|
-
}
|
|
12
|
-
}
|
|
4
|
+
#dismissTimeoutId = null
|
|
13
5
|
|
|
14
6
|
handleEvent(event) {
|
|
15
7
|
if (event.type === "click") {
|
|
@@ -18,18 +10,33 @@ class Message extends HTMLElement {
|
|
|
18
10
|
}
|
|
19
11
|
|
|
20
12
|
connectedCallback() {
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
if (!this.querySelector(":scope > alchemy-icon")) {
|
|
14
|
+
const closeIcon =
|
|
15
|
+
this.dismissable && this.type === "error"
|
|
16
|
+
? '<alchemy-icon name="close"></alchemy-icon>'
|
|
17
|
+
: ""
|
|
18
|
+
this.insertAdjacentHTML(
|
|
19
|
+
"afterbegin",
|
|
20
|
+
`<alchemy-icon name="${this.iconName}"></alchemy-icon>${closeIcon}`
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
if (this.dismissable || this.type === "error") {
|
|
24
|
+
this.addEventListener("click", this)
|
|
25
|
+
}
|
|
26
26
|
if (this.dismissable && this.type !== "error") {
|
|
27
|
-
setTimeout(() => {
|
|
27
|
+
this.#dismissTimeoutId = setTimeout(() => {
|
|
28
28
|
this.dismiss()
|
|
29
29
|
}, this.dismissDelay)
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
disconnectedCallback() {
|
|
34
|
+
if (this.#dismissTimeoutId !== null) {
|
|
35
|
+
clearTimeout(this.#dismissTimeoutId)
|
|
36
|
+
this.#dismissTimeoutId = null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
dismiss() {
|
|
34
41
|
this.addEventListener("transitionend", () => this.remove())
|
|
35
42
|
this.classList.add("dismissed")
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
render() {
|
|
5
|
-
return `
|
|
1
|
+
class Overlay extends HTMLElement {
|
|
2
|
+
connectedCallback() {
|
|
3
|
+
this.innerHTML = `
|
|
6
4
|
<alchemy-spinner></alchemy-spinner>
|
|
7
5
|
<div id="overlay_text_box">
|
|
8
|
-
<span id="overlay_text">${this.getAttribute("text")}</span>
|
|
6
|
+
<span id="overlay_text">${this.getAttribute("text") ?? ""}</span>
|
|
9
7
|
</div>
|
|
10
|
-
|
|
8
|
+
`
|
|
11
9
|
}
|
|
12
10
|
|
|
13
11
|
set show(value) {
|
|
@@ -1,31 +1,44 @@
|
|
|
1
1
|
// Handles the page publication date fields
|
|
2
2
|
export class PagePublicationFields extends HTMLElement {
|
|
3
3
|
connectedCallback() {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
4
|
+
this.publicField?.addEventListener("click", this.#onClick)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
disconnectedCallback() {
|
|
8
|
+
this.publicField?.removeEventListener("click", this.#onClick)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
#onClick = (evt) => {
|
|
12
|
+
const checkbox = evt.target
|
|
13
|
+
const date = new Date()
|
|
14
|
+
const now = new Date(
|
|
15
|
+
date.getTime() - date.getTimezoneOffset() * 60000
|
|
16
|
+
).toISOString()
|
|
17
|
+
|
|
18
|
+
if (checkbox.checked) {
|
|
19
|
+
this.publicationDateFields.classList.remove("hidden")
|
|
20
|
+
this.publicOnPicker.value = now.substring(0, now.indexOf("T") + 6)
|
|
21
|
+
} else {
|
|
22
|
+
this.publicationDateFields.classList.add("hidden")
|
|
23
|
+
this.publicOnPicker.value = ""
|
|
24
|
+
}
|
|
25
|
+
this.publicUntilPicker.value = ""
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get publicField() {
|
|
29
|
+
return this.querySelector("#page_public")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get publicOnPicker() {
|
|
33
|
+
return this.querySelector("input#page_public_on")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get publicUntilPicker() {
|
|
37
|
+
return this.querySelector("input#page_public_until")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get publicationDateFields() {
|
|
41
|
+
return this.querySelector(".page-publication-date-fields")
|
|
29
42
|
}
|
|
30
43
|
}
|
|
31
44
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
class PictureDescriptionSelect extends HTMLElement {
|
|
2
|
-
|
|
3
|
-
super()
|
|
2
|
+
connectedCallback() {
|
|
4
3
|
this.addEventListener("change", this)
|
|
5
4
|
}
|
|
6
5
|
|
|
6
|
+
disconnectedCallback() {
|
|
7
|
+
this.removeEventListener("change", this)
|
|
8
|
+
}
|
|
9
|
+
|
|
7
10
|
handleEvent(event) {
|
|
8
11
|
switch (event.type) {
|
|
9
12
|
case "change":
|
|
@@ -8,9 +8,7 @@ const IMAGE_PLACEHOLDER = '<alchemy-icon name="image" size="xl"></alchemy-icon>'
|
|
|
8
8
|
const THUMBNAIL_SIZE = "160x120"
|
|
9
9
|
|
|
10
10
|
export class PictureEditor extends HTMLElement {
|
|
11
|
-
|
|
12
|
-
super()
|
|
13
|
-
|
|
11
|
+
connectedCallback() {
|
|
14
12
|
this.cropFromField = this.querySelector("[data-crop-from]")
|
|
15
13
|
this.cropSizeField = this.querySelector("[data-crop-size]")
|
|
16
14
|
this.pictureIdField = this.querySelector("[data-picture-id]")
|
|
@@ -31,12 +29,9 @@ export class PictureEditor extends HTMLElement {
|
|
|
31
29
|
this.updateCropLink()
|
|
32
30
|
}, UPDATE_DELAY)
|
|
33
31
|
|
|
34
|
-
this.deleteButton?.addEventListener("click", this.removeImage
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
connectedCallback() {
|
|
38
|
-
this.observer = new MutationObserver(this.mutationCallback.bind(this))
|
|
32
|
+
this.deleteButton?.addEventListener("click", this.removeImage)
|
|
39
33
|
|
|
34
|
+
this.observer = new MutationObserver(this.mutationCallback)
|
|
40
35
|
this.observer.observe(this.cropFromField, { attributes: true })
|
|
41
36
|
this.observer.observe(this.cropSizeField, { attributes: true })
|
|
42
37
|
this.observer.observe(this.pictureIdField, { attributes: true })
|
|
@@ -46,7 +41,7 @@ export class PictureEditor extends HTMLElement {
|
|
|
46
41
|
this.observer.disconnect()
|
|
47
42
|
}
|
|
48
43
|
|
|
49
|
-
mutationCallback(mutationsList) {
|
|
44
|
+
mutationCallback = (mutationsList) => {
|
|
50
45
|
for (const mutation of mutationsList) {
|
|
51
46
|
if ("pictureId" in mutation.target.dataset) {
|
|
52
47
|
this.cropFromField.value = ""
|
|
@@ -80,7 +75,7 @@ export class PictureEditor extends HTMLElement {
|
|
|
80
75
|
})
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
removeImage() {
|
|
78
|
+
removeImage = () => {
|
|
84
79
|
this.pictureThumbnail.innerHTML = IMAGE_PLACEHOLDER
|
|
85
80
|
this.pictureIdField.value = ""
|
|
86
81
|
this.image = null
|
|
@@ -7,12 +7,7 @@ export default class PictureThumbnail extends HTMLElement {
|
|
|
7
7
|
constructor() {
|
|
8
8
|
super()
|
|
9
9
|
|
|
10
|
-
this.classList.add("thumbnail_background")
|
|
11
10
|
this.spinner = new Spinner("small")
|
|
12
|
-
|
|
13
|
-
if (this.src) {
|
|
14
|
-
this.start()
|
|
15
|
-
}
|
|
16
11
|
}
|
|
17
12
|
|
|
18
13
|
handleEvent(evt) {
|
|
@@ -29,6 +24,10 @@ export default class PictureThumbnail extends HTMLElement {
|
|
|
29
24
|
}
|
|
30
25
|
|
|
31
26
|
connectedCallback() {
|
|
27
|
+
this.classList.add("thumbnail_background")
|
|
28
|
+
if (this.src && !this.image) {
|
|
29
|
+
this.start()
|
|
30
|
+
}
|
|
32
31
|
this.#setImage()
|
|
33
32
|
}
|
|
34
33
|
|