alchemy_cms 5.1.0.beta1 → 5.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of alchemy_cms might be problematic. Click here for more details.

Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +126 -0
  3. data/CHANGELOG.md +29 -1
  4. data/Gemfile +2 -2
  5. data/README.md +1 -1
  6. data/alchemy_cms.gemspec +1 -1
  7. data/app/assets/javascripts/alchemy/admin.js +0 -1
  8. data/app/assets/javascripts/alchemy/alchemy.element_editors.js.coffee +1 -4
  9. data/app/assets/javascripts/alchemy/alchemy.preview.js.coffee +0 -3
  10. data/app/assets/javascripts/alchemy/alchemy.preview_window.js.coffee +29 -4
  11. data/app/assets/stylesheets/alchemy/_variables.scss +7 -0
  12. data/app/assets/stylesheets/alchemy/admin.scss +0 -1
  13. data/app/assets/stylesheets/alchemy/buttons.scss +26 -15
  14. data/app/assets/stylesheets/alchemy/elements.scss +58 -19
  15. data/app/assets/stylesheets/alchemy/frame.scss +0 -1
  16. data/app/assets/stylesheets/alchemy/hints.scss +2 -1
  17. data/app/assets/stylesheets/alchemy/search.scss +1 -1
  18. data/app/assets/stylesheets/alchemy/selects.scss +26 -20
  19. data/app/assets/stylesheets/alchemy/tables.scss +38 -9
  20. data/app/controllers/alchemy/admin/pages_controller.rb +58 -8
  21. data/app/decorators/alchemy/element_editor.rb +1 -0
  22. data/app/models/alchemy/legacy_page_url.rb +1 -1
  23. data/app/models/alchemy/page.rb +8 -0
  24. data/app/models/alchemy/site/layout.rb +30 -2
  25. data/app/serializers/alchemy/page_tree_serializer.rb +4 -4
  26. data/app/views/alchemy/admin/elements/_element_toolbar.html.erb +1 -1
  27. data/app/views/alchemy/admin/elements/publish.js.erb +1 -0
  28. data/app/views/alchemy/admin/pages/_create_language_form.html.erb +19 -29
  29. data/app/views/alchemy/admin/pages/_new_page_form.html.erb +10 -1
  30. data/app/views/alchemy/admin/pages/_page_layout_filter.html.erb +29 -0
  31. data/app/views/alchemy/admin/pages/_table.html.erb +27 -0
  32. data/app/views/alchemy/admin/pages/_table_row.html.erb +107 -0
  33. data/app/views/alchemy/admin/pages/_toolbar.html.erb +77 -0
  34. data/app/views/alchemy/admin/pages/edit.html.erb +9 -1
  35. data/app/views/alchemy/admin/pages/index.html.erb +41 -74
  36. data/app/views/alchemy/admin/pages/list/_table.html.erb +31 -0
  37. data/app/views/alchemy/admin/pages/unlock.js.erb +2 -2
  38. data/app/views/alchemy/admin/pages/update.js.erb +19 -10
  39. data/app/views/alchemy/admin/resources/_filter_bar.html.erb +13 -11
  40. data/config/locales/alchemy.en.yml +6 -4
  41. data/lib/alchemy.rb +66 -0
  42. data/lib/alchemy/admin/preview_url.rb +2 -0
  43. data/lib/alchemy/engine.rb +0 -4
  44. data/lib/alchemy/permissions.rb +1 -0
  45. data/lib/alchemy/test_support/integration_helpers.rb +0 -7
  46. data/lib/alchemy/version.rb +1 -1
  47. data/lib/alchemy_cms.rb +2 -4
  48. data/vendor/assets/javascripts/jquery_plugins/select2.js +3729 -0
  49. data/vendor/assets/stylesheets/alchemy_admin/select2.scss +740 -0
  50. metadata +30 -29
  51. data/.travis.yml +0 -48
@@ -18,6 +18,7 @@ module Alchemy
18
18
  folded ? "folded" : "expanded",
19
19
  compact? ? "compact" : nil,
20
20
  fixed? ? "is-fixed" : "not-fixed",
21
+ public? ? "visible" : "hidden",
21
22
  ].join(" ")
22
23
  end
23
24
 
@@ -18,5 +18,5 @@ class Alchemy::LegacyPageUrl < ActiveRecord::Base
18
18
 
19
19
  validates :urlname,
20
20
  presence: true,
21
- format: {with: /\A[:\.\w\-+_\/\?&%;=]*\z/}
21
+ format: {with: /\A[:\.\w\-+_\/\?&%;=#]*\z/}
22
22
  end
@@ -164,6 +164,14 @@ module Alchemy
164
164
  @_url_path_class = klass
165
165
  end
166
166
 
167
+ def alchemy_resource_filters
168
+ %w[published not_public restricted]
169
+ end
170
+
171
+ def searchable_alchemy_resource_attributes
172
+ %w[name urlname title]
173
+ end
174
+
167
175
  # Used to store the current page previewed in the edit page template.
168
176
  #
169
177
  def current_preview=(page)
@@ -25,10 +25,38 @@ module Alchemy
25
25
  end
26
26
  end
27
27
 
28
- # Returns site's layout definition
28
+ # Returns sites layout definition
29
29
  #
30
30
  def definition
31
- self.class.definitions.detect { |l| l["name"] == partial_name }
31
+ self.class.definitions.detect { |l| l["name"] == partial_name } || {}
32
+ end
33
+
34
+ # Returns sites page layout names
35
+ #
36
+ # If no site layout file is defined all page layouts are returned
37
+ #
38
+ # @param [Boolean] layoutpages Return layout pages only (default false)
39
+ #
40
+ # @return [Array<String>] Array of page layout names
41
+ #
42
+ def page_layout_names(layoutpages: false)
43
+ page_layout_definitions.select do |layout|
44
+ !!layout["layoutpage"] && layoutpages || !layout["layoutpage"] && !layoutpages
45
+ end.collect { |layout| layout["name"] }
46
+ end
47
+
48
+ # Returns sites page layout definitions
49
+ #
50
+ # If no site layout file is defined all page layouts are returned
51
+ #
52
+ def page_layout_definitions
53
+ if definition["page_layouts"].presence
54
+ Alchemy::PageLayout.all.select do |layout|
55
+ layout["name"].in?(definition["page_layouts"])
56
+ end
57
+ else
58
+ Alchemy::PageLayout.all
59
+ end
32
60
  end
33
61
 
34
62
  # Returns the name for the layout partial
@@ -40,7 +40,7 @@ module Alchemy
40
40
 
41
41
  level = path.count + base_level
42
42
 
43
- path.last[:children] << page_hash(page, has_children, level, folded)
43
+ path.last[:children] << page_hash(page, level, folded)
44
44
  end
45
45
 
46
46
  tree
@@ -48,7 +48,7 @@ module Alchemy
48
48
 
49
49
  protected
50
50
 
51
- def page_hash(page, has_children, level, folded)
51
+ def page_hash(page, level, folded)
52
52
  p_hash = {
53
53
  id: page.id,
54
54
  name: page.name,
@@ -59,8 +59,8 @@ module Alchemy
59
59
  urlname: page.urlname,
60
60
  url_path: page.url_path,
61
61
  level: level,
62
- root: page.depth == 1,
63
- root_or_leaf: page.depth == 1 || !has_children,
62
+ root: page.root?,
63
+ root_or_leaf: page.root? || page.leaf?,
64
64
  children: [],
65
65
  }
66
66
 
@@ -1,4 +1,4 @@
1
- <% remarkable_type = element.class.name.demodulize.underscore.pluralize %>
1
+ <% remarkable_type = "elements" %>
2
2
  <div class="element-toolbar">
3
3
  <span class="element_tools">
4
4
  <div class="button_with_label">
@@ -16,5 +16,6 @@ var eye = el.find('> .element-toolbar .publish-element-button .icon');
16
16
  eye.addClass('fa-eye');
17
17
  label.text('<%= Alchemy.t(:show_element) %>');
18
18
  <%- end -%>
19
+ el.toggleClass('visible hidden');
19
20
 
20
21
  Alchemy.reloadPreview();
@@ -1,8 +1,7 @@
1
1
  <div class="panel">
2
2
  <%= render_message do %>
3
- <p><%= Alchemy.t(:language_does_not_exist) %></p>
3
+ <p><%= Alchemy.t(:homepage_does_not_exist) %></p>
4
4
  <% end %>
5
- <%- if @language -%>
6
5
 
7
6
  <%- if @languages_with_page_tree.size >= 1 -%>
8
7
  <%= form_tag(alchemy.copy_language_tree_admin_pages_path) do %>
@@ -19,32 +18,23 @@
19
18
  <% end %>
20
19
  <%- end -%>
21
20
 
22
- <%- if params[:action] == "index" -%>
23
- <%= alchemy_form_for([:admin, Alchemy::Page.new], id: 'create_language_tree') do |form| %>
24
- <% if @languages_with_page_tree.size >= 1 %>
25
- <h3><%= Alchemy.t(:create_language_tree_heading) %></h3>
26
- <p><%= Alchemy.t(:want_to_create_new_language) %></p>
27
- <% end %>
28
- <%= form.input :name, input_html: {value: @language.frontpage_name} %>
29
- <%= form.input :page_layout,
30
- collection: @page_layouts,
31
- selected: @language.page_layout,
32
- label: Alchemy.t(:page_type),
33
- include_blank: Alchemy.t('Please choose'),
34
- required: true,
35
- input_html: {class: 'alchemy_selectbox'} %>
36
- <%= form.hidden_field :language_id, value: @language.id %>
37
- <%= form.hidden_field :language_code, value: @language.code %>
38
- <%= form.hidden_field :language_root, value: true %>
39
- <%= form.hidden_field :public, value: Alchemy::Language.all.size == 1 %>
40
- <%= form.submit Alchemy.t("create_tree_as_new_language", language: @language.name), autofocus: true %>
21
+ <%= alchemy_form_for([:admin, Alchemy::Page.new], id: 'create_language_tree') do |form| %>
22
+ <% if @languages_with_page_tree.size >= 1 %>
23
+ <h3><%= Alchemy.t(:create_language_tree_heading) %></h3>
24
+ <p><%= Alchemy.t(:want_to_create_new_language) %></p>
41
25
  <% end %>
42
- <%- end -%>
43
-
44
- <%- else -%>
45
-
46
- <p><%= Alchemy.t("Actually this language does not exist. Please create this language first.") %></p>
47
-
48
- <%- end -%>
49
-
26
+ <%= form.input :name, input_html: {value: @language.frontpage_name} %>
27
+ <%= form.input :page_layout,
28
+ collection: @page_layouts,
29
+ selected: @language.page_layout,
30
+ label: Alchemy.t(:page_type),
31
+ include_blank: Alchemy.t('Please choose'),
32
+ required: true,
33
+ input_html: {class: 'alchemy_selectbox'} %>
34
+ <%= form.hidden_field :language_id, value: @language.id %>
35
+ <%= form.hidden_field :language_code, value: @language.code %>
36
+ <%= form.hidden_field :language_root, value: true %>
37
+ <%= form.hidden_field :public, value: Alchemy::Language.all.size == 1 %>
38
+ <%= form.submit Alchemy.t(:create), autofocus: true %>
39
+ <% end %>
50
40
  </div>
@@ -1,5 +1,14 @@
1
1
  <%= alchemy_form_for([:admin, @page]) do |f| %>
2
- <%= f.hidden_field(:parent_id) %>
2
+ <% if @page.parent_id || @page.layoutpage %>
3
+ <%= f.hidden_field(:parent_id) %>
4
+ <% else %>
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" } %>
11
+ <% end %>
3
12
  <%= f.hidden_field(:language_id) %>
4
13
  <%= f.hidden_field(:layoutpage) %>
5
14
  <%= f.input :page_layout,
@@ -0,0 +1,29 @@
1
+ <div id="page_layout_filter">
2
+ <label>
3
+ <h3><%= Alchemy::Page.human_attribute_name(:page_layout) %></h3>
4
+ <%= select_tag(
5
+ "page_layout",
6
+ options_for_select(
7
+ @current_language.site.page_layout_names.map do |layout|
8
+ [
9
+ Alchemy::PageLayout.human_layout_name(layout),
10
+ layout
11
+ ]
12
+ end,
13
+ search_filter_params[:page_layout]
14
+ ),
15
+ include_blank: Alchemy.t(:all, scope: ["resources", "page", "filters"]),
16
+ class: "alchemy_selectbox full_width"
17
+ ) %>
18
+ </label>
19
+ </div>
20
+
21
+ <script type="text/javascript">
22
+ $(function() {
23
+ $("#page_layout").on("change", function(e) {
24
+ var url = "<%= alchemy.admin_pages_path(search_filter_params.except(:page_layout, :page).merge(view: "list")) %>";
25
+ delimiter = url.match(/\?/) ? "&" : "?";
26
+ Turbolinks.visit(url + delimiter + "page_layout=" + encodeURIComponent($(this).val()));
27
+ });
28
+ });
29
+ </script>
@@ -0,0 +1,27 @@
1
+ <table class="list">
2
+ <thead>
3
+ <tr>
4
+ <th class="icon"></th>
5
+ <th class="string name">
6
+ <%= sort_link [:alchemy, @query],
7
+ "name",
8
+ Alchemy::Page.human_attribute_name(:name),
9
+ default_order: "asc" %>
10
+ </th>
11
+ <th><%= Alchemy::Page.human_attribute_name(:urlname) %></th>
12
+ <th><%= Alchemy::Page.human_attribute_name(:page_type) %></th>
13
+ <th><%= Alchemy::Page.human_attribute_name(:tag_list) %></th>
14
+ <th>
15
+ <%= sort_link [:alchemy, @query],
16
+ :updated_at,
17
+ Alchemy::Page.human_attribute_name(:updated_at),
18
+ default_order: "desc" %>
19
+ </th>
20
+ <th class="status center"><%= Alchemy::Page.human_attribute_name(:status) %></th>
21
+ <th class="tools"></th>
22
+ </tr>
23
+ </thead>
24
+ <tbody>
25
+ <%= render partial: "table_row", collection: @pages, as: "page" %>
26
+ </tbody>
27
+ </table>
@@ -0,0 +1,107 @@
1
+ <tr class="<%= cycle(:even, :odd) %>" data-page-id="<%= page.id %>">
2
+ <td class="icon">
3
+ <% if can?(:edit_content, page) %>
4
+ <% if page.locked? %>
5
+ <span class="with-hint">
6
+ <i class="icon fas fa-edit fa-fw"></i>
7
+ <span class="hint-bubble">
8
+ <%= Alchemy.t("This page is locked", name: page.locker_name) %>
9
+ </span>
10
+ </span>
11
+ <% else %>
12
+ <i class="icon far fa-file fa-lg"></i>
13
+ <% end %>
14
+ <% else %>
15
+ <span class="with-hint">
16
+ <i class="icon fas fa-ban fa-fw"></i>
17
+ <span class="hint-bubble">
18
+ <%= Alchemy.t("Your user role does not allow you to edit this page") %>
19
+ </span>
20
+ </span>
21
+ <% end %>
22
+ </td>
23
+ <td class="string name">
24
+ <%= link_to_if(
25
+ can?(:edit_content, page),
26
+ page.name,
27
+ alchemy.edit_admin_page_path(page),
28
+ title: Alchemy.t(:edit_page),
29
+ ) { content_tag(:span, page.name) } -%>
30
+ </td>
31
+ <td class="url">
32
+ <%= page.url_path %>
33
+ </td>
34
+ <td class="page_layout">
35
+ <%= Alchemy.t(page.page_layout, scope: "page_layout_names", default: page.page_layout.to_s.humanize) %>
36
+ </td>
37
+ <td class="tags">
38
+ <% page.tag_list.each do |tag| %>
39
+ <%= content_tag(:span, tag, class: "tag") %>
40
+ <% end %>
41
+ </td>
42
+ <td class="date">
43
+ <%= l(page.updated_at, format: :"alchemy.default") %>
44
+ </td>
45
+ <td class="status center">
46
+ <span class="page_status with-hint">
47
+ <i class="icon fas fa-fw fa-compass <% unless page.public? %>disabled<% end %>" data-fa-transform="shrink-2"></i>
48
+ <span class="hint-bubble"><%= page.status_title(:public) %></span>
49
+ </span>
50
+ <span class="page_status with-hint">
51
+ <i class="icon fas fa-fw fa-lock <% unless page.restricted? %>disabled<% end %>" data-fa-transform="shrink-2"></i>
52
+ <span class="hint-bubble"><%= page.status_title(:restricted) %></span>
53
+ </span>
54
+ </td>
55
+ <td class="tools">
56
+ <% if can?(:info, page) %>
57
+ <div class="button_with_label">
58
+ <%= link_to_dialog(
59
+ render_icon('info-circle'),
60
+ alchemy.info_admin_page_path(page),
61
+ {
62
+ title: Alchemy.t(:page_infos),
63
+ size: '520x290'
64
+ }
65
+ ) %>
66
+ <label class="center"><%= Alchemy.t(:page_infos) %></label>
67
+ </div>
68
+ <% end %>
69
+ <% if can?(:configure, page) %>
70
+ <div class="button_with_label sitemap_tool">
71
+ <%= link_to_dialog(
72
+ render_icon(:cog),
73
+ alchemy.configure_admin_page_path(page),
74
+ {
75
+ title: Alchemy.t(:edit_page_properties),
76
+ size: '450x680'
77
+ }
78
+ ) -%>
79
+ <label class="center"><%= Alchemy.t(:edit_page_properties) %></label>
80
+ </div>
81
+ <% end %>
82
+ <% if can?(:copy, page) %>
83
+ <div class="button_with_label sitemap_tool">
84
+ <%= link_to(
85
+ render_icon(:copy),
86
+ alchemy.insert_admin_clipboard_path(
87
+ remarkable_type: :pages,
88
+ remarkable_id: page.id,
89
+ ),
90
+ remote: true,
91
+ method: :post
92
+ ) %>
93
+ <label class="center"><%= Alchemy.t(:copy_page) %></label>
94
+ </div>
95
+ <% end %>
96
+ <% if can?(:destroy, page) %>
97
+ <div class="button_with_label">
98
+ <%= link_to_confirm_dialog(
99
+ render_icon(:minus),
100
+ Alchemy.t(:confirm_to_delete_page),
101
+ alchemy.admin_page_path(page)
102
+ ) -%>
103
+ <label class="center"><%= Alchemy.t(:delete_page) %></label>
104
+ </div>
105
+ <% end %>
106
+ </td>
107
+ </tr>
@@ -0,0 +1,77 @@
1
+ <div class="toolbar_buttons">
2
+ <%= render "alchemy/admin/partials/site_select" %>
3
+ <%= render "alchemy/admin/partials/language_tree_select" %>
4
+ <%= toolbar_button(
5
+ icon: :plus,
6
+ url: alchemy.new_admin_page_path(language: @current_language),
7
+ hotkey: 'alt+n',
8
+ dialog_options: {
9
+ title: Alchemy.t('Add a page'),
10
+ size: '340x215',
11
+ overflow: true
12
+ },
13
+ title: Alchemy.t('Add a page'),
14
+ label: Alchemy.t('Add a page'),
15
+ if_permitted_to: [:create, Alchemy::Page]
16
+ ) %>
17
+ <div class="toolbar_spacer"></div>
18
+ <% if can?(:flush, Alchemy::Page) %>
19
+ <div class="button_with_label">
20
+ <%= link_to(
21
+ render_icon(:eraser),
22
+ alchemy.flush_admin_pages_path,
23
+ remote: true,
24
+ method: :post,
25
+ class: "icon_button please_wait",
26
+ title: Alchemy.t("Flush page cache")
27
+ ) %>
28
+ <label><%= Alchemy.t("Flush page cache") %></label>
29
+ </div>
30
+ <% end %>
31
+ <% if can?(:sort, Alchemy::Page) %>
32
+ <div class="button_with_label">
33
+ <%= link_to(
34
+ render_icon(:random),
35
+ alchemy.sort_admin_pages_path,
36
+ method: :get,
37
+ class: "icon_button",
38
+ title: Alchemy.t("Sort pages")
39
+ ) %>
40
+ <label><%= Alchemy.t("Sort pages") %></label>
41
+ </div>
42
+ <% end %>
43
+ <div class="button_with_label" id="clipboard_button">
44
+ <%= link_to_dialog(
45
+ render_icon(clipboard_empty?("pages") ? :clipboard : :paste),
46
+ alchemy.admin_clipboard_path(remarkable_type: "pages"),
47
+ {
48
+ title: Alchemy.t("Clipboard"),
49
+ size: "380x305"
50
+ },
51
+ class: "icon_button",
52
+ title: Alchemy.t("Show clipboard")
53
+ ) %>
54
+ <label><%= Alchemy.t("Show clipboard") %></label>
55
+ </div>
56
+ <div class="toolbar_spacer"></div>
57
+ <div class="button_with_label<%= @view != "list" ? " active" : nil %>">
58
+ <%= link_to(
59
+ render_icon(:stream),
60
+ alchemy.admin_pages_path(view: "tree"),
61
+ class: "icon_button"
62
+ ) %>
63
+ <label><%= Alchemy.t("Hierarchical") %></label>
64
+ </div>
65
+ <div class="button_with_label<%= @view == "list" ? " active" : nil %>">
66
+ <%= link_to(
67
+ render_icon("sort-amount-down-alt"),
68
+ alchemy.admin_pages_path(view: "list"),
69
+ class: "icon_button"
70
+ ) %>
71
+ <label><%= Alchemy.t("Sortable List") %></label>
72
+ </div>
73
+ </div>
74
+ <% search_filter_params[:view] = "list" %>
75
+ <%= render "alchemy/admin/partials/search_form",
76
+ url: alchemy.admin_pages_path(search_filter_params.except(:q, :page)),
77
+ additional_params: [:view, :page_layout, :tagged_with, :filter] %>
@@ -88,6 +88,14 @@
88
88
  class: 'alchemy_selectbox medium' %>
89
89
  </div>
90
90
  <div class="toolbar_spacer"></div>
91
+ <% if @preview_urls.many? %>
92
+ <div class="select_with_label">
93
+ <label><%= Alchemy.t(:preview_url) %></label>
94
+ <%= select_tag 'preview_url',
95
+ options_for_select(@preview_urls),
96
+ class: 'alchemy_selectbox large' %>
97
+ </div>
98
+ <% end %>
91
99
  <div class="button_with_label">
92
100
  <%= link_to render_icon(:redo), nil, {
93
101
  title: Alchemy.t('Reload Preview'),
@@ -190,7 +198,7 @@
190
198
  }
191
199
  });
192
200
 
193
- Alchemy.PreviewWindow.init('<%= @preview_url %>');
201
+ Alchemy.PreviewWindow.init(<%== @preview_urls.first %>);
194
202
 
195
203
  $('#preview_size').bind('open.selectBoxIt', function (e) {
196
204
  $('#top_menu').css('z-index', 5000);