alchemy_cms 5.2.7 → 5.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rspec +1 -0
  4. data/CHANGELOG.md +19 -0
  5. data/Rakefile +18 -0
  6. data/alchemy_cms.gemspec +1 -1
  7. data/app/assets/javascripts/alchemy/admin.js +0 -2
  8. data/app/assets/javascripts/alchemy/alchemy.base.js.coffee +5 -2
  9. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +6 -1
  10. data/app/assets/javascripts/alchemy/page_select.js +13 -8
  11. data/app/assets/javascripts/alchemy/templates/index.js +1 -0
  12. data/app/assets/javascripts/alchemy/templates/page.hbs +17 -7
  13. data/app/assets/javascripts/alchemy/templates/page_folder.hbs +3 -0
  14. data/app/assets/stylesheets/alchemy/archive.scss +4 -0
  15. data/app/assets/stylesheets/alchemy/page-select.scss +29 -4
  16. data/app/assets/stylesheets/alchemy/sitemap.scss +9 -7
  17. data/app/controllers/alchemy/admin/pages_controller.rb +10 -13
  18. data/app/controllers/alchemy/api/pages_controller.rb +14 -4
  19. data/app/serializers/alchemy/page_serializer.rb +7 -1
  20. data/app/serializers/alchemy/page_tree_serializer.rb +3 -3
  21. data/app/views/alchemy/admin/pages/_form.html.erb +19 -0
  22. data/app/views/alchemy/admin/pages/_new_page_form.html.erb +16 -5
  23. data/app/views/alchemy/admin/pages/_page.html.erb +111 -133
  24. data/app/views/alchemy/admin/pages/_sitemap.html.erb +10 -16
  25. data/app/views/alchemy/admin/pages/_toolbar.html.erb +0 -12
  26. data/app/views/alchemy/admin/pages/edit.html.erb +1 -1
  27. data/app/views/alchemy/admin/pages/index.html.erb +1 -1
  28. data/app/views/alchemy/admin/pages/update.js.erb +7 -0
  29. data/app/views/alchemy/admin/partials/_routes.html.erb +8 -1
  30. data/app/views/alchemy/essences/_essence_page_editor.html.erb +1 -1
  31. data/config/locales/alchemy.en.yml +0 -3
  32. data/config/routes.rb +4 -2
  33. data/lib/alchemy/permissions.rb +0 -1
  34. data/lib/alchemy/version.rb +1 -1
  35. data/package/admin.js +5 -1
  36. data/package/src/node_tree.js +3 -3
  37. data/package/src/page_publication_fields.js +27 -0
  38. data/package/src/page_sorter.js +62 -0
  39. data/package/src/sitemap.js +148 -0
  40. data/package/src/utils/__tests__/ajax.spec.js +61 -15
  41. data/package/src/utils/ajax.js +12 -0
  42. data/package.json +1 -1
  43. metadata +9 -9
  44. data/app/assets/javascripts/alchemy/alchemy.page_sorter.js +0 -24
  45. data/app/assets/javascripts/alchemy/alchemy.sitemap.js.coffee +0 -119
  46. data/app/views/alchemy/admin/pages/fold.js.erb +0 -2
  47. data/app/views/alchemy/admin/pages/sort.html.erb +0 -19
  48. data/vendor/assets/javascripts/jquery_plugins/jquery.ui.nestedSortable.js +0 -434
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d356d5cf9b97a20b587b2cdc07db82751af3cf71e9ce15c88ebb969667bccb33
4
- data.tar.gz: 8af9070583ecd3cdb3431c131f88bbb1dc6acec03b44ee1a7a6feeec2ebc621c
3
+ metadata.gz: e2df8b8b8a114a3c838038e49ed3f464fdb45aca2706d7a691aa9674a6d0fceb
4
+ data.tar.gz: e94ecf5d831ada56e030d86f83e98e79dca35a9cd8638349aca7d3376ad20cd0
5
5
  SHA512:
6
- metadata.gz: 9892f2979e766cb0159cc1d547709f9afe122e0ba1fbaf4a8e4d568d5158db6819db7031132dde61f1793318207a30021a65281305770c802318b25fab05becd
7
- data.tar.gz: c2c20b120289d05b74d19c1c51168f6218e5df1c8bec0d6fc3bfead3af65d9de51c93c01bbc9cb5795f53452b5835e573397744ba81c75547ce5689a32f47fb1
6
+ metadata.gz: 24b96afb3447382b6d66cdf47be50ba012e83c86236fd9bd8b45a83b8685752c8820a938829920ba615c45217863966a7e46af07db027b04cff24ee9227879ae
7
+ data.tar.gz: e2206807c7a623254540441742d1017c40d75fabd8cc097793d040c55271f0c33a6feedee3994deb68d75dc3fddcf0f1b2a8ad98205ce254852f164877d5940a
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,22 @@
1
+ ## 5.3.2 (2022-03-24)
2
+
3
+ - ImageLoader: Add error handling ([tvdeyen](https://github.com/tvdeyen))
4
+ - Fix new Sitemap ([tvdeyen](https://github.com/tvdeyen))
5
+
6
+ ## 5.3.1 (2022-03-11)
7
+
8
+ - Allow all pages in API again ([tvdeyen](https://github.com/tvdeyen))
9
+ - New sortable page tree ([tvdeyen](https://github.com/tvdeyen))
10
+ - Send language_id to Api::PagesController#index so Pages can be restricted to the language of the page. ([dbwinger](https://github.com/dbwinger))
11
+ - Eager load in PageTree serializer ([tvdeyen](https://github.com/tvdeyen))
12
+
13
+ ## 5.3.0 (2022-03-09)
14
+
15
+ - Allow parent page change ([tvdeyen](https://github.com/tvdeyen))
16
+ - Precompile packs during test setup ([tvdeyen](https://github.com/tvdeyen))
17
+ - Rework Sitemap JS ([tvdeyen](https://github.com/tvdeyen))
18
+ - Fix rendering errors in page configure overlay ([tvdeyen](https://github.com/tvdeyen))
19
+
1
20
  ## 5.2.7 (2022-03-01)
2
21
 
3
22
  - Fix copying page with descendants to a different language ([dbwinger](https://github.com/dbwinger))
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
@@ -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']
@@ -37,10 +37,8 @@
37
37
  //= require alchemy/alchemy.link_dialog
38
38
  //= require alchemy/alchemy.list_filter
39
39
  //= require alchemy/alchemy.initializer
40
- //= require alchemy/alchemy.page_sorter
41
40
  //= require alchemy/alchemy.uploader
42
41
  //= require alchemy/alchemy.preview_window
43
- //= require alchemy/alchemy.sitemap
44
42
  //= require alchemy/alchemy.spinner
45
43
  //= require alchemy/alchemy.tinymce
46
44
  //= require alchemy/alchemy.tooltips
@@ -54,9 +54,12 @@ $.extend Alchemy,
54
54
  image.on 'load', ->
55
55
  spinner.stop()
56
56
  image.fadeIn 400
57
- image.on 'error', ->
57
+ image.on 'error', (evt) ->
58
+ message = "Could not load #{this.src}"
58
59
  spinner.stop()
59
- $parent.html('<span class="icon warn"/>')
60
+ console.error(message, evt)
61
+ $parent.html('<span class="icon fas fa-exclamation-triangle" title="' + message + '" />')
62
+ return
60
63
 
61
64
  # Removes the picture from essence picture thumbnail
62
65
  removePicture: (selector) ->
@@ -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
  }
@@ -2,3 +2,4 @@
2
2
  //= require alchemy/templates/page
3
3
  //= require alchemy/templates/node_folder
4
4
  //= require alchemy/templates/node
5
+ //= require alchemy/templates/page_folder
@@ -1,9 +1,19 @@
1
1
  <div class="page-select--page">
2
- <i class="icon far fa-file fa-lg"></i>
3
- <span class="page-select--page-name">
4
- {{ page.name }}
5
- </span>
6
- <span class="page-select--page-urlname">
7
- {{ page.url_path }}
8
- </span>
2
+ <div class="page-select--top">
3
+ <i class="icon far fa-file fa-lg"></i>
4
+ <span class="page-select--page-name">
5
+ {{ page.name }}
6
+ </span>
7
+ <span class="page-select--page-urlname">
8
+ {{ page.url_path }}
9
+ </span>
10
+ </div>
11
+ <div class="page-select--bottom">
12
+ <span class="page-select--site-name">
13
+ {{ page.site.name }}
14
+ </span>
15
+ <span class="page-select--language-code">
16
+ {{ page.language.name }}
17
+ </span>
18
+ </div>
9
19
  </div>
@@ -0,0 +1,3 @@
1
+ <a class="page_folder" data-page-id="{{ page.id }}">
2
+ <i class="far fa-{{#if page.folded }}plus{{else}}minus{{/if}}-square fa-fw"></i>
3
+ </a>
@@ -33,6 +33,10 @@ div#image_assign_filter_and_image_sizing {
33
33
  background-color: $thumbnail-background-color;
34
34
  width: 100%;
35
35
  height: 120px;
36
+
37
+ &:hover {
38
+ text-decoration: none;
39
+ }
36
40
  }
37
41
 
38
42
  .picture_thumbnail {
@@ -7,24 +7,49 @@
7
7
 
8
8
  .page-select--page {
9
9
  display: flex;
10
- align-items: center;
10
+ flex-direction: column;
11
11
 
12
12
  .icon {
13
13
  margin: 0 8px 0 4px;
14
14
 
15
15
  .select2-highlighted & {
16
- color: $white
16
+ color: $white;
17
17
  }
18
18
  }
19
19
  }
20
20
 
21
+ .page-select--top,
22
+ .page-select--bottom {
23
+ display: flex;
24
+ flex-direction: row;
25
+ align-items: center;
26
+ }
27
+
28
+ .page-select--bottom {
29
+ font-size: $small-font-size;
30
+ padding-left: 6 * $default-padding;
31
+ }
32
+
33
+ .page-select--language-code {
34
+ display: inline-block;
35
+ background-color: $medium-gray;
36
+ margin-left: auto;
37
+ border-radius: $default-border-radius;
38
+ padding: $default-padding / 2 $default-padding;
39
+
40
+ .select2-highlighted & {
41
+ color: $select-hover-bg-color;
42
+ background-color: white;
43
+ }
44
+ }
45
+
21
46
  .page-select--page-urlname {
22
47
  margin-left: auto;
23
- padding: $default-padding 2*$default-padding;
48
+ padding: $default-padding 2 * $default-padding;
24
49
  color: $dark-gray;
25
50
  font-size: $small-font-size;
26
51
 
27
52
  .select2-highlighted & {
28
- color: $white
53
+ color: $white;
29
54
  }
30
55
  }
@@ -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 {
@@ -142,18 +142,16 @@ $sitemap-url-xlarge-width: 350px;
142
142
  #sitemap {
143
143
  padding: 0 0 104px 0;
144
144
 
145
- &.sorting {
146
- padding-top: 100px;
147
-
148
- .page_icon {
149
- cursor: move;
150
- }
145
+ .handle {
146
+ cursor: move;
151
147
  }
152
148
 
153
149
  .page_folder {
154
150
  position: absolute;
155
151
  left: -23px;
156
152
  top: 0;
153
+ width: 16px;
154
+ height: $sitemap-line-height;
157
155
  }
158
156
 
159
157
  .placeholder {
@@ -258,3 +256,7 @@ $sitemap-url-xlarge-width: 350px;
258
256
  }
259
257
  }
260
258
  }
259
+
260
+ #search_field_clear {
261
+ cursor: pointer;
262
+ }
@@ -7,7 +7,7 @@ module Alchemy
7
7
 
8
8
  helper "alchemy/pages"
9
9
 
10
- before_action :load_resource, except: [:index, :flush, :new, :order, :create, :copy_language_tree, :link, :sort]
10
+ before_action :load_resource, except: [:index, :flush, :new, :order, :create, :copy_language_tree, :link]
11
11
 
12
12
  authorize_resource class: Alchemy::Page, except: [:index, :tree]
13
13
 
@@ -21,7 +21,7 @@ module Alchemy
21
21
  except: [:show]
22
22
 
23
23
  before_action :set_root_page,
24
- only: [:index, :show, :sort, :order]
24
+ only: [:index, :show, :order]
25
25
 
26
26
  before_action :run_on_page_layout_callbacks,
27
27
  if: :run_on_page_layout_callbacks?,
@@ -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
 
@@ -172,9 +173,7 @@ module Alchemy
172
173
  def fold
173
174
  # @page is fetched via before filter
174
175
  @page.fold!(current_alchemy_user.id, !@page.folded?(current_alchemy_user.id))
175
- respond_to do |format|
176
- format.js
177
- end
176
+ render json: serialized_page_tree
178
177
  end
179
178
 
180
179
  # Leaves the page editing mode and unlocks the page for other users
@@ -219,10 +218,6 @@ module Alchemy
219
218
  redirect_to admin_pages_path
220
219
  end
221
220
 
222
- def sort
223
- @sorting = true
224
- end
225
-
226
221
  # Receives a JSON object representing a language tree to be ordered
227
222
  # and updates all pages in that language structure to their correct indexes
228
223
  def order
@@ -404,9 +399,11 @@ module Alchemy
404
399
  end
405
400
 
406
401
  def serialized_page_tree
407
- PageTreeSerializer.new(@page, ability: current_ability,
408
- user: current_alchemy_user,
409
- full: params[:full] == "true")
402
+ PageTreeSerializer.new(
403
+ @page,
404
+ ability: current_ability,
405
+ user: current_alchemy_user,
406
+ )
410
407
  end
411
408
 
412
409
  def load_languages_and_layouts
@@ -2,17 +2,19 @@
2
2
 
3
3
  module Alchemy
4
4
  class Api::PagesController < Api::BaseController
5
+ serialization_scope :current_ability
5
6
  before_action :load_page, only: [:show]
6
7
 
7
8
  # Returns all pages as json object
8
9
  #
9
10
  def index
10
11
  # Fix for cancancan not able to merge multiple AR scopes for logged in users
11
- if cannot? :edit_content, Alchemy::Page
12
- @pages = Alchemy::Page.accessible_by(current_ability, :index)
13
- @pages = @pages.where(language: Language.current)
12
+ if can? :edit_content, Alchemy::Page
13
+ @pages = Alchemy::Page.all
14
14
  else
15
- @pages = Language.current&.pages.presence || Alchemy::Page.none
15
+ language = Alchemy::Language.find_by(id: params[:language_id]) || Alchemy::Language.current
16
+ @pages = Alchemy::Page.accessible_by(current_ability, :index)
17
+ @pages = @pages.where(language: language)
16
18
  end
17
19
  @pages = @pages.includes(*page_includes)
18
20
  @pages = @pages.ransack(params[:q]).result
@@ -49,6 +51,14 @@ module Alchemy
49
51
  respond_with @page
50
52
  end
51
53
 
54
+ def move
55
+ @page = Page.find(params[:id])
56
+ authorize! :update, @page
57
+ target_parent_page = Page.find(params[:target_parent_id])
58
+ @page.move_to_child_with_index(target_parent_page, params[:new_position])
59
+ render json: @page, serializer: PageSerializer
60
+ end
61
+
52
62
  private
53
63
 
54
64
  def load_page
@@ -14,8 +14,14 @@ module Alchemy
14
14
  :created_at,
15
15
  :updated_at,
16
16
  :status,
17
- :url_path
17
+ :url_path,
18
+ :parent_id
18
19
 
19
20
  has_many :elements
21
+
22
+ with_options if: -> { scope.can?(:edit_content, object) } do
23
+ belongs_to :site
24
+ belongs_to :language
25
+ end
20
26
  end
21
27
  end
@@ -3,13 +3,13 @@
3
3
  module Alchemy
4
4
  class PageTreeSerializer < BaseSerializer
5
5
  def attributes
6
- {"pages" => nil}
6
+ { "pages" => nil }
7
7
  end
8
8
 
9
9
  def pages
10
10
  tree = []
11
- path = [{id: object.parent_id, children: tree}]
12
- page_list = object.self_and_descendants
11
+ path = [{ id: object.parent_id, children: tree }]
12
+ page_list = object.self_and_descendants.includes(language: :site)
13
13
  base_level = object.level - 1
14
14
  # Load folded pages in advance
15
15
  folded_user_pages = FoldedPage.folded_for_user(opts[:user]).pluck(:page_id)
@@ -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>