pages_core 3.14.0 → 3.15.0

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.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +19 -8
  4. data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
  5. data/app/assets/builds/pages_core/admin.css +672 -379
  6. data/app/assets/fonts/Inter-Black.woff2 +0 -0
  7. data/app/assets/fonts/Inter-BlackItalic.woff2 +0 -0
  8. data/app/assets/fonts/Inter-Bold.woff2 +0 -0
  9. data/app/assets/fonts/Inter-BoldItalic.woff2 +0 -0
  10. data/app/assets/fonts/Inter-ExtraBold.woff2 +0 -0
  11. data/app/assets/fonts/Inter-ExtraBoldItalic.woff2 +0 -0
  12. data/app/assets/fonts/Inter-ExtraLight.woff2 +0 -0
  13. data/app/assets/fonts/Inter-ExtraLightItalic.woff2 +0 -0
  14. data/app/assets/fonts/Inter-Italic.woff2 +0 -0
  15. data/app/assets/fonts/Inter-Light.woff2 +0 -0
  16. data/app/assets/fonts/Inter-LightItalic.woff2 +0 -0
  17. data/app/assets/fonts/Inter-Medium.woff2 +0 -0
  18. data/app/assets/fonts/Inter-MediumItalic.woff2 +0 -0
  19. data/app/assets/fonts/Inter-Regular.woff2 +0 -0
  20. data/app/assets/fonts/Inter-SemiBold.woff2 +0 -0
  21. data/app/assets/fonts/Inter-SemiBoldItalic.woff2 +0 -0
  22. data/app/assets/fonts/Inter-Thin.woff2 +0 -0
  23. data/app/assets/fonts/Inter-ThinItalic.woff2 +0 -0
  24. data/app/assets/fonts/InterDisplay-Black.woff2 +0 -0
  25. data/app/assets/fonts/InterDisplay-BlackItalic.woff2 +0 -0
  26. data/app/assets/fonts/InterDisplay-Bold.woff2 +0 -0
  27. data/app/assets/fonts/InterDisplay-BoldItalic.woff2 +0 -0
  28. data/app/assets/fonts/InterDisplay-ExtraBold.woff2 +0 -0
  29. data/app/assets/fonts/InterDisplay-ExtraBoldItalic.woff2 +0 -0
  30. data/app/assets/fonts/InterDisplay-ExtraLight.woff2 +0 -0
  31. data/app/assets/fonts/InterDisplay-ExtraLightItalic.woff2 +0 -0
  32. data/app/assets/fonts/InterDisplay-Italic.woff2 +0 -0
  33. data/app/assets/fonts/InterDisplay-Light.woff2 +0 -0
  34. data/app/assets/fonts/InterDisplay-LightItalic.woff2 +0 -0
  35. data/app/assets/fonts/InterDisplay-Medium.woff2 +0 -0
  36. data/app/assets/fonts/InterDisplay-MediumItalic.woff2 +0 -0
  37. data/app/assets/fonts/InterDisplay-Regular.woff2 +0 -0
  38. data/app/assets/fonts/InterDisplay-SemiBold.woff2 +0 -0
  39. data/app/assets/fonts/InterDisplay-SemiBoldItalic.woff2 +0 -0
  40. data/app/assets/fonts/InterDisplay-Thin.woff2 +0 -0
  41. data/app/assets/fonts/InterDisplay-ThinItalic.woff2 +0 -0
  42. data/app/assets/fonts/InterVariable-Italic.woff2 +0 -0
  43. data/app/assets/fonts/InterVariable.woff2 +0 -0
  44. data/app/assets/stylesheets/pages_core/admin/components/archive.css +1 -1
  45. data/app/assets/stylesheets/pages_core/admin/components/attachments.css +22 -34
  46. data/app/assets/stylesheets/pages_core/admin/components/base.css +1 -68
  47. data/app/assets/stylesheets/pages_core/admin/components/forms.css +107 -48
  48. data/app/assets/stylesheets/pages_core/admin/components/header.css +56 -58
  49. data/app/assets/stylesheets/pages_core/admin/components/image_editor.css +35 -24
  50. data/app/assets/stylesheets/pages_core/admin/components/image_grid.css +28 -27
  51. data/app/assets/stylesheets/pages_core/admin/components/image_uploader.css +5 -5
  52. data/app/assets/stylesheets/pages_core/admin/components/layout.css +7 -1
  53. data/app/assets/stylesheets/pages_core/admin/components/list_table.css +24 -15
  54. data/app/assets/stylesheets/pages_core/admin/components/page_tree.css +63 -104
  55. data/app/assets/stylesheets/pages_core/admin/components/pagination.css +12 -13
  56. data/app/assets/stylesheets/pages_core/admin/components/search.css +1 -16
  57. data/app/assets/stylesheets/pages_core/admin/components/sidebar.css +5 -11
  58. data/app/assets/stylesheets/pages_core/admin/components/tag_editor.css +22 -36
  59. data/app/assets/stylesheets/pages_core/admin/components/toast.css +1 -2
  60. data/app/assets/stylesheets/pages_core/admin/components/toolbar.css +10 -10
  61. data/app/assets/stylesheets/pages_core/admin/components/totp.css +1 -1
  62. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +37 -51
  63. data/app/assets/stylesheets/pages_core/admin/global/fonts.css +271 -0
  64. data/app/assets/stylesheets/pages_core/admin/global/typography.css +109 -0
  65. data/app/assets/stylesheets/pages_core/admin/vars.css +1 -3
  66. data/app/assets/stylesheets/pages_core/admin.postcss.css +1 -0
  67. data/app/controllers/admin/account_recoveries_controller.rb +2 -2
  68. data/app/controllers/admin/pages_controller.rb +22 -42
  69. data/app/controllers/concerns/pages_core/error_reporting.rb +1 -1
  70. data/app/controllers/concerns/pages_core/page_parameters.rb +29 -0
  71. data/app/controllers/concerns/pages_core/policies_helper.rb +1 -1
  72. data/app/controllers/concerns/pages_core/preview_pages_controller.rb +20 -20
  73. data/app/controllers/pages_core/admin_controller.rb +0 -2
  74. data/app/controllers/pages_core/frontend/pages_controller.rb +2 -6
  75. data/app/formatters/pages_core/html_formatter.rb +2 -4
  76. data/app/helpers/admin/menu_helper.rb +5 -4
  77. data/app/helpers/admin/pages_helper.rb +1 -21
  78. data/app/helpers/pages_core/admin/admin_helper.rb +2 -3
  79. data/app/helpers/pages_core/admin/content_tabs_helper.rb +1 -2
  80. data/app/helpers/pages_core/admin/labelled_field_helper.rb +1 -1
  81. data/app/helpers/pages_core/frontend_helper.rb +1 -1
  82. data/app/helpers/pages_core/images_helper.rb +10 -8
  83. data/app/helpers/pages_core/labelled_form_builder.rb +2 -7
  84. data/app/helpers/pages_core/page_path_helper.rb +1 -1
  85. data/app/javascript/components/Attachments/Attachment.tsx +20 -18
  86. data/app/javascript/components/Attachments/AttachmentEditor.tsx +11 -9
  87. data/app/javascript/components/{Attachments.jsx → Attachments/List.tsx} +58 -63
  88. data/app/javascript/components/Attachments/useAttachments.ts +15 -0
  89. data/app/javascript/components/Attachments.tsx +14 -0
  90. data/app/javascript/components/DateRangeSelect.tsx +105 -0
  91. data/app/javascript/components/DateTimeSelect.tsx +136 -0
  92. data/app/javascript/components/EditableImage.tsx +11 -9
  93. data/app/javascript/components/FileUploadButton.tsx +7 -7
  94. data/app/javascript/components/ImageCropper/FocalPoint.tsx +9 -12
  95. data/app/javascript/components/ImageCropper/Image.tsx +10 -8
  96. data/app/javascript/components/ImageCropper/Toolbar.tsx +11 -12
  97. data/app/javascript/components/ImageCropper/useCrop.ts +24 -53
  98. data/app/javascript/components/ImageCropper.tsx +10 -15
  99. data/app/javascript/components/ImageEditor/Form.tsx +12 -8
  100. data/app/javascript/components/ImageEditor.tsx +12 -7
  101. data/app/javascript/components/ImageGrid/DragElement.tsx +9 -12
  102. data/app/javascript/components/{ImageGrid.jsx → ImageGrid/Grid.tsx} +62 -71
  103. data/app/javascript/components/ImageGrid/GridImage.tsx +22 -23
  104. data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -2
  105. data/app/javascript/components/ImageGrid/useImageGrid.ts +26 -0
  106. data/app/javascript/components/ImageGrid.tsx +15 -0
  107. data/app/javascript/components/ImageUploader.tsx +35 -22
  108. data/app/javascript/components/LabelledField.tsx +34 -0
  109. data/app/javascript/components/Modal.tsx +2 -2
  110. data/app/javascript/components/PageForm/Block.tsx +81 -0
  111. data/app/javascript/components/PageForm/Content.tsx +54 -0
  112. data/app/javascript/components/PageForm/Dates.tsx +66 -0
  113. data/app/javascript/components/PageForm/Files.tsx +28 -0
  114. data/app/javascript/components/PageForm/Form.tsx +41 -0
  115. data/app/javascript/components/PageForm/Images.tsx +28 -0
  116. data/app/javascript/components/PageForm/LocaleLinks.tsx +36 -0
  117. data/app/javascript/components/PageForm/Metadata.tsx +67 -0
  118. data/app/javascript/components/PageForm/Options.tsx +180 -0
  119. data/app/javascript/components/PageForm/PageDescription.tsx +48 -0
  120. data/app/javascript/components/PageForm/PathSegment.tsx +65 -0
  121. data/app/javascript/components/PageForm/TabPanel.tsx +21 -0
  122. data/app/javascript/components/PageForm/Tabs.tsx +33 -0
  123. data/app/javascript/components/PageForm/UnconfiguredContent.tsx +42 -0
  124. data/app/javascript/components/PageForm/pageParams.ts +95 -0
  125. data/app/javascript/components/PageForm/preview.ts +23 -0
  126. data/app/javascript/components/PageForm/usePage.ts +169 -0
  127. data/app/javascript/components/PageForm/useTabs.ts +46 -0
  128. data/app/javascript/components/PageForm.tsx +163 -0
  129. data/app/javascript/components/PageImages.tsx +7 -9
  130. data/app/javascript/components/PageTree/Draggable.tsx +40 -39
  131. data/app/javascript/components/PageTree/Node.tsx +62 -56
  132. data/app/javascript/components/PageTree/PageName.tsx +28 -0
  133. data/app/javascript/components/PageTree.tsx +65 -53
  134. data/app/javascript/components/{RichTextArea.jsx → RichTextArea.tsx} +98 -79
  135. data/app/javascript/components/RichTextToolbarButton.tsx +4 -6
  136. data/app/javascript/components/TagEditor/AddTagForm.tsx +19 -12
  137. data/app/javascript/components/TagEditor/Editor.tsx +32 -0
  138. data/app/javascript/components/TagEditor/Tag.tsx +6 -4
  139. data/app/javascript/components/TagEditor/useTags.ts +58 -0
  140. data/app/javascript/components/TagEditor.tsx +8 -58
  141. data/app/javascript/components/Toast.tsx +3 -3
  142. data/app/javascript/components/drag/draggedOrder.ts +22 -14
  143. data/app/javascript/components/drag/useDragCollection.ts +35 -30
  144. data/app/javascript/components/drag/useDragUploader.ts +32 -21
  145. data/app/javascript/components/drag/useDraggable.ts +7 -6
  146. data/app/javascript/components/drag.ts +0 -1
  147. data/app/javascript/components.ts +1 -3
  148. data/app/javascript/features/RichText.tsx +2 -3
  149. data/app/javascript/features/contentTabs.ts +79 -0
  150. data/app/javascript/index.ts +5 -12
  151. data/app/javascript/lib/Tree.ts +31 -45
  152. data/app/javascript/lib/request.ts +11 -11
  153. data/app/javascript/stores/useToastStore.ts +1 -1
  154. data/app/javascript/types/Attachments.ts +29 -0
  155. data/app/javascript/types/Crop.ts +36 -0
  156. data/app/javascript/types/Drag.ts +34 -0
  157. data/app/javascript/types/Images.ts +47 -0
  158. data/app/javascript/types/PageEditor.ts +26 -0
  159. data/app/javascript/types/Pages.ts +75 -0
  160. data/app/javascript/types/Tags.ts +9 -0
  161. data/app/javascript/types/Template.ts +24 -0
  162. data/app/javascript/types/Trees.ts +19 -0
  163. data/app/javascript/types.ts +2 -25
  164. data/app/models/attachment.rb +1 -1
  165. data/app/models/concerns/pages_core/authenticable_user.rb +63 -0
  166. data/app/models/concerns/pages_core/emailable.rb +16 -0
  167. data/app/models/concerns/pages_core/page_model/templateable.rb +2 -16
  168. data/app/models/invite.rb +2 -6
  169. data/app/models/otp_secret.rb +4 -4
  170. data/app/models/page.rb +0 -3
  171. data/app/models/user.rb +2 -46
  172. data/app/policies/page_policy.rb +6 -2
  173. data/app/resources/admin/page_resource.rb +95 -0
  174. data/app/resources/admin/page_tree_resource.rb +27 -0
  175. data/app/resources/admin/template_configuration_resource.rb +50 -0
  176. data/app/views/admin/news/_sidebar.html.erb +2 -4
  177. data/app/views/admin/news/index.html.erb +0 -1
  178. data/app/views/admin/pages/_form.html.erb +10 -30
  179. data/app/views/admin/pages/_search_bar.html.erb +1 -1
  180. data/app/views/admin/pages/edit.html.erb +1 -57
  181. data/app/views/admin/pages/index.html.erb +1 -1
  182. data/app/views/admin/pages/new.html.erb +1 -44
  183. data/app/views/admin/sessions/new.html.erb +9 -11
  184. data/app/views/admin/users/_access_control.html.erb +5 -1
  185. data/app/views/admin/users/_list.html.erb +12 -7
  186. data/app/views/layouts/admin/_header.html.erb +2 -4
  187. data/app/views/layouts/admin/_page_header.html.erb +1 -2
  188. data/app/views/layouts/admin.html.erb +1 -1
  189. data/config/locales/en.yml +0 -4
  190. data/config/routes.rb +3 -7
  191. data/db/migrate/20240126160700_add_2fa_fields.rb +5 -1
  192. data/db/migrate/20240131140700_change_email_to_citext.rb +18 -0
  193. data/db/migrate/20240201160700_remove_persistent_data.rb +7 -0
  194. data/db/migrate/20240508145300_remove_categories.rb +21 -0
  195. data/lib/pages_core/configuration/base.rb +2 -2
  196. data/lib/pages_core/templates/configuration.rb +1 -1
  197. data/lib/pages_core/templates/configuration_proxy.rb +2 -2
  198. data/lib/pages_core/templates/template_configuration.rb +11 -1
  199. data/lib/pages_core/templates.rb +6 -4
  200. data/lib/pages_core/version.rb +1 -1
  201. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +6 -7
  202. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +17 -12
  203. data/lib/rails/generators/pages_core/rspec/rspec_generator.rb +0 -2
  204. data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +3 -4
  205. metadata +95 -29
  206. data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -27
  207. data/app/controllers/admin/categories_controller.rb +0 -56
  208. data/app/controllers/concerns/pages_core/admin/persistent_params.rb +0 -75
  209. data/app/helpers/pages_core/admin/page_blocks_helper.rb +0 -66
  210. data/app/helpers/pages_core/admin/page_json_helper.rb +0 -23
  211. data/app/javascript/components/DateRangeSelect.jsx +0 -225
  212. data/app/javascript/components/PageDates.jsx +0 -73
  213. data/app/javascript/components/PageFiles.jsx +0 -25
  214. data/app/javascript/components/PageTree/types.ts +0 -15
  215. data/app/javascript/components/drag/types.ts +0 -28
  216. data/app/javascript/controllers/EditPageController.ts +0 -22
  217. data/app/javascript/controllers/MainController.ts +0 -74
  218. data/app/javascript/controllers/PageOptionsController.js +0 -67
  219. data/app/models/category.rb +0 -22
  220. data/app/models/concerns/pages_core/has_otp.rb +0 -27
  221. data/app/models/page_category.rb +0 -6
  222. data/app/views/admin/pages/_edit_content.html.erb +0 -19
  223. data/app/views/admin/pages/_edit_files.html.erb +0 -4
  224. data/app/views/admin/pages/_edit_images.html.erb +0 -4
  225. data/app/views/admin/pages/_edit_metadata.html.erb +0 -35
  226. data/app/views/admin/pages/_edit_options.html.erb +0 -91
  227. data/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -29,21 +29,6 @@ module PagesCore
29
29
  template
30
30
  end
31
31
 
32
- def unconfigured_blocks
33
- blocks = (localizations.where(locale:).pluck(:name)
34
- .map(&:to_sym) -
35
- configured_blocks) &
36
- PagesCore::Templates::TemplateConfiguration.all_blocks
37
-
38
- if block_given?
39
- blocks.each do |block_name|
40
- yield block_name, template_config.block(block_name)
41
- end
42
- end
43
-
44
- blocks
45
- end
46
-
47
32
  private
48
33
 
49
34
  def configured_blocks
@@ -69,8 +54,9 @@ module PagesCore
69
54
  end
70
55
 
71
56
  def base_template
57
+ reject_words = %w[index list archive liste arkiv]
72
58
  template.split("_")
73
- .reject { |w| %w[index list archive liste arkiv].include?(w) }
59
+ .reject { |w| reject_words.include?(w) }
74
60
  .join(" ")
75
61
  end
76
62
 
data/app/models/invite.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Invite < ApplicationRecord
4
+ include PagesCore::Emailable
4
5
  include PagesCore::HasRoles
5
6
 
6
7
  belongs_to :user
@@ -8,11 +9,6 @@ class Invite < ApplicationRecord
8
9
 
9
10
  before_validation :ensure_token
10
11
 
11
- validates :email,
12
- presence: true,
13
- format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
14
- uniqueness: { case_sensitive: false }
15
-
16
12
  validates :token, presence: true
17
13
 
18
14
  validate :user_already_exists
@@ -24,7 +20,7 @@ class Invite < ApplicationRecord
24
20
  end
25
21
 
26
22
  def user_already_exists
27
- return unless User.find_by_email(email)
23
+ return unless User.find_by(email:)
28
24
 
29
25
  errors.add(:email, :taken)
30
26
  end
@@ -31,7 +31,7 @@ class OtpSecret
31
31
  end
32
32
 
33
33
  def generate_recovery_codes
34
- 10.times.map { SecureRandom.alphanumeric(16) }
34
+ Array.new(10) { SecureRandom.alphanumeric(16) }
35
35
  end
36
36
 
37
37
  def provisioning_uri
@@ -58,7 +58,7 @@ class OtpSecret
58
58
  end
59
59
 
60
60
  def validate_otp_or_recovery_code!(code)
61
- if code =~ /^[\d]{6}$/
61
+ if /^[\d]{6}$/.match?(code)
62
62
  validate_otp!(code)
63
63
  else
64
64
  validate_recovery_code!(code)
@@ -81,7 +81,7 @@ class OtpSecret
81
81
  end
82
82
 
83
83
  def totp
84
- ROTP::TOTP.new(secret)
84
+ ROTP::TOTP.new(secret, issuer: PagesCore.config.site_name)
85
85
  end
86
86
 
87
87
  def valid_otp?(otp)
@@ -93,7 +93,7 @@ class OtpSecret
93
93
  end
94
94
 
95
95
  def verify_secret(signed)
96
- payload = message_verifier.verify(signed)
96
+ payload = message_verifier.verify(signed).symbolize_keys
97
97
  raise "Wrong user" unless payload[:user_id] == user.id
98
98
 
99
99
  payload[:secret]
data/app/models/page.rb CHANGED
@@ -25,9 +25,6 @@ class Page < ApplicationRecord
25
25
  optional: true,
26
26
  inverse_of: :pages
27
27
 
28
- has_many :page_categories, dependent: :destroy
29
- has_many :categories, through: :page_categories
30
-
31
28
  validates(:unique_name,
32
29
  format: { with: /\A[\w\d_-]+\z/,
33
30
  allow_blank: true },
data/app/models/user.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class User < ApplicationRecord
4
- include PagesCore::HasOtp
4
+ include PagesCore::AuthenticableUser
5
+ include PagesCore::Emailable
5
6
  include PagesCore::HasRoles
6
7
 
7
- has_secure_password
8
-
9
8
  belongs_to(:creator,
10
9
  class_name: "User",
11
10
  foreign_key: "created_by",
@@ -21,50 +20,14 @@ class User < ApplicationRecord
21
20
  has_many :invites, dependent: :destroy
22
21
  belongs_to_image :image, foreign_key: :image_id, optional: true
23
22
 
24
- serialize :persistent_data
25
-
26
23
  validates :name, presence: true
27
24
 
28
- validates :email,
29
- presence: true,
30
- format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
31
- uniqueness: { case_sensitive: false }
32
-
33
- validates :password,
34
- length: {
35
- minimum: 8,
36
- maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED,
37
- allow_blank: true
38
- }
39
-
40
- before_save :update_session_token
41
25
  before_create :ensure_first_user_has_all_roles
42
26
 
43
27
  scope :by_name, -> { order("name ASC") }
44
28
  scope :activated, -> { by_name.includes(:roles).where(activated: true) }
45
29
  scope :deactivated, -> { by_name.includes(:roles).where(activated: false) }
46
30
 
47
- class << self
48
- def authenticate(email, password:)
49
- User.find_by_email(email).try(:authenticate, password)
50
- end
51
-
52
- def find_by_email(str)
53
- find_by("LOWER(email) = ?", str.to_s.downcase.strip)
54
- end
55
- end
56
-
57
- def authenticate!(password)
58
- return false unless can_login? && valid_password?(password)
59
-
60
- rehash_password!(password) if password_needs_rehash?
61
- true
62
- end
63
-
64
- def can_login?
65
- activated?
66
- end
67
-
68
31
  def mark_active!
69
32
  return if last_login_at && last_login_at > 10.minutes.ago
70
33
 
@@ -93,11 +56,4 @@ class User < ApplicationRecord
93
56
  roles.new(name: r.name) unless role?(r.name)
94
57
  end
95
58
  end
96
-
97
- def update_session_token
98
- return unless !session_token? || password_digest_changed? ||
99
- otp_enabled_changed?
100
-
101
- self.session_token = SecureRandom.hex(32)
102
- end
103
59
  end
@@ -18,7 +18,7 @@ class PagePolicy < Policy
18
18
  end
19
19
 
20
20
  def new?
21
- user.role?(:pages)
21
+ user&.role?(:pages)
22
22
  end
23
23
 
24
24
  def show?
@@ -26,7 +26,11 @@ class PagePolicy < Policy
26
26
  end
27
27
 
28
28
  def edit?
29
- user.role?(:pages)
29
+ user&.role?(:pages)
30
+ end
31
+
32
+ def edit2?
33
+ edit?
30
34
  end
31
35
 
32
36
  def move?
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Admin
4
+ class PageResource
5
+ include Alba::Resource
6
+ include Rails.application.routes.url_helpers
7
+ include PagesCore::PagePathHelper
8
+ include DynamicImage::Helper
9
+
10
+ attributes :id, :starts_at, :ends_at, :all_day, :status, :published_at,
11
+ :pinned, :template, :unique_name, :feed_enabled, :news_page,
12
+ :parent_page_id, :user_id, :redirect_to
13
+
14
+ has_many :page_images, resource: Admin::PageImageResource
15
+ has_many :page_files, resource: Admin::PageFileResource
16
+
17
+ attribute :blocks do
18
+ PagesCore::Templates::TemplateConfiguration.all_blocks
19
+ .index_with do |attr|
20
+ if object.template_config.block(attr)[:localized]
21
+ localized_attribute(object, attr)
22
+ else
23
+ object.send(attr)
24
+ end
25
+ end
26
+ end
27
+
28
+ attribute :errors do
29
+ object.errors.map do |e|
30
+ { attribute: e.attribute,
31
+ message: e.message }
32
+ end
33
+ end
34
+
35
+ attribute :urls do
36
+ if object.id?
37
+ localized_objects.filter(&:name?).each_with_object({}) do |p, obj|
38
+ obj[p.locale] = page_path(p.locale, p)
39
+ obj
40
+ end
41
+ else
42
+ {}
43
+ end
44
+ end
45
+
46
+ attribute :enabled_tags do
47
+ object.tags.map(&:name)
48
+ end
49
+
50
+ attribute :tags_and_suggestions do
51
+ Tag.tags_and_suggestions_for(object, limit: 20)
52
+ .map(&:name)
53
+ end
54
+
55
+ attribute :meta_image do
56
+ image_uploader(object.meta_image)
57
+ end
58
+
59
+ attribute :path_segment do
60
+ localized_attribute(object, :path_segment)
61
+ end
62
+
63
+ attribute :ancestors do
64
+ object.ancestors.map do |p|
65
+ { id: p.id,
66
+ name: localized_attribute(p, :name),
67
+ path_segment: localized_attribute(p, :path_segment) }
68
+ end
69
+ end
70
+
71
+ attribute :permissions do
72
+ [(:edit if Policy.for(params[:user], object).edit?),
73
+ (:create if Policy.for(params[:user], object).edit?)].compact
74
+ end
75
+
76
+ private
77
+
78
+ def image_uploader(image)
79
+ return { src: nil, image: nil } unless image
80
+
81
+ { src: dynamic_image_path(image, size: "500x"),
82
+ image: ::Admin::ImageResource.new(image).to_hash }
83
+ end
84
+
85
+ def localized_objects
86
+ object.locales.map { |l| object.localize(l) }
87
+ end
88
+
89
+ def localized_attribute(record, attr)
90
+ record.locales.index_with do |locale|
91
+ record.localize(locale).send(attr)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Admin
4
+ class PageTreeResource
5
+ include Alba::Resource
6
+
7
+ attributes :id, :parent_page_id, :status, :news_page, :pinned,
8
+ :published_at
9
+
10
+ attribute :blocks do
11
+ { name: localized_attribute(object, :name) }
12
+ end
13
+
14
+ attribute :permissions do
15
+ [(:edit if Policy.for(params[:user], object).edit?),
16
+ (:create if Policy.for(params[:user], object).edit?)].compact
17
+ end
18
+
19
+ private
20
+
21
+ def localized_attribute(record, attr)
22
+ record.locales.index_with do |locale|
23
+ record.localize(locale).send(attr)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Admin
4
+ class TemplateConfigurationResource
5
+ include Alba::Resource
6
+
7
+ attributes :name, :template_name
8
+
9
+ attribute :blocks do
10
+ object.enabled_blocks.map do |block_name|
11
+ block(block_name)
12
+ end
13
+ end
14
+
15
+ attribute :metadata_blocks do
16
+ object.metadata_blocks.map do |block_name|
17
+ block(block_name)
18
+ end
19
+ end
20
+
21
+ attribute :images do
22
+ object.value(:images) || object.value(:image)
23
+ end
24
+
25
+ %i[dates tags files].each do |attr|
26
+ attribute attr do
27
+ object.value(attr)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def block(block_name)
34
+ reify_options(object.block(block_name).merge(name: block_name))
35
+ end
36
+
37
+ def reify_options(block)
38
+ return block unless block.key?(:options)
39
+
40
+ opts = block[:options]
41
+ opts = opts.call if opts.is_a?(Proc)
42
+ unless opts.present? && opts.first.is_a?(Array)
43
+ opts = opts.map { |v| [v, v] }
44
+ end
45
+ opts = ([["", nil]] + opts).uniq
46
+
47
+ block.merge(options: opts)
48
+ end
49
+ end
50
+ end
@@ -19,9 +19,8 @@
19
19
  <h2>
20
20
  <%= link_to_unless_current(
21
21
  year,
22
- admin_news_index_path(locale, year: year, category: category&.slug)
22
+ admin_news_index_path(locale, year: year)
23
23
  ) %>
24
- <%= ": #{@category.name}" if @category %>
25
24
  <span class="count">
26
25
  (<%= year_count %>)
27
26
  </span>
@@ -37,8 +36,7 @@
37
36
  <% else %>
38
37
  <%= link_to(
39
38
  month_name(month),
40
- admin_news_index_path(locale, year: year, month: month,
41
- category: category&.slug)
39
+ admin_news_index_path(locale, year: year, month: month)
42
40
  ) %>
43
41
  <span class="count">
44
42
  (<%= page_count %>)
@@ -18,7 +18,6 @@
18
18
  <%= render(partial: "sidebar",
19
19
  locals: {
20
20
  locale: content_locale,
21
- category: @category,
22
21
  news_pages: @news_pages,
23
22
  archive_finder: @archive_finder
24
23
  }) %>
@@ -1,31 +1,11 @@
1
- <%= content_tab "Content" do %>
2
- <%= render partial: "edit_content", locals: { f: f } %>
3
- <% end %>
4
-
5
- <% if @page.unconfigured_blocks.any? %>
6
- <%= content_tab "Unconfigured content" do %>
7
- <p>
8
- This page has additional content fields not enabled by the
9
- selected template.
10
- </p>
11
- <% @page.unconfigured_blocks do |block_name, block_options| %>
12
- <%= page_block_field(f, block_name, block_options) %>
13
- <% end %>
14
- <% end %>
15
- <% end %>
16
-
17
- <% if @page.template_config.value(:images) || @page.template_config.value(:image) %>
18
- <%= content_tab "Images" do %>
19
- <%= render partial: "edit_images", locals: { f: f } %>
20
- <% end %>
21
- <% end %>
22
-
23
- <% if @page.template_config.value(:files) %>
24
- <%= content_tab "Files" do %>
25
- <%= render partial: "edit_files", locals: { f: f } %>
26
- <% end %>
27
- <% end %>
28
-
29
- <%= content_tab "Metadata" do %>
30
- <%= render partial: "edit_metadata", locals: { f: f } %>
1
+ <% content_for :main_wrapper do %>
2
+ <%= react_component(
3
+ "PageForm",
4
+ { locale: content_locale,
5
+ locales: locales_with_dir,
6
+ page: Admin::PageResource.new(page, params: { user: current_user }),
7
+ templates: PagesCore::Templates.all.map { |t| Admin::TemplateConfigurationResource.new(t) },
8
+ authors: page_authors(page).map{ |a| [a.name, a.id] },
9
+ statuses: Page.status_labels }
10
+ ) %>
31
11
  <% end %>
@@ -1,7 +1,7 @@
1
1
  <% query ||= "" %>
2
2
  <%= form_tag(search_admin_pages_path(content_locale),
3
3
  method: "get",
4
- class: "search-bar") do %>
4
+ class: "search-bar inline-form") do %>
5
5
  <%= text_field_tag(:q, query,
6
6
  placeholder: "Search all pages",
7
7
  aria: { label: "Search all pages" }) %>
@@ -5,60 +5,4 @@
5
5
  Edit page
6
6
  <% end %>
7
7
  <% end %>
8
- <% content_for :page_description do %>
9
- Editing
10
- <% @page.ancestors.reverse.each do |page| %>
11
- <%= link_to(page.name? ? page.name : tag.i("Untitled"),
12
- edit_admin_page_path(content_locale, page)) %>
13
- &raquo;
14
- <% end %>
15
- <%= link_to(@page.name? ? @page.name : tag.i("Untitled"),
16
- edit_admin_page_path(content_locale, @page)) %>
17
- <% end %>
18
-
19
- <% content_for :page_description_links do %>
20
- <%= locale_links { |l| edit_admin_page_path(l, @page.localize(l)) } %>
21
- <% end %>
22
-
23
- <% content_for :main_wrapper do %>
24
- <%= form_for(@page,
25
- url: admin_page_url(content_locale, @page),
26
- builder: PagesCore::Admin::FormBuilder,
27
- html: {
28
- class: "edit-page main-wrapper",
29
- method: :put,
30
- data: {
31
- controller: "edit-page",
32
- "edit-page-target": "form",
33
- "preview-url": preview_page_url(@page.locale, @page)
34
- }
35
- }) do |f| %>
36
-
37
- <% content_for :main do %>
38
- <div class="content">
39
- <%= render(partial: "form", locals: { f: f }) %>
40
-
41
- <div class="buttons">
42
- <button type="button"
43
- id="previewButton"
44
- data-action="click->edit-page#preview"
45
- data-url="<%= preview_page_url(@page.locale, @page) %>">
46
- Preview
47
- </button>
48
- <button type="submit">
49
- Save
50
- </button>
51
- </div>
52
- </div>
53
- <% end %>
54
-
55
- <main data-controller="main">
56
- <%= render(partial: "layouts/admin/page_header") %>
57
- <%= yield :main %>
58
- </main>
59
-
60
- <aside class="sidebar" id="page-form-sidebar">
61
- <%= render partial: 'edit_options', locals: { f: f } %>
62
- </aside>
63
- <% end %>
64
- <% end %>
8
+ <%= render(partial: "form", locals: { page: @page }) %>
@@ -16,7 +16,7 @@
16
16
  <% cache Page.visible.roots.to_a + [current_user, content_locale] do %>
17
17
  <%= react_component(
18
18
  "PageTree", {
19
- pages: @pages.map { |p| page_json(p) },
19
+ pages: @pages.map { |p| ::Admin::PageTreeResource.new(p, params: { user: current_user }) },
20
20
  locale: content_locale,
21
21
  dir: locale_direction(content_locale),
22
22
  permissions: [(:create if policy(Page).create?)] }
@@ -1,45 +1,2 @@
1
1
  <% content_for :page_title, "New page" %>
2
- <% content_for :page_description do %>
3
- <% if @page.parent %>
4
- <em><%= @page.parent.name %></em> &raquo; New Page
5
- <% else %>
6
- You are creating a new root page
7
- <% end %>
8
- <% end %>
9
-
10
- <% content_for :main_wrapper do %>
11
- <%= form_for(@page,
12
- url: admin_pages_url(content_locale),
13
- builder: PagesCore::Admin::FormBuilder,
14
- html: {
15
- class: "edit-page main-wrapper",
16
- data: {
17
- controller: "edit-page",
18
- "edit-page-target": "form"
19
- }
20
- }) do |f| %>
21
-
22
- <% content_for :main do %>
23
- <div class="content">
24
- <%= f.hidden_field "parent_page_id" if @page.parent %>
25
-
26
- <%= render(partial: "form", locals: { f: f }) %>
27
-
28
- <div class="buttons">
29
- <button type="submit">
30
- Save
31
- </button>
32
- </div>
33
- </div>
34
- <% end %>
35
-
36
- <main data-controller="main">
37
- <%= render(partial: "layouts/admin/page_header") %>
38
- <%= yield :main %>
39
- </main>
40
-
41
- <aside class="sidebar" id="page-form-sidebar">
42
- <%= render partial: "edit_options", locals: { f: f } %>
43
- </aside>
44
- <% end %>
45
- <% end %>
2
+ <%= render(partial: "form", locals: { page: @page }) %>
@@ -12,22 +12,20 @@
12
12
 
13
13
  <div class="login-form">
14
14
  <%= form_tag admin_session_path do %>
15
- <p>
15
+ <div class="field">
16
16
  <label>Email address</label>
17
17
  <%= text_field_tag(:email, "", autocomplete: "email") %>
18
- </p>
19
- <p>
18
+ </div>
19
+ <div class="field">
20
20
  <label>Password</label>
21
21
  <%= password_field_tag(:password, "", autocomplete: "current-password") %>
22
- </p>
23
- <p>
22
+ </div>
23
+ <div class="buttons">
24
24
  <button type="submit">Sign in</button>
25
+ </div>
26
+ <p>
27
+ <%= link_to("<b>Help!</b> I forgot my password!".html_safe,
28
+ new_admin_account_recovery_path) %>
25
29
  </p>
26
- <ul>
27
- <li>
28
- <%= link_to("<b>Help!</b> I forgot my password!".html_safe,
29
- new_admin_account_recovery_path) %>
30
- </li>
31
- </ul>
32
30
  <% end %>
33
31
  </div>
@@ -4,7 +4,11 @@
4
4
  </h2>
5
5
  <p>
6
6
  <% if f.object.kind_of?(User) && f.object != current_user %>
7
- <%= f.check_box :activated %> The user account is activated<br />
7
+ <%= f.check_box :activated %>
8
+ <label for="user_activated">
9
+ The user account is activated
10
+ </label>
11
+ <br />
8
12
  <% end %>
9
13
  <% Role.roles.each do |role| %>
10
14
  <%= check_box_tag("#{model_name_from_record_or_class(f.object).param_key}[role_names][]",
@@ -3,8 +3,9 @@
3
3
  <th>Name</th>
4
4
  <th>Email</th>
5
5
  <th>Can access</th>
6
+ <th>2FA</th>
6
7
  <th>Last seen</th>
7
- <th></th>
8
+ <th colspan="2"></th>
8
9
  </tr>
9
10
  <% @invites.each do |invite| %>
10
11
  <tr class="invite">
@@ -15,13 +16,14 @@
15
16
  <td>
16
17
  <%= invite.roles.map(&:to_s).sort.to_sentence %>
17
18
  </td>
19
+ <td></td>
18
20
  <td>
19
21
  <% if invite.sent_at? %>
20
22
  Invited
21
23
  <%= time_ago_in_words(invite.sent_at) %> ago
22
24
  <% end %>
23
25
  </td>
24
- <td>
26
+ <td colspan="2">
25
27
  <% if current_user.role?(:users) %>
26
28
  <%= link_to("View invite",
27
29
  admin_invite_with_token_url(invite, invite.token)) %> /
@@ -36,16 +38,14 @@
36
38
  <% end %>
37
39
  <% @users.each do |user| -%>
38
40
  <tr class="user-<%= user.id %>">
39
- <td>
40
- <strong><%= link_to user.name, admin_user_url( user ) %></strong>
41
- <% if policy(user).edit? %>
42
- (<%= link_to "edit", edit_admin_user_url( user ), class: :edit %>)
43
- <% end %>
41
+ <td class="name">
42
+ <%= link_to user.name, admin_user_url( user ) %>
44
43
  </td>
45
44
  <td>
46
45
  <%= user.email %>
47
46
  </td>
48
47
  <td><%= user.roles.map(&:to_s).sort.to_sentence %></td>
48
+ <td><%= user.otp_enabled? ? "Enabled" : "" %></td>
49
49
  <td>
50
50
  <% if user.online? -%>
51
51
  <strong>Online now</strong>
@@ -62,6 +62,11 @@
62
62
  notes.join( ", " )
63
63
  %>
64
64
  </td>
65
+ <td>
66
+ <% if policy(user).edit? %>
67
+ <%= link_to("Edit", edit_admin_user_url(user), class: :edit) %>
68
+ <% end %>
69
+ </td>
65
70
  </tr>
66
71
  <% end -%>
67
72
  </table>