alchemy_cms 4.2.0.rc1 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +4 -0
  3. data/.rubocop.yml +4 -0
  4. data/CHANGELOG.md +28 -3
  5. data/Gemfile +1 -1
  6. data/README.md +1 -1
  7. data/alchemy_cms.gemspec +5 -6
  8. data/app/assets/javascripts/alchemy/alchemy.dialog.js.coffee +3 -3
  9. data/app/assets/javascripts/alchemy/alchemy.elements_window.js.coffee +1 -1
  10. data/app/assets/stylesheets/alchemy/buttons.scss +15 -2
  11. data/app/assets/stylesheets/alchemy/dashboard.scss +2 -0
  12. data/app/assets/stylesheets/alchemy/elements.scss +129 -127
  13. data/app/assets/stylesheets/alchemy/frame.scss +9 -29
  14. data/app/assets/stylesheets/alchemy/navigation.scss +30 -6
  15. data/app/assets/stylesheets/alchemy/preview_window.scss +4 -0
  16. data/app/assets/stylesheets/alchemy/spinner.scss +1 -1
  17. data/app/controllers/alchemy/pages_controller.rb +9 -4
  18. data/app/helpers/alchemy/base_helper.rb +3 -4
  19. data/app/models/alchemy/element.rb +1 -1
  20. data/app/models/alchemy/page/page_scopes.rb +1 -1
  21. data/app/views/alchemy/admin/dashboard/index.html.erb +1 -1
  22. data/app/views/alchemy/admin/dashboard/info.html.erb +23 -22
  23. data/app/views/alchemy/admin/pages/edit.html.erb +10 -10
  24. data/app/views/alchemy/admin/resources/_form.html.erb +0 -8
  25. data/app/views/alchemy/admin/resources/_table_header.html.erb +2 -1
  26. data/app/views/alchemy/essences/_essence_select_editor.html.erb +1 -1
  27. data/config/locales/alchemy.en.yml +6 -1
  28. data/lib/alchemy/on_page_layout.rb +1 -1
  29. data/lib/alchemy/permissions.rb +5 -2
  30. data/lib/alchemy/resources_helper.rb +12 -10
  31. data/lib/alchemy/shell.rb +11 -3
  32. data/lib/alchemy/test_support/factories.rb +3 -1
  33. data/lib/alchemy/test_support/factories/attachment_factory.rb +2 -0
  34. data/lib/alchemy/test_support/factories/content_factory.rb +5 -0
  35. data/lib/alchemy/test_support/factories/dummy_user_factory.rb +2 -0
  36. data/lib/alchemy/test_support/factories/element_factory.rb +3 -0
  37. data/lib/alchemy/test_support/factories/essence_file_factory.rb +2 -0
  38. data/lib/alchemy/test_support/factories/essence_picture_factory.rb +2 -0
  39. data/lib/alchemy/test_support/factories/essence_text_factory.rb +2 -0
  40. data/lib/alchemy/test_support/factories/language_factory.rb +2 -0
  41. data/lib/alchemy/test_support/factories/page_factory.rb +2 -0
  42. data/lib/alchemy/test_support/factories/picture_factory.rb +2 -0
  43. data/lib/alchemy/test_support/factories/site_factory.rb +2 -0
  44. data/lib/alchemy/upgrader/four_point_one.rb +16 -16
  45. data/lib/alchemy/upgrader/four_point_two.rb +37 -20
  46. data/lib/alchemy/upgrader/tasks/cells_upgrader.rb +3 -2
  47. data/lib/alchemy/upgrader/tasks/element_partial_name_variable_updater.rb +28 -0
  48. data/lib/alchemy/upgrader/tasks/picture_gallery_upgrader.rb +34 -19
  49. data/lib/alchemy/version.rb +1 -1
  50. data/lib/tasks/alchemy/upgrade.rake +7 -1
  51. metadata +32 -14
  52. data/.teatro.yml +0 -8
@@ -42,18 +42,15 @@ div#overlay_text_box {
42
42
  }
43
43
 
44
44
  #header {
45
+ display: flex;
45
46
  height: $header-height;
46
47
  line-height: $header-height;
47
48
  background: $header-background;
48
49
  border-bottom: $default-border;
49
50
  position: relative;
50
51
 
51
- a {
52
- display: inline-block;
53
-
54
- &:hover {
55
- text-decoration: none;
56
- }
52
+ a:hover {
53
+ text-decoration: none;
57
54
  }
58
55
 
59
56
  .page_status_and_name {
@@ -76,8 +73,13 @@ div#overlay_text_box {
76
73
  }
77
74
 
78
75
  .page_name {
76
+ display: inline-block;
77
+ max-width: 100px;
79
78
  margin-right: $default-margin;
80
79
  line-height: $header-height;
80
+ white-space: nowrap;
81
+ overflow: hidden;
82
+ text-overflow: ellipsis;
81
83
  }
82
84
  }
83
85
 
@@ -96,12 +98,10 @@ div#overlay_text_box {
96
98
  }
97
99
 
98
100
  #user_info {
99
- position: absolute;
100
- top: 0;
101
- right: 0;
102
101
  height: $header-height;
103
102
  font-size: $small-font-size;
104
103
  padding-left: 2*$default-padding;
104
+ margin-left: auto;
105
105
  border-bottom: $default-border;
106
106
  background-color: $header-background;
107
107
 
@@ -126,23 +126,3 @@ div#overlay_text_box {
126
126
  padding-right: 2*$default-padding;
127
127
  }
128
128
  }
129
-
130
- #locked_pages {
131
-
132
- label {
133
- float: left;
134
- font-size: $small-font-size;
135
- line-height: 27px;
136
- height: 27px;
137
- margin-left: 8px;
138
- margin-right: 4px;
139
- }
140
-
141
- img {
142
- float: left;
143
- }
144
-
145
- .subnavi_tab > a {
146
- padding-right: 0;
147
- }
148
- }
@@ -209,19 +209,42 @@
209
209
  }
210
210
  }
211
211
 
212
+ #locked_pages {
213
+ display: flex;
214
+ flex-grow: 1;
215
+ flex-wrap: nowrap;
216
+ height: $header-height;
217
+
218
+ label {
219
+ float: left;
220
+ font-size: $small-font-size;
221
+ line-height: 27px;
222
+ height: 27px;
223
+ margin-left: 8px;
224
+ margin-right: 4px;
225
+ }
226
+
227
+ img {
228
+ float: left;
229
+ }
230
+
231
+ .subnavi_tab > a {
232
+ padding-right: 0;
233
+ }
234
+ }
235
+
212
236
  .locked_page {
213
- position: relative;
214
- float: left;
237
+ display: flex;
238
+ flex-wrap: nowrap;
215
239
  line-height: $header-height - 1;
216
240
  color: #444;
217
241
  border-right: $default-border;
218
242
  border-bottom: $default-border;
219
- padding-right: 24px;
220
243
 
221
244
  form {
222
- position: absolute;
223
- right: $default-padding;
224
- top: 7px;
245
+ align-self: center;
246
+ justify-self: flex-end;
247
+ margin-right: $default-padding;
225
248
  line-height: 1;
226
249
  }
227
250
 
@@ -231,6 +254,7 @@
231
254
  }
232
255
 
233
256
  > a {
257
+ display: flex;
234
258
  cursor: pointer;
235
259
  padding: 0px 8px;
236
260
 
@@ -16,5 +16,9 @@
16
16
 
17
17
  .collapsed-menu.elements-window-visible & {
18
18
  width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width} - #{$elements-window-width});
19
+
20
+ @media screen and (min-width: $large-screen-break-point) {
21
+ max-width: calc(100vw - #{$collapsed-main-menu-width - $default-border-width} - #{$elements-window-min-width});
22
+ }
19
23
  }
20
24
  }
@@ -7,7 +7,7 @@
7
7
  line-height: 1;
8
8
 
9
9
  path {
10
- fill: $text-color;
10
+ fill: currentColor;
11
11
  opacity: 1;
12
12
  animation: spinner 1s infinite ease-in-out both;
13
13
  }
@@ -155,10 +155,10 @@ module Alchemy
155
155
  end
156
156
 
157
157
  def set_expiration_headers
158
- if @page.cache_page?
159
- expires_in @page.expiration_time, public: !@page.restricted, must_revalidate: true
160
- else
158
+ if must_not_cache?
161
159
  expires_now
160
+ else
161
+ expires_in @page.expiration_time, public: !@page.restricted, must_revalidate: true
162
162
  end
163
163
  end
164
164
 
@@ -190,12 +190,17 @@ module Alchemy
190
190
  # or the cache is stale, because it's been republished by the user.
191
191
  #
192
192
  def render_fresh_page?
193
- !@page.cache_page? || stale?(etag: page_etag,
193
+ must_not_cache? || stale?(etag: page_etag,
194
194
  last_modified: @page.published_at,
195
195
  public: !@page.restricted,
196
196
  template: 'pages/show')
197
197
  end
198
198
 
199
+ # don't cache pages if we have flash message to display or the page has caching disabled
200
+ def must_not_cache?
201
+ flash.present? || !@page.cache_page?
202
+ end
203
+
199
204
  def page_not_found!
200
205
  not_found_error!("Alchemy::Page not found \"#{request.fullpath}\"")
201
206
  end
@@ -11,12 +11,11 @@ module Alchemy
11
11
  # Logs a message in the Rails logger (warn level)
12
12
  # and optionally displays an error message to the user.
13
13
  def warning(message, text = nil)
14
- Logger.warn(message, caller(0..0))
14
+ Logger.warn(message, caller(1..1))
15
15
  unless text.nil?
16
- warning = content_tag('p', class: 'content_editor_error') do
17
- render_icon('warning') + text
16
+ render_message(:warning) do
17
+ text.html_safe
18
18
  end
19
- return warning
20
19
  end
21
20
  end
22
21
 
@@ -98,7 +98,7 @@ module Alchemy
98
98
  scope :not_restricted, -> { joins(:page).merge(Page.not_restricted) }
99
99
  scope :available, -> { published.not_trashed }
100
100
  scope :named, ->(names) { where(name: names) }
101
- scope :excluded, ->(names) { where(arel_table[:name].not_in(names)) }
101
+ scope :excluded, ->(names) { where.not(name: names) }
102
102
  scope :fixed, -> { where(fixed: true) }
103
103
  scope :unfixed, -> { where(fixed: false) }
104
104
  scope :from_current_site, -> { where(Language.table_name => {site_id: Site.current || Site.default}).joins(page: 'language') }
@@ -66,7 +66,7 @@ module Alchemy
66
66
  # Returns all content pages.
67
67
  #
68
68
  scope :contentpages, -> {
69
- where(layoutpage: [false, nil]).where(Page.arel_table[:parent_id].not_eq(nil))
69
+ where(layoutpage: [false, nil]).where.not(parent_id: nil)
70
70
  }
71
71
 
72
72
  # Returns all public contentpages that are not locked.
@@ -7,7 +7,7 @@
7
7
  title: Alchemy.t(:info),
8
8
  dialog_options: {
9
9
  title: Alchemy.t(:info),
10
- size: "420x380"
10
+ size: "420x435"
11
11
  },
12
12
  if_permitted_to: [:info, :alchemy_admin_dashboard],
13
13
  hotkey: 'alt+i'
@@ -1,6 +1,11 @@
1
- <p class="center"><%= image_tag('alchemy/alchemy-logo.svg') %></p>
1
+ <p class="center">
2
+ <%= image_tag('alchemy/alchemy-logo.svg', width: 267, height: 91) %>
3
+ </p>
2
4
  <h2 class="center">
3
- <%= Alchemy.t("Version") %>: <%= @alchemy_version %>
5
+ v<%= @alchemy_version %><br>
6
+ <small>
7
+ Ruby v<%= RUBY_VERSION %>, Rails v<%= Rails.version %>
8
+ </small>
4
9
  </h2>
5
10
  <% if can? :update_check, :alchemy_admin_dashboard %>
6
11
  <p class="center" id="update_check">
@@ -17,6 +22,22 @@
17
22
  <%= Alchemy.t 'Update status unavailable' %>
18
23
  </span>
19
24
  </p>
25
+ <script type="text/javascript">
26
+ var el = $('#update_check');
27
+ var spinner = new Alchemy.Spinner('small')
28
+ spinner.spin(el[0])
29
+ $.get('<%= alchemy.update_check_path %>', function(data, textStatus, jqXHR) {
30
+ if (data == 'true') {
31
+ $('#update_available').show()
32
+ } else {
33
+ $('#up_to_date').show()
34
+ }
35
+ }).fail(function(jqXHR, textStatus, errorThrown) {
36
+ $('#error').show()
37
+ }).always(function() {
38
+ spinner.stop()
39
+ })
40
+ </script>
20
41
  <% end %>
21
42
  <%= render_message do %>
22
43
  <p><%= Alchemy.t('Alchemy is open software and itself uses open software and free resources:') %></p>
@@ -41,23 +62,3 @@
41
62
  </li>
42
63
  </ul>
43
64
  <% end %>
44
- <% if can? :update_check, :alchemy_admin_dashboard %>
45
- <script type="text/javascript">
46
- (function() {
47
- var el = $('#update_check');
48
- var spinner = new Alchemy.Spinner('small');
49
- spinner.spin(el[0]);
50
- $.get('<%= alchemy.update_check_path %>', function(data, textStatus, jqXHR) {
51
- if (data == 'true') {
52
- $('#update_available').show();
53
- } else {
54
- $('#up_to_date').show();
55
- }
56
- }).fail(function(jqXHR, textStatus, errorThrown) {
57
- $('#error').show();
58
- }).always(function() {
59
- spinner.stop();
60
- });
61
- })();
62
- </script>
63
- <% end %>
@@ -13,16 +13,6 @@
13
13
  <% end %>
14
14
  </div>
15
15
  <div class="toolbar_spacer"></div>
16
- <% unless @page.layoutpage? %>
17
- <div class="button_with_label">
18
- <%= form_tag alchemy.visit_admin_page_path(@page), id: 'visit_page_form' do %>
19
- <button class="icon_button" title="<%= Alchemy.t('Visit page') %>">
20
- <%= render_icon('external-link-alt') %>
21
- </button>
22
- <label><%= Alchemy.t("Visit page") %></label>
23
- <% end %>
24
- </div>
25
- <% end %>
26
16
  <div class="button_with_label">
27
17
  <%= link_to_dialog(
28
18
  render_icon('info-circle'),
@@ -76,6 +66,16 @@
76
66
  <% end %>
77
67
  </div>
78
68
  <% end %>
69
+ <% unless @page.layoutpage? %>
70
+ <div class="button_with_label">
71
+ <%= form_tag alchemy.visit_admin_page_path(@page), id: 'visit_page_form' do %>
72
+ <%= button_tag class: 'icon_button', disabled: !@page.public? do %>
73
+ <%= render_icon('external-link-alt') %>
74
+ <% end %>
75
+ <label><%= Alchemy.t("Visit page") %></label>
76
+ <% end %>
77
+ </div>
78
+ <% end %>
79
79
  <% if @page.has_hint? %>
80
80
  <div class="toolbar_spacer"></div>
81
81
  <%= render_hint_for(@page) %>
@@ -5,14 +5,6 @@
5
5
  label_method: relation[:attr_method],
6
6
  include_blank: Alchemy.t(:blank, scope: 'resources.relation_select'),
7
7
  input_html: {class: 'alchemy_selectbox'} %>
8
- <% elsif attribute[:type] == :datetime || attribute[:type] == :time %>
9
- <div class="input <%= attribute[:type] %>">
10
- <label class="control-label">
11
- <%= f.object.class.human_attribute_name(attribute[:name]) %>
12
- </label>
13
- <%= alchemy_datepicker resource_instance_variable, attribute[:name],
14
- type: attribute[:type] %>
15
- </div>
16
8
  <% else %>
17
9
  <%= f.input attribute[:name], resource_attribute_field_options(attribute) %>
18
10
  <% end %>
@@ -1,5 +1,6 @@
1
1
  <div class="resources-header">
2
2
  <h2>
3
- <%= pluralize resources_instance_variable.total_count, resource_model.model_name.human %>
3
+ <%= resources_instance_variable.total_count %>
4
+ <%= resource_model.model_name.human(count: resources_instance_variable.total_count) %>
4
5
  </h2>
5
6
  </div>
@@ -11,7 +11,7 @@
11
11
  <%= content_label(content) %>
12
12
 
13
13
  <% if select_values.nil? %>
14
- <%== warning(':select_values is nil',
14
+ <%= warning(':select_values is nil',
15
15
  "<strong>No select values given.</strong>
16
16
  <br>Please provide :<code>select_values</code> either as argument to
17
17
  <code>render_essence_editor</code> helper or as setting on the content definition in
@@ -280,7 +280,6 @@ en:
280
280
  "User deleted": "%{name} User deleted"
281
281
  "User updated": "%{name} User updated"
282
282
  "Validation failed": "Validation failed"
283
- "Version": "Version"
284
283
  "View File": "View File"
285
284
  "Attachment Preview": "Attachment Preview"
286
285
  "Visit page": "Visit page"
@@ -612,6 +611,12 @@ en:
612
611
  error_notification:
613
612
  default_message: "Please review the problems below:"
614
613
 
614
+ # Alchemy date formats
615
+ date:
616
+ formats:
617
+ alchemy:
618
+ default: "%Y-%m-%d"
619
+
615
620
  # Alchemy time formats
616
621
  time:
617
622
  formats:
@@ -47,7 +47,7 @@ module Alchemy
47
47
  # Registers a callback for given page layout
48
48
  def self.register_callback(page_layout, callback)
49
49
  @callbacks ||= {}
50
- @callbacks[page_layout] ||= []
50
+ @callbacks[page_layout] ||= Set.new
51
51
  @callbacks[page_layout] << callback
52
52
  end
53
53
 
@@ -64,8 +64,7 @@ module Alchemy
64
64
 
65
65
  # Resources
66
66
  can [:show, :download], Alchemy::Attachment
67
- can :read, Alchemy::Page, Alchemy::Page.published, &:public?
68
- can :see, Alchemy::Page, restricted: true, visible: true
67
+ can :see, Alchemy::Page, restricted: true, visible: true
69
68
 
70
69
  can :read, Alchemy::Content, Alchemy::Content.available do |c|
71
70
  c.public? && !c.trashed?
@@ -74,6 +73,10 @@ module Alchemy
74
73
  can :read, Alchemy::Element, Alchemy::Element.available do |e|
75
74
  e.public? && !e.trashed?
76
75
  end
76
+
77
+ can :read, Alchemy::Page, Alchemy::Page.published do |p|
78
+ p.public?
79
+ end
77
80
  end
78
81
  end
79
82
 
@@ -78,9 +78,11 @@ module Alchemy
78
78
  if attribute[:relation]
79
79
  record = resource.send(attribute[:relation][:name])
80
80
  value = record.present? ? record.send(attribute[:relation][:attr_method]) : Alchemy.t(:not_found)
81
- elsif attribute_value && (attribute[:type] == :datetime || attribute[:type] == :time)
81
+ elsif attribute_value && attribute[:type].to_s =~ /(date|time)/
82
82
  localization_format = if attribute[:type] == :datetime
83
83
  options[:datetime_format] || :'alchemy.default'
84
+ elsif attribute[:type] == :date
85
+ options[:date_format] || :'alchemy.default'
84
86
  else
85
87
  options[:time_format] || :'alchemy.time'
86
88
  end
@@ -100,19 +102,19 @@ module Alchemy
100
102
  # Returns a options hash for simple_form input fields.
101
103
  def resource_attribute_field_options(attribute)
102
104
  options = {hint: resource_handler.help_text_for(attribute)}
103
- case attribute[:type].to_s
105
+ input_type = attribute[:type].to_s
106
+ case input_type
104
107
  when 'boolean'
105
108
  options
106
- when 'date', 'datetime'
107
- options.merge as: 'string',
109
+ when 'date', 'time', 'datetime'
110
+ date = resource_instance_variable.send(attribute[:name]) || Time.current
111
+ options.merge(
112
+ as: 'string',
108
113
  input_html: {
109
- type: attribute[:type].to_s,
110
- value: l(resource_instance_variable.send(attribute[:name]) || Time.current,
111
- format: "#{attribute[:type]}picker".to_sym
112
- )
114
+ 'data-datepicker-type' => input_type,
115
+ value: date ? date.iso8601 : nil
113
116
  }
114
- when 'time'
115
- options.merge(as: 'time')
117
+ )
116
118
  when 'text'
117
119
  options.merge(as: 'text', input_html: {rows: 4})
118
120
  else