pages_core 3.13.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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/app/assets/builds/pages_core/admin-dist.js +19 -8
- data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
- data/app/assets/builds/pages_core/admin.css +704 -388
- data/app/assets/fonts/Inter-Black.woff2 +0 -0
- data/app/assets/fonts/Inter-BlackItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Bold.woff2 +0 -0
- data/app/assets/fonts/Inter-BoldItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraBold.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraBoldItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraLight.woff2 +0 -0
- data/app/assets/fonts/Inter-ExtraLightItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Italic.woff2 +0 -0
- data/app/assets/fonts/Inter-Light.woff2 +0 -0
- data/app/assets/fonts/Inter-LightItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Medium.woff2 +0 -0
- data/app/assets/fonts/Inter-MediumItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Regular.woff2 +0 -0
- data/app/assets/fonts/Inter-SemiBold.woff2 +0 -0
- data/app/assets/fonts/Inter-SemiBoldItalic.woff2 +0 -0
- data/app/assets/fonts/Inter-Thin.woff2 +0 -0
- data/app/assets/fonts/Inter-ThinItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Black.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-BlackItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Bold.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-BoldItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraBold.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraBoldItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraLight.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ExtraLightItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Italic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Light.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-LightItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Medium.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-MediumItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Regular.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-SemiBold.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-SemiBoldItalic.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-Thin.woff2 +0 -0
- data/app/assets/fonts/InterDisplay-ThinItalic.woff2 +0 -0
- data/app/assets/fonts/InterVariable-Italic.woff2 +0 -0
- data/app/assets/fonts/InterVariable.woff2 +0 -0
- data/app/assets/stylesheets/pages_core/admin/components/archive.css +1 -1
- data/app/assets/stylesheets/pages_core/admin/components/attachments.css +22 -34
- data/app/assets/stylesheets/pages_core/admin/components/base.css +1 -68
- data/app/assets/stylesheets/pages_core/admin/components/forms.css +107 -48
- data/app/assets/stylesheets/pages_core/admin/components/header.css +56 -58
- data/app/assets/stylesheets/pages_core/admin/components/image_editor.css +35 -24
- data/app/assets/stylesheets/pages_core/admin/components/image_grid.css +28 -27
- data/app/assets/stylesheets/pages_core/admin/components/image_uploader.css +5 -5
- data/app/assets/stylesheets/pages_core/admin/components/layout.css +7 -1
- data/app/assets/stylesheets/pages_core/admin/components/list_table.css +24 -15
- data/app/assets/stylesheets/pages_core/admin/components/page_tree.css +63 -104
- data/app/assets/stylesheets/pages_core/admin/components/pagination.css +12 -13
- data/app/assets/stylesheets/pages_core/admin/components/search.css +1 -16
- data/app/assets/stylesheets/pages_core/admin/components/sidebar.css +5 -11
- data/app/assets/stylesheets/pages_core/admin/components/tag_editor.css +22 -36
- data/app/assets/stylesheets/pages_core/admin/components/toast.css +1 -2
- data/app/assets/stylesheets/pages_core/admin/components/toolbar.css +10 -10
- data/app/assets/stylesheets/pages_core/admin/components/totp.css +26 -0
- data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +37 -51
- data/app/assets/stylesheets/pages_core/admin/global/fonts.css +271 -0
- data/app/assets/stylesheets/pages_core/admin/global/typography.css +109 -0
- data/app/assets/stylesheets/pages_core/admin/vars.css +1 -3
- data/app/assets/stylesheets/pages_core/admin.postcss.css +1 -0
- data/app/controllers/admin/account_recoveries_controller.rb +87 -0
- data/app/controllers/admin/invites_controller.rb +3 -2
- data/app/controllers/admin/otp_secrets_controller.rb +45 -0
- data/app/controllers/admin/pages_controller.rb +22 -42
- data/app/controllers/admin/recovery_codes_controller.rb +32 -0
- data/app/controllers/admin/sessions_controller.rb +65 -0
- data/app/controllers/admin/users_controller.rb +2 -8
- data/app/controllers/concerns/pages_core/authentication.rb +12 -10
- data/app/controllers/concerns/pages_core/error_reporting.rb +1 -1
- data/app/controllers/concerns/pages_core/page_parameters.rb +29 -0
- data/app/controllers/concerns/pages_core/policies_helper.rb +1 -1
- data/app/controllers/concerns/pages_core/preview_pages_controller.rb +20 -20
- data/app/controllers/pages_core/admin_controller.rb +1 -3
- data/app/controllers/pages_core/frontend/pages_controller.rb +2 -6
- data/app/formatters/pages_core/html_formatter.rb +2 -4
- data/app/helpers/admin/menu_helper.rb +5 -4
- data/app/helpers/admin/pages_helper.rb +1 -21
- data/app/helpers/pages_core/admin/admin_helper.rb +13 -3
- data/app/helpers/pages_core/admin/content_tabs_helper.rb +1 -2
- data/app/helpers/pages_core/admin/labelled_field_helper.rb +1 -1
- data/app/helpers/pages_core/frontend_helper.rb +1 -1
- data/app/helpers/pages_core/images_helper.rb +10 -8
- data/app/helpers/pages_core/labelled_form_builder.rb +2 -7
- data/app/helpers/pages_core/page_path_helper.rb +1 -1
- data/app/javascript/components/Attachments/Attachment.tsx +20 -18
- data/app/javascript/components/Attachments/AttachmentEditor.tsx +11 -9
- data/app/javascript/components/{Attachments.jsx → Attachments/List.tsx} +58 -63
- data/app/javascript/components/Attachments/useAttachments.ts +15 -0
- data/app/javascript/components/Attachments.tsx +14 -0
- data/app/javascript/components/DateRangeSelect.tsx +105 -0
- data/app/javascript/components/DateTimeSelect.tsx +136 -0
- data/app/javascript/components/EditableImage.tsx +11 -9
- data/app/javascript/components/FileUploadButton.tsx +7 -7
- data/app/javascript/components/ImageCropper/FocalPoint.tsx +9 -12
- data/app/javascript/components/ImageCropper/Image.tsx +10 -8
- data/app/javascript/components/ImageCropper/Toolbar.tsx +11 -12
- data/app/javascript/components/ImageCropper/useCrop.ts +24 -53
- data/app/javascript/components/ImageCropper.tsx +10 -15
- data/app/javascript/components/ImageEditor/Form.tsx +12 -8
- data/app/javascript/components/ImageEditor.tsx +12 -7
- data/app/javascript/components/ImageGrid/DragElement.tsx +9 -12
- data/app/javascript/components/{ImageGrid.jsx → ImageGrid/Grid.tsx} +62 -71
- data/app/javascript/components/ImageGrid/GridImage.tsx +22 -23
- data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -2
- data/app/javascript/components/ImageGrid/useImageGrid.ts +26 -0
- data/app/javascript/components/ImageGrid.tsx +15 -0
- data/app/javascript/components/ImageUploader.tsx +35 -22
- data/app/javascript/components/LabelledField.tsx +34 -0
- data/app/javascript/components/Modal.tsx +2 -2
- data/app/javascript/components/PageForm/Block.tsx +81 -0
- data/app/javascript/components/PageForm/Content.tsx +54 -0
- data/app/javascript/components/PageForm/Dates.tsx +66 -0
- data/app/javascript/components/PageForm/Files.tsx +28 -0
- data/app/javascript/components/PageForm/Form.tsx +41 -0
- data/app/javascript/components/PageForm/Images.tsx +28 -0
- data/app/javascript/components/PageForm/LocaleLinks.tsx +36 -0
- data/app/javascript/components/PageForm/Metadata.tsx +67 -0
- data/app/javascript/components/PageForm/Options.tsx +180 -0
- data/app/javascript/components/PageForm/PageDescription.tsx +48 -0
- data/app/javascript/components/PageForm/PathSegment.tsx +65 -0
- data/app/javascript/components/PageForm/TabPanel.tsx +21 -0
- data/app/javascript/components/PageForm/Tabs.tsx +33 -0
- data/app/javascript/components/PageForm/UnconfiguredContent.tsx +42 -0
- data/app/javascript/components/PageForm/pageParams.ts +95 -0
- data/app/javascript/components/PageForm/preview.ts +23 -0
- data/app/javascript/components/PageForm/usePage.ts +169 -0
- data/app/javascript/components/PageForm/useTabs.ts +46 -0
- data/app/javascript/components/PageForm.tsx +163 -0
- data/app/javascript/components/PageImages.tsx +7 -9
- data/app/javascript/components/PageTree/Draggable.tsx +40 -39
- data/app/javascript/components/PageTree/Node.tsx +62 -56
- data/app/javascript/components/PageTree/PageName.tsx +28 -0
- data/app/javascript/components/PageTree.tsx +65 -53
- data/app/javascript/components/{RichTextArea.jsx → RichTextArea.tsx} +98 -79
- data/app/javascript/components/RichTextToolbarButton.tsx +4 -6
- data/app/javascript/components/TagEditor/AddTagForm.tsx +19 -12
- data/app/javascript/components/TagEditor/Editor.tsx +32 -0
- data/app/javascript/components/TagEditor/Tag.tsx +6 -4
- data/app/javascript/components/TagEditor/useTags.ts +58 -0
- data/app/javascript/components/TagEditor.tsx +8 -58
- data/app/javascript/components/Toast.tsx +3 -3
- data/app/javascript/components/drag/draggedOrder.ts +22 -14
- data/app/javascript/components/drag/useDragCollection.ts +35 -30
- data/app/javascript/components/drag/useDragUploader.ts +32 -21
- data/app/javascript/components/drag/useDraggable.ts +7 -6
- data/app/javascript/components/drag.ts +0 -1
- data/app/javascript/components.ts +1 -3
- data/app/javascript/features/RichText.tsx +2 -3
- data/app/javascript/features/contentTabs.ts +79 -0
- data/app/javascript/index.ts +5 -14
- data/app/javascript/lib/Tree.ts +31 -45
- data/app/javascript/lib/request.ts +11 -11
- data/app/javascript/stores/useToastStore.ts +1 -1
- data/app/javascript/types/Attachments.ts +29 -0
- data/app/javascript/types/Crop.ts +36 -0
- data/app/javascript/types/Drag.ts +34 -0
- data/app/javascript/types/Images.ts +47 -0
- data/app/javascript/types/PageEditor.ts +26 -0
- data/app/javascript/types/Pages.ts +75 -0
- data/app/javascript/types/Tags.ts +9 -0
- data/app/javascript/types/Template.ts +24 -0
- data/app/javascript/types/Trees.ts +19 -0
- data/app/javascript/types.ts +2 -25
- data/app/mailers/admin_mailer.rb +2 -2
- data/app/models/attachment.rb +1 -1
- data/app/models/concerns/pages_core/authenticable_user.rb +63 -0
- data/app/models/concerns/pages_core/emailable.rb +16 -0
- data/app/models/concerns/pages_core/page_model/templateable.rb +2 -16
- data/app/models/invite.rb +2 -6
- data/app/models/otp_secret.rb +101 -0
- data/app/models/page.rb +0 -3
- data/app/models/user.rb +2 -68
- data/app/policies/page_policy.rb +6 -2
- data/app/policies/user_policy.rb +4 -0
- data/app/resources/admin/page_resource.rb +95 -0
- data/app/resources/admin/page_tree_resource.rb +27 -0
- data/app/resources/admin/template_configuration_resource.rb +50 -0
- data/app/views/admin/account_recoveries/new.html.erb +22 -0
- data/app/views/admin/account_recoveries/show.html.erb +37 -0
- data/app/views/admin/invites/show.html.erb +1 -1
- data/app/views/admin/news/_sidebar.html.erb +2 -4
- data/app/views/admin/news/index.html.erb +0 -1
- data/app/views/admin/otp_secrets/create.html.erb +7 -0
- data/app/views/admin/otp_secrets/new.html.erb +60 -0
- data/app/views/admin/pages/_form.html.erb +10 -30
- data/app/views/admin/pages/_search_bar.html.erb +1 -1
- data/app/views/admin/pages/edit.html.erb +1 -57
- data/app/views/admin/pages/index.html.erb +1 -1
- data/app/views/admin/pages/new.html.erb +1 -44
- data/app/views/admin/recovery_codes/_codes.html.erb +14 -0
- data/app/views/admin/recovery_codes/create.html.erb +7 -0
- data/app/views/admin/recovery_codes/new.html.erb +11 -0
- data/app/views/admin/sessions/_otp_form.html.erb +13 -0
- data/app/views/admin/sessions/new.html.erb +31 -0
- data/app/views/admin/sessions/verify_otp.html.erb +19 -0
- data/app/views/admin/users/_access_control.html.erb +5 -1
- data/app/views/admin/users/_list.html.erb +12 -7
- data/app/views/admin/users/edit.html.erb +31 -1
- data/app/views/admin/users/new.html.erb +1 -1
- data/app/views/admin_mailer/account_recovery.text.erb +10 -0
- data/app/views/layouts/admin/_header.html.erb +3 -5
- data/app/views/layouts/admin/_page_header.html.erb +1 -2
- data/app/views/layouts/admin/_toast.html.erb +12 -0
- data/app/views/layouts/admin.html.erb +2 -2
- data/config/locales/en.yml +11 -7
- data/config/routes.rb +13 -12
- data/db/migrate/20240126160700_add_2fa_fields.rb +26 -0
- data/db/migrate/20240129201300_remove_password_reset_tokens.rb +13 -0
- data/db/migrate/20240131140700_change_email_to_citext.rb +18 -0
- data/db/migrate/20240201160700_remove_persistent_data.rb +7 -0
- data/db/migrate/20240508145300_remove_categories.rb +21 -0
- data/lib/pages_core/configuration/base.rb +2 -2
- data/lib/pages_core/templates/configuration.rb +1 -1
- data/lib/pages_core/templates/configuration_proxy.rb +2 -2
- data/lib/pages_core/templates/template_configuration.rb +11 -1
- data/lib/pages_core/templates.rb +6 -4
- data/lib/pages_core/version.rb +1 -1
- data/lib/pages_core.rb +6 -0
- data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +6 -7
- data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +17 -12
- data/lib/rails/generators/pages_core/rspec/rspec_generator.rb +0 -2
- data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +3 -4
- metadata +143 -35
- data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -33
- data/app/controllers/admin/categories_controller.rb +0 -56
- data/app/controllers/admin/password_resets_controller.rb +0 -85
- data/app/controllers/concerns/pages_core/admin/persistent_params.rb +0 -75
- data/app/controllers/sessions_controller.rb +0 -27
- data/app/helpers/pages_core/admin/page_blocks_helper.rb +0 -66
- data/app/helpers/pages_core/admin/page_json_helper.rb +0 -23
- data/app/javascript/components/DateRangeSelect.jsx +0 -225
- data/app/javascript/components/PageDates.jsx +0 -73
- data/app/javascript/components/PageFiles.jsx +0 -25
- data/app/javascript/components/PageTree/types.ts +0 -15
- data/app/javascript/components/drag/types.ts +0 -28
- data/app/javascript/controllers/EditPageController.ts +0 -22
- data/app/javascript/controllers/LoginController.ts +0 -32
- data/app/javascript/controllers/MainController.ts +0 -74
- data/app/javascript/controllers/PageOptionsController.js +0 -67
- data/app/models/category.rb +0 -22
- data/app/models/page_category.rb +0 -6
- data/app/models/password_reset_token.rb +0 -34
- data/app/views/admin/pages/_edit_content.html.erb +0 -19
- data/app/views/admin/pages/_edit_files.html.erb +0 -4
- data/app/views/admin/pages/_edit_images.html.erb +0 -4
- data/app/views/admin/pages/_edit_metadata.html.erb +0 -35
- data/app/views/admin/pages/_edit_options.html.erb +0 -91
- data/app/views/admin/password_resets/show.html.erb +0 -21
- data/app/views/admin/users/login.html.erb +0 -65
- data/app/views/admin_mailer/password_reset.text.erb +0 -11
- data/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -1,31 +1,11 @@
|
|
1
|
-
|
2
|
-
<%=
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
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
|
-
»
|
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|
|
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
|
-
|
3
|
-
<% if @page.parent %>
|
4
|
-
<em><%= @page.parent.name %></em> » 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 }) %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<h2>
|
2
|
+
Recovery codes
|
3
|
+
</h2>
|
4
|
+
<p>
|
5
|
+
Please save the recovery codes below in a safe place, ideally
|
6
|
+
using a secure password manager.<br>
|
7
|
+
Without them, you will lose access to your account if you lose your device.
|
8
|
+
</p>
|
9
|
+
|
10
|
+
<ul class="recovery-codes">
|
11
|
+
<% recovery_codes.each do |c| %>
|
12
|
+
<li><%= c %></li>
|
13
|
+
<% end %>
|
14
|
+
</ul>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% content_for :page_title, "New recovery codes" %>
|
2
|
+
<% content_for :page_description, "Generate new recovery codes" %>
|
3
|
+
|
4
|
+
<%= form_tag(admin_recovery_codes_path, method: :post) do |f| %>
|
5
|
+
<%= render(partial: "admin/sessions/otp_form") %>
|
6
|
+
<p>
|
7
|
+
<button type="submit">
|
8
|
+
Verify
|
9
|
+
</button>
|
10
|
+
</p>
|
11
|
+
<% end %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<h2>
|
2
|
+
Two-factor authentication
|
3
|
+
</h2>
|
4
|
+
<p>
|
5
|
+
Enter a one-time code from your authenticator app to proceed.
|
6
|
+
</p>
|
7
|
+
<div class="field">
|
8
|
+
<label for="otp">6 digit code</label>
|
9
|
+
<%= text_field_tag(:otp, "",
|
10
|
+
autofocus: true,
|
11
|
+
autocomplete: "one-time-code",
|
12
|
+
size: 6) %>
|
13
|
+
</div>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<% content_for :page_title, "Sign in" %>
|
2
|
+
<% content_for(:page_description,
|
3
|
+
"Please enter your email address and password to sign in") %>
|
4
|
+
<% content_for :body_class, "login" %>
|
5
|
+
|
6
|
+
<% content_for :sidebar do %>
|
7
|
+
<h2>Please note</h2>
|
8
|
+
<p>
|
9
|
+
Please contact support if you experience problems logging in or using Pages.
|
10
|
+
</p>
|
11
|
+
<% end %>
|
12
|
+
|
13
|
+
<div class="login-form">
|
14
|
+
<%= form_tag admin_session_path do %>
|
15
|
+
<div class="field">
|
16
|
+
<label>Email address</label>
|
17
|
+
<%= text_field_tag(:email, "", autocomplete: "email") %>
|
18
|
+
</div>
|
19
|
+
<div class="field">
|
20
|
+
<label>Password</label>
|
21
|
+
<%= password_field_tag(:password, "", autocomplete: "current-password") %>
|
22
|
+
</div>
|
23
|
+
<div class="buttons">
|
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) %>
|
29
|
+
</p>
|
30
|
+
<% end %>
|
31
|
+
</div>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<% content_for :page_title, "Two-factor authentication" %>
|
2
|
+
<% content_for :page_description, "Two-factor authentication" %>
|
3
|
+
|
4
|
+
<%= form_tag(verify_otp_admin_session_path, method: :post) do |f| %>
|
5
|
+
<%= hidden_field_tag :signed_user_id, @signed_user_id %>
|
6
|
+
<%= render(partial: "admin/sessions/otp_form") %>
|
7
|
+
|
8
|
+
<p>
|
9
|
+
<button type="submit">
|
10
|
+
Verify
|
11
|
+
</button>
|
12
|
+
</p>
|
13
|
+
|
14
|
+
<p>
|
15
|
+
Lost your authenticator device?
|
16
|
+
<%= link_to("Recover your account here",
|
17
|
+
new_admin_account_recovery_path) %>.
|
18
|
+
</p>
|
19
|
+
<% end %>
|
@@ -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 %>
|
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
|
-
|
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>
|
@@ -31,12 +31,42 @@
|
|
31
31
|
<% if policy(@user).change_password? %>
|
32
32
|
<h2>Password</h2>
|
33
33
|
<%= f.labelled_password_field :password, 'Change password' %>
|
34
|
-
<%= f.labelled_password_field :
|
34
|
+
<%= f.labelled_password_field :password_confirmation, 'Confirm password' %>
|
35
35
|
<p>
|
36
36
|
Leave the password blank if you do not wish to change the password.
|
37
37
|
</p>
|
38
38
|
<% end %>
|
39
39
|
|
40
|
+
<% if policy(@user).otp? %>
|
41
|
+
<h2>Two-factor authentication</h2>
|
42
|
+
<% if @user.otp_enabled? %>
|
43
|
+
<p>
|
44
|
+
Two-factor authentication has been enabled.
|
45
|
+
<%= link_to("Disable",
|
46
|
+
admin_otp_secret_path,
|
47
|
+
class: :delete,
|
48
|
+
method: :delete,
|
49
|
+
data: { confirm: "Are you sure you want to disable 2FA?" }) %>
|
50
|
+
</p>
|
51
|
+
<p>
|
52
|
+
|
53
|
+
You have
|
54
|
+
<%= t("pages_core.recovery_codes",
|
55
|
+
count: @user.hashed_recovery_codes.length) %>
|
56
|
+
remaining.
|
57
|
+
<%= link_to("Generate new codes", new_admin_recovery_codes_path) %>
|
58
|
+
</p>
|
59
|
+
<% else %>
|
60
|
+
<p>
|
61
|
+
Protect your account with an additional layer of security by
|
62
|
+
requiring an authentication app to sign in.
|
63
|
+
</p>
|
64
|
+
<p>
|
65
|
+
<%= link_to("Enable 2FA", new_admin_otp_secret_path) %>
|
66
|
+
</p>
|
67
|
+
<% end %>
|
68
|
+
<% end %>
|
69
|
+
|
40
70
|
<%= render partial: "access_control", locals: { user: @user, f: f } %>
|
41
71
|
|
42
72
|
<p>
|
@@ -11,7 +11,7 @@
|
|
11
11
|
<%= f.labelled_text_field(:email, autocomplete: "email") %>
|
12
12
|
<%= f.labelled_password_field(:password,
|
13
13
|
autocomplete: "new-password") %>
|
14
|
-
<%= f.labelled_password_field(:
|
14
|
+
<%= f.labelled_password_field(:password_confirmation,
|
15
15
|
autocomplete: "new-password") %>
|
16
16
|
|
17
17
|
<p>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Hi, <%= @user.name %>!
|
2
|
+
|
3
|
+
We've received a request to recover your account on <%= PagesCore.config(:site_name) %>.
|
4
|
+
|
5
|
+
Please click the following link to continue:
|
6
|
+
<%= @url %>
|
7
|
+
|
8
|
+
The link will expire in 24 hours.
|
9
|
+
|
10
|
+
If you do not want to recover your password, please ignore this email.
|
@@ -1,16 +1,14 @@
|
|
1
1
|
<header>
|
2
|
-
<div class="logo">
|
3
|
-
<%= link_to image_tag("pages/admin/icon.svg"), '/admin' %>
|
4
|
-
</div>
|
5
2
|
<div class="site-name">
|
6
3
|
<h1>
|
7
|
-
<%= link_to
|
4
|
+
<%= link_to("Pages", "/admin", class: "logo") %>
|
5
|
+
<%= PagesCore.config :site_name %>
|
8
6
|
</h1>
|
9
7
|
</div>
|
10
8
|
<% if logged_in? %>
|
11
9
|
<div class="user">
|
12
10
|
Hello, <%= link_to(current_user.name, admin_user_url(current_user)) %>
|
13
|
-
<%= link_to("Log out",
|
11
|
+
<%= link_to("Log out", admin_session_path, method: "delete") %>
|
14
12
|
</div>
|
15
13
|
<% end %>
|
16
14
|
<nav class="tabs">
|
@@ -13,8 +13,7 @@
|
|
13
13
|
role="tablist">
|
14
14
|
<% content_tabs.map do |t| %>
|
15
15
|
<li id="content-tab-link-<%= t[:key] %>"
|
16
|
-
data-tab="<%= t[:key] %>"
|
17
|
-
data-main-target="link">
|
16
|
+
data-tab="<%= t[:key] %>">
|
18
17
|
<% if t[:options][:disabled] == true %>
|
19
18
|
<%= t[:name] %>
|
20
19
|
<% else %>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<%= react_component "Toast", { notice: flash[:notice], error: flash[:error] } %>
|
2
|
+
<% if Rails.env.test? && flash.any? %>
|
3
|
+
<div class="flash-test-helper">
|
4
|
+
<% %i[notice error].each do |type| %>
|
5
|
+
<% if flash[type] || true %>
|
6
|
+
<div class="<%= type %>">
|
7
|
+
<%= flash[type] %>
|
8
|
+
</div>
|
9
|
+
<% end %>
|
10
|
+
<% end %>
|
11
|
+
</div>
|
12
|
+
<% end %>
|
@@ -44,7 +44,7 @@
|
|
44
44
|
<%= yield :main_wrapper %>
|
45
45
|
<% else %>
|
46
46
|
<div class="main-wrapper">
|
47
|
-
<main
|
47
|
+
<main>
|
48
48
|
<%= render(partial: "layouts/admin/page_header") %>
|
49
49
|
<%= yield %>
|
50
50
|
</main>
|
@@ -58,6 +58,6 @@
|
|
58
58
|
<% end %>
|
59
59
|
</div>
|
60
60
|
<%= react_component "Modal", {} %>
|
61
|
-
<%=
|
61
|
+
<%= render(partial: "layouts/admin/toast") %>
|
62
62
|
</body>
|
63
63
|
</html>
|
data/config/locales/en.yml
CHANGED
@@ -19,22 +19,26 @@ en:
|
|
19
19
|
image: Profile picture
|
20
20
|
pages_core:
|
21
21
|
account_holder_exists: Account holder already exists
|
22
|
-
categories_controller:
|
23
|
-
created: New category created
|
24
|
-
deleted: Category was deleted
|
25
|
-
updated: Category was updated
|
26
22
|
changes_saved: Your changes were saved
|
27
23
|
invalid_login: >
|
28
24
|
The provided email address and password combination was not valid
|
29
25
|
invite_expired: This invite is no longer valid.
|
30
26
|
logged_out: You have been logged out
|
31
|
-
|
27
|
+
otp:
|
28
|
+
already_enabled: 2FA has already been enabled
|
29
|
+
disabled: 2FA has been disabled
|
30
|
+
invalid_code: Invalid 2FA code
|
31
|
+
required: 2FA is required for this
|
32
|
+
account_recovery:
|
32
33
|
changed: Your password has been changed
|
33
|
-
|
34
|
-
invalid_request: Invalid password reset request
|
34
|
+
invalid_request: This link is no longer valid
|
35
35
|
not_found: Couldn't find a user with that email address
|
36
36
|
sent: An email with further instructions has been sent
|
37
37
|
problems_saving: There were problems saving your changes
|
38
|
+
recovery_codes:
|
39
|
+
zero: "no recovery codes"
|
40
|
+
one: "one recovery code"
|
41
|
+
other: "%{count} recovery codes"
|
38
42
|
templates:
|
39
43
|
default:
|
40
44
|
blocks:
|
data/config/routes.rb
CHANGED
@@ -17,9 +17,7 @@ Rails.application.routes.draw do
|
|
17
17
|
collection do
|
18
18
|
get "search"
|
19
19
|
post "search"
|
20
|
-
|
21
|
-
member do
|
22
|
-
put "preview"
|
20
|
+
post "preview"
|
23
21
|
end
|
24
22
|
resources :files, controller: "page_files"
|
25
23
|
end
|
@@ -33,13 +31,12 @@ Rails.application.routes.draw do
|
|
33
31
|
get "pages/:locale/*glob" => redirect("/%{locale}/pages/%{glob}"),
|
34
32
|
locale: /\w\w\w/
|
35
33
|
|
36
|
-
# Authentication
|
37
|
-
resource :session, only: %i[create destroy]
|
38
|
-
|
39
34
|
# Sitemap
|
40
35
|
resource :sitemap, only: [:show]
|
41
36
|
|
42
37
|
namespace :admin do
|
38
|
+
get "users/login" => redirect("/admin/login")
|
39
|
+
|
43
40
|
# Invites
|
44
41
|
resources :invites do
|
45
42
|
member do
|
@@ -51,9 +48,9 @@ Rails.application.routes.draw do
|
|
51
48
|
end
|
52
49
|
|
53
50
|
# Password resets
|
54
|
-
|
55
|
-
controller :
|
56
|
-
get "/
|
51
|
+
resource :account_recovery
|
52
|
+
controller :account_recoveries do
|
53
|
+
get "/account_recovery/:token" => :show, as: :account_recovery_with_token
|
57
54
|
end
|
58
55
|
|
59
56
|
# Attachments
|
@@ -66,15 +63,19 @@ Rails.application.routes.draw do
|
|
66
63
|
resources :users do
|
67
64
|
collection do
|
68
65
|
get "deactivated"
|
69
|
-
get "login"
|
70
66
|
end
|
71
67
|
member do
|
72
68
|
delete "delete_image"
|
73
69
|
end
|
74
70
|
end
|
75
71
|
|
76
|
-
#
|
77
|
-
|
72
|
+
# Authentication
|
73
|
+
resource :session, only: %i[create destroy] do
|
74
|
+
member { post :verify_otp }
|
75
|
+
end
|
76
|
+
resource :otp_secret, only: %i[new create destroy]
|
77
|
+
resource :recovery_codes, only: %i[new create]
|
78
|
+
get "login" => "sessions#new", as: "login"
|
78
79
|
|
79
80
|
# Pages
|
80
81
|
scope ":locale" do
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Add2faFields < ActiveRecord::Migration[7.0]
|
4
|
+
class User < ApplicationRecord; end
|
5
|
+
|
6
|
+
def change
|
7
|
+
change_table :users do |t|
|
8
|
+
t.boolean :otp_enabled, null: false, default: false
|
9
|
+
t.string :otp_secret
|
10
|
+
t.datetime :last_otp_at
|
11
|
+
t.jsonb :hashed_recovery_codes, null: false, default: []
|
12
|
+
t.string :session_token
|
13
|
+
end
|
14
|
+
|
15
|
+
rename_column :users, :hashed_password, :password_digest
|
16
|
+
|
17
|
+
reversible do |dir|
|
18
|
+
dir.up do
|
19
|
+
User.find_each do |u|
|
20
|
+
u.update_columns(session_token: SecureRandom.hex(32))
|
21
|
+
end
|
22
|
+
change_column_null :users, :session_token, false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RemovePasswordResetTokens < ActiveRecord::Migration[7.0]
|
4
|
+
def change
|
5
|
+
drop_table :password_reset_tokens do |t|
|
6
|
+
t.integer :user_id
|
7
|
+
t.string :token
|
8
|
+
t.datetime :expires_at
|
9
|
+
t.datetime :created_at
|
10
|
+
t.datetime :updated_at
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ChangeEmailToCitext < ActiveRecord::Migration[7.0]
|
4
|
+
def up
|
5
|
+
enable_extension "citext"
|
6
|
+
%i[users invites].each do |t|
|
7
|
+
change_column t, :email, :citext
|
8
|
+
add_index t, :email, unique: true, name: "index_#{t}_on_email"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def down
|
13
|
+
%i[users invites].each do |t|
|
14
|
+
change_column t, :email, :string
|
15
|
+
remove_index t, name: "index_#{t}_on_email"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RemoveCategories < ActiveRecord::Migration[7.0]
|
4
|
+
def change
|
5
|
+
drop_table :page_categories do |t|
|
6
|
+
t.integer :page_id
|
7
|
+
t.integer :category_id
|
8
|
+
t.index :category_id
|
9
|
+
t.index :page_id
|
10
|
+
end
|
11
|
+
|
12
|
+
drop_table :categories do |t|
|
13
|
+
t.string :name
|
14
|
+
t.string :slug
|
15
|
+
t.integer :position
|
16
|
+
t.datetime :created_at, null: false
|
17
|
+
t.datetime :updated_at, null: false
|
18
|
+
t.index :slug
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|