alchemy_cms 5.1.0.beta1 → 5.1.2

Sign up to get free protection for your applications and to get access to all the features.
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);