alchemy_cms 5.2.5 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a2b93464ba8a1fb5b20e6447d23b908e7fd59e29e028f5c4ab70f536cf87c27
4
- data.tar.gz: 77c91a87955eba711b381ea272e9bc982d3f744869cbfeefc17fb7d9be022e97
3
+ metadata.gz: b27eb5b4a0bdb143597a3e2cc33d0a8bfa4dd0d8febb2fd9261f2ad692716bc8
4
+ data.tar.gz: 7c67ed54f66b70419ac653421bdf3f8d38389bd3e67acfe70675bfcc8b357abd
5
5
  SHA512:
6
- metadata.gz: 5cb3dd94c9170ad15e0ccb1ac780f8503d27b5deb5cab962f1170f1303dc226f492bf8e8cc8e40661847e71c5349fb4bfff373094e3f04c92b43c1db2b12be6a
7
- data.tar.gz: ed77728e349bf1612ca18616e5bdaed478519e83a3d990238b0b227cf4668d3553f2bdd30856a36c490053d94c347c86e7261e309a481bbc676febad5fba4c2f
6
+ metadata.gz: aad3d674038b933605c7c8b5d11d74c707ed2687f9a5350e7762d56882a8994f85ea0a7e86e60ed4ee6cddb32aa46ecde70c42bcf91ceca62ad00e071195c497
7
+ data.tar.gz: c2f4a299345aafda98c2d40465ab8e011ee5d345104d32342503197afedccc4ed7bc527cce09afe8163dad148fe2019f3a2e18b0a0770eb48d971c9df51239dc
data/.gitignore CHANGED
@@ -34,7 +34,6 @@ spec/dummy/uploads/
34
34
  .ruby-gemset
35
35
  .ruby-version
36
36
  .env
37
- .rspec
38
37
  node_modules
39
38
  yarn-error.log
40
39
  yarn-debug.log*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 5.2.7 (2022-03-01)
2
+
3
+ - Fix copying page with descendants to a different language ([dbwinger](https://github.com/dbwinger))
4
+ - Handle copying/pasting global pages ([dbwinger](https://github.com/dbwinger))
5
+
6
+ ## 5.2.6 (2022-02-28)
7
+
8
+ - Add crop_resize Dragonfly processor ([tvdeyen](https://github.com/tvdeyen))
9
+
1
10
  ## 5.2.5 (2021-11-24)
2
11
 
3
12
  - Adjust tinymce skin assets urls again ([tvdeyen](https://github.com/tvdeyen))
data/Rakefile CHANGED
@@ -41,12 +41,16 @@ namespace :alchemy do
41
41
  task :prepare do
42
42
  system(
43
43
  <<~BASH
44
+ yarn install && \
45
+ yarn link && \
44
46
  cd spec/dummy && \
45
47
  export RAILS_ENV=test && \
46
48
  bin/rake db:create && \
47
49
  bin/rake db:environment:set && \
48
50
  bin/rake db:migrate:reset && \
49
51
  bin/rails g alchemy:install --skip --skip-demo-files --auto-accept && \
52
+ yarn link @alchemy_cms/admin && \
53
+ RAILS_ENV=test bin/webpack && \
50
54
  cd -
51
55
  BASH
52
56
  ) || fail
@@ -72,4 +76,18 @@ namespace :alchemy do
72
76
  File.delete(backup)
73
77
  end
74
78
  end
79
+
80
+ desc "Release a new Ruby gem and npm package in one command"
81
+ task :release do
82
+ require "json"
83
+ require_relative "lib/alchemy/version"
84
+ package = File.read("package.json")
85
+ unless JSON.parse(package)["version"] == Alchemy.version
86
+ abort "Ruby gem and npm package versions are out of sync! Please fix."
87
+ end
88
+ # Release the Ruby gem with bundler
89
+ Rake::Task["release"].invoke
90
+ # Publish npm package via CLI
91
+ system "npm publish"
92
+ end
75
93
  end
data/alchemy_cms.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |gem|
23
23
  gem.add_runtime_dependency 'awesome_nested_set', ['~> 3.1']
24
24
  gem.add_runtime_dependency 'cancancan', ['>= 2.1', '< 4.0']
25
25
  gem.add_runtime_dependency 'coffee-rails', ['>= 4.0', '< 6.0']
26
- gem.add_runtime_dependency 'dragonfly', ['~> 1.0', '>= 1.0.7', '< 1.4']
26
+ gem.add_runtime_dependency 'dragonfly', ['~> 1.4']
27
27
  gem.add_runtime_dependency 'dragonfly_svg', ['~> 0.0.4']
28
28
  gem.add_runtime_dependency 'gutentag', ['~> 2.2', '>= 2.2.1']
29
29
  gem.add_runtime_dependency 'handlebars_assets', ['~> 0.23']
@@ -33,7 +33,7 @@ Gem::Specification.new do |gem|
33
33
  gem.add_runtime_dependency 'originator', ['~> 3.1']
34
34
  gem.add_runtime_dependency 'non-stupid-digest-assets', ['~> 1.0.8']
35
35
  gem.add_runtime_dependency 'rails', ['>= 5.2.0', '< 6.1']
36
- gem.add_runtime_dependency 'ransack', ['>= 1.8', '< 2.4.2'] # 2.4.2 dropped Ruby 2.5 support in a patch level release
36
+ gem.add_runtime_dependency 'ransack', ['>= 1.8', '< 3.0']
37
37
  gem.add_runtime_dependency 'request_store', ['~> 1.2']
38
38
  gem.add_runtime_dependency 'responders', ['>= 2.0', '< 4.0']
39
39
  gem.add_runtime_dependency 'sassc-rails', ['~> 2.1']
@@ -40,7 +40,6 @@
40
40
  //= require alchemy/alchemy.page_sorter
41
41
  //= require alchemy/alchemy.uploader
42
42
  //= require alchemy/alchemy.preview_window
43
- //= require alchemy/alchemy.sitemap
44
43
  //= require alchemy/alchemy.spinner
45
44
  //= require alchemy/alchemy.tinymce
46
45
  //= require alchemy/alchemy.tooltips
@@ -82,7 +82,12 @@ class window.Alchemy.Dialog
82
82
  @dialog_body.hide()
83
83
  @dialog_body.html(data)
84
84
  @init()
85
- @dialog.trigger('DialogReady.Alchemy', @dialog_body)
85
+ @dialog[0].dispatchEvent(new CustomEvent(
86
+ "DialogReady.Alchemy",
87
+ bubbles: true
88
+ detail:
89
+ body: @dialog_body[0]
90
+ ))
86
91
  if @options.ready?
87
92
  @options.ready(@dialog_body)
88
93
  @dialog_body.show('fade', 200)
@@ -1,9 +1,11 @@
1
- $.fn.alchemyPageSelect = function(options) {
1
+ $.fn.alchemyPageSelect = function (options) {
2
2
  var pageTemplate = HandlebarsTemplates.page
3
3
 
4
4
  return this.select2({
5
5
  placeholder: options.placeholder,
6
- allowClear: true,
6
+ allowClear: options.hasOwnProperty("allowClear")
7
+ ? options.allowClear
8
+ : true,
7
9
  minimumInputLength: 3,
8
10
  initSelection: function (_$el, callback) {
9
11
  if (options.initialSelection) {
@@ -12,13 +14,16 @@ $.fn.alchemyPageSelect = function(options) {
12
14
  },
13
15
  ajax: {
14
16
  url: options.url,
15
- datatype: 'json',
17
+ datatype: "json",
16
18
  quietMillis: 300,
17
19
  data: function (term, page) {
18
20
  return {
19
- q: $.extend({
20
- name_cont: term
21
- }, options.query_params),
21
+ q: $.extend(
22
+ {
23
+ name_cont: term
24
+ },
25
+ options.query_params
26
+ ),
22
27
  page: page
23
28
  }
24
29
  },
@@ -34,8 +39,8 @@ $.fn.alchemyPageSelect = function(options) {
34
39
  formatSelection: function (page) {
35
40
  return page.text || page.name
36
41
  },
37
- formatResult: function(page) {
38
- return pageTemplate({page: page})
42
+ formatResult: function (page) {
43
+ return pageTemplate({ page: page })
39
44
  }
40
45
  })
41
46
  }
@@ -23,7 +23,7 @@ $sitemap-url-xlarge-width: 350px;
23
23
 
24
24
  #sitemap-wrapper {
25
25
  position: relative;
26
- min-height: calc(100vh - 96px);
26
+ min-height: calc(100vh - 148px);
27
27
  }
28
28
 
29
29
  .sitemap_pagename_link {
@@ -154,6 +154,8 @@ $sitemap-url-xlarge-width: 350px;
154
154
  position: absolute;
155
155
  left: -23px;
156
156
  top: 0;
157
+ width: 16px;
158
+ height: $sitemap-line-height;
157
159
  }
158
160
 
159
161
  .placeholder {
@@ -258,3 +260,7 @@ $sitemap-url-xlarge-width: 350px;
258
260
  }
259
261
  }
260
262
  }
263
+
264
+ #search_field_clear {
265
+ cursor: pointer;
266
+ }
@@ -40,9 +40,7 @@ module Alchemy
40
40
  def exception_handler(error)
41
41
  exception_logger(error)
42
42
  show_error_notice(error)
43
- if defined?(Airbrake)
44
- notify_airbrake(error) unless Rails.env.development? || Rails.env.test?
45
- end
43
+ notify_error_tracker(error)
46
44
  end
47
45
 
48
46
  # Displays an error notice in the Alchemy backend.
@@ -148,6 +146,14 @@ module Alchemy
148
146
  site
149
147
  end
150
148
  end
149
+
150
+ def notify_error_tracker(exception)
151
+ if ::Alchemy::ErrorTracking.notification_handler.respond_to?(:call)
152
+ ::Alchemy::ErrorTracking.notification_handler.call(exception)
153
+ else
154
+ Rails.logger.warn("To use the Alchemy::ErrorTracking.notification_handler, it must respond to #call.")
155
+ end
156
+ end
151
157
  end
152
158
  end
153
159
  end
@@ -128,6 +128,7 @@ module Alchemy
128
128
  def update
129
129
  # stores old page_layout value, because unfurtunally rails @page.changes does not work here.
130
130
  @old_page_layout = @page.page_layout
131
+ @old_parent_id = @page.parent_id
131
132
  if @page.update(page_params)
132
133
  @notice = Alchemy.t("Page saved", name: @page.name)
133
134
  @while_page_edit = request.referer.include?("edit")
@@ -136,7 +137,7 @@ module Alchemy
136
137
  @tree = serialized_page_tree
137
138
  end
138
139
  else
139
- configure
140
+ render :configure
140
141
  end
141
142
  end
142
143
 
@@ -217,8 +217,8 @@ module Alchemy
217
217
 
218
218
  def copy_and_paste(source, new_parent, new_name)
219
219
  page = copy(source, {
220
- parent_id: new_parent.id,
221
- language: new_parent.language,
220
+ parent: new_parent,
221
+ language: new_parent&.language,
222
222
  name: new_name,
223
223
  title: new_name,
224
224
  })
@@ -415,6 +415,7 @@ module Alchemy
415
415
  next if child == new_parent
416
416
 
417
417
  new_child = Page.copy(child, {
418
+ parent_id: new_parent.id,
418
419
  language_id: new_parent.language_id,
419
420
  language_code: new_parent.language_code,
420
421
  })
@@ -197,12 +197,12 @@ module Alchemy
197
197
  # Use imagemagick to custom crop an image. Uses -thumbnail for better performance when resizing.
198
198
  #
199
199
  def xy_crop_resize(dimensions, top_left, crop_dimensions, upsample)
200
- crop_argument = "-crop #{dimensions_to_string(crop_dimensions)}"
200
+ crop_argument = dimensions_to_string(crop_dimensions)
201
201
  crop_argument += "+#{top_left[:x]}+#{top_left[:y]}"
202
202
 
203
- resize_argument = "-resize #{dimensions_to_string(dimensions)}"
203
+ resize_argument = dimensions_to_string(dimensions)
204
204
  resize_argument += ">" unless upsample
205
- image_file.convert "#{crop_argument} #{resize_argument}"
205
+ image_file.crop_resize(crop_argument, resize_argument)
206
206
  end
207
207
 
208
208
  # Used when centercropping.
@@ -1,4 +1,8 @@
1
1
  <%= alchemy_form_for [:admin, @page], class: 'edit_page' do |f| %>
2
+ <% unless @page.language_root? || @page.layoutpage %>
3
+ <%= f.input :parent_id, required: true, input_html: { class: 'alchemy_selectbox' } %>
4
+ <% end %>
5
+
2
6
  <%= f.input :page_layout,
3
7
  collection: @page_layouts,
4
8
  label: page_layout_label(@page),
@@ -43,3 +47,18 @@
43
47
 
44
48
  <%= f.submit Alchemy.t(:save) %>
45
49
  <% end %>
50
+
51
+ <script>
52
+ $('#page_parent_id').alchemyPageSelect({
53
+ placeholder: "<%= Alchemy.t(:search_page) %>",
54
+ url: "<%= alchemy.api_pages_path %>",
55
+ allowClear: false,
56
+ <% if @page.parent %>
57
+ initialSelection: {
58
+ id: <%= @page.parent.id %>,
59
+ text: "<%= @page.parent.name %>",
60
+ url_path: "<%= @page.parent.url_path %>"
61
+ }
62
+ <% end %>
63
+ })
64
+ </script>
@@ -3,11 +3,7 @@
3
3
  <%= f.hidden_field(:parent_id) %>
4
4
  <% else %>
5
5
  <% @page.parent = @current_language.root_page %>
6
- <%= f.input :parent_id,
7
- collection: @current_language.pages.contentpages,
8
- label_method: :name,
9
- value_method: :id,
10
- input_html: { class: "alchemy_selectbox" } %>
6
+ <%= f.input :parent_id, as: :string, input_html: { class: 'alchemy_selectbox' } %>
11
7
  <% end %>
12
8
  <%= f.hidden_field(:language_id) %>
13
9
  <%= f.hidden_field(:layoutpage) %>
@@ -21,3 +17,18 @@
21
17
  <%= f.input :name %>
22
18
  <%= f.submit Alchemy.t(:create) %>
23
19
  <% end %>
20
+
21
+ <script>
22
+ $('input[type="text"]#page_parent_id').alchemyPageSelect({
23
+ placeholder: "<%= Alchemy.t(:search_page) %>",
24
+ url: "<%= alchemy.api_pages_path %>",
25
+ allowClear: false,
26
+ <% if @page.parent %>
27
+ initialSelection: {
28
+ id: <%= @page.parent.id %>,
29
+ text: "<%= @page.parent.name %>",
30
+ url_path: "<%= @page.parent.url_path %>"
31
+ }
32
+ <% end %>
33
+ })
34
+ </script>
@@ -1,11 +1,10 @@
1
- <div id="sitemap-wrapper">
2
- <h4 id="sitemap_heading">
3
- <span class="page_name"><%= Alchemy::Page.human_attribute_name(:name) %></span>
4
- <span class="page_urlname"><%= Alchemy::Page.human_attribute_name(:urlname) %></span>
5
- <span class="page_status"><%= Alchemy.t(:page_status) %></span>
6
- </h4>
1
+ <h4 id="sitemap_heading">
2
+ <span class="page_name"><%= Alchemy::Page.human_attribute_name(:name) %></span>
3
+ <span class="page_urlname"><%= Alchemy::Page.human_attribute_name(:urlname) %></span>
4
+ <span class="page_status"><%= Alchemy.t(:page_status) %></span>
5
+ </h4>
7
6
 
8
- <p class="loading"></p>
7
+ <div id="sitemap-wrapper">
9
8
  </div>
10
9
 
11
10
  <script id="sitemap-template" type="text/x-handlebars-template">
@@ -22,7 +21,7 @@
22
21
 
23
22
  <script type="text/javascript">
24
23
  $(function() {
25
- Alchemy.Sitemap.init({
24
+ Alchemy.currentSitemap = new Alchemy.Sitemap({
26
25
  url: '<%= alchemy.tree_admin_pages_path %>',
27
26
  page_root_id: <%= @page_root.id %>,
28
27
  full: <%= full %>
@@ -32,5 +31,6 @@
32
31
  }
33
32
  <% end %>
34
33
  });
34
+ Alchemy.PagePublicationFields();
35
35
  });
36
36
  </script>
@@ -133,7 +133,7 @@
133
133
  if (!not_dirty) Alchemy.pleaseWaitOverlay(false);
134
134
  return not_dirty;
135
135
  });
136
- Alchemy.Sitemap.watchPagePublicationState();
136
+ Alchemy.PagePublicationFields();
137
137
  Alchemy.PageLeaveObserver();
138
138
  Alchemy.ElementsWindow.init('<%= alchemy.admin_elements_path(page_id: @page.id) %>', {
139
139
  texts: {
@@ -1,2 +1,2 @@
1
1
  $('#fold_button_<%= @page.id %>').css('background', 'none');
2
- Alchemy.Sitemap.fetch(<%= @page.id %>);
2
+ Alchemy.currentSitemap.reload(<%= @page.id %>);
@@ -13,6 +13,13 @@
13
13
  Alchemy.growl("<%= j @notice %>");
14
14
  Alchemy.closeCurrentDialog();
15
15
 
16
+ <% elsif @page.parent_id != @old_parent_id -%>
17
+
18
+ Alchemy.closeCurrentDialog(function() {
19
+ Alchemy.growl("<%= j @notice %>");
20
+ Alchemy.currentSitemap.load(<%= @page.get_language_root.id %>);
21
+ });
22
+
16
23
  <% else -%>
17
24
 
18
25
  if (page) {
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "dragonfly_svg"
3
+ require "alchemy/dragonfly/processors/crop_resize"
3
4
 
4
5
  # Logger
5
6
  Dragonfly.logger = Rails.logger
@@ -9,3 +10,10 @@ if defined?(ActiveRecord::Base)
9
10
  ActiveRecord::Base.extend Dragonfly::Model
10
11
  ActiveRecord::Base.extend Dragonfly::Model::Validations
11
12
  end
13
+
14
+ # Dragonfly 1.4.0 only allows `quality` as argument to `encode`
15
+ Dragonfly::ImageMagick::Processors::Encode::WHITELISTED_ARGS << "flatten"
16
+
17
+ Rails.application.config.after_initialize do
18
+ Dragonfly.app(:alchemy_pictures).add_processor(:crop_resize, Alchemy::Dragonfly::Processors::CropResize.new)
19
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dragonfly/image_magick/commands"
4
+
5
+ module Alchemy
6
+ module Dragonfly
7
+ module Processors
8
+ class CropResize
9
+ include ::Dragonfly::ParamValidators
10
+
11
+ IS_CROP_ARGUMENT = ->(args_string) {
12
+ args_string.match?(::Dragonfly::ImageMagick::Processors::Thumb::CROP_GEOMETRY)
13
+ }
14
+
15
+ IS_RESIZE_ARGUMENT = ->(args_string) {
16
+ args_string.match?(::Dragonfly::ImageMagick::Processors::Thumb::RESIZE_GEOMETRY)
17
+ }
18
+
19
+ def call(content, crop_argument, resize_argument)
20
+ validate!(crop_argument, &IS_CROP_ARGUMENT)
21
+ validate!(resize_argument, &IS_RESIZE_ARGUMENT)
22
+ ::Dragonfly::ImageMagick::Commands.convert(
23
+ content,
24
+ "-crop #{crop_argument} -resize #{resize_argument}"
25
+ )
26
+ end
27
+
28
+ def update_url(attrs, _args = "", opts = {})
29
+ format = opts["format"]
30
+ attrs.ext = format if format
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -44,5 +44,12 @@ module Alchemy
44
44
  end
45
45
  end
46
46
  end
47
+
48
+ initializer "alchemy.error_tracking" do
49
+ if defined?(Airbrake)
50
+ require_relative "error_tracking/airbrake_handler"
51
+ Alchemy::ErrorTracking.notification_handler = Alchemy::ErrorTracking::AirbrakeHandler
52
+ end
53
+ end
47
54
  end
48
55
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module ErrorTracking
5
+ class AirbrakeHandler < BaseHandler
6
+ def self.call(exception)
7
+ return if ["development", "test"].include?(Rails.env)
8
+
9
+ notify_airbrake(exception)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ module ErrorTracking
5
+ class BaseHandler
6
+ def self.call(exception)
7
+ # implement your own notification method
8
+ end
9
+ end
10
+
11
+ mattr_accessor :notification_handler
12
+ @@notification_handler = BaseHandler
13
+ end
14
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Alchemy
4
- VERSION = "5.2.5"
4
+ VERSION = "5.3.0"
5
5
 
6
6
  def self.version
7
7
  VERSION
data/lib/alchemy_cms.rb CHANGED
@@ -37,6 +37,7 @@ require_relative "alchemy/controller_actions"
37
37
  require_relative "alchemy/deprecation"
38
38
  require_relative "alchemy/element_definition"
39
39
  require_relative "alchemy/elements_finder"
40
+ require_relative "alchemy/error_tracking"
40
41
  require_relative "alchemy/errors"
41
42
  require_relative "alchemy/essence"
42
43
  require_relative "alchemy/filetypes"
data/package/admin.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import translate from "./src/i18n"
2
2
  import translationData from "./src/translations"
3
3
  import NodeTree from "./src/node_tree"
4
+ import Sitemap from "./src/sitemap"
5
+ import PagePublicationFields from "./src/page_publication_fields.js"
4
6
 
5
7
  // Global Alchemy object
6
8
  if (typeof window.Alchemy === "undefined") {
@@ -12,5 +14,7 @@ Object.assign(Alchemy, {
12
14
  // Global utility method for translating a given string
13
15
  t: translate,
14
16
  translations: Object.assign(Alchemy.translations || {}, translationData),
15
- NodeTree
17
+ NodeTree,
18
+ Sitemap,
19
+ PagePublicationFields
16
20
  })
@@ -0,0 +1,27 @@
1
+ // Handles the page publication date fields
2
+ export default function () {
3
+ document.addEventListener("DialogReady.Alchemy", function (evt) {
4
+ const dialog = evt.detail.body
5
+ const public_on_field = dialog.querySelector("#page_public_on")
6
+ const public_until_field = dialog.querySelector("#page_public_until")
7
+ const publication_date_fields = dialog.querySelector(
8
+ ".page-publication-date-fields"
9
+ )
10
+
11
+ dialog
12
+ .querySelector("#page_public")
13
+ .addEventListener("click", function (evt) {
14
+ const checkbox = evt.target
15
+ const now = new Date()
16
+
17
+ if (checkbox.checked) {
18
+ publication_date_fields.classList.remove("hidden")
19
+ public_on_field._flatpickr.setDate(now)
20
+ } else {
21
+ publication_date_fields.classList.add("hidden")
22
+ public_on_field.value = ""
23
+ }
24
+ public_until_field.value = ""
25
+ })
26
+ })
27
+ }
@@ -0,0 +1,133 @@
1
+ // The admin sitemap Alchemy class
2
+
3
+ export default class Sitemap {
4
+ // Storing some objects.
5
+ constructor(options) {
6
+ const list_template_regexp = new RegExp("/" + options.page_root_id, "g")
7
+ const list_template_html = document
8
+ .getElementById("sitemap-list")
9
+ .innerHTML.replace(list_template_regexp, "/{{id}}")
10
+ this.search_field = document.querySelector(".search_input_field")
11
+ this.filter_field_clear = document.querySelector(".search_field_clear")
12
+ this.filter_field_clear.removeAttribute("href")
13
+ this.display = document.getElementById("page_filter_result")
14
+ this.sitemap_wrapper = document.getElementById("sitemap-wrapper")
15
+ this.template = Handlebars.compile(
16
+ document.getElementById("sitemap-template").innerHTML
17
+ )
18
+ this.list_template = Handlebars.compile(list_template_html)
19
+ this.items = null
20
+ this.options = options
21
+ Handlebars.registerPartial("list", list_template_html)
22
+ this.load(options.page_root_id)
23
+ }
24
+
25
+ // Loads the sitemap
26
+ load(pageId) {
27
+ const spinner = this.options.spinner || new Alchemy.Spinner("medium")
28
+ const spinTarget = this.sitemap_wrapper
29
+ spinTarget.innerHTML = ""
30
+ spinner.spin(spinTarget)
31
+ this.fetch(
32
+ `${this.options.url}?id=${pageId}&full=${this.options.full}`
33
+ ).then(async (response) => {
34
+ this.render(await response.json())
35
+ spinner.stop()
36
+ })
37
+ }
38
+
39
+ // Reload the sitemap for a specific branch
40
+ reload(pageId) {
41
+ const spinner = new Alchemy.Spinner("small")
42
+ const spinTarget = document.getElementById(`fold_button_${pageId}`)
43
+ spinTarget.querySelector(".far").remove()
44
+ spinner.spin(spinTarget)
45
+ this.fetch(`${this.options.url}?id=${pageId}`).then(async (response) => {
46
+ this.render(await response.json(), pageId)
47
+ spinner.stop()
48
+ })
49
+ }
50
+
51
+ fetch(url) {
52
+ return fetch(url).catch((error) => console.warn(`Request failed: ${error}`))
53
+ }
54
+
55
+ // Renders the sitemap
56
+ render(data, foldingId) {
57
+ let renderTarget, renderTemplate
58
+
59
+ if (foldingId) {
60
+ renderTarget = document.getElementById(`page_${foldingId}`)
61
+ renderTemplate = this.list_template
62
+ renderTarget.outerHTML = renderTemplate({ children: data.pages })
63
+ } else {
64
+ renderTarget = this.sitemap_wrapper
65
+ renderTemplate = this.template
66
+ renderTarget.innerHTML = renderTemplate({ children: data.pages })
67
+ }
68
+ this.items = document
69
+ .getElementById("sitemap")
70
+ .querySelectorAll(".sitemap_page")
71
+ this.sitemap_wrapper = document.getElementById("sitemap-wrapper")
72
+ this._observe()
73
+
74
+ if (this.options.ready) {
75
+ this.options.ready()
76
+ }
77
+ }
78
+
79
+ // Filters the sitemap
80
+ filter(term) {
81
+ const results = []
82
+
83
+ this.items.forEach(function (item) {
84
+ if (
85
+ term !== "" &&
86
+ item.getAttribute("name").toLowerCase().indexOf(term) !== -1
87
+ ) {
88
+ item.classList.add("highlight")
89
+ item.classList.remove("no-match")
90
+ results.push(item)
91
+ } else {
92
+ item.classList.add("no-match")
93
+ item.classList.remove("highlight")
94
+ }
95
+ })
96
+ this.filter_field_clear.style.display = "inline-block"
97
+ const { length } = results
98
+
99
+ if (length === 1) {
100
+ this.display.style.display = "block"
101
+ this.display.innerText = `1 ${Alchemy.t("page_found")}`
102
+ results[0].scrollIntoView({ behavior: "smooth", block: "center" })
103
+ } else if (length > 1) {
104
+ this.display.style.display = "block"
105
+ this.display.innerText = `${length} ${Alchemy.t("pages_found")}`
106
+ } else {
107
+ this.items.forEach((item) =>
108
+ item.classList.remove("no-match", "highlight")
109
+ )
110
+ this.display.style.display = "none"
111
+ window.scrollTo({
112
+ top: 0,
113
+ left: 0,
114
+ behavior: "smooth"
115
+ })
116
+ this.filter_field_clear.style.display = "none"
117
+ }
118
+ }
119
+
120
+ // Adds onkey up observer to search field
121
+ _observe() {
122
+ this.search_field.addEventListener("keyup", (evt) => {
123
+ const term = evt.target.value
124
+ this.filter(term.toLowerCase())
125
+ })
126
+ this.search_field.addEventListener("focus", () => key.setScope("search"))
127
+ this.filter_field_clear.addEventListener("click", () => {
128
+ this.search_field.value = ""
129
+ this.filter("")
130
+ return false
131
+ })
132
+ }
133
+ }
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alchemy_cms/admin",
3
- "version": "5.2.5",
3
+ "version": "5.3.0",
4
4
  "description": "AlchemyCMS",
5
5
  "browser": "package/admin.js",
6
6
  "files": [
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alchemy_cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.5
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2021-11-24 00:00:00.000000000 Z
16
+ date: 2022-03-09 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: active_model_serializers
@@ -108,12 +108,6 @@ dependencies:
108
108
  requirement: !ruby/object:Gem::Requirement
109
109
  requirements:
110
110
  - - "~>"
111
- - !ruby/object:Gem::Version
112
- version: '1.0'
113
- - - ">="
114
- - !ruby/object:Gem::Version
115
- version: 1.0.7
116
- - - "<"
117
111
  - !ruby/object:Gem::Version
118
112
  version: '1.4'
119
113
  type: :runtime
@@ -121,12 +115,6 @@ dependencies:
121
115
  version_requirements: !ruby/object:Gem::Requirement
122
116
  requirements:
123
117
  - - "~>"
124
- - !ruby/object:Gem::Version
125
- version: '1.0'
126
- - - ">="
127
- - !ruby/object:Gem::Version
128
- version: 1.0.7
129
- - - "<"
130
118
  - !ruby/object:Gem::Version
131
119
  version: '1.4'
132
120
  - !ruby/object:Gem::Dependency
@@ -282,7 +270,7 @@ dependencies:
282
270
  version: '1.8'
283
271
  - - "<"
284
272
  - !ruby/object:Gem::Version
285
- version: 2.4.2
273
+ version: '3.0'
286
274
  type: :runtime
287
275
  prerelease: false
288
276
  version_requirements: !ruby/object:Gem::Requirement
@@ -292,7 +280,7 @@ dependencies:
292
280
  version: '1.8'
293
281
  - - "<"
294
282
  - !ruby/object:Gem::Version
295
- version: 2.4.2
283
+ version: '3.0'
296
284
  - !ruby/object:Gem::Dependency
297
285
  name: request_store
298
286
  requirement: !ruby/object:Gem::Requirement
@@ -602,6 +590,7 @@ files:
602
590
  - ".hound.yml"
603
591
  - ".localeapp/config.rb"
604
592
  - ".prettierrc"
593
+ - ".rspec"
605
594
  - ".rubocop.yml"
606
595
  - ".yardopts"
607
596
  - CHANGELOG.md
@@ -647,7 +636,6 @@ files:
647
636
  - app/assets/javascripts/alchemy/alchemy.page_sorter.js
648
637
  - app/assets/javascripts/alchemy/alchemy.preview.js.coffee
649
638
  - app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee
650
- - app/assets/javascripts/alchemy/alchemy.sitemap.js.coffee
651
639
  - app/assets/javascripts/alchemy/alchemy.spinner.js
652
640
  - app/assets/javascripts/alchemy/alchemy.string_extension.js.coffee
653
641
  - app/assets/javascripts/alchemy/alchemy.tinymce.js.coffee
@@ -1092,9 +1080,12 @@ files:
1092
1080
  - lib/alchemy/configuration_methods.rb
1093
1081
  - lib/alchemy/controller_actions.rb
1094
1082
  - lib/alchemy/deprecation.rb
1083
+ - lib/alchemy/dragonfly/processors/crop_resize.rb
1095
1084
  - lib/alchemy/element_definition.rb
1096
1085
  - lib/alchemy/elements_finder.rb
1097
1086
  - lib/alchemy/engine.rb
1087
+ - lib/alchemy/error_tracking.rb
1088
+ - lib/alchemy/error_tracking/airbrake_handler.rb
1098
1089
  - lib/alchemy/errors.rb
1099
1090
  - lib/alchemy/essence.rb
1100
1091
  - lib/alchemy/filetypes.rb
@@ -1198,6 +1189,8 @@ files:
1198
1189
  - package/src/__tests__/i18n.spec.js
1199
1190
  - package/src/i18n.js
1200
1191
  - package/src/node_tree.js
1192
+ - package/src/page_publication_fields.js
1193
+ - package/src/sitemap.js
1201
1194
  - package/src/translations.js
1202
1195
  - package/src/utils/__tests__/ajax.spec.js
1203
1196
  - package/src/utils/__tests__/events.spec.js
@@ -1,119 +0,0 @@
1
- window.Alchemy = {} if typeof(window.Alchemy) is 'undefined'
2
-
3
- # The admin sitemap Alchemy module
4
- Alchemy.Sitemap =
5
-
6
- # Storing some objects.
7
- init: (options) ->
8
- @search_field = $(".search_input_field")
9
- @filter_field_clear = $('.search_field_clear')
10
- @display = $('#page_filter_result')
11
- @sitemap_wrapper = $('#sitemap-wrapper p.loading')
12
- @template = Handlebars.compile($('#sitemap-template').html())
13
- list_template_regexp = new RegExp '\/' + options.page_root_id, 'g'
14
- list_template_html = $('#sitemap-list').html().replace(list_template_regexp, '/{{id}}')
15
- @list_template = Handlebars.compile(list_template_html)
16
- @items = null
17
- @options = options
18
- @watchPagePublicationState()
19
- true
20
-
21
- Handlebars.registerPartial('list', list_template_html)
22
-
23
- @fetch()
24
-
25
- # Fetches the sitemap from JSON
26
- fetch: (foldingId) ->
27
- self = Alchemy.Sitemap
28
-
29
- if foldingId
30
- spinner = new Alchemy.Spinner('small')
31
- spinTarget = $('#fold_button_' + foldingId)
32
- renderTarget = $('#page_' + foldingId)
33
- renderTemplate = @list_template
34
- pageId = foldingId
35
- else
36
- spinner = @options.spinner || new Alchemy.Spinner('medium')
37
- spinTarget = @sitemap_wrapper
38
- renderTarget = @sitemap_wrapper
39
- renderTemplate = @template
40
- pageId = @options.page_root_id
41
-
42
- spinner.spin(spinTarget[0])
43
-
44
- request = $.ajax url: @options.url, data:
45
- id: pageId
46
- full: @options.full
47
-
48
- request.done (data) ->
49
- # This will also remove the spinner
50
- renderTarget.replaceWith(renderTemplate({children: data.pages}))
51
- self.items = $(".sitemap_page", '#sitemap')
52
- self._observe()
53
-
54
- if self.options.ready
55
- self.options.ready()
56
-
57
- request.fail (jqXHR, status) ->
58
- console.warn("Request failed: " + status)
59
-
60
- # Filters the sitemap
61
- filter: (term) ->
62
- results = []
63
- self = Alchemy.Sitemap
64
- self.items.map ->
65
- item = $(this)
66
- if term != '' && item.attr('name').toLowerCase().indexOf(term) != -1
67
- item.addClass('highlight')
68
- item.removeClass('no-match')
69
- results.push item
70
- else
71
- item.addClass('no-match')
72
- item.removeClass('highlight')
73
- self.filter_field_clear.show()
74
- length = results.length
75
- if length == 1
76
- self.display.show().text("1 #{Alchemy.t('page_found')}")
77
- $.scrollTo(results[0], {duration: 400, offset: -80})
78
- else if length > 1
79
- self.display.show().text("#{length} #{Alchemy.t('pages_found')}")
80
- else
81
- self.items.removeClass('no-match highlight')
82
- self.display.hide()
83
- $.scrollTo('0', 400)
84
- self.filter_field_clear.hide()
85
-
86
- # Adds onkey up observer to search field
87
- _observe: ->
88
- filter = @filter
89
- @search_field.on 'keyup', ->
90
- term = $(this).val()
91
- filter(term.toLowerCase())
92
- @search_field.on 'focus', ->
93
- key.setScope('search')
94
- @filter_field_clear.click =>
95
- @search_field.val('')
96
- filter('')
97
- false
98
-
99
- # Handles the page publication date fields
100
- watchPagePublicationState: ->
101
- $(document).on 'DialogReady.Alchemy', (e, $dialog) ->
102
- $public_on_field = $('#page_public_on', $dialog)
103
- $public_until_field = $('#page_public_until', $dialog)
104
- $publication_date_fields = $('.page-publication-date-fields', $dialog)
105
-
106
- $('#page_public', $dialog).click ->
107
- $checkbox = $(this)
108
- format = $checkbox.data('date-format')
109
- now = new Date()
110
- if $checkbox.is(':checked')
111
- $publication_date_fields.removeClass('hidden')
112
- $public_on_field[0]._flatpickr.setDate(now)
113
- else
114
- $publication_date_fields.addClass('hidden')
115
- $public_on_field.val('')
116
- $public_until_field.val('')
117
- true
118
-
119
- return