alchemy_cms 7.3.5 → 7.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -0
- data/Gemfile +3 -3
- data/README.md +2 -2
- data/alchemy_cms.gemspec +1 -4
- data/app/assets/builds/alchemy/admin.css +9 -1
- data/app/assets/builds/alchemy/admin.css.map +1 -1
- data/app/assets/builds/alchemy/custom-properties.css +1 -1
- data/app/assets/builds/alchemy/custom-properties.css.map +1 -1
- data/app/assets/builds/alchemy/preview.min.js +1 -0
- data/app/assets/builds/alchemy/welcome.css +1 -1
- data/app/assets/builds/alchemy/welcome.css.map +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
- data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +1 -1
- data/app/assets/config/alchemy_manifest.js +0 -4
- data/app/assets/javascripts/alchemy/admin.js +8 -6
- data/app/assets/stylesheets/alchemy/admin/elements.scss +43 -7
- data/app/assets/stylesheets/alchemy/admin/forms.scss +4 -0
- data/app/assets/stylesheets/alchemy/admin/navigation.scss +9 -1
- data/app/assets/stylesheets/alchemy/admin/preview_window.scss +22 -17
- data/app/assets/stylesheets/alchemy/admin.scss +1 -1
- data/app/assets/stylesheets/alchemy/custom-properties.css +2 -1
- data/app/components/alchemy/ingredients/link_view.rb +7 -1
- data/app/components/alchemy/ingredients/picture_view.rb +5 -2
- data/app/components/alchemy/ingredients/text_view.rb +4 -1
- data/app/components/concerns/alchemy/ingredients/link_target.rb +18 -0
- data/app/controllers/alchemy/admin/base_controller.rb +8 -3
- data/app/controllers/alchemy/admin/elements_controller.rb +2 -2
- data/app/controllers/alchemy/admin/layoutpages_controller.rb +1 -0
- data/app/controllers/alchemy/admin/pages_controller.rb +5 -1
- data/app/controllers/alchemy/elements_controller.rb +3 -0
- data/app/helpers/alchemy/admin/form_helper.rb +1 -1
- data/app/helpers/alchemy/admin/navigation_helper.rb +22 -1
- data/app/javascript/alchemy_admin/components/action.js +2 -1
- data/app/javascript/alchemy_admin/components/dialog_link.js +3 -18
- data/app/javascript/alchemy_admin/components/element_editor.js +9 -0
- data/app/javascript/alchemy_admin/components/elements_window.js +34 -0
- data/app/javascript/alchemy_admin/components/elements_window_handle.js +65 -0
- data/app/javascript/alchemy_admin/components/icon.js +2 -2
- data/app/javascript/alchemy_admin/components/index.js +1 -0
- data/app/javascript/alchemy_admin/components/preview_window.js +5 -5
- data/app/javascript/alchemy_admin/components/uploader/file_upload.js +1 -1
- data/app/javascript/alchemy_admin/confirm_dialog.js +9 -11
- data/app/javascript/alchemy_admin/dialog.js +329 -0
- data/app/javascript/alchemy_admin/hotkeys.js +3 -2
- data/app/javascript/alchemy_admin/image_cropper.js +57 -40
- data/app/javascript/alchemy_admin/image_overlay.js +73 -0
- data/app/javascript/alchemy_admin/initializer.js +51 -2
- data/app/javascript/alchemy_admin/link_dialog.js +2 -1
- data/app/javascript/alchemy_admin/node_tree.js +3 -1
- data/app/javascript/alchemy_admin/page_sorter.js +1 -1
- data/app/javascript/alchemy_admin/picture_selector.js +2 -1
- data/app/javascript/alchemy_admin/shoelace_theme.js +2 -2
- data/app/javascript/alchemy_admin/templates/compiled.js +1 -0
- data/app/javascript/alchemy_admin.js +10 -6
- data/app/javascript/preview.js +117 -0
- data/app/models/alchemy/image_cropper_settings.rb +3 -4
- data/app/views/alchemy/_preview_mode_code.html.erb +1 -1
- data/app/views/alchemy/admin/crop.html.erb +19 -16
- data/app/views/alchemy/admin/dashboard/info.html.erb +1 -1
- data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +9 -8
- data/app/views/alchemy/admin/elements/_clipboard_button.html.erb +14 -0
- data/app/views/alchemy/admin/elements/_element.html.erb +2 -0
- data/app/views/alchemy/admin/elements/_form.html.erb +15 -13
- data/app/views/alchemy/admin/elements/create.turbo_stream.erb +34 -0
- data/app/views/alchemy/admin/elements/index.html.erb +3 -15
- data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +1 -1
- data/app/views/alchemy/admin/layoutpages/edit.html.erb +7 -5
- data/app/views/alchemy/admin/nodes/_form.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_current_page.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_form.html.erb +43 -40
- data/app/views/alchemy/admin/pages/_locked_page.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_sitemap.html.erb +1 -1
- data/app/views/alchemy/admin/pages/_table.html.erb +2 -2
- data/app/views/alchemy/admin/pages/edit.html.erb +1 -1
- data/app/views/alchemy/admin/pages/update.turbo_stream.erb +39 -0
- data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +3 -4
- data/app/views/alchemy/admin/pictures/_picture_description_field.html.erb +7 -5
- data/app/views/alchemy/admin/pictures/index.html.erb +13 -9
- data/app/views/alchemy/admin/resources/_filter_bar.html.erb +1 -1
- data/app/views/layouts/alchemy/admin.html.erb +8 -4
- data/bun.lockb +0 -0
- data/bundles/tinymce.js +2 -0
- data/config/alchemy/config.yml +3 -3
- data/config/alchemy/modules.yml +7 -6
- data/config/importmap.rb +4 -0
- data/config/routes.rb +1 -1
- data/lib/alchemy/engine.rb +6 -0
- data/lib/alchemy/modules.rb +0 -27
- data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +10 -10
- data/lib/alchemy/tinymce.rb +2 -1
- data/lib/alchemy/upgrader/seven_point_four.rb +26 -0
- data/lib/alchemy/version.rb +1 -1
- data/lib/alchemy.rb +14 -0
- data/lib/alchemy_cms.rb +0 -2
- data/lib/generators/alchemy/ingredient/ingredient_generator.rb +5 -0
- data/lib/generators/alchemy/ingredient/templates/view.html.erb +1 -1
- data/lib/generators/alchemy/ingredient/templates/view_component.rb.tt +10 -0
- data/lib/generators/alchemy/install/install_generator.rb +0 -1
- data/lib/generators/alchemy/install/templates/elements.yml.tt +1 -1
- data/lib/tasks/alchemy/upgrade.rake +19 -20
- data/rollup.config.mjs +44 -1
- data/vendor/javascript/cropperjs.min.js +10 -0
- data/vendor/javascript/handlebars.min.js +29 -0
- data/vendor/javascript/jquery.min.js +2 -0
- data/vendor/javascript/select2.min.js +23 -0
- data/vendor/javascript/tinymce.min.js +1 -1
- metadata +39 -91
- data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +0 -271
- data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +0 -54
- data/app/assets/javascripts/alchemy/alchemy.preview.js.coffee +0 -97
- data/app/assets/javascripts/alchemy/preview.js +0 -1
- data/app/assets/javascripts/alchemy/templates/index.js +0 -2
- data/app/javascript/alchemy_admin/gui.js +0 -12
- data/app/views/alchemy/admin/elements/create.js.erb +0 -35
- data/app/views/alchemy/admin/pages/update.js.erb +0 -43
- data/lib/alchemy/upgrader/seven_point_zero.rb +0 -36
- data/vendor/assets/images/Jcrop.gif +0 -0
- data/vendor/assets/javascripts/jquery_plugins/jquery.Jcrop.min.js +0 -7
- data/vendor/assets/javascripts/jquery_plugins/select2.js +0 -3729
- data/vendor/assets/stylesheets/jquery.Jcrop.min.css +0 -2
- data/vendor/assets/stylesheets/tinymce/skins/content/default/content.min.css +0 -1
- /data/app/{assets/javascripts/alchemy → javascript/alchemy_admin}/templates/node_folder.hbs +0 -0
- /data/app/{assets/javascripts/alchemy → javascript/alchemy_admin}/templates/page_folder.hbs +0 -0
- /data/app/{assets/javascripts/tinymce/icons/remixicons/icons.js → javascript/tinymce/icons/remixicons/index.js} +0 -0
- /data/app/{assets/javascripts/tinymce/plugins/alchemy_link/plugin.min.js → javascript/tinymce/plugins/alchemy_link/index.js} +0 -0
- /data/vendor/assets/{fonts → images}/remixicon.symbol.svg +0 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
import ImageLoader from "alchemy_admin/image_loader"
|
2
|
+
import { Dialog } from "alchemy_admin/dialog"
|
3
|
+
|
4
|
+
export default class ImageOverlay extends Dialog {
|
5
|
+
constructor(url, options = {}) {
|
6
|
+
super(url, options)
|
7
|
+
}
|
8
|
+
|
9
|
+
init() {
|
10
|
+
ImageLoader.init(this.dialog_body[0])
|
11
|
+
$(".zoomed-picture-background").on("click", (e) => {
|
12
|
+
e.stopPropagation()
|
13
|
+
if (e.target.nodeName === "IMG") {
|
14
|
+
return
|
15
|
+
}
|
16
|
+
this.close()
|
17
|
+
return false
|
18
|
+
})
|
19
|
+
$(".picture-overlay-handle").on("click", (e) => {
|
20
|
+
this.dialog.toggleClass("hide-form")
|
21
|
+
return false
|
22
|
+
})
|
23
|
+
this.$previous = $(".previous-picture")
|
24
|
+
this.$next = $(".next-picture")
|
25
|
+
this.#initKeyboardNavigation()
|
26
|
+
super.init()
|
27
|
+
}
|
28
|
+
|
29
|
+
previous() {
|
30
|
+
if (this.$previous[0] != null) {
|
31
|
+
this.$previous[0].click()
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
next() {
|
36
|
+
if (this.$next[0] != null) {
|
37
|
+
this.$next[0].click()
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
build() {
|
42
|
+
this.dialog_container = $('<div class="alchemy-image-overlay-container" />')
|
43
|
+
this.dialog = $('<div class="alchemy-image-overlay-dialog" />')
|
44
|
+
this.dialog_body = $('<div class="alchemy-image-overlay-body" />')
|
45
|
+
this.close_button = $(`<a class="alchemy-image-overlay-close">
|
46
|
+
<alchemy-icon name="close" size="xl"></alchemy-icon>
|
47
|
+
</a>`)
|
48
|
+
this.dialog.append(this.close_button)
|
49
|
+
this.dialog.append(this.dialog_body)
|
50
|
+
this.dialog_container.append(this.dialog)
|
51
|
+
this.overlay = $('<div class="alchemy-image-overlay" />')
|
52
|
+
this.$body.append(this.overlay)
|
53
|
+
this.$body.append(this.dialog_container)
|
54
|
+
}
|
55
|
+
|
56
|
+
#initKeyboardNavigation() {
|
57
|
+
this.$document.keydown((e) => {
|
58
|
+
if (e.target.nodeName === "INPUT" || e.target.nodeName === "TEXTAREA") {
|
59
|
+
return true
|
60
|
+
}
|
61
|
+
switch (e.which) {
|
62
|
+
case 37:
|
63
|
+
this.previous()
|
64
|
+
return false
|
65
|
+
case 39:
|
66
|
+
this.next()
|
67
|
+
return false
|
68
|
+
default:
|
69
|
+
return true
|
70
|
+
}
|
71
|
+
})
|
72
|
+
}
|
73
|
+
}
|
@@ -1,3 +1,11 @@
|
|
1
|
+
import {
|
2
|
+
confirmToDeleteDialog,
|
3
|
+
openConfirmDialog
|
4
|
+
} from "alchemy_admin/confirm_dialog"
|
5
|
+
|
6
|
+
import Hotkeys from "alchemy_admin/hotkeys"
|
7
|
+
import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
|
8
|
+
|
1
9
|
/**
|
2
10
|
* add change listener to select to redirect the user after selecting another locale or site
|
3
11
|
* @param {string} selectId
|
@@ -18,12 +26,53 @@ function selectHandler(selectId, parameterName, forcedReload = false) {
|
|
18
26
|
})
|
19
27
|
}
|
20
28
|
|
29
|
+
// Watches elements for Alchemy Dialogs
|
30
|
+
//
|
31
|
+
// Links having a data-alchemy-confirm-delete
|
32
|
+
// and input/buttons having a data-alchemy-confirm attribute get watched.
|
33
|
+
//
|
34
|
+
// You can pass a scope so that only elements inside this scope are queried.
|
35
|
+
//
|
36
|
+
// The href attribute of the link is the url for the overlay window.
|
37
|
+
//
|
38
|
+
// See Dialog for further options you can add to the data attribute.
|
39
|
+
//
|
40
|
+
function watchForConfirmDialogs(scope) {
|
41
|
+
if (scope == null) {
|
42
|
+
scope = "#alchemy"
|
43
|
+
}
|
44
|
+
$(scope).on("click", "[data-alchemy-confirm-delete]", function (event) {
|
45
|
+
const $this = $(this)
|
46
|
+
const options = $this.data("alchemy-confirm-delete")
|
47
|
+
confirmToDeleteDialog($this.attr("href"), options)
|
48
|
+
event.preventDefault()
|
49
|
+
})
|
50
|
+
$(scope).on("click", "[data-alchemy-confirm]", function (event) {
|
51
|
+
const options = $(this).data("alchemy-confirm")
|
52
|
+
openConfirmDialog(
|
53
|
+
options.message,
|
54
|
+
$.extend(options, {
|
55
|
+
ok_label: options.ok_label,
|
56
|
+
cancel_label: options.cancel_label,
|
57
|
+
on_ok: () => {
|
58
|
+
pleaseWaitOverlay()
|
59
|
+
this.form.submit()
|
60
|
+
}
|
61
|
+
})
|
62
|
+
)
|
63
|
+
event.preventDefault()
|
64
|
+
})
|
65
|
+
}
|
66
|
+
|
21
67
|
export default function Initializer() {
|
22
68
|
// We obviously have javascript enabled.
|
23
69
|
$("html").removeClass("no-js")
|
24
70
|
|
25
|
-
// Initialize
|
26
|
-
|
71
|
+
// Initialize hotkeys.
|
72
|
+
Hotkeys()
|
73
|
+
|
74
|
+
// Watch for click on confirm dialog links.
|
75
|
+
watchForConfirmDialogs()
|
27
76
|
|
28
77
|
// Add observer for please wait overlay.
|
29
78
|
$(".please_wait")
|
@@ -1,9 +1,10 @@
|
|
1
1
|
import { translate } from "alchemy_admin/i18n"
|
2
|
+
import { Dialog } from "alchemy_admin/dialog"
|
2
3
|
|
3
4
|
// Represents the link Dialog that appears, if a user clicks the link buttons
|
4
5
|
// in TinyMCE or on an Ingredient that has links enabled (e.g. Picture)
|
5
6
|
//
|
6
|
-
export class LinkDialog extends
|
7
|
+
export class LinkDialog extends Dialog {
|
7
8
|
#onCreateLink
|
8
9
|
|
9
10
|
constructor(link) {
|
@@ -14,7 +14,9 @@ function displayNodeFolders() {
|
|
14
14
|
}
|
15
15
|
|
16
16
|
if (list.children.length > 0 || node.folded) {
|
17
|
-
leftIconArea.innerHTML =
|
17
|
+
leftIconArea.innerHTML = Handlebars.templates["node_folder.hbs"]({
|
18
|
+
node: node
|
19
|
+
})
|
18
20
|
} else {
|
19
21
|
leftIconArea.innerHTML = " "
|
20
22
|
}
|
@@ -44,7 +44,7 @@ export function displayPageFolders() {
|
|
44
44
|
}
|
45
45
|
|
46
46
|
if (list.children.length > 0 || page.folded) {
|
47
|
-
pageFolderEl.outerHTML =
|
47
|
+
pageFolderEl.outerHTML = Handlebars.templates["page_folder.hbs"]({ page })
|
48
48
|
} else {
|
49
49
|
pageFolderEl.innerHTML = ""
|
50
50
|
}
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { on } from "alchemy_admin/utils/events"
|
2
|
+
import { openDialog } from "alchemy_admin/dialog"
|
2
3
|
|
3
4
|
function toggleCheckboxes(state) {
|
4
5
|
document
|
@@ -58,7 +59,7 @@ export default function PictureSelector() {
|
|
58
59
|
|
59
60
|
const url = editMultiplePicturesUrl(event.target.href)
|
60
61
|
|
61
|
-
|
62
|
+
openDialog(url, {
|
62
63
|
title: event.target.title,
|
63
64
|
size: "400x295"
|
64
65
|
})
|
@@ -43,8 +43,8 @@ setDefaultAnimation("dialog.hide", {
|
|
43
43
|
})
|
44
44
|
|
45
45
|
const spriteUrl = document
|
46
|
-
.querySelector('
|
47
|
-
.getAttribute("
|
46
|
+
.querySelector('link[rel="preload"][as="image"]')
|
47
|
+
.getAttribute("href")
|
48
48
|
|
49
49
|
const iconMap = {
|
50
50
|
"x-lg": "close"
|
@@ -0,0 +1 @@
|
|
1
|
+
(()=>{var n=Handlebars.template,e=Handlebars.templates=Handlebars.templates||{};e["node_folder.hbs"]=n({1:function(n,e,l,a,r){return"right"},3:function(n,e,l,a,r){return"down"},compiler:[8,">= 4.3.0"],main:function(n,e,l,a,r){var o,t=n.lambda,u=n.escapeExpression,c=n.lookupProperty||function(n,e){if(Object.prototype.hasOwnProperty.call(n,e))return n[e]};return'<a class="node_folder" data-record-id="'+u(t(null!=(o=null!=e?c(e,"node"):e)?c(o,"id"):o,e))+'" data-record-type="'+u(t(null!=(o=null!=e?c(e,"node"):e)?c(o,"type"):o,e))+'">\n <alchemy-icon name="arrow-'+(null!=(o=c(l,"if").call(null!=e?e:n.nullContext||{},null!=(o=null!=e?c(e,"node"):e)?c(o,"folded"):o,{name:"if",hash:{},fn:n.program(1,r,0),inverse:n.program(3,r,0),data:r,loc:{start:{line:2,column:28},end:{line:2,column:72}}}))?o:"")+'-s"></alchemy-icon>\n</a>\n'},useData:!0}),e["page_folder.hbs"]=n({1:function(n,e,l,a,r){return"right"},3:function(n,e,l,a,r){return"down"},compiler:[8,">= 4.3.0"],main:function(n,e,l,a,r){var o,t=n.lookupProperty||function(n,e){if(Object.prototype.hasOwnProperty.call(n,e))return n[e]};return'<a class="page_folder icon_button" data-page-id="'+n.escapeExpression(n.lambda(null!=(o=null!=e?t(e,"page"):e)?t(o,"id"):o,e))+'">\n <alchemy-icon name="arrow-'+(null!=(o=t(l,"if").call(null!=e?e:n.nullContext||{},null!=(o=null!=e?t(e,"page"):e)?t(o,"folded"):o,{name:"if",hash:{},fn:n.program(1,r,0),inverse:n.program(3,r,0),data:r,loc:{start:{line:2,column:28},end:{line:2,column:72}}}))?o:"")+'-s"></alchemy-icon>\n</a>\n'},useData:!0})})();
|
@@ -1,18 +1,20 @@
|
|
1
|
+
// We still use jQuery in some places (ie. select2)
|
2
|
+
import "handlebars"
|
3
|
+
import "jquery"
|
1
4
|
import "@ungap/custom-elements"
|
2
5
|
import "@hotwired/turbo-rails"
|
6
|
+
import "select2"
|
3
7
|
|
4
8
|
import Rails from "@rails/ujs"
|
5
9
|
|
6
|
-
import GUI from "alchemy_admin/gui"
|
7
10
|
import { translate } from "alchemy_admin/i18n"
|
11
|
+
import { currentDialog, closeCurrentDialog } from "alchemy_admin/dialog"
|
8
12
|
import Dirty from "alchemy_admin/dirty"
|
9
13
|
import * as FixedElements from "alchemy_admin/fixed_elements"
|
10
14
|
import { growl } from "alchemy_admin/growler"
|
11
15
|
import ImageLoader from "alchemy_admin/image_loader"
|
12
|
-
import ImageCropper from "alchemy_admin/image_cropper"
|
13
16
|
import Initializer from "alchemy_admin/initializer"
|
14
17
|
import { LinkDialog } from "alchemy_admin/link_dialog"
|
15
|
-
import pictureSelector from "alchemy_admin/picture_selector"
|
16
18
|
import pleaseWaitOverlay from "alchemy_admin/please_wait_overlay"
|
17
19
|
import Sitemap from "alchemy_admin/sitemap"
|
18
20
|
import Spinner from "alchemy_admin/spinner"
|
@@ -26,6 +28,9 @@ import {
|
|
26
28
|
// Web Components
|
27
29
|
import "alchemy_admin/components"
|
28
30
|
|
31
|
+
// Handlebars Templates
|
32
|
+
import "alchemy_admin/templates/compiled"
|
33
|
+
|
29
34
|
// Shoelace Setup
|
30
35
|
import "alchemy_admin/shoelace_theme"
|
31
36
|
|
@@ -36,15 +41,14 @@ if (typeof window.Alchemy === "undefined") {
|
|
36
41
|
|
37
42
|
// Enhance the global Alchemy object with imported features
|
38
43
|
Object.assign(Alchemy, {
|
44
|
+
closeCurrentDialog,
|
45
|
+
currentDialog,
|
39
46
|
...Dirty,
|
40
|
-
GUI,
|
41
47
|
t: translate, // Global utility method for translating a given string
|
42
48
|
FixedElements,
|
43
49
|
growl,
|
44
50
|
ImageLoader: ImageLoader.init,
|
45
|
-
ImageCropper,
|
46
51
|
LinkDialog,
|
47
|
-
pictureSelector,
|
48
52
|
pleaseWaitOverlay,
|
49
53
|
Sitemap,
|
50
54
|
Spinner,
|
@@ -0,0 +1,117 @@
|
|
1
|
+
window.Alchemy = Alchemy || {}
|
2
|
+
|
3
|
+
Object.assign(Alchemy, {
|
4
|
+
ElementSelector: {
|
5
|
+
styles: {
|
6
|
+
reset: {
|
7
|
+
outline: "",
|
8
|
+
"outline-offset": "",
|
9
|
+
cursor: ""
|
10
|
+
},
|
11
|
+
hover: {
|
12
|
+
outline: "2px dashed #f0b437",
|
13
|
+
"outline-offset": "4px",
|
14
|
+
cursor: "pointer"
|
15
|
+
},
|
16
|
+
selected: {
|
17
|
+
outline: "2px dashed #90b9d0",
|
18
|
+
"outline-offset": "4px"
|
19
|
+
}
|
20
|
+
},
|
21
|
+
|
22
|
+
init() {
|
23
|
+
window.addEventListener("message", (event) => {
|
24
|
+
switch (event.data.message) {
|
25
|
+
case "Alchemy.blurElements":
|
26
|
+
this.blurElements()
|
27
|
+
break
|
28
|
+
case "Alchemy.focusElement":
|
29
|
+
this.focusElement(event.data)
|
30
|
+
break
|
31
|
+
default:
|
32
|
+
console.info("Received unknown message!", event.data)
|
33
|
+
}
|
34
|
+
})
|
35
|
+
this.elements = Array.from(
|
36
|
+
document.querySelectorAll("[data-alchemy-element]")
|
37
|
+
)
|
38
|
+
this.elements.forEach((element) => {
|
39
|
+
element.addEventListener("mouseover", () => {
|
40
|
+
if (!element.classList.contains("selected")) {
|
41
|
+
Object.assign(element.style, this.getStyle("hover"))
|
42
|
+
}
|
43
|
+
})
|
44
|
+
element.addEventListener("mouseout", () => {
|
45
|
+
if (!element.classList.contains("selected")) {
|
46
|
+
Object.assign(element.style, this.getStyle("reset"))
|
47
|
+
}
|
48
|
+
})
|
49
|
+
element.addEventListener("click", (e) => {
|
50
|
+
e.stopPropagation()
|
51
|
+
e.preventDefault()
|
52
|
+
this.selectElement(element)
|
53
|
+
this.focusElementEditor(element)
|
54
|
+
})
|
55
|
+
})
|
56
|
+
},
|
57
|
+
|
58
|
+
// Mark element in preview frame as selected and scrolls to it.
|
59
|
+
selectElement(element) {
|
60
|
+
this.blurElements(element)
|
61
|
+
element.classList.add("selected")
|
62
|
+
Object.assign(element.style, this.getStyle("selected"))
|
63
|
+
element.scrollIntoView({
|
64
|
+
behavior: "smooth",
|
65
|
+
block: "start"
|
66
|
+
})
|
67
|
+
},
|
68
|
+
|
69
|
+
// Blur all elements in preview frame.
|
70
|
+
blurElements(selectedElement) {
|
71
|
+
this.elements.forEach((element) => {
|
72
|
+
if (element !== selectedElement) {
|
73
|
+
element.classList.remove("selected")
|
74
|
+
Object.assign(element.style, this.getStyle("reset"))
|
75
|
+
}
|
76
|
+
})
|
77
|
+
},
|
78
|
+
|
79
|
+
// Focus the element in the Alchemy preview window.
|
80
|
+
focusElement(data) {
|
81
|
+
const element = this.getElement(data.element_id)
|
82
|
+
if (element) {
|
83
|
+
return this.selectElement(element)
|
84
|
+
} else {
|
85
|
+
return console.warn("Could not focus element with id", data.element_id)
|
86
|
+
}
|
87
|
+
},
|
88
|
+
|
89
|
+
getElement(element_id) {
|
90
|
+
return this.elements.find(
|
91
|
+
(element) => element.dataset.alchemyElement === element_id.toString()
|
92
|
+
)
|
93
|
+
},
|
94
|
+
|
95
|
+
// Focus the element editor in the Alchemy element window.
|
96
|
+
focusElementEditor(element) {
|
97
|
+
const element_id = element.dataset.alchemyElement
|
98
|
+
window.parent.postMessage(
|
99
|
+
{
|
100
|
+
message: "Alchemy.focusElementEditor",
|
101
|
+
element_id
|
102
|
+
},
|
103
|
+
window.location.origin
|
104
|
+
)
|
105
|
+
},
|
106
|
+
|
107
|
+
getStyle(state) {
|
108
|
+
if (state === "reset") {
|
109
|
+
return this.styles["reset"]
|
110
|
+
} else {
|
111
|
+
return this.styles[state]
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
})
|
116
|
+
|
117
|
+
Alchemy.ElementSelector.init()
|
@@ -20,8 +20,7 @@ module Alchemy
|
|
20
20
|
{
|
21
21
|
min_size: large_enough? ? min_size : false,
|
22
22
|
ratio: ratio,
|
23
|
-
default_box: default_box
|
24
|
-
image_size: [image_width, image_height]
|
23
|
+
default_box: default_box
|
25
24
|
}.freeze
|
26
25
|
end
|
27
26
|
|
@@ -79,8 +78,8 @@ module Alchemy
|
|
79
78
|
[
|
80
79
|
default_crop_from[0],
|
81
80
|
default_crop_from[1],
|
82
|
-
|
83
|
-
|
81
|
+
default_crop_size[0],
|
82
|
+
default_crop_size[1]
|
84
83
|
]
|
85
84
|
end
|
86
85
|
end
|
@@ -8,7 +8,7 @@
|
|
8
8
|
<%= simple_format Alchemy.t(:explain_cropping) %>
|
9
9
|
<% end %>
|
10
10
|
<div class="thumbnail_background">
|
11
|
-
<%= image_tag @picture.
|
11
|
+
<%= image_tag @picture.url(flatten: true), id: 'imageToCrop' %>
|
12
12
|
</div>
|
13
13
|
<form>
|
14
14
|
<%= button_tag Alchemy.t(:apply), type: 'submit' %>
|
@@ -17,20 +17,23 @@
|
|
17
17
|
</div>
|
18
18
|
<% end %>
|
19
19
|
<% if @settings %>
|
20
|
-
<script type="
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
20
|
+
<script type="module">
|
21
|
+
import ImageCropper from "alchemy_admin/image_cropper";
|
22
|
+
import ImageLoader from "alchemy_admin/image_loader";
|
23
|
+
|
24
|
+
const image = document.getElementById("imageToCrop");
|
25
|
+
|
26
|
+
new ImageLoader(image);
|
27
|
+
new ImageCropper(
|
28
|
+
image,
|
29
|
+
<%= @settings[:min_size].to_json %>,
|
30
|
+
<%= @settings[:default_box].to_json %>,
|
31
|
+
<%= @settings[:ratio] %>,
|
32
|
+
[
|
33
|
+
"<%= params[:crop_from_form_field_id] %>",
|
34
|
+
"<%= params[:crop_size_form_field_id] %>",
|
35
|
+
],
|
36
|
+
<%= @element.id %>
|
37
|
+
);
|
35
38
|
</script>
|
36
39
|
<% end %>
|
@@ -3,14 +3,15 @@
|
|
3
3
|
(nestable_element = element.nestable_elements.first) &&
|
4
4
|
Alchemy::Element.all_from_clipboard_for_parent_element(get_clipboard("elements"), element).none?
|
5
5
|
%>
|
6
|
-
<%=
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
<%= turbo_frame_tag("new_nested_element_#{element.id}") do %>
|
7
|
+
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)], html: { class: 'add-nested-element-form', id: nil } do |f| %>
|
8
|
+
<%= f.hidden_field :name %>
|
9
|
+
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
|
10
|
+
<%= f.hidden_field :parent_element_id, value: element.id %>
|
11
|
+
<button class="add-nestable-element-button" is="alchemy-button">
|
12
|
+
<%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element.to_sym, scope: 'element_names')) %>
|
13
|
+
</button>
|
14
|
+
<% end %>
|
14
15
|
<% end %>
|
15
16
|
<% else %>
|
16
17
|
<%= link_to_dialog (nestable_element ? Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element.to_sym, scope: 'element_names')) : Alchemy.t("New Element")),
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<%= render Alchemy::Admin::ToolbarButton.new(
|
2
|
+
url: alchemy.admin_clipboard_path(remarkable_type: "elements"),
|
3
|
+
label: Alchemy.t("Show clipboard"),
|
4
|
+
icon: :clipboard,
|
5
|
+
icon_style: clipboard_empty?("elements") ? "line" : "fill",
|
6
|
+
dialog_options: {
|
7
|
+
title: Alchemy.t("Clipboard"),
|
8
|
+
size: "400x305"
|
9
|
+
},
|
10
|
+
link_options: {
|
11
|
+
id: "clipboard_button"
|
12
|
+
},
|
13
|
+
if_permitted_to: [:index, :alchemy_admin_clipboard]
|
14
|
+
) %>
|
@@ -4,6 +4,7 @@
|
|
4
4
|
data-element-name="<%= element.name %>"
|
5
5
|
class="<%= element.css_classes.join(" ") %>"
|
6
6
|
<%= element.compact? ? "compact" : nil %>
|
7
|
+
<%= local_assigns[:created] ? "created" : nil %>
|
7
8
|
<%= element.fixed? ? "fixed" : nil %>
|
8
9
|
>
|
9
10
|
<% unless element.fixed? %>
|
@@ -68,6 +69,7 @@
|
|
68
69
|
<% if element.nestable_elements.any? %>
|
69
70
|
<div class="nestable-elements">
|
70
71
|
<%= content_tag :div,
|
72
|
+
id: "element_#{element.id}_nested_elements",
|
71
73
|
class: "nested-elements", data: {
|
72
74
|
'droppable-elements' => element.nestable_elements.join(' '),
|
73
75
|
'element-name' => element.name
|
@@ -3,18 +3,20 @@
|
|
3
3
|
<%= Alchemy.t(:no_more_elements_to_add) %>
|
4
4
|
<% end %>
|
5
5
|
<%- else -%>
|
6
|
-
<%=
|
7
|
-
<%=
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
6
|
+
<%= turbo_frame_tag @element do %>
|
7
|
+
<%= alchemy_form_for [:admin, @element], remote: false do |form| %>
|
8
|
+
<%= form.hidden_field :page_version_id %>
|
9
|
+
<%= form.input :name,
|
10
|
+
label: Alchemy.t(:element_of_type),
|
11
|
+
collection: elements_for_select(@elements),
|
12
|
+
prompt: Alchemy.t(:select_element),
|
13
|
+
selected: (@elements.first if @elements.count == 1),
|
14
|
+
input_html: {is: 'alchemy-select', autofocus: true} %>
|
15
|
+
<% if @elements.count == 1 %>
|
16
|
+
<%= form.hidden_field :name, value: @elements.first[:name] %>
|
17
|
+
<% end %>
|
18
|
+
<%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
|
19
|
+
<%= form.submit Alchemy.t(:add) %>
|
20
|
+
<%- end -%>
|
19
21
|
<%- end -%>
|
20
22
|
<%- end -%>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<% opts = {
|
2
|
+
partial: "alchemy/admin/elements/element",
|
3
|
+
locals: {
|
4
|
+
element: Alchemy::ElementEditor.new(@element),
|
5
|
+
created: true
|
6
|
+
}
|
7
|
+
} %>
|
8
|
+
|
9
|
+
<% if @element.fixed? %>
|
10
|
+
<% target = "fixed_element_#{@element.id}" %>
|
11
|
+
<% elsif @element.parent_element %>
|
12
|
+
<% target = "element_#{@element.parent_element_id}_nested_elements" %>
|
13
|
+
<% else %>
|
14
|
+
<% target = "main-content-elements" %>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<%- if @cut_element_id -%>
|
18
|
+
<%= turbo_stream.remove "element_#{@cut_element_id}" %>
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
<% if @insert_at_top %>
|
22
|
+
<%= turbo_stream.prepend target, **opts %>
|
23
|
+
<% else %>
|
24
|
+
<%= turbo_stream.append target, **opts %>
|
25
|
+
<% end %>
|
26
|
+
|
27
|
+
<%= turbo_stream.replace "clipboard_button",
|
28
|
+
partial: "alchemy/admin/elements/clipboard_button" %>
|
29
|
+
|
30
|
+
<alchemy-growl>
|
31
|
+
<%= Alchemy.t(:successfully_added_element) %>
|
32
|
+
</alchemy-growl>
|
33
|
+
|
34
|
+
<alchemy-action name="closeCurrentDialog"></alchemy-action>
|
@@ -1,4 +1,5 @@
|
|
1
1
|
<%= turbo_frame_tag "alchemy_elements_window" do %>
|
2
|
+
<alchemy-elements-window-handle></alchemy-elements-window-handle>
|
2
3
|
<alchemy-elements-window>
|
3
4
|
<div class="elements-window-toolbar">
|
4
5
|
<%= render Alchemy::Admin::ToolbarButton.new(
|
@@ -12,20 +13,7 @@
|
|
12
13
|
},
|
13
14
|
if_permitted_to: [:create, Alchemy::Element]
|
14
15
|
) %>
|
15
|
-
<%= render
|
16
|
-
url: alchemy.admin_clipboard_path(remarkable_type: "elements"),
|
17
|
-
label: Alchemy.t("Show clipboard"),
|
18
|
-
icon: :clipboard,
|
19
|
-
icon_style: clipboard_empty?("elements") ? "line" : "fill",
|
20
|
-
dialog_options: {
|
21
|
-
title: Alchemy.t("Clipboard"),
|
22
|
-
size: "400x305"
|
23
|
-
},
|
24
|
-
link_options: {
|
25
|
-
id: "clipboard_button"
|
26
|
-
},
|
27
|
-
if_permitted_to: [:index, :alchemy_admin_clipboard]
|
28
|
-
) %>
|
16
|
+
<%= render "alchemy/admin/elements/clipboard_button" %>
|
29
17
|
<sl-tooltip content="<%= Alchemy.t("Collapse all elements") %>" placement="top-end" class="right">
|
30
18
|
<button id="collapse-all-elements-button" class="icon_button">
|
31
19
|
<alchemy-icon name="contract-up-down"></alchemy-icon>
|
@@ -53,7 +41,7 @@
|
|
53
41
|
<%= render @elements.map { |element| Alchemy::ElementEditor.new(element) } %>
|
54
42
|
</sl-tab-panel>
|
55
43
|
<% @fixed_elements.each do |element| %>
|
56
|
-
<sl-tab-panel name="fixed-element-<%= element.id %>" style="--padding: 0" class="scrollable-elements">
|
44
|
+
<sl-tab-panel id="fixed_element_<%= element.id %>" name="fixed-element-<%= element.id %>" style="--padding: 0" class="scrollable-elements">
|
57
45
|
<%= render Alchemy::ElementEditor.new(element) %>
|
58
46
|
</sl-tab-panel>
|
59
47
|
<% end %>
|
@@ -1,6 +1,6 @@
|
|
1
1
|
<%= f.input :caption, as: ingredient.settings[:caption_as_textarea] ? 'text' : 'string' %>
|
2
2
|
<%= f.input :title %>
|
3
|
-
<%= f.input :alt_tag, as: :text, placeholder: ingredient.alt_text(language: @language) %>
|
3
|
+
<%= f.input :alt_tag, as: :text, placeholder: ingredient.alt_text(language: @language), input_html: {rows: 4} %>
|
4
4
|
<%- if ingredient.settings[:sizes].present? && ingredient.settings[:srcset].blank? -%>
|
5
5
|
<%= f.input :render_size,
|
6
6
|
collection: [
|
@@ -1,7 +1,9 @@
|
|
1
|
-
<%=
|
2
|
-
<%=
|
3
|
-
|
4
|
-
<%=
|
1
|
+
<%= turbo_frame_tag @page do %>
|
2
|
+
<%= alchemy_form_for [:admin, @page], url: alchemy.admin_layoutpage_path(@page), class: 'edit_page', remote: false do |f| %>
|
3
|
+
<%= f.input :name, autofocus: true %>
|
4
|
+
<%= render Alchemy::Admin::TagsAutocomplete.new do %>
|
5
|
+
<%= f.input :tag_list, input_html: { value: f.object.tag_list.join(",") } %>
|
6
|
+
<% end %>
|
7
|
+
<%= f.submit Alchemy.t(:save) %>
|
5
8
|
<% end %>
|
6
|
-
<%= f.submit Alchemy.t(:save) %>
|
7
9
|
<% end %>
|