alchemy_cms 7.3.4 → 7.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -1
  3. data/Gemfile +10 -3
  4. data/README.md +2 -2
  5. data/Rakefile +2 -0
  6. data/alchemy_cms.gemspec +1 -4
  7. data/app/assets/builds/alchemy/admin.css +9 -1
  8. data/app/assets/builds/alchemy/admin.css.map +1 -1
  9. data/app/assets/builds/alchemy/custom-properties.css +1 -1
  10. data/app/assets/builds/alchemy/custom-properties.css.map +1 -1
  11. data/app/assets/builds/alchemy/preview.min.js +1 -0
  12. data/app/assets/builds/alchemy/welcome.css +1 -1
  13. data/app/assets/builds/alchemy/welcome.css.map +1 -1
  14. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css +1 -1
  15. data/app/assets/builds/tinymce/skins/content/alchemy/content.min.css.map +1 -1
  16. data/app/assets/config/alchemy_manifest.js +0 -4
  17. data/app/assets/javascripts/alchemy/admin.js +8 -6
  18. data/app/assets/stylesheets/alchemy/admin/elements.scss +43 -7
  19. data/app/assets/stylesheets/alchemy/admin/forms.scss +4 -0
  20. data/app/assets/stylesheets/alchemy/admin/image_library.scss +40 -26
  21. data/app/assets/stylesheets/alchemy/admin/navigation.scss +9 -1
  22. data/app/assets/stylesheets/alchemy/admin/preview_window.scss +22 -17
  23. data/app/assets/stylesheets/alchemy/admin.scss +1 -1
  24. data/app/assets/stylesheets/alchemy/custom-properties.css +2 -1
  25. data/app/components/alchemy/ingredients/link_view.rb +7 -1
  26. data/app/components/alchemy/ingredients/picture_view.rb +5 -2
  27. data/app/components/alchemy/ingredients/text_view.rb +4 -1
  28. data/app/components/concerns/alchemy/ingredients/link_target.rb +18 -0
  29. data/app/controllers/alchemy/admin/base_controller.rb +34 -5
  30. data/app/controllers/alchemy/admin/elements_controller.rb +2 -2
  31. data/app/controllers/alchemy/admin/languages_controller.rb +1 -1
  32. data/app/controllers/alchemy/admin/layoutpages_controller.rb +1 -0
  33. data/app/controllers/alchemy/admin/pages_controller.rb +6 -6
  34. data/app/controllers/alchemy/admin/resources_controller.rb +1 -1
  35. data/app/controllers/alchemy/elements_controller.rb +3 -0
  36. data/app/helpers/alchemy/admin/form_helper.rb +1 -1
  37. data/app/helpers/alchemy/admin/navigation_helper.rb +22 -1
  38. data/app/javascript/alchemy_admin/components/action.js +2 -1
  39. data/app/javascript/alchemy_admin/components/dialog_link.js +3 -18
  40. data/app/javascript/alchemy_admin/components/element_editor.js +9 -0
  41. data/app/javascript/alchemy_admin/components/elements_window.js +34 -0
  42. data/app/javascript/alchemy_admin/components/elements_window_handle.js +65 -0
  43. data/app/javascript/alchemy_admin/components/icon.js +2 -2
  44. data/app/javascript/alchemy_admin/components/index.js +1 -0
  45. data/app/javascript/alchemy_admin/components/preview_window.js +5 -5
  46. data/app/javascript/alchemy_admin/components/uploader/file_upload.js +1 -1
  47. data/app/javascript/alchemy_admin/confirm_dialog.js +9 -11
  48. data/app/javascript/alchemy_admin/dialog.js +329 -0
  49. data/app/javascript/alchemy_admin/hotkeys.js +3 -2
  50. data/app/javascript/alchemy_admin/image_cropper.js +57 -40
  51. data/app/javascript/alchemy_admin/image_overlay.js +73 -0
  52. data/app/javascript/alchemy_admin/initializer.js +51 -2
  53. data/app/javascript/alchemy_admin/link_dialog.js +2 -1
  54. data/app/javascript/alchemy_admin/node_tree.js +3 -1
  55. data/app/javascript/alchemy_admin/page_sorter.js +1 -1
  56. data/app/javascript/alchemy_admin/picture_selector.js +2 -1
  57. data/app/javascript/alchemy_admin/shoelace_theme.js +2 -2
  58. data/app/javascript/alchemy_admin/templates/compiled.js +1 -0
  59. data/app/javascript/alchemy_admin.js +10 -6
  60. data/app/javascript/preview.js +117 -0
  61. data/app/models/alchemy/image_cropper_settings.rb +3 -4
  62. data/app/views/alchemy/_menubar.html.erb +1 -1
  63. data/app/views/alchemy/_preview_mode_code.html.erb +1 -1
  64. data/app/views/alchemy/admin/crop.html.erb +19 -16
  65. data/app/views/alchemy/admin/dashboard/info.html.erb +1 -1
  66. data/app/views/alchemy/admin/elements/_add_nested_element_form.html.erb +9 -8
  67. data/app/views/alchemy/admin/elements/_clipboard_button.html.erb +14 -0
  68. data/app/views/alchemy/admin/elements/_element.html.erb +2 -0
  69. data/app/views/alchemy/admin/elements/_form.html.erb +15 -13
  70. data/app/views/alchemy/admin/elements/create.turbo_stream.erb +34 -0
  71. data/app/views/alchemy/admin/elements/index.html.erb +3 -15
  72. data/app/views/alchemy/admin/ingredients/_picture_fields.html.erb +1 -1
  73. data/app/views/alchemy/admin/layoutpages/edit.html.erb +7 -5
  74. data/app/views/alchemy/admin/nodes/_form.html.erb +1 -1
  75. data/app/views/alchemy/admin/pages/_current_page.html.erb +1 -1
  76. data/app/views/alchemy/admin/pages/_form.html.erb +43 -40
  77. data/app/views/alchemy/admin/pages/_locked_page.html.erb +1 -1
  78. data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +1 -1
  79. data/app/views/alchemy/admin/pages/_sitemap.html.erb +1 -1
  80. data/app/views/alchemy/admin/pages/_table.html.erb +2 -2
  81. data/app/views/alchemy/admin/pages/edit.html.erb +1 -1
  82. data/app/views/alchemy/admin/pages/update.turbo_stream.erb +39 -0
  83. data/app/views/alchemy/admin/partials/_main_navigation_entry.html.erb +3 -4
  84. data/app/views/alchemy/admin/pictures/_picture_description_field.html.erb +7 -5
  85. data/app/views/alchemy/admin/pictures/index.html.erb +13 -9
  86. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +1 -1
  87. data/app/views/layouts/alchemy/admin.html.erb +8 -4
  88. data/bun.lockb +0 -0
  89. data/bundles/tinymce.js +2 -0
  90. data/config/alchemy/config.yml +3 -3
  91. data/config/alchemy/modules.yml +7 -6
  92. data/config/importmap.rb +4 -0
  93. data/config/routes.rb +1 -1
  94. data/lib/alchemy/engine.rb +6 -0
  95. data/lib/alchemy/modules.rb +0 -27
  96. data/lib/alchemy/resource.rb +14 -4
  97. data/lib/alchemy/test_support/having_picture_thumbnails_examples.rb +10 -10
  98. data/lib/alchemy/tinymce.rb +2 -1
  99. data/lib/alchemy/upgrader/seven_point_four.rb +26 -0
  100. data/lib/alchemy/version.rb +1 -1
  101. data/lib/alchemy.rb +14 -0
  102. data/lib/alchemy_cms.rb +0 -2
  103. data/lib/generators/alchemy/ingredient/ingredient_generator.rb +5 -0
  104. data/lib/generators/alchemy/ingredient/templates/view.html.erb +1 -1
  105. data/lib/generators/alchemy/ingredient/templates/view_component.rb.tt +10 -0
  106. data/lib/generators/alchemy/install/install_generator.rb +0 -1
  107. data/lib/generators/alchemy/install/templates/elements.yml.tt +1 -1
  108. data/lib/tasks/alchemy/upgrade.rake +19 -20
  109. data/rollup.config.mjs +44 -1
  110. data/vendor/javascript/cropperjs.min.js +10 -0
  111. data/vendor/javascript/handlebars.min.js +29 -0
  112. data/vendor/javascript/jquery.min.js +2 -0
  113. data/vendor/javascript/select2.min.js +23 -0
  114. data/vendor/javascript/tinymce.min.js +1 -1
  115. metadata +40 -94
  116. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +0 -271
  117. data/app/assets/javascripts/alchemy/alchemy.image_overlay.coffee +0 -54
  118. data/app/assets/javascripts/alchemy/alchemy.preview.js.coffee +0 -97
  119. data/app/assets/javascripts/alchemy/preview.js +0 -1
  120. data/app/assets/javascripts/alchemy/templates/index.js +0 -2
  121. data/app/javascript/alchemy_admin/gui.js +0 -12
  122. data/app/views/alchemy/admin/elements/create.js.erb +0 -35
  123. data/app/views/alchemy/admin/pages/update.js.erb +0 -43
  124. data/lib/alchemy/upgrader/seven_point_zero.rb +0 -36
  125. data/vendor/assets/images/Jcrop.gif +0 -0
  126. data/vendor/assets/javascripts/jquery_plugins/jquery.Jcrop.min.js +0 -7
  127. data/vendor/assets/javascripts/jquery_plugins/select2.js +0 -3729
  128. data/vendor/assets/stylesheets/jquery.Jcrop.min.css +0 -2
  129. data/vendor/assets/stylesheets/tinymce/skins/content/default/content.min.css +0 -1
  130. /data/app/{assets/javascripts/alchemy → javascript/alchemy_admin}/templates/node_folder.hbs +0 -0
  131. /data/app/{assets/javascripts/alchemy → javascript/alchemy_admin}/templates/page_folder.hbs +0 -0
  132. /data/app/{assets/javascripts/tinymce/icons/remixicons/icons.js → javascript/tinymce/icons/remixicons/index.js} +0 -0
  133. /data/app/{assets/javascripts/tinymce/plugins/alchemy_link/plugin.min.js → javascript/tinymce/plugins/alchemy_link/index.js} +0 -0
  134. /data/vendor/assets/{fonts → images}/remixicon.symbol.svg +0 -0
@@ -1 +1 @@
1
- {"version":3,"sourceRoot":"","sources":["../../stylesheets/alchemy/custom-properties.css","../../stylesheets/alchemy/_fonts.scss","../../stylesheets/alchemy/welcome.scss","../../stylesheets/alchemy/_variables.scss"],"names":[],"mappings":"AAAA,MAEE,iBACA,iBACA,iBACA,kBACA,kBACA,kBACA,kBACA,kBACA,kBAGA,wBACA,yBACA,qBACA,uBACA,uBACA,wBAGA,8FAEA,6GAIA,wBACA,yBACA,wBAEA,0BACA,wBAGA,4BAGA,+CACA,0CACA,2CACA,yCAEA,+CACA,2CACA,4CACA,0CAEA,2CACA,4CACA,0CAEA,6CACA,0CACA,+CAEA,4CACA,uCACA,wCACA,sCAEA,4CACA,uCACA,wCACA,sCACA,2CAEA,mCAEA,0CACA,gDACA,2CAGF,eACE,0CAEA,6CACA,wCAEA,iDACA,4CACA,0CAEA,yDACA,sEACA,iEACA,+DAEA,0EACA,uEACA,wEACA,6EACA,iFC7FF,WACE,wBACA,ylxFACA,gBACA,kBAGF,WACE,wBACA,i7wFACA,gBACA,kBCPF,KACE,iBCgImB,uBD/HnB,yBACA,YCiBoB,0FDhBpB,mBACA,eACA,iBAGF,GACE,iBACA,eACA,gBACA,mBACA,iBAGF,WACE,kBACA,QACA,SACA,YACA,iBACA,8CAGF,GACE,gBACA,mBACA,UACA,kBACA,WACA,4BAEA,MACE,yBACA,mBACA,sBACA,qBACA,iBAIJ,EACE,yBACA,0BACA,+BAEA,QACE,yBACA","file":"welcome.css"}
1
+ {"version":3,"sourceRoot":"","sources":["../../stylesheets/alchemy/custom-properties.css","../../stylesheets/alchemy/_fonts.scss","../../stylesheets/alchemy/welcome.scss","../../stylesheets/alchemy/_variables.scss"],"names":[],"mappings":"AAAA,MAEE,iBACA,iBACA,iBACA,kBACA,kBACA,kBACA,kBACA,kBACA,kBAGA,wBACA,yBACA,qBACA,uBACA,uBACA,wBAGA,8FAEA,6GAIA,wBACA,yBACA,wBAEA,0BACA,wBAGA,4BACA,0BAGA,+CACA,0CACA,2CACA,yCAEA,+CACA,2CACA,4CACA,0CAEA,2CACA,4CACA,0CAEA,6CACA,0CACA,+CAEA,4CACA,uCACA,wCACA,sCAEA,4CACA,uCACA,wCACA,sCACA,2CAEA,mCAEA,0CACA,gDACA,2CAGF,eACE,0CAEA,6CACA,wCAEA,iDACA,4CACA,0CAEA,yDACA,sEACA,iEACA,+DAEA,0EACA,uEACA,wEACA,6EACA,iFC9FF,WACE,wBACA,ylxFACA,gBACA,kBAGF,WACE,wBACA,i7wFACA,gBACA,kBCPF,KACE,iBCgImB,uBD/HnB,yBACA,YCiBoB,0FDhBpB,mBACA,eACA,iBAGF,GACE,iBACA,eACA,gBACA,mBACA,iBAGF,WACE,kBACA,QACA,SACA,YACA,iBACA,8CAGF,GACE,gBACA,mBACA,UACA,kBACA,WACA,4BAEA,MACE,yBACA,mBACA,sBACA,qBACA,iBAIJ,EACE,yBACA,0BACA,+BAEA,QACE,yBACA","file":"welcome.css"}
@@ -1 +1 @@
1
- :root{--spacing-0: 2px;--spacing-1: 4px;--spacing-2: 8px;--spacing-3: 12px;--spacing-4: 16px;--spacing-5: 20px;--spacing-6: 24px;--spacing-7: 28px;--spacing-8: 32px;--icon-size-xs: 0.75rem;--icon-size-sm: 0.875rem;--icon-size-1x: 1rem;--icon-size-md: 1.3rem;--icon-size-xl: 1.5rem;--icon-size-xxl: 1.6rem;--font-mono: Menlo, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;--font-sans: "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Verdana, Tahoma, sans-serif;--font-size_small: 10px;--font-size_medium: 12px;--font-size_large: 16px;--font-weigth_normal: 500;--font-weigth_bold: 700;--border-radius_medium: 3px;--color-blue_very_light: hsl(203deg, 32%, 97%);--color-blue_light: hsl(203deg, 32%, 85%);--color-blue_medium: hsl(212deg, 52%, 36%);--color-blue_dark: hsl(212deg, 52%, 26%);--color-green_very_light: hsl(88deg, 47%, 88%);--color-green_light: hsl(127deg, 25%, 69%);--color-green_medium: hsl(127deg, 25%, 48%);--color-green_dark: hsl(128deg, 32%, 26%);--color-yellow_light: hsl(60deg, 81%, 92%);--color-yellow_medium: hsl(56deg, 68%, 85%);--color-yellow_dark: hsl(56deg, 53%, 29%);--color-orange_medium: hsl(42deg, 100%, 74%);--color-orange_dark: hsl(28deg, 77%, 68%);--color-orange_very_dark: hsl(28deg, 77%, 48%);--color-red_very_light: hsl(0deg, 47%, 88%);--color-red_light: hsl(0deg, 25%, 69%);--color-red_medium: hsl(0deg, 51%, 42%);--color-red_dark: hsl(0deg, 51%, 25%);--color-grey_very_light: hsl(0deg, 0%, 97%);--color-grey_light: hsl(0deg, 0%, 94%);--color-grey_medium: hsl(0deg, 0%, 78%);--color-grey_dark: hsl(0deg, 0%, 40%);--color-grey_very_dark: hsl(0deg, 0%, 20%);--color-white: hsl(0deg, 0%, 100%);--color-text: hsla(224deg, 23%, 26%, 0.8);--color-text_muted: hsla(224deg, 23%, 26%, 0.5);--color-icon: hsla(224deg, 23%, 26%, 0.75)}.alchemy-light{--outline-color: var(--color-orange_dark);--font-color_failed: var(--color-red_medium);--font-color_default: var(--color-text);--tabs_indicator-color: var(--color-orange_dark);--tabs_track-color: var(--color-grey_light);--sl-input-label-color: var(--color-text);--file-upload_background-color: hsla(0deg, 0%, 80%, 0.8);--file-upload_single-upload-background-color: var(--color-grey_light);--file-upload_progress-track-color: var(--color-blue_very_light);--file-upload_progress-indicator-color: var(--color-blue_dark);--file-upload_progress-indicator-color-canceled: hsla(0deg, 0%, 60%, 0.8);--file-upload_progress-indicator-color-failed: var(--color-red_medium);--file-upload_progress-indicator-color-invalid: var(--color-red_medium);--file-upload_progress-indicator-color-successful: var(--color-green_medium);--file-upload_progress-indicator-color-upload-finished: var( --color-blue_dark )}html{font-size:13px}body{font-family:"Open Sans","Lucida Grande","Lucida Sans Unicode","Lucida Sans",Verdana,Tahoma,sans-serif;line-height:1.4;margin:1em;color:var(--color-text)}a{color:var(--color-blue_dark)}table{border-collapse:collapse}table td,table th{border:1px solid rgba(176,176,176,.5);padding:.4rem}figure{display:table;margin:1rem auto}figure figcaption{color:var(--color-grey_dark);display:block;margin-top:.25rem;text-align:center}hr{border-color:rgba(176,176,176,.5);border-style:solid;border-width:1px 0 0 0}code{background-color:var(--color-grey_very_light);border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid rgba(176,176,176,.5);margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid rgba(176,176,176,.5);margin-right:1.5rem;padding-right:1rem}/*# sourceMappingURL=content.min.css.map */
1
+ :root{--spacing-0: 2px;--spacing-1: 4px;--spacing-2: 8px;--spacing-3: 12px;--spacing-4: 16px;--spacing-5: 20px;--spacing-6: 24px;--spacing-7: 28px;--spacing-8: 32px;--icon-size-xs: 0.75rem;--icon-size-sm: 0.875rem;--icon-size-1x: 1rem;--icon-size-md: 1.3rem;--icon-size-xl: 1.5rem;--icon-size-xxl: 1.6rem;--font-mono: Menlo, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;--font-sans: "Open Sans", "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Verdana, Tahoma, sans-serif;--font-size_small: 10px;--font-size_medium: 12px;--font-size_large: 16px;--font-weigth_normal: 500;--font-weigth_bold: 700;--border-radius_medium: 3px;--border-width_small: 1px;--color-blue_very_light: hsl(203deg, 32%, 97%);--color-blue_light: hsl(203deg, 32%, 85%);--color-blue_medium: hsl(212deg, 52%, 36%);--color-blue_dark: hsl(212deg, 52%, 26%);--color-green_very_light: hsl(88deg, 47%, 88%);--color-green_light: hsl(127deg, 25%, 69%);--color-green_medium: hsl(127deg, 25%, 48%);--color-green_dark: hsl(128deg, 32%, 26%);--color-yellow_light: hsl(60deg, 81%, 92%);--color-yellow_medium: hsl(56deg, 68%, 85%);--color-yellow_dark: hsl(56deg, 53%, 29%);--color-orange_medium: hsl(42deg, 100%, 74%);--color-orange_dark: hsl(28deg, 77%, 68%);--color-orange_very_dark: hsl(28deg, 77%, 48%);--color-red_very_light: hsl(0deg, 47%, 88%);--color-red_light: hsl(0deg, 25%, 69%);--color-red_medium: hsl(0deg, 51%, 42%);--color-red_dark: hsl(0deg, 51%, 25%);--color-grey_very_light: hsl(0deg, 0%, 97%);--color-grey_light: hsl(0deg, 0%, 94%);--color-grey_medium: hsl(0deg, 0%, 78%);--color-grey_dark: hsl(0deg, 0%, 40%);--color-grey_very_dark: hsl(0deg, 0%, 20%);--color-white: hsl(0deg, 0%, 100%);--color-text: hsla(224deg, 23%, 26%, 0.8);--color-text_muted: hsla(224deg, 23%, 26%, 0.5);--color-icon: hsla(224deg, 23%, 26%, 0.75)}.alchemy-light{--outline-color: var(--color-orange_dark);--font-color_failed: var(--color-red_medium);--font-color_default: var(--color-text);--tabs_indicator-color: var(--color-orange_dark);--tabs_track-color: var(--color-grey_light);--sl-input-label-color: var(--color-text);--file-upload_background-color: hsla(0deg, 0%, 80%, 0.8);--file-upload_single-upload-background-color: var(--color-grey_light);--file-upload_progress-track-color: var(--color-blue_very_light);--file-upload_progress-indicator-color: var(--color-blue_dark);--file-upload_progress-indicator-color-canceled: hsla(0deg, 0%, 60%, 0.8);--file-upload_progress-indicator-color-failed: var(--color-red_medium);--file-upload_progress-indicator-color-invalid: var(--color-red_medium);--file-upload_progress-indicator-color-successful: var(--color-green_medium);--file-upload_progress-indicator-color-upload-finished: var( --color-blue_dark )}html{font-size:13px}body{font-family:"Open Sans","Lucida Grande","Lucida Sans Unicode","Lucida Sans",Verdana,Tahoma,sans-serif;line-height:1.4;margin:1em;color:var(--color-text)}a{color:var(--color-blue_dark)}table{border-collapse:collapse}table td,table th{border:1px solid rgba(176,176,176,.5);padding:.4rem}figure{display:table;margin:1rem auto}figure figcaption{color:var(--color-grey_dark);display:block;margin-top:.25rem;text-align:center}hr{border-color:rgba(176,176,176,.5);border-style:solid;border-width:1px 0 0 0}code{background-color:var(--color-grey_very_light);border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid rgba(176,176,176,.5);margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid rgba(176,176,176,.5);margin-right:1.5rem;padding-right:1rem}/*# sourceMappingURL=content.min.css.map */
@@ -1 +1 @@
1
- {"version":3,"sourceRoot":"","sources":["../../../../../stylesheets/alchemy/custom-properties.css","../../../../../stylesheets/tinymce/skins/content/alchemy/content.scss","../../../../../stylesheets/alchemy/_variables.scss"],"names":[],"mappings":"AAAA,MAEE,iBACA,iBACA,iBACA,kBACA,kBACA,kBACA,kBACA,kBACA,kBAGA,wBACA,yBACA,qBACA,uBACA,uBACA,wBAGA,8FAEA,6GAIA,wBACA,yBACA,wBAEA,0BACA,wBAGA,4BAGA,+CACA,0CACA,2CACA,yCAEA,+CACA,2CACA,4CACA,0CAEA,2CACA,4CACA,0CAEA,6CACA,0CACA,+CAEA,4CACA,uCACA,wCACA,sCAEA,4CACA,uCACA,wCACA,sCACA,2CAEA,mCAEA,0CACA,gDACA,2CAGF,eACE,0CAEA,6CACA,wCAEA,iDACA,4CACA,0CAEA,yDACA,sEACA,iEACA,+DAEA,0EACA,uEACA,wEACA,6EACA,iFCnFF,KACE,eAGF,KACE,YCSoB,0FDRpB,gBACA,WACA,wBAGF,EACE,6BAGF,MACE,yBAGF,kBAEE,sCACA,cAGF,OACE,cACA,iBAGF,kBACE,6BACA,cACA,kBACA,kBAGF,GACE,aCFqB,qBDGrB,mBACA,uBAGF,KACE,8CACA,cCHsB,IDItB,oBAGF,4CACE,2CACA,mBACA,kBAGF,sCACE,4CACA,oBACA","file":"content.min.css"}
1
+ {"version":3,"sourceRoot":"","sources":["../../../../../stylesheets/alchemy/custom-properties.css","../../../../../stylesheets/tinymce/skins/content/alchemy/content.scss","../../../../../stylesheets/alchemy/_variables.scss"],"names":[],"mappings":"AAAA,MAEE,iBACA,iBACA,iBACA,kBACA,kBACA,kBACA,kBACA,kBACA,kBAGA,wBACA,yBACA,qBACA,uBACA,uBACA,wBAGA,8FAEA,6GAIA,wBACA,yBACA,wBAEA,0BACA,wBAGA,4BACA,0BAGA,+CACA,0CACA,2CACA,yCAEA,+CACA,2CACA,4CACA,0CAEA,2CACA,4CACA,0CAEA,6CACA,0CACA,+CAEA,4CACA,uCACA,wCACA,sCAEA,4CACA,uCACA,wCACA,sCACA,2CAEA,mCAEA,0CACA,gDACA,2CAGF,eACE,0CAEA,6CACA,wCAEA,iDACA,4CACA,0CAEA,yDACA,sEACA,iEACA,+DAEA,0EACA,uEACA,wEACA,6EACA,iFCpFF,KACE,eAGF,KACE,YCSoB,0FDRpB,gBACA,WACA,wBAGF,EACE,6BAGF,MACE,yBAGF,kBAEE,sCACA,cAGF,OACE,cACA,iBAGF,kBACE,6BACA,cACA,kBACA,kBAGF,GACE,aCFqB,qBDGrB,mBACA,uBAGF,KACE,8CACA,cCHsB,IDItB,oBAGF,4CACE,2CACA,mBACA,kBAGF,sCACE,4CACA,oBACA","file":"content.min.css"}
@@ -1,11 +1,7 @@
1
1
  //= link alchemy/admin/all.js
2
- //= link alchemy/preview.js
3
- //= link tinymce/plugins/alchemy_link/plugin.min.js
4
- //= link tinymce/icons/remixicons/icons.js
5
2
  //= link_tree ../builds/alchemy/
6
3
  //= link_tree ../builds/tinymce/
7
4
  //= link_tree ../images/alchemy/
8
- //= link_tree ../../../vendor/assets/fonts/
9
5
  //= link_tree ../../../vendor/assets/images/
10
6
  //= link_tree ../../javascript .js
11
7
  //= link_tree ../../../vendor/javascript .js
@@ -1,8 +1,10 @@
1
1
  // Alchemy CMS Sprockets Manifest
2
2
  // ------------------------------
3
- //= require jquery3
4
- //= require_tree ../../../../vendor/assets/javascripts/jquery_plugins/
5
- //= require handlebars
6
- //= require alchemy/templates
7
- //= require alchemy/alchemy.dialog
8
- //= require alchemy/alchemy.image_overlay
3
+ //
4
+ // This manifest file is deprecated and will be removed in Alchemy 8.0.
5
+ //
6
+
7
+ console.warn(
8
+ "The 'alchemy/admin' Sprockets manifest is deprecated and will be removed in 8.0. " +
9
+ "Please remove 'require alchemy/admin' from your 'vendor/assets/javascripts/alchemy/admin/all.js' file."
10
+ )
@@ -1,10 +1,20 @@
1
+ :root {
2
+ --elements-window-width: 0px;
3
+ --elements-window-min-width: #{$elements-window-min-width};
4
+ }
5
+
1
6
  #alchemy_elements_window {
7
+ --width: var(--elements-window-width, 100vw);
2
8
  position: absolute;
3
9
  right: 0;
4
10
  top: $top-menu-height;
5
11
  z-index: 20;
6
12
  display: block;
7
- width: calc(100vw - #{$collapsed-main-menu-width});
13
+ width: var(--width);
14
+ min-width: var(--elements-window-min-width);
15
+ max-width: calc(
16
+ 100vw - var(--main-menu-width) - var(--preview-window-min-width)
17
+ );
8
18
  height: calc(100vh - #{$top-menu-height});
9
19
  border-left: $default-border;
10
20
  background-color: var(--color-grey_very_light);
@@ -14,15 +24,41 @@
14
24
  .elements-window-visible & {
15
25
  transform: translate3d(0, 0, 0);
16
26
  }
27
+ }
17
28
 
18
- // Fix for Tinymce fullscreen window positioning issues (GH#1511)
19
- .mce-fullscreen & {
20
- width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width});
21
- }
29
+ .elements-window-visible {
30
+ --elements-window-width: calc(100vw - var(--main-menu-width));
22
31
 
23
32
  @media screen and (min-width: $large-screen-break-point) {
24
- width: $elements-window-width;
25
- min-width: $elements-window-min-width;
33
+ --elements-window-width: var(--elements-window-min-width);
34
+ }
35
+
36
+ @media screen and (min-width: $xlarge-screen-break-point) {
37
+ --elements-window-min-width: 475px;
38
+ }
39
+
40
+ // 1778px * 0.225 = 400px (the min width of the elements window)
41
+ @media screen and (min-width: 1778px) {
42
+ --elements-window-width: #{$elements-window-width};
43
+ }
44
+ }
45
+
46
+ alchemy-elements-window-handle {
47
+ --width: var(--spacing-1);
48
+ position: absolute;
49
+ left: calc(-1 * var(--width) / 2);
50
+ top: 0;
51
+ z-index: 1500; // tinymce .tox-fullscreen has 1200
52
+ height: inherit;
53
+ width: var(--width);
54
+ transition-duration: $transition-duration;
55
+ transition-property: background-color width left;
56
+ transition-timing-function: ease-in-out;
57
+
58
+ &:hover,
59
+ &.is-dragged {
60
+ background: var(--color-blue_dark);
61
+ cursor: ew-resize;
26
62
  }
27
63
  }
28
64
 
@@ -43,6 +43,10 @@ form {
43
43
  float: right;
44
44
  }
45
45
 
46
+ textarea {
47
+ padding-top: 7px;
48
+ }
49
+
46
50
  .input > select,
47
51
  .input > .select2-container {
48
52
  width: 100%;
@@ -1,8 +1,3 @@
1
- $picture-overlay-handle-width: 24px;
2
- $image-overlay-form-width: 350px - $picture-overlay-handle-width;
3
- $image-overlay-transition-duration: $transition-duration;
4
- $image-overlay-transition-easing: ease-in;
5
-
6
1
  .alchemy-image-overlay {
7
2
  &.open {
8
3
  background-color: rgba(0, 0, 0, 0.6);
@@ -17,10 +12,21 @@ $image-overlay-transition-easing: ease-in;
17
12
  max-width: 100%;
18
13
  }
19
14
 
15
+ .alchemy-image-overlay-container {
16
+ --picture-overlay-handle-width: 24px;
17
+ --image-overlay-form-width: calc(350px - var(--picture-overlay-handle-width));
18
+ --image-overlay-transition-duration: 250ms;
19
+ --image-overlay-transition-easing: ease-in;
20
+
21
+ &.open {
22
+ overflow: hidden;
23
+ }
24
+ }
25
+
20
26
  .alchemy-image-overlay-dialog {
21
27
  &.hide-form {
22
28
  .picture-details-overlay {
23
- right: -$image-overlay-form-width;
29
+ right: calc(-1 * var(--image-overlay-form-width));
24
30
  }
25
31
 
26
32
  .zoomed-picture-background {
@@ -28,11 +34,11 @@ $image-overlay-transition-easing: ease-in;
28
34
  }
29
35
 
30
36
  .alchemy-image-overlay-close {
31
- right: $picture-overlay-handle-width + var(--spacing-2);
37
+ right: calc(var(--picture-overlay-handle-width) + var(--spacing-2));
32
38
  }
33
39
 
34
40
  .next-picture {
35
- right: $picture-overlay-handle-width;
41
+ right: var(--picture-overlay-handle-width);
36
42
  }
37
43
 
38
44
  .picture-overlay-handle {
@@ -64,17 +70,18 @@ $image-overlay-transition-easing: ease-in;
64
70
  height: 32px;
65
71
  top: var(--spacing-2);
66
72
  right: calc(
67
- var(--spacing-2) + #{$picture-overlay-handle-width} + #{$image-overlay-form-width}
73
+ var(--spacing-2) + var(--picture-overlay-handle-width) +
74
+ var(--image-overlay-form-width)
68
75
  );
69
76
  cursor: pointer;
70
- transition: right $image-overlay-transition-duration
71
- $image-overlay-transition-easing;
77
+ transition: right var(--image-overlay-transition-duration)
78
+ var(--image-overlay-transition-easing);
72
79
 
73
80
  .icon {
74
81
  font-size: 2em;
75
82
  color: var(--color-grey_light);
76
83
  text-shadow: 0 0 4px var(--color-text);
77
- transition: color $image-overlay-transition-duration linear;
84
+ transition: color var(--image-overlay-transition-duration) linear;
78
85
 
79
86
  &:hover {
80
87
  color: var(--color-white);
@@ -88,13 +95,13 @@ $image-overlay-transition-easing: ease-in;
88
95
  top: 0;
89
96
  background-color: var(--color-grey_light);
90
97
  box-shadow: -2px 0 4px -2px var(--color-text);
91
- transition: right $image-overlay-transition-duration
92
- $image-overlay-transition-easing;
98
+ transition: right var(--image-overlay-transition-duration)
99
+ var(--image-overlay-transition-easing);
93
100
  }
94
101
 
95
102
  .picture-details-overlay {
96
103
  right: 0;
97
- width: $image-overlay-form-width;
104
+ width: var(--image-overlay-form-width);
98
105
  height: 100%;
99
106
  padding: var(--spacing-2) var(--spacing-4) var(--spacing-2) var(--spacing-1);
100
107
  overflow: auto;
@@ -117,9 +124,9 @@ $image-overlay-transition-easing: ease-in;
117
124
  }
118
125
 
119
126
  .picture-overlay-handle {
120
- width: $picture-overlay-handle-width;
127
+ width: var(--picture-overlay-handle-width);
121
128
  height: 100%;
122
- right: $image-overlay-form-width;
129
+ right: var(--image-overlay-form-width);
123
130
  cursor: pointer;
124
131
 
125
132
  .icon {
@@ -129,8 +136,8 @@ $image-overlay-transition-easing: ease-in;
129
136
  transform: translate(-50%, -50%);
130
137
  font-size: 1.2em;
131
138
  color: var(--color-icon);
132
- transition: transform $image-overlay-transition-duration
133
- $image-overlay-transition-easing;
139
+ transition: transform var(--image-overlay-transition-duration)
140
+ var(--image-overlay-transition-easing);
134
141
  }
135
142
 
136
143
  &:hover {
@@ -143,15 +150,16 @@ $image-overlay-transition-easing: ease-in;
143
150
  height: 100%;
144
151
  padding-top: var(--spacing-2);
145
152
  padding-right: calc(
146
- #{$image-overlay-form-width} + var(--spacing-2) + #{$picture-overlay-handle-width}
153
+ var(--image-overlay-form-width) + var(--spacing-2) +
154
+ var(--picture-overlay-handle-width)
147
155
  );
148
156
  padding-bottom: var(--spacing-2);
149
157
  padding-left: var(--spacing-2);
150
158
  margin: 0 auto;
151
159
  text-align: center;
152
160
  cursor: pointer;
153
- transition: padding-right $image-overlay-transition-duration
154
- $image-overlay-transition-easing;
161
+ transition: padding-right var(--image-overlay-transition-duration)
162
+ var(--image-overlay-transition-easing);
155
163
 
156
164
  &:before {
157
165
  content: "";
@@ -233,7 +241,7 @@ $image-overlay-transition-easing: ease-in;
233
241
  justify-content: center;
234
242
  align-items: center;
235
243
  text-decoration: none;
236
- transition: background-color $image-overlay-transition-duration linear;
244
+ transition: background-color var(--image-overlay-transition-duration) linear;
237
245
 
238
246
  .icon {
239
247
  width: 32px;
@@ -246,16 +254,20 @@ $image-overlay-transition-easing: ease-in;
246
254
  background-color: rgba(0, 0, 0, 0.3);
247
255
 
248
256
  .icon {
249
- transition: fill $image-overlay-transition-duration linear;
257
+ transition: fill var(--image-overlay-transition-duration) linear;
250
258
  }
251
259
  }
252
260
  }
253
261
 
262
+ .next-picture {
263
+ transition-property: background-color, right;
264
+ }
265
+
254
266
  .icon {
255
267
  --icon-size: 4em;
256
268
  fill: var(--color-grey_light);
257
269
  filter: drop-shadow(0 0 4px var(--color-icon));
258
- transition: all $image-overlay-transition-duration linear;
270
+ transition: all var(--image-overlay-transition-duration) linear;
259
271
  vertical-align: middle;
260
272
  }
261
273
 
@@ -265,7 +277,9 @@ $image-overlay-transition-easing: ease-in;
265
277
  }
266
278
 
267
279
  .next-picture {
268
- right: $image-overlay-form-width + $picture-overlay-handle-width;
280
+ right: calc(
281
+ var(--image-overlay-form-width) + var(--picture-overlay-handle-width)
282
+ );
269
283
  @include border-left-radius($default-border-radius);
270
284
  }
271
285
  }
@@ -193,6 +193,14 @@
193
193
  }
194
194
  }
195
195
 
196
+ :root {
197
+ --main-menu-width: #{$main-menu-width};
198
+ }
199
+
200
+ .collapsed-menu {
201
+ --main-menu-width: #{$collapsed-main-menu-width};
202
+ }
203
+
196
204
  @media screen and (min-width: $large-screen-break-point) {
197
205
  body:not(.collapsed-menu) {
198
206
  .sub_navigation {
@@ -222,7 +230,7 @@
222
230
  display: flex;
223
231
  flex-grow: 1;
224
232
  flex-wrap: nowrap;
225
- height: $header-height;
233
+ height: $header-height + 1px;
226
234
 
227
235
  label {
228
236
  float: left;
@@ -1,28 +1,33 @@
1
+ :root {
2
+ --preview-window-min-width: 0px;
3
+
4
+ @media screen and (min-width: $large-screen-break-point) {
5
+ --preview-window-min-width: 240px;
6
+ }
7
+ }
8
+
1
9
  #alchemy_preview_window {
2
10
  position: absolute;
3
- left: $main-menu-width;
11
+ left: var(--main-menu-width);
4
12
  top: 75px;
5
- width: calc(100vw - #{$main-menu-width - $default-border-width});
13
+ width: calc(
14
+ 100vw - var(--main-menu-width) - var(--elements-window-width) +
15
+ var(--border-width_small)
16
+ );
17
+ min-width: var(--preview-window-min-width);
18
+ max-width: calc(100vw - var(--main-menu-width));
6
19
  height: calc(100vh - #{$top-menu-height});
7
20
  border: 0 none;
8
21
  background: #fff;
9
22
  border-right: $default-border;
10
- transition: width $transition-duration ease-in-out;
23
+ transition-duration: $transition-duration;
24
+ transition-timing-function: ease-in-out;
25
+ transition-property: width max-width;
11
26
 
12
- .collapsed-menu & {
13
- left: $collapsed-main-menu-width;
14
- width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width});
15
- }
16
-
17
- .collapsed-menu.elements-window-visible & {
18
- width: calc(
19
- 100vw - #{$collapsed-main-menu-width - $default-border-width} - #{$elements-window-min-width}
27
+ .elements-window-visible & {
28
+ max-width: calc(
29
+ 100vw - var(--main-menu-width) - var(--elements-window-min-width) +
30
+ var(--border-width_small)
20
31
  );
21
-
22
- @media screen and (min-width: 1777px) {
23
- width: calc(
24
- 100vw - #{$collapsed-main-menu-width - $default-border-width} - #{$elements-window-width}
25
- );
26
- }
27
32
  }
28
33
  }
@@ -39,4 +39,4 @@
39
39
  @import "alchemy/admin/toolbar";
40
40
  @import "alchemy/admin/typography";
41
41
  @import "alchemy/admin/upload";
42
- @import "jquery.Jcrop.min";
42
+ @import "cropper.min";
@@ -32,8 +32,9 @@
32
32
  --font-weigth_normal: 500;
33
33
  --font-weigth_bold: 700;
34
34
 
35
- /* border-radius */
35
+ /* Borders */
36
36
  --border-radius_medium: 3px;
37
+ --border-width_small: 1px;
37
38
 
38
39
  /* Colors */
39
40
  --color-blue_very_light: hsl(203deg, 32%, 97%);
@@ -1,6 +1,8 @@
1
1
  module Alchemy
2
2
  module Ingredients
3
3
  class LinkView < BaseView
4
+ include LinkTarget
5
+
4
6
  attr_reader :link_text
5
7
 
6
8
  # @param ingredient [Alchemy::Ingredient]
@@ -12,7 +14,11 @@ module Alchemy
12
14
  end
13
15
 
14
16
  def call
15
- link_to(link_text, value, {target: ingredient.link_target.presence}.merge(html_options)).html_safe
17
+ target = ingredient.link_target.presence
18
+ link_to(link_text, value, {
19
+ target: link_target_value(target),
20
+ rel: link_rel_value(target)
21
+ }.merge(html_options)).html_safe
16
22
  end
17
23
  end
18
24
  end
@@ -4,6 +4,8 @@ module Alchemy
4
4
  module Ingredients
5
5
  # Renders a picture ingredient view
6
6
  class PictureView < BaseView
7
+ include LinkTarget
8
+
7
9
  attr_reader :ingredient,
8
10
  :show_caption,
9
11
  :disable_link,
@@ -46,10 +48,11 @@ module Alchemy
46
48
  output = caption ? img_tag + caption : img_tag
47
49
 
48
50
  if is_linked?
51
+ target = ingredient.link_target.presence
49
52
  output = link_to(output, url_for(ingredient.link), {
50
53
  title: ingredient.link_title.presence,
51
- target: (ingredient.link_target == "blank") ? "_blank" : nil,
52
- data: {link_target: ingredient.link_target.presence}
54
+ rel: link_rel_value(target),
55
+ target: link_target_value(target)
53
56
  })
54
57
  end
55
58
 
@@ -1,6 +1,8 @@
1
1
  module Alchemy
2
2
  module Ingredients
3
3
  class TextView < BaseView
4
+ include LinkTarget
5
+
4
6
  attr_reader :disable_link
5
7
 
6
8
  delegate :dom_id, :link, :link_title, :link_target,
@@ -21,7 +23,8 @@ module Alchemy
21
23
  link_to(value, url_for(link), {
22
24
  id: dom_id.presence,
23
25
  title: link_title,
24
- target: link_target
26
+ target: link_target_value(link_target),
27
+ rel: link_rel_value(link_target)
25
28
  }.merge(html_options))
26
29
  end.html_safe
27
30
  end
@@ -0,0 +1,18 @@
1
+ module Alchemy
2
+ module Ingredients
3
+ module LinkTarget
4
+ BLANK_VALUE = "_blank"
5
+ REL_VALUE = "noopener noreferrer"
6
+
7
+ def link_rel_value(target)
8
+ if link_target_value(target) == BLANK_VALUE
9
+ REL_VALUE
10
+ end
11
+ end
12
+
13
+ def link_target_value(target)
14
+ (target == "blank") ? BLANK_VALUE : target
15
+ end
16
+ end
17
+ end
18
+ end
@@ -31,6 +31,27 @@ module Alchemy
31
31
 
32
32
  private
33
33
 
34
+ def safe_redirect_path(path = params[:redirect_to], fallback: admin_path)
35
+ if is_safe_redirect_path?(path)
36
+ path
37
+ elsif is_safe_redirect_path?(fallback)
38
+ fallback
39
+ else
40
+ admin_path
41
+ end
42
+ end
43
+
44
+ def is_safe_redirect_path?(path)
45
+ mount_path = alchemy.root_path
46
+ path.to_s.match? %r{^#{mount_path}admin/}
47
+ end
48
+
49
+ def relative_referer_path(referer = request.referer)
50
+ return unless referer
51
+
52
+ URI(referer).path
53
+ end
54
+
34
55
  # Disable layout rendering for xhr requests.
35
56
  def set_layout
36
57
  (request.xhr? || turbo_frame_request?) ? false : "alchemy/admin"
@@ -105,15 +126,23 @@ module Alchemy
105
126
  end
106
127
  end
107
128
 
108
- # Does redirects for html and js requests
129
+ # Does redirects for html, turbo_stream and js requests
130
+ #
131
+ # Makes sure that the redirect path is safe.
109
132
  #
110
133
  def do_redirect_to(url_or_path)
134
+ redirect_path = safe_redirect_path(url_or_path)
111
135
  respond_to do |format|
112
- format.js {
113
- @redirect_url = url_or_path
136
+ format.js do
137
+ @redirect_url = redirect_path
114
138
  render :redirect
115
- }
116
- format.html { redirect_to url_or_path }
139
+ end
140
+ format.turbo_stream do
141
+ redirect_to(redirect_path, allow_other_host: false)
142
+ end
143
+ format.html do
144
+ redirect_to(redirect_path, allow_other_host: false)
145
+ end
117
146
  end
118
147
  end
119
148
 
@@ -41,12 +41,12 @@ module Alchemy
41
41
  end
42
42
  end
43
43
  if @element.save
44
- render :create
44
+ render :create, status: :created
45
45
  else
46
46
  @element.page_version = @page_version
47
47
  @elements = @page.available_element_definitions
48
48
  load_clipboard_items
49
- render :new
49
+ render :new, status: :unprocessable_entity
50
50
  end
51
51
  end
52
52
 
@@ -40,7 +40,7 @@ module Alchemy
40
40
  def switch
41
41
  @language = set_alchemy_language(params[:language_id])
42
42
  session[:alchemy_language_id] = @language.id
43
- do_redirect_to request.referer || alchemy.admin_dashboard_path
43
+ do_redirect_to relative_referer_path || alchemy.admin_dashboard_path
44
44
  end
45
45
 
46
46
  private
@@ -22,6 +22,7 @@ module Alchemy
22
22
  @page = Page.find(params[:id])
23
23
  if @page.update(page_params)
24
24
  @notice = Alchemy.t("Page saved", name: @page.name)
25
+ @while_page_edit = request.referer.include?("edit")
25
26
  render "alchemy/admin/pages/update"
26
27
  else
27
28
  render :edit, status: :unprocessable_entity
@@ -29,7 +29,7 @@ module Alchemy
29
29
  unless: -> { @page_root },
30
30
  only: [:index]
31
31
 
32
- before_action :set_view, only: [:index]
32
+ before_action :set_view, only: [:index, :update]
33
33
 
34
34
  before_action :set_page_version, only: [:show, :edit]
35
35
 
@@ -131,6 +131,10 @@ module Alchemy
131
131
  @notice = Alchemy.t("Page saved", name: @page.name)
132
132
  @while_page_edit = request.referer.include?("edit")
133
133
 
134
+ if @view == "list"
135
+ flash[:notice] = @notice
136
+ end
137
+
134
138
  unless @while_page_edit
135
139
  @tree = serialized_page_tree
136
140
  end
@@ -189,11 +193,7 @@ module Alchemy
189
193
  end
190
194
 
191
195
  def unlock_redirect_path
192
- if params[:redirect_to].to_s.match?(/\A\/admin\/(layout_)?pages/)
193
- params[:redirect_to]
194
- else
195
- admin_pages_path
196
- end
196
+ safe_redirect_path(fallback: admin_pages_path)
197
197
  end
198
198
 
199
199
  # Sets the page public and updates the published_at attribute that is used as cache_key
@@ -78,7 +78,7 @@ module Alchemy
78
78
  flash[:error] = resource_instance_variable.errors.full_messages.join(", ")
79
79
  end
80
80
  flash_notice_for_resource_action
81
- do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: "index"))
81
+ do_redirect_to resource_url_proxy.url_for(search_filter_params.merge(action: "index", only_path: true))
82
82
  end
83
83
 
84
84
  def resource_handler
@@ -16,7 +16,10 @@ module Alchemy
16
16
  # * html
17
17
  # * js (Tries to replace a given +container_id+ with the elements view partial content via jQuery.)
18
18
  #
19
+ # @deprecated This controller action will be removed in Alchemy 8.0.
19
20
  def show
21
+ Alchemy::Deprecation.warn "The elements#show controller action is deprecated and will be removed in Alchemy 8.0."
22
+
20
23
  @page = @element.page
21
24
  @options = params[:options]
22
25
 
@@ -16,7 +16,7 @@ module Alchemy
16
16
  def alchemy_form_for(object, *args, &block)
17
17
  options = args.extract_options!
18
18
  options[:builder] = Alchemy::Forms::Builder
19
- options[:remote] = request.xhr?
19
+ options.key?(:remote) || options[:remote] = request.xhr?
20
20
  options[:html] = {
21
21
  id: options.delete(:id),
22
22
  class: ["alchemy", options.delete(:class)].compact.join(" ")