pages_core 3.11.3 → 3.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/fonts/661557ef.ttf +0 -0
  4. data/app/assets/builds/fonts/a18fc2d2.woff2 +0 -0
  5. data/app/assets/builds/fonts/b2c7b78f.woff2 +0 -0
  6. data/app/assets/builds/fonts/ceddc204.ttf +0 -0
  7. data/app/assets/builds/pages_core/admin-dist.js +4 -3
  8. data/app/assets/builds/pages_core/admin.css +9233 -0
  9. data/app/assets/images/pages/admin/angle-down-solid.svg +1 -0
  10. data/app/assets/images/pages/admin/icon.svg +1 -0
  11. data/app/assets/stylesheets/pages_core/admin/components/archive.css +6 -0
  12. data/app/assets/stylesheets/{pages/admin/components/attachments.scss → pages_core/admin/components/attachments.css} +35 -28
  13. data/app/assets/stylesheets/{pages/admin.scss → pages_core/admin/components/base.css} +125 -143
  14. data/app/assets/stylesheets/pages_core/admin/components/forms.css +223 -0
  15. data/app/assets/stylesheets/{pages/admin/components/header.scss → pages_core/admin/components/header.css} +78 -48
  16. data/app/assets/stylesheets/{pages/admin/components/image_editor.scss → pages_core/admin/components/image_editor.css} +42 -31
  17. data/app/assets/stylesheets/{pages/admin/components/image_grid.scss → pages_core/admin/components/image_grid.css} +76 -64
  18. data/app/assets/stylesheets/{pages/admin/components/image_uploader.scss → pages_core/admin/components/image_uploader.css} +12 -12
  19. data/app/assets/stylesheets/{pages/admin/components/layout.scss → pages_core/admin/components/layout.css} +13 -9
  20. data/app/assets/stylesheets/pages_core/admin/components/links.css +40 -0
  21. data/app/assets/stylesheets/pages_core/admin/components/list_table.css +66 -0
  22. data/app/assets/stylesheets/{pages/admin/components/login.scss → pages_core/admin/components/login.css} +6 -5
  23. data/app/assets/stylesheets/{pages/admin/components/modal.scss → pages_core/admin/components/modal.css} +10 -32
  24. data/app/assets/stylesheets/{pages/admin/components/page_tree.scss → pages_core/admin/components/page_tree.css} +54 -55
  25. data/app/assets/stylesheets/{pages/admin/components/pagination.scss → pages_core/admin/components/pagination.css} +17 -17
  26. data/app/assets/stylesheets/{pages/admin/components/sidebar.scss → pages_core/admin/components/sidebar.css} +8 -7
  27. data/app/assets/stylesheets/{pages/admin/components/tag_editor.scss → pages_core/admin/components/tag_editor.css} +10 -15
  28. data/app/assets/stylesheets/{pages/admin/components/textarea.scss → pages_core/admin/components/textarea.css} +1 -1
  29. data/app/assets/stylesheets/{pages/admin/components/toast.scss → pages_core/admin/components/toast.css} +5 -3
  30. data/app/assets/stylesheets/{pages/admin/components/toolbar.scss → pages_core/admin/components/toolbar.css} +56 -29
  31. data/app/assets/stylesheets/{pages/admin/controllers/pages.scss → pages_core/admin/controllers/pages.css} +63 -52
  32. data/app/assets/stylesheets/pages_core/admin/controllers/users.css +3 -0
  33. data/app/assets/stylesheets/pages_core/admin/vars.css +34 -0
  34. data/app/assets/stylesheets/pages_core/admin.postcss.css +9 -0
  35. data/app/controllers/admin/calendars_controller.rb +36 -0
  36. data/app/controllers/admin/categories_controller.rb +2 -2
  37. data/app/controllers/admin/news_controller.rb +58 -0
  38. data/app/controllers/admin/pages_controller.rb +2 -3
  39. data/app/controllers/admin/password_resets_controller.rb +4 -4
  40. data/app/controllers/admin/users_controller.rb +4 -4
  41. data/app/controllers/errors_controller.rb +1 -1
  42. data/app/controllers/sessions_controller.rb +1 -1
  43. data/app/formatters/pages_core/image_embedder.rb +5 -27
  44. data/app/helpers/admin/calendars_helper.rb +37 -0
  45. data/app/helpers/admin/news_helper.rb +13 -0
  46. data/app/helpers/pages_core/admin/admin_helper.rb +11 -54
  47. data/app/helpers/pages_core/admin/content_tabs_helper.rb +1 -0
  48. data/app/helpers/pages_core/admin/deprecated_admin_helper.rb +40 -0
  49. data/app/helpers/pages_core/admin/labelled_field_helper.rb +1 -1
  50. data/app/helpers/pages_core/application_helper.rb +1 -1
  51. data/app/helpers/pages_core/images_helper.rb +37 -0
  52. data/app/javascript/components/Attachments/Attachment.jsx +2 -2
  53. data/app/javascript/components/EditableImage.jsx +1 -1
  54. data/app/javascript/components/ImageCropper/Toolbar.jsx +3 -3
  55. data/app/javascript/components/PageTreeNode.jsx +9 -17
  56. data/app/javascript/components/RichTextToolbarButton.jsx +1 -1
  57. data/app/javascript/components/Toast.jsx +1 -1
  58. data/app/mailers/admin_mailer.rb +1 -0
  59. data/app/models/category.rb +1 -1
  60. data/app/models/concerns/pages_core/page_model/dated_page.rb +38 -0
  61. data/app/models/invite.rb +8 -0
  62. data/app/models/page.rb +1 -1
  63. data/app/policies/page_policy.rb +2 -6
  64. data/app/views/admin/calendars/_sidebar.html.erb +47 -0
  65. data/app/views/admin/calendars/show.html.erb +45 -0
  66. data/app/views/admin/invites/new.html.erb +2 -8
  67. data/app/views/admin/invites/show.html.erb +8 -8
  68. data/app/views/admin/news/_sidebar.html.erb +48 -0
  69. data/app/views/admin/news/index.html.erb +53 -0
  70. data/app/views/admin/pages/_list_item.html.erb +2 -1
  71. data/app/views/admin/pages/deleted.html.erb +10 -8
  72. data/app/views/admin/pages/edit.html.erb +20 -11
  73. data/app/views/admin/pages/index.html.erb +7 -8
  74. data/app/views/admin/pages/new.html.erb +10 -14
  75. data/app/views/admin/password_resets/show.html.erb +7 -7
  76. data/app/views/admin/users/deactivated.html.erb +6 -7
  77. data/app/views/admin/users/edit.html.erb +7 -9
  78. data/app/views/admin/users/index.html.erb +3 -6
  79. data/app/views/admin/users/login.html.erb +7 -8
  80. data/app/views/admin/users/new.html.erb +8 -8
  81. data/app/views/admin/users/new_password.html.erb +5 -6
  82. data/app/views/admin/users/show.html.erb +11 -9
  83. data/app/views/errors/401.html.erb +2 -1
  84. data/app/views/errors/403.html.erb +2 -1
  85. data/app/views/errors/404.html.erb +1 -3
  86. data/app/views/errors/405.html.erb +2 -1
  87. data/app/views/errors/422.html.erb +2 -1
  88. data/app/views/errors/500.html.erb +2 -3
  89. data/app/views/layouts/admin/_header.html.erb +5 -18
  90. data/app/views/layouts/admin/_page_header.html.erb +9 -7
  91. data/app/views/layouts/admin.html.erb +3 -3
  92. data/app/views/layouts/errors.html.erb +129 -6
  93. data/config/routes.rb +7 -2
  94. data/lib/pages_core/engine.rb +4 -3
  95. data/lib/pages_core/pages_plugin.rb +6 -1
  96. data/lib/pages_core.rb +0 -1
  97. data/lib/rails/generators/pages_core/install/install_generator.rb +2 -2
  98. data/lib/rails/generators/pages_core/install/templates/delayed_job +2 -3
  99. data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +2 -2
  100. metadata +45 -199
  101. data/app/assets/images/pages/admin/icon.png +0 -0
  102. data/app/assets/images/pages/admin/image-editor-bg.png +0 -0
  103. data/app/assets/images/pages/admin/list-table-pin-blue.gif +0 -0
  104. data/app/assets/images/pages/admin/list-table-pin-disabled.gif +0 -0
  105. data/app/assets/images/pages/admin/list-table-pin-green.gif +0 -0
  106. data/app/assets/images/pages/admin/list-table-pin-red.gif +0 -0
  107. data/app/assets/images/pages/admin/list-table-pin-yellow.gif +0 -0
  108. data/app/assets/images/pages/admin/loading-modal.gif +0 -0
  109. data/app/assets/images/pages/feed-icon-14x14.png +0 -0
  110. data/app/assets/stylesheets/pages/admin/components/archive.scss +0 -6
  111. data/app/assets/stylesheets/pages/admin/components/buttons.scss +0 -23
  112. data/app/assets/stylesheets/pages/admin/components/forms.scss +0 -169
  113. data/app/assets/stylesheets/pages/admin/components/links.scss +0 -43
  114. data/app/assets/stylesheets/pages/admin/components/list_table.scss +0 -61
  115. data/app/assets/stylesheets/pages/admin/controllers/users.scss +0 -3
  116. data/app/assets/stylesheets/pages/admin/mixins/breakpoints.scss +0 -21
  117. data/app/assets/stylesheets/pages/admin/mixins/clearfix.scss +0 -7
  118. data/app/assets/stylesheets/pages/admin/mixins/gradients.scss +0 -7
  119. data/app/assets/stylesheets/pages/admin/vars.scss +0 -30
  120. data/app/assets/stylesheets/pages/errors.css +0 -128
  121. data/app/controllers/concerns/pages_core/admin/news_page_controller.rb +0 -67
  122. data/app/views/admin/pages/news.html.erb +0 -83
  123. data/vendor/assets/stylesheets/ReactCrop.css +0 -167
  124. /data/app/assets/stylesheets/{pages/admin/components/tabs.scss → pages_core/admin/components/tabs.css} +0 -0
@@ -3,7 +3,6 @@
3
3
  module Admin
4
4
  class PagesController < Admin::AdminController
5
5
  include PagesCore::Admin::PageJsonHelper
6
- include PagesCore::Admin::NewsPageController
7
6
 
8
7
  before_action :find_categories
9
8
  before_action :find_page, only: %i[show edit update destroy move]
@@ -34,6 +33,8 @@ module Admin
34
33
  end
35
34
  end
36
35
 
36
+ def edit; end
37
+
37
38
  def create
38
39
  @page = build_page(@locale, page_params, param_categories)
39
40
  if @page.valid?
@@ -46,8 +47,6 @@ module Admin
46
47
  end
47
48
  end
48
49
 
49
- def edit; end
50
-
51
50
  def update
52
51
  if @page.update(page_params)
53
52
  @page.categories = param_categories
@@ -8,6 +8,10 @@ module Admin
8
8
 
9
9
  layout "admin"
10
10
 
11
+ def show
12
+ @user = @password_reset_token.user
13
+ end
14
+
11
15
  def create
12
16
  @user = find_user_by_email(params[:email])
13
17
  if @user
@@ -20,10 +24,6 @@ module Admin
20
24
  redirect_to login_admin_users_url
21
25
  end
22
26
 
23
- def show
24
- @user = @password_reset_token.user
25
- end
26
-
27
27
  def update
28
28
  @user = @password_reset_token.user
29
29
  if user_params[:password].present? && @user.update(user_params)
@@ -25,10 +25,14 @@ module Admin
25
25
  redirect_to admin_default_url
26
26
  end
27
27
 
28
+ def show; end
29
+
28
30
  def new
29
31
  @user = User.new
30
32
  end
31
33
 
34
+ def edit; end
35
+
32
36
  def create
33
37
  @user = PagesCore::CreateUserService.call(user_params)
34
38
  if @user.valid?
@@ -39,10 +43,6 @@ module Admin
39
43
  end
40
44
  end
41
45
 
42
- def show; end
43
-
44
- def edit; end
45
-
46
46
  def update
47
47
  if @user.update(user_params_with_roles)
48
48
  flash[:notice] = "Your changed to #{@user.name} were saved."
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class ErrorsController < ::ApplicationController
3
+ class ErrorsController < ApplicationController
4
4
  layout "errors"
5
5
 
6
6
  def show
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class SessionsController < ::ApplicationController
3
+ class SessionsController < ApplicationController
4
4
  def create
5
5
  user = find_user(params[:email], params[:password])
6
6
  authenticate!(user) if user
@@ -21,12 +21,10 @@ module PagesCore
21
21
  end
22
22
 
23
23
  def embed_image(id, size:, class_name:, link:)
24
- image = Image.find(id).localize(I18n.locale)
25
- class_name = ["image", image_class_name(image), class_name].compact
26
- image_tag = dynamic_image_tag(image,
27
- size: size, crop: false, upscale: false)
28
- tag.figure((link ? link_to(image_tag, link) : image_tag) +
29
- image_caption(image), class: class_name)
24
+ image_figure(
25
+ Image.find(id).localize(I18n.locale),
26
+ size: size, class_name: class_name, link: link
27
+ )
30
28
  rescue ActiveRecord::RecordNotFound
31
29
  nil
32
30
  end
@@ -35,30 +33,10 @@ module PagesCore
35
33
  if str =~ /size="(\d*x\d*)"/
36
34
  Regexp.last_match(1)
37
35
  else
38
- "2000x2000"
36
+ default_image_size
39
37
  end
40
38
  end
41
39
 
42
- def image_caption(image)
43
- return unless image.caption?
44
-
45
- tag.figcaption(image.caption)
46
- end
47
-
48
- def image_class_name(image)
49
- if image.size.x == image.size.y
50
- "square"
51
- elsif image.size.x > image.size.y
52
- "landscape"
53
- else
54
- "portrait"
55
- end
56
- end
57
-
58
- def link_to(content, href)
59
- tag.a(content, href: href)
60
- end
61
-
62
40
  def parse_image(str)
63
41
  id = str.match(image_expression)[1]
64
42
  options = str.match(image_expression)[2]
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Admin
4
+ module CalendarsHelper
5
+ def calendar_pages(locale)
6
+ Page.where(
7
+ id: Page.with_dates.visible.pluck(:parent_page_id).uniq.compact
8
+ ).in_locale(locale)
9
+ end
10
+
11
+ def calendar_page_options(locale)
12
+ options_for_select(
13
+ calendar_pages(locale).map do |p|
14
+ [page_name(p, include_parents: true).gsub("&raquo;", "»"), p.id]
15
+ end
16
+ )
17
+ end
18
+
19
+ def calendar_years_with_count
20
+ calendar_counts.each_with_object({}) do |entry, obj|
21
+ obj[entry[:year]] ||= 0
22
+ obj[entry[:year]] += entry[:count]
23
+ end
24
+ end
25
+
26
+ def calendar_months_count(year)
27
+ calendar_counts.filter { |e| e[:year] == year }
28
+ .map { |e| [e[:month], e[:count]] }
29
+ end
30
+
31
+ private
32
+
33
+ def calendar_counts
34
+ @calendar_counts ||= Page.count_by_month
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Admin
4
+ module NewsHelper
5
+ def news_page_options(news_pages)
6
+ options_for_select(
7
+ news_pages.map do |p|
8
+ [news_section_name(p, news_pages).gsub("&raquo;", "»"), p.id]
9
+ end
10
+ )
11
+ end
12
+ end
13
+ end
@@ -4,6 +4,7 @@ module PagesCore
4
4
  module Admin
5
5
  module AdminHelper
6
6
  include PagesCore::Admin::ContentTabsHelper
7
+ include PagesCore::Admin::DeprecatedAdminHelper
7
8
  include PagesCore::Admin::DateRangeHelper
8
9
  include PagesCore::Admin::ImageUploadsHelper
9
10
  include PagesCore::Admin::LocalesHelper
@@ -11,20 +12,6 @@ module PagesCore
11
12
  include PagesCore::Admin::LabelledFieldHelper
12
13
  include PagesCore::Admin::TagEditorHelper
13
14
 
14
- attr_writer :page_title, :page_description, :page_description_class,
15
- :page_description_links
16
-
17
- def add_body_class(class_name)
18
- @body_classes ||= []
19
- @body_classes << class_name
20
- end
21
-
22
- def body_classes
23
- classes = @body_classes || []
24
- classes << "with_notice" if flash[:notice]
25
- classes
26
- end
27
-
28
15
  def rich_text_area_tag(name, content = nil, options = {})
29
16
  react_component("RichTextArea",
30
17
  options.merge(id: sanitize_to_id(name),
@@ -32,50 +19,20 @@ module PagesCore
32
19
  value: content))
33
20
  end
34
21
 
35
- def link_separator
36
- safe_join [" ", tag.span("|", class: "separator"), " "]
37
- end
38
-
39
- def deprecate_page_description_args(string = nil, class_name = nil)
40
- if class_name
41
- ActiveSupport::Deprecation.warn("Setting class through " \
42
- "page_description is deprecated, " \
43
- "use page_description_class=")
44
- end
45
- return unless string
22
+ def locale_links(&block)
23
+ return unless PagesCore.config.localizations?
46
24
 
47
- ActiveSupport::Deprecation.warn("Setting description with " \
48
- "page_description is deprecated, " \
49
- "use page_description=")
50
- end
51
-
52
- def page_description(string = nil, class_name = nil)
53
- deprecate_page_description_args(string, class_name)
54
- @page_description_class = class_name if class_name
55
- if string
56
- @page_description = string
57
- else
58
- @page_description
59
- end
60
- end
61
-
62
- def page_description_links(links = nil)
63
- return @page_description_links unless links
64
-
65
- ActiveSupport::Deprecation.warn(
66
- "Setting page description_links with page_description_links " \
67
- "is deprecated, use page_description_links="
25
+ safe_join(
26
+ PagesCore.config.locales.map do |locale, name|
27
+ link_to(name, block.call(locale),
28
+ class: ("current" if locale == params[:locale].to_sym))
29
+ end
68
30
  )
69
- @page_description_links = links
70
31
  end
71
32
 
72
- def page_title(title = nil)
73
- return @page_title unless title
74
-
75
- ActiveSupport::Deprecation.warn(
76
- "Setting page title with page_title is deprecated, use page_title="
77
- )
78
- @page_title = title
33
+ def month_name(month)
34
+ %w[January February March April May June July August September October
35
+ November December][month - 1]
79
36
  end
80
37
  end
81
38
  end
@@ -23,6 +23,7 @@ module PagesCore
23
23
  tag.div(content,
24
24
  class: "content-tab",
25
25
  id: "content-tab-#{key}",
26
+ role: "tabpanel",
26
27
  data: { tab: key,
27
28
  "main-target" => "tab" })
28
29
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PagesCore
4
+ module Admin
5
+ module DeprecatedAdminHelper
6
+ def link_separator
7
+ ActiveSupport::Deprecation.warn("link_separator is deprecated")
8
+
9
+ safe_join [" ", tag.span("|", class: "separator"), " "]
10
+ end
11
+
12
+ def page_description=(description)
13
+ ActiveSupport::Deprecation.warn(content_helper_deprecation)
14
+ content_for(:page_description, description.html_safe)
15
+ end
16
+ alias page_description page_description=
17
+
18
+ def page_description_links=(links)
19
+ ActiveSupport::Deprecation.warn(content_helper_deprecation)
20
+ content_for(:page_description_links, links.html_safe)
21
+ end
22
+ alias page_description_links page_description_links=
23
+
24
+ def page_title=(title)
25
+ ActiveSupport::Deprecation.warn(content_helper_deprecation)
26
+ content_for(:page_title, title)
27
+ end
28
+ alias page_title page_title=
29
+
30
+ private
31
+
32
+ def content_helper_deprecation
33
+ name = caller_locations(1, 1)[0].label
34
+ replacement = name.gsub(/=$/, "")
35
+
36
+ "The #{name} helper is deprecated, use content_for(:#{replacement})"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -53,7 +53,7 @@ module PagesCore
53
53
 
54
54
  def labelled_field_label(label, options = {})
55
55
  tag.label do
56
- label + labelled_field_errors(options[:errors])
56
+ safe_join([label, labelled_field_errors(options[:errors])])
57
57
  end
58
58
  end
59
59
 
@@ -28,7 +28,7 @@ module PagesCore
28
28
  output = capture(page.localize(locale), &block)
29
29
  concat(output)
30
30
  end
31
- page ? page.localize(locale) : nil
31
+ page&.localize(locale)
32
32
  end
33
33
 
34
34
  private
@@ -11,6 +11,25 @@ module PagesCore
11
11
  )
12
12
  end
13
13
 
14
+ def image_caption(image, caption: nil)
15
+ return if caption == false
16
+
17
+ caption = image.caption unless caption.is_a?(String)
18
+ return if caption.blank?
19
+
20
+ tag.figcaption(caption)
21
+ end
22
+
23
+ def image_figure(image, size: nil, class_name: nil, link: nil, caption: nil)
24
+ class_name = ["image", image_class_name(image), class_name].compact
25
+ size ||= default_image_size
26
+ image_tag = dynamic_image_tag(image,
27
+ size: size, crop: false, upscale: false)
28
+ tag.figure((link ? image_link_to(image_tag, link) : image_tag) +
29
+ image_caption(image, caption: caption),
30
+ class: class_name)
31
+ end
32
+
14
33
  def original_dynamic_image_tag(record_or_array, options = {})
15
34
  super(
16
35
  record_or_array,
@@ -27,11 +46,29 @@ module PagesCore
27
46
 
28
47
  private
29
48
 
49
+ def default_image_size
50
+ "2000x2000"
51
+ end
52
+
30
53
  def extract_alt_text(record_or_array)
31
54
  record = extract_dynamic_image_record(record_or_array)
32
55
  return {} unless record.alternative?
33
56
 
34
57
  { alt: record.alternative }
35
58
  end
59
+
60
+ def image_class_name(image)
61
+ if image.size.x == image.size.y
62
+ "square"
63
+ elsif image.size.x > image.size.y
64
+ "landscape"
65
+ else
66
+ "portrait"
67
+ end
68
+ end
69
+
70
+ def image_link_to(content, href)
71
+ tag.a(content, href: href)
72
+ end
36
73
  end
37
74
  end
@@ -62,7 +62,7 @@ export default function Attachment(props) {
62
62
  classes.push("uploading");
63
63
  }
64
64
 
65
- const icon = uploading ? "cloud-upload" : "paperclip";
65
+ const icon = uploading ? "cloud-arrow-up" : "paperclip";
66
66
 
67
67
  const localeDir = (locales && locales[locale] && locales[locale].dir) || "ltr";
68
68
 
@@ -95,7 +95,7 @@ export default function Attachment(props) {
95
95
  {attachment &&
96
96
  <div className="attachment-info">
97
97
  <h3>
98
- <i className={`fa fa-${icon} icon`} />
98
+ <i className={`fa-solid fa-${icon} icon`} />
99
99
  {name() || <em>Untitled</em>}<br />
100
100
  </h3>
101
101
  {!uploading &&
@@ -40,7 +40,7 @@ export default function EditableImage(props) {
40
40
  <div className="editable-image">
41
41
  {altWarning &&
42
42
  <span className="alt-warning" title="Alternative text is missing">
43
- <i className="fa fa-exclamation-triangle icon" />
43
+ <i className="fa-solid fa-triangle-exclamation icon" />
44
44
  </span>}
45
45
  <img src={src}
46
46
  width={props.width}
@@ -30,12 +30,12 @@ export default function Toolbar(props) {
30
30
  <button title="Crop image"
31
31
  onClick={props.toggleCrop}
32
32
  className={cropping ? "active" : ""}>
33
- <i className="fa fa-crop" />
33
+ <i className="fa-solid fa-crop" />
34
34
  </button>
35
35
  <button disabled={cropping}
36
36
  title="Toggle focal point"
37
37
  onClick={props.toggleFocal}>
38
- <i className="fa fa-bullseye" />
38
+ <i className="fa-solid fa-bullseye" />
39
39
  </button>
40
40
  <a href={props.image.original_url}
41
41
  className="button"
@@ -43,7 +43,7 @@ export default function Toolbar(props) {
43
43
  disabled={cropping}
44
44
  download={props.image.filename}
45
45
  onClick={evt => cropping && evt.preventDefault()}>
46
- <i className="fa fa-download" />
46
+ <i className="fa-solid fa-download" />
47
47
  </a>
48
48
  </div>
49
49
  {cropping && (
@@ -54,7 +54,7 @@ export default class PageTreeNode extends React.Component {
54
54
  <button type="button"
55
55
  className="add"
56
56
  onClick={() => this.props.addChild(this.props.index)}>
57
- <i className="fa fa-plus icon" />
57
+ <i className="fa-solid fa-plus icon" />
58
58
  Add child
59
59
  </button>
60
60
  </span>
@@ -113,7 +113,7 @@ export default class PageTreeNode extends React.Component {
113
113
  }
114
114
 
115
115
  button(label, options) {
116
- let icon = "fa fa-" + options.icon + " icon";
116
+ let icon = "fa-solid fa-" + options.icon + " icon";
117
117
  return (
118
118
  <button type="button"
119
119
  className={options.className}
@@ -182,9 +182,9 @@ export default class PageTreeNode extends React.Component {
182
182
  var classnames = null;
183
183
 
184
184
  if (collapsed) {
185
- classnames = "collapse fa fa-caret-right";
185
+ classnames = "collapse fa-solid fa-caret-right";
186
186
  } else {
187
- classnames = "collapse fa fa-caret-down";
187
+ classnames = "collapse fa-solid fa-caret-down";
188
188
  }
189
189
 
190
190
  return (
@@ -302,7 +302,7 @@ export default class PageTreeNode extends React.Component {
302
302
 
303
303
  return (
304
304
  <div className="page edit">
305
- <i className="fa fa-file-o icon"></i>
305
+ <i className="fa-regular fa-file icon"></i>
306
306
  <form onSubmit={performEdit}>
307
307
  <input type="text"
308
308
  value={this.state.newName}
@@ -311,7 +311,7 @@ export default class PageTreeNode extends React.Component {
311
311
  autoFocus
312
312
  onChange={handleNameChange} />
313
313
  <button className="save" type="submit">
314
- <i className="fa fa-cloud icon"></i>
314
+ <i className="fa-solid fa-cloud icon"></i>
315
315
  Save
316
316
  </button>
317
317
  {this.button("Cancel", {
@@ -328,11 +328,10 @@ export default class PageTreeNode extends React.Component {
328
328
  let index = this.props.index;
329
329
  let node = index.node;
330
330
 
331
- var dateLabel = "";
332
331
  var pageName = <span className="name">{this.pageName()}</span>;
333
332
  var className = "page";
334
333
 
335
- var iconClass = "fa fa-file-o icon";
334
+ var iconClass = "fa-regular fa-file icon";
336
335
 
337
336
  if (typeof(node.status) != "undefined") {
338
337
  className = `page status-${this.node().status}`;
@@ -344,23 +343,16 @@ export default class PageTreeNode extends React.Component {
344
343
  </a>;
345
344
  }
346
345
 
347
- if (node.id && node.starts_at) {
348
- dateLabel = <span className="date">
349
- {node.starts_at}
350
- </span>;
351
- }
352
-
353
346
  if (node.news_page) {
354
- iconClass = "fa fa-newspaper-o icon";
347
+ iconClass = "fa-regular fa-file-lines icon";
355
348
  } else if (node.pinned) {
356
- iconClass = "fa fa-flag-o icon";
349
+ iconClass = "fa-regular fa-flag icon";
357
350
  }
358
351
 
359
352
  return (
360
353
  <div className={className}>
361
354
  <i className={iconClass}></i>
362
355
  {pageName}
363
- {dateLabel}
364
356
  {this.statusLabel()}
365
357
  {this.collapsedLabel()}
366
358
  {this.actions()}
@@ -7,7 +7,7 @@ export default class RichTextToolbarButton extends React.Component {
7
7
  <a title={this.props.name}
8
8
  className={"button " + this.props.className}
9
9
  onClick={this.props.onClick}>
10
- <i className={"fa fa-" + this.props.className} />
10
+ <i className={"fa-solid fa-" + this.props.className} />
11
11
  </a>
12
12
  );
13
13
  }
@@ -55,7 +55,7 @@ export default class Toast extends React.Component {
55
55
  }
56
56
 
57
57
  return (
58
- <div className="toast-wrapper">
58
+ <div className="toast-wrapper" aria-live="polite">
59
59
  {toast && (
60
60
  <div className={classNames.join(" ")}>
61
61
  {toast.message}
@@ -2,6 +2,7 @@
2
2
 
3
3
  class AdminMailer < ApplicationMailer
4
4
  default from: proc { "\"Pages\" <no-reply@anyone.no>" }
5
+ layout false
5
6
 
6
7
  def password_reset(user, url)
7
8
  @user = user
@@ -15,7 +15,7 @@ class Category < ApplicationRecord
15
15
  def set_slug
16
16
  self.slug = name.downcase
17
17
  .gsub(/[^\w\s]/, "")
18
- .split(/[^\w\d\-]+/)
18
+ .split(/[^\w\d-]+/)
19
19
  .compact
20
20
  .join("-")
21
21
  end
@@ -11,9 +11,47 @@ module PagesCore
11
11
 
12
12
  scope :upcoming, -> { where("ends_at > ?", Time.zone.now) }
13
13
  scope :past, -> { where("ends_at <= ?", Time.zone.now) }
14
+ scope :with_dates, -> { where.not(starts_at: nil) }
14
15
  end
15
16
 
16
17
  module ClassMethods
18
+ def count_by_month
19
+ connection.select_all(count_by_month_query).map(&:symbolize_keys)
20
+ end
21
+
22
+ def in_year(year)
23
+ time = Date.new(year.to_i).to_time
24
+ where("ends_at >= ? AND starts_at <= ?",
25
+ time.beginning_of_year,
26
+ time.end_of_year)
27
+ end
28
+
29
+ def in_year_and_month(year, month)
30
+ time = Date.new(year.to_i, month.to_i).to_time
31
+ where("ends_at >= ? AND starts_at <= ?",
32
+ time.beginning_of_month,
33
+ time.end_of_month)
34
+ end
35
+
36
+ private
37
+
38
+ def count_by_month_query
39
+ <<-SQL.squish
40
+ SELECT extract('year' FROM s.d)::integer AS year,
41
+ extract('month' FROM s.d)::integer AS month,
42
+ count(p.id) AS count
43
+ FROM (SELECT generate_series(
44
+ date_trunc('month', min(starts_at)::date),
45
+ max(ends_at)::date,
46
+ '1 month'::interval)::date AS d FROM pages) s
47
+ RIGHT JOIN pages p
48
+ ON p.ends_at::date >= s.d
49
+ AND p.starts_at::date <= (s.d + interval '1 month - 1 day')::date
50
+ WHERE p.starts_at IS NOT NULL
51
+ GROUP BY s.d
52
+ ORDER BY s.d DESC
53
+ SQL
54
+ end
17
55
  end
18
56
 
19
57
  # Finds the page's next sibling by date. Returns nil if there
data/app/models/invite.rb CHANGED
@@ -15,9 +15,17 @@ class Invite < ApplicationRecord
15
15
 
16
16
  validates :token, presence: true
17
17
 
18
+ validate :user_already_exists
19
+
18
20
  private
19
21
 
20
22
  def ensure_token
21
23
  self.token ||= SecureRandom.hex(32)
22
24
  end
25
+
26
+ def user_already_exists
27
+ return unless User.find_by_email(email)
28
+
29
+ errors.add(:email, :taken)
30
+ end
23
31
  end
data/app/models/page.rb CHANGED
@@ -29,7 +29,7 @@ class Page < ApplicationRecord
29
29
  has_many :categories, through: :page_categories
30
30
 
31
31
  validates(:unique_name,
32
- format: { with: /\A[\w\d_\-]+\z/,
32
+ format: { with: /\A[\w\d_-]+\z/,
33
33
  allow_blank: true },
34
34
  uniqueness: { allow_blank: true })
35
35