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,28 +1,27 @@
|
|
1
1
|
import React, { Component } from "react";
|
2
|
-
|
2
|
+
|
3
|
+
import Tree from "../lib/Tree";
|
3
4
|
import { postJson, putJson } from "../lib/request";
|
4
|
-
import
|
5
|
-
import
|
5
|
+
import * as Trees from "../types/Trees";
|
6
|
+
import * as Pages from "../types/Pages";
|
6
7
|
|
7
|
-
|
8
|
-
parent_page_id: number | null;
|
9
|
-
}
|
8
|
+
import Draggable from "./PageTree/Draggable";
|
10
9
|
|
11
10
|
type CollapsedState = Record<number, boolean>;
|
12
11
|
|
13
12
|
interface ParentMap {
|
14
|
-
[index: number]:
|
13
|
+
[index: number]: Pages.TreeNode[];
|
15
14
|
}
|
16
15
|
|
17
|
-
interface
|
16
|
+
interface Props {
|
18
17
|
dir: string;
|
19
18
|
locale: string;
|
20
|
-
pages:
|
19
|
+
pages: Pages.TreeNode[];
|
21
20
|
permissions: string[];
|
22
21
|
}
|
23
22
|
|
24
|
-
interface
|
25
|
-
tree: Tree<
|
23
|
+
interface State {
|
24
|
+
tree: Tree<Pages.TreeNode>;
|
26
25
|
}
|
27
26
|
|
28
27
|
function collapsedState(): CollapsedState {
|
@@ -38,15 +37,15 @@ function collapsedState(): CollapsedState {
|
|
38
37
|
return {};
|
39
38
|
}
|
40
39
|
|
41
|
-
export default class PageTree extends Component<
|
42
|
-
constructor(props:
|
40
|
+
export default class PageTree extends Component<Props, State> {
|
41
|
+
constructor(props: Props) {
|
43
42
|
super(props);
|
44
43
|
|
45
|
-
this.state = { tree: this.buildTree(props.pages) };
|
44
|
+
this.state = { tree: this.buildTree(props.pages, props.locale) };
|
46
45
|
}
|
47
46
|
|
48
|
-
applyCollapsed(tree: Tree<
|
49
|
-
const depth = (t: Tree, index:
|
47
|
+
applyCollapsed(tree: Tree<Pages.TreeNode>) {
|
48
|
+
const depth = (t: Tree, index: Trees.Index) => {
|
50
49
|
let depth = 0;
|
51
50
|
let pointer = t.getIndex(index.parent);
|
52
51
|
while (pointer) {
|
@@ -56,7 +55,7 @@ export default class PageTree extends Component<PageTreeProps, PageTreeState> {
|
|
56
55
|
return depth;
|
57
56
|
};
|
58
57
|
|
59
|
-
const walk = (id:
|
58
|
+
const walk = (id: Trees.Id) => {
|
60
59
|
const index = tree.getIndex(id);
|
61
60
|
const node = index.node;
|
62
61
|
if (node.id && node.id in collapsedState()) {
|
@@ -73,30 +72,35 @@ export default class PageTree extends Component<PageTreeProps, PageTreeState> {
|
|
73
72
|
walk(1);
|
74
73
|
}
|
75
74
|
|
76
|
-
createPage(index:
|
77
|
-
void postJson(`/admin/${
|
75
|
+
createPage(index: Trees.Index<Pages.TreeNode>, attributes: Pages.TreeItem) {
|
76
|
+
void postJson(`/admin/${this.props.locale}/pages.json`, {
|
78
77
|
page: attributes
|
79
|
-
}).then((response:
|
78
|
+
}).then((response: Pages.TreeItem) => this.updateNode(index, response));
|
80
79
|
}
|
81
80
|
|
82
|
-
buildTree(pages:
|
81
|
+
buildTree(pages: Pages.TreeNode[], locale: string) {
|
83
82
|
// Build tree
|
84
|
-
const parentMap: ParentMap = pages.reduce(
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
83
|
+
const parentMap: ParentMap = pages.reduce(
|
84
|
+
(m: ParentMap, page: Pages.TreeNode) => {
|
85
|
+
const id = page.parent_page_id || 0;
|
86
|
+
m[id] = [...(m[id] || []), page];
|
87
|
+
return m;
|
88
|
+
},
|
89
|
+
{}
|
90
|
+
);
|
89
91
|
|
90
|
-
pages.forEach((p
|
92
|
+
pages.forEach((p) => {
|
91
93
|
p.children = parentMap[p.id] || [];
|
92
94
|
});
|
93
95
|
|
94
|
-
const tree = new Tree({
|
95
|
-
|
96
|
-
|
96
|
+
const tree = new Tree<Pages.TreeNode>({
|
97
|
+
blocks: {
|
98
|
+
name: { [locale]: "All Pages" }
|
99
|
+
},
|
97
100
|
permissions: this.props.permissions,
|
98
101
|
root: true,
|
99
|
-
children: parentMap[0]
|
102
|
+
children: parentMap[0],
|
103
|
+
collapsed: false
|
100
104
|
});
|
101
105
|
this.applyCollapsed(tree);
|
102
106
|
tree.updateNodesPosition();
|
@@ -104,26 +108,30 @@ export default class PageTree extends Component<PageTreeProps, PageTreeState> {
|
|
104
108
|
}
|
105
109
|
|
106
110
|
movePage(
|
107
|
-
index:
|
108
|
-
parent:
|
111
|
+
index: Trees.Index<Pages.TreeNode>,
|
112
|
+
parent: Trees.Index<Pages.TreeNode>,
|
109
113
|
position: number
|
110
114
|
) {
|
111
115
|
const data = {
|
112
116
|
parent_id: parent.node.id,
|
113
117
|
position: position
|
114
118
|
};
|
115
|
-
const url = `/admin/${
|
119
|
+
const url = `/admin/${this.props.locale}/pages/${index.node.id}/move.json`;
|
116
120
|
this.performUpdate(index, url, data);
|
117
121
|
}
|
118
122
|
|
119
|
-
performUpdate(
|
120
|
-
|
123
|
+
performUpdate(
|
124
|
+
index: Trees.Index<Pages.TreeNode>,
|
125
|
+
url: string,
|
126
|
+
data: Record<string, unknown>
|
127
|
+
) {
|
128
|
+
void putJson(url, data).then((response: Pages.TreeItem) =>
|
121
129
|
this.updateNode(index, response)
|
122
130
|
);
|
123
131
|
}
|
124
132
|
|
125
133
|
render() {
|
126
|
-
const addChild = (id:
|
134
|
+
const addChild = (id: Trees.Id, attributes: Pages.TreeNode) => {
|
127
135
|
const tree = this.state.tree;
|
128
136
|
const index = tree.append(attributes, id);
|
129
137
|
this.reorderChildren(id);
|
@@ -132,7 +140,7 @@ export default class PageTree extends Component<PageTreeProps, PageTreeState> {
|
|
132
140
|
this.setState({ tree: tree });
|
133
141
|
};
|
134
142
|
|
135
|
-
const movedPage = (id:
|
143
|
+
const movedPage = (id: Trees.Id) => {
|
136
144
|
const tree = this.state.tree;
|
137
145
|
const index = tree.getIndex(id);
|
138
146
|
this.reorderChildren(index.parent);
|
@@ -144,22 +152,27 @@ export default class PageTree extends Component<PageTreeProps, PageTreeState> {
|
|
144
152
|
this.setState({ tree: tree });
|
145
153
|
};
|
146
154
|
|
147
|
-
const toggleCollapsed = (id:
|
155
|
+
const toggleCollapsed = (id: Trees.Id) => {
|
148
156
|
const tree = this.state.tree;
|
149
157
|
const node = tree.getIndex(id).node;
|
150
158
|
this.setCollapsed(id, !node.collapsed);
|
151
159
|
this.setState({ tree: tree });
|
152
160
|
};
|
153
161
|
|
154
|
-
const updatePage = (id:
|
162
|
+
const updatePage = (id: Trees.Id, attributes: Pages.TreeItem) => {
|
155
163
|
const tree = this.state.tree;
|
156
164
|
const index = tree.getIndex(id);
|
157
|
-
const url = `/admin/${
|
165
|
+
const url = `/admin/${this.props.locale}/pages/${index.node.id}.json`;
|
158
166
|
this.updateNode(index, attributes);
|
159
|
-
|
167
|
+
|
168
|
+
const data: Record<string, unknown> = { ...attributes };
|
169
|
+
if ("blocks" in attributes && "name" in attributes.blocks) {
|
170
|
+
data.name = attributes.blocks.name[this.props.locale];
|
171
|
+
}
|
172
|
+
this.performUpdate(index, url, { page: data });
|
160
173
|
};
|
161
174
|
|
162
|
-
const updateTree = (tree: Tree) => {
|
175
|
+
const updateTree = (tree: Tree<Pages.TreeNode>) => {
|
163
176
|
this.setState({ tree: tree });
|
164
177
|
};
|
165
178
|
|
@@ -177,7 +190,7 @@ export default class PageTree extends Component<PageTreeProps, PageTreeState> {
|
|
177
190
|
);
|
178
191
|
}
|
179
192
|
|
180
|
-
reorderChildren(id:
|
193
|
+
reorderChildren(id: Trees.Id) {
|
181
194
|
const tree = this.state.tree;
|
182
195
|
const index = this.state.tree.getIndex(id);
|
183
196
|
const node = index.node;
|
@@ -188,7 +201,10 @@ export default class PageTree extends Component<PageTreeProps, PageTreeState> {
|
|
188
201
|
const aNode = tree.getIndex(a).node;
|
189
202
|
const bNode = tree.getIndex(b).node;
|
190
203
|
if (aNode.pinned == bNode.pinned) {
|
191
|
-
return
|
204
|
+
return (
|
205
|
+
new Date(bNode.published_at).getTime() -
|
206
|
+
new Date(aNode.published_at).getTime()
|
207
|
+
);
|
192
208
|
} else {
|
193
209
|
return aNode.pinned ? -1 : 1;
|
194
210
|
}
|
@@ -196,26 +212,22 @@ export default class PageTree extends Component<PageTreeProps, PageTreeState> {
|
|
196
212
|
tree.updateNodesPosition();
|
197
213
|
}
|
198
214
|
|
199
|
-
setCollapsed(id:
|
215
|
+
setCollapsed(id: Trees.Id, value: boolean) {
|
200
216
|
const node = this.state.tree.getIndex(id).node;
|
201
217
|
node.collapsed = value;
|
202
218
|
this.storeCollapsed(id, node.collapsed);
|
203
219
|
this.state.tree.updateNodesPosition();
|
204
220
|
}
|
205
221
|
|
206
|
-
storeCollapsed(id:
|
222
|
+
storeCollapsed(id: Trees.Id, newState: boolean) {
|
207
223
|
const node = this.state.tree.getIndex(id).node;
|
208
224
|
const store = collapsedState();
|
209
225
|
store[node.id] = newState;
|
210
226
|
window.localStorage.collapsedPages = JSON.stringify(store);
|
211
227
|
}
|
212
228
|
|
213
|
-
updateNode(index:
|
214
|
-
|
215
|
-
if (Object.prototype.hasOwnProperty.call(attributes, attr)) {
|
216
|
-
index.node[attr] = attributes[attr];
|
217
|
-
}
|
218
|
-
}
|
229
|
+
updateNode(index: Trees.Index<Pages.TreeNode>, attributes: Pages.TreeItem) {
|
230
|
+
index.node = { ...index.node, ...attributes };
|
219
231
|
this.setState({ tree: this.state.tree });
|
220
232
|
}
|
221
233
|
}
|
@@ -1,140 +1,152 @@
|
|
1
|
-
import React from "react";
|
2
|
-
import PropTypes from "prop-types";
|
1
|
+
import React, { createRef, ChangeEvent, Component, RefObject } from "react";
|
3
2
|
import RichTextToolbarButton from "./RichTextToolbarButton";
|
4
3
|
|
5
|
-
|
6
|
-
|
4
|
+
interface Props {
|
5
|
+
id: string;
|
6
|
+
name: string;
|
7
|
+
value: string;
|
8
|
+
rows: number;
|
9
|
+
className?: string;
|
10
|
+
simple?: boolean;
|
11
|
+
lang?: string;
|
12
|
+
dir?: string;
|
13
|
+
onChange?: (str: string) => void;
|
14
|
+
}
|
15
|
+
|
16
|
+
interface State {
|
17
|
+
value: string;
|
18
|
+
rows: number;
|
19
|
+
}
|
20
|
+
|
21
|
+
type Replacement = [string, string, string];
|
22
|
+
type ActionFn = (str: string) => Replacement;
|
23
|
+
|
24
|
+
interface Action {
|
25
|
+
name: string;
|
26
|
+
className: string;
|
27
|
+
fn: ActionFn;
|
28
|
+
hotkey?: string;
|
29
|
+
}
|
30
|
+
|
31
|
+
export default class RichTextArea extends Component<Props, State> {
|
32
|
+
inputRef: RefObject<HTMLTextAreaElement>;
|
33
|
+
|
34
|
+
constructor(props: Props) {
|
7
35
|
super(props);
|
8
36
|
this.state = {
|
9
37
|
value: props.value || "",
|
10
38
|
rows: props.rows || 5
|
11
39
|
};
|
12
|
-
this.inputRef =
|
13
|
-
this.handleChange = this.handleChange.bind(this);
|
14
|
-
this.handleKeyPress = this.handleKeyPress.bind(this);
|
15
|
-
this.getSelection = this.getSelection.bind(this);
|
16
|
-
this.link = this.link.bind(this);
|
17
|
-
this.replaceSelection = this.replaceSelection.bind(this);
|
40
|
+
this.inputRef = createRef<HTMLTextAreaElement>();
|
18
41
|
}
|
19
42
|
|
20
|
-
actions() {
|
21
|
-
const simple = [
|
43
|
+
actions = () => {
|
44
|
+
const simple: Action[] = [
|
22
45
|
{
|
23
46
|
name: "bold",
|
24
47
|
className: "bold",
|
25
48
|
hotkey: "b",
|
26
|
-
fn: (str) => ["<b>", str, "</b>"]
|
49
|
+
fn: (str: string) => ["<b>", str, "</b>"]
|
27
50
|
},
|
28
51
|
{
|
29
52
|
name: "italic",
|
30
53
|
className: "italic",
|
31
54
|
hotkey: "i",
|
32
|
-
fn: (str) => ["<i>", str, "</i>"]
|
55
|
+
fn: (str: string) => ["<i>", str, "</i>"]
|
33
56
|
}
|
34
57
|
];
|
35
58
|
|
36
|
-
const advanced = [
|
59
|
+
const advanced: Action[] = [
|
37
60
|
{
|
38
61
|
name: "Heading 2",
|
39
62
|
className: "header h2",
|
40
|
-
fn: (str) => ["h2. ", str, ""]
|
63
|
+
fn: (str: string) => ["h2. ", str, ""]
|
41
64
|
},
|
42
65
|
{
|
43
66
|
name: "Heading 3",
|
44
67
|
className: "header h3",
|
45
|
-
fn: (str) => ["h3. ", str, ""]
|
68
|
+
fn: (str: string) => ["h3. ", str, ""]
|
46
69
|
},
|
47
70
|
{
|
48
71
|
name: "Heading 4",
|
49
72
|
className: "header h4",
|
50
|
-
fn: (str) => ["h4. ", str, ""]
|
73
|
+
fn: (str: string) => ["h4. ", str, ""]
|
51
74
|
},
|
52
75
|
{
|
53
76
|
name: "Blockquote",
|
54
77
|
className: "quote-left",
|
55
|
-
fn: (str) => ["bq. ", str, ""]
|
78
|
+
fn: (str: string) => ["bq. ", str, ""]
|
56
79
|
},
|
57
80
|
{
|
58
81
|
name: "List",
|
59
82
|
className: "list-ul",
|
60
|
-
fn: (str) => ["", this.strToList(str, "*"), ""]
|
83
|
+
fn: (str: string) => ["", this.strToList(str, "*"), ""]
|
61
84
|
},
|
62
85
|
{
|
63
86
|
name: "Ordered list",
|
64
87
|
className: "list-ol",
|
65
|
-
fn: (str) => ["", this.strToList(str, "#"), ""]
|
88
|
+
fn: (str: string) => ["", this.strToList(str, "#"), ""]
|
66
89
|
},
|
67
|
-
{
|
68
|
-
|
69
|
-
className: "link",
|
70
|
-
fn: this.link
|
71
|
-
},
|
72
|
-
{
|
73
|
-
name: "Email link",
|
74
|
-
className: "envelope",
|
75
|
-
fn: this.emailLink
|
76
|
-
}
|
90
|
+
{ name: "Link", className: "link", fn: this.link },
|
91
|
+
{ name: "Email link", className: "envelope", fn: this.emailLink }
|
77
92
|
];
|
78
93
|
|
79
94
|
return this.props.simple ? simple : [...simple, ...advanced];
|
80
|
-
}
|
95
|
+
};
|
81
96
|
|
82
|
-
applyAction(fn) {
|
83
|
-
|
97
|
+
applyAction(fn: ActionFn) {
|
98
|
+
const [prefix, replacement, postfix] = fn(this.getSelection());
|
84
99
|
this.replaceSelection(prefix, replacement, postfix);
|
85
100
|
}
|
86
101
|
|
87
|
-
emailLink(selection) {
|
88
|
-
|
89
|
-
|
102
|
+
emailLink = (selection: string): Replacement => {
|
103
|
+
const address = prompt("Enter email address", "");
|
104
|
+
const name = selection.length > 0 ? selection : address;
|
90
105
|
return ['"', name, `":mailto:${address}`];
|
91
|
-
}
|
106
|
+
};
|
92
107
|
|
93
|
-
getSelection() {
|
94
|
-
|
108
|
+
getSelection = (): string => {
|
109
|
+
const { selectionStart, selectionEnd, value } = this.inputRef.current;
|
95
110
|
return value.substr(selectionStart, selectionEnd - selectionStart);
|
96
|
-
}
|
111
|
+
};
|
97
112
|
|
98
|
-
handleChange(evt) {
|
99
|
-
this.
|
100
|
-
}
|
113
|
+
handleChange = (evt: ChangeEvent<HTMLTextAreaElement>) => {
|
114
|
+
this.updateValue(evt.target.value);
|
115
|
+
};
|
101
116
|
|
102
|
-
handleKeyPress(evt) {
|
103
|
-
let key;
|
117
|
+
handleKeyPress = (evt: React.KeyboardEvent) => {
|
118
|
+
let key: string;
|
104
119
|
if (evt.which >= 65 && evt.which <= 90) {
|
105
120
|
key = String.fromCharCode(evt.keyCode).toLowerCase();
|
106
121
|
} else if (evt.keyCode === 13) {
|
107
122
|
key = "enter";
|
108
123
|
}
|
109
124
|
|
110
|
-
|
125
|
+
const hotkeys: Record<string, ActionFn> = {};
|
111
126
|
this.actions().forEach((a) => {
|
112
127
|
if (a.hotkey) {
|
113
128
|
hotkeys[a.hotkey] = a.fn;
|
114
129
|
}
|
115
130
|
});
|
116
131
|
|
117
|
-
if (
|
118
|
-
(evt.metaKey || evt.ctrlKey) &&
|
119
|
-
Object.prototype.hasOwnProperty.call(hotkeys, key)
|
120
|
-
) {
|
132
|
+
if ((evt.metaKey || evt.ctrlKey) && key in hotkeys) {
|
121
133
|
evt.preventDefault();
|
122
134
|
this.applyAction(hotkeys[key]);
|
123
135
|
}
|
124
|
-
}
|
136
|
+
};
|
125
137
|
|
126
|
-
link(selection) {
|
127
|
-
|
128
|
-
|
138
|
+
link = (selection: string): Replacement => {
|
139
|
+
const name = selection.length > 0 ? selection : "Link text";
|
140
|
+
const url = prompt("Enter link URL", "");
|
129
141
|
if (url) {
|
130
142
|
return ['"', name, `":${this.relativeUrl(url)}`];
|
131
143
|
} else {
|
132
144
|
return ["", name, ""];
|
133
145
|
}
|
134
|
-
}
|
146
|
+
};
|
135
147
|
|
136
148
|
localeOptions() {
|
137
|
-
|
149
|
+
const opts: React.HTMLProps<HTMLTextAreaElement> = {};
|
138
150
|
|
139
151
|
if (this.props.lang) {
|
140
152
|
opts.lang = this.props.lang;
|
@@ -147,8 +159,8 @@ export default class RichTextArea extends React.Component {
|
|
147
159
|
return opts;
|
148
160
|
}
|
149
161
|
|
150
|
-
relativeUrl(str) {
|
151
|
-
let url = null;
|
162
|
+
relativeUrl(str: string): string {
|
163
|
+
let url: URL = null;
|
152
164
|
|
153
165
|
if (!str.match(/^https:\/\//) || !document || !document.location) {
|
154
166
|
return str;
|
@@ -171,10 +183,11 @@ export default class RichTextArea extends React.Component {
|
|
171
183
|
}
|
172
184
|
|
173
185
|
render() {
|
174
|
-
|
175
|
-
|
186
|
+
const { rows } = this.state;
|
187
|
+
const { id, name } = this.props;
|
188
|
+
const value = this.getValue();
|
176
189
|
|
177
|
-
const clickHandler = (fn) => (evt) => {
|
190
|
+
const clickHandler = (fn: ActionFn) => (evt: React.MouseEvent) => {
|
178
191
|
evt.preventDefault();
|
179
192
|
this.applyAction(fn);
|
180
193
|
};
|
@@ -192,7 +205,7 @@ export default class RichTextArea extends React.Component {
|
|
192
205
|
))}
|
193
206
|
</div>
|
194
207
|
<textarea
|
195
|
-
className="rich"
|
208
|
+
className={this.props.className || "rich"}
|
196
209
|
ref={this.inputRef}
|
197
210
|
id={id}
|
198
211
|
name={name}
|
@@ -206,9 +219,9 @@ export default class RichTextArea extends React.Component {
|
|
206
219
|
);
|
207
220
|
}
|
208
221
|
|
209
|
-
replaceSelection(prefix, replacement, postfix) {
|
210
|
-
|
211
|
-
|
222
|
+
replaceSelection = (prefix: string, replacement: string, postfix: string) => {
|
223
|
+
const textarea = this.inputRef.current;
|
224
|
+
const { selectionStart, selectionEnd, value } = textarea;
|
212
225
|
|
213
226
|
textarea.value =
|
214
227
|
value.substr(0, selectionStart) +
|
@@ -222,23 +235,29 @@ export default class RichTextArea extends React.Component {
|
|
222
235
|
selectionStart + prefix.length,
|
223
236
|
selectionStart + prefix.length + replacement.length
|
224
237
|
);
|
225
|
-
this.
|
226
|
-
}
|
238
|
+
this.updateValue(textarea.value);
|
239
|
+
};
|
227
240
|
|
228
|
-
strToList(str, prefix) {
|
241
|
+
strToList(str: string, prefix: string) {
|
229
242
|
return str
|
230
243
|
.split("\n")
|
231
244
|
.map((l) => prefix + " " + l)
|
232
245
|
.join("\n");
|
233
246
|
}
|
234
|
-
}
|
235
247
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
248
|
+
getValue() {
|
249
|
+
if (this.props.onChange) {
|
250
|
+
return this.props.value;
|
251
|
+
} else {
|
252
|
+
return this.state.value;
|
253
|
+
}
|
254
|
+
}
|
255
|
+
|
256
|
+
updateValue(str: string) {
|
257
|
+
if (this.props.onChange) {
|
258
|
+
this.props.onChange(str);
|
259
|
+
} else {
|
260
|
+
this.setState({ value: str });
|
261
|
+
}
|
262
|
+
}
|
263
|
+
}
|
@@ -1,14 +1,12 @@
|
|
1
|
-
import React from "react";
|
1
|
+
import React, { MouseEvent } from "react";
|
2
2
|
|
3
|
-
interface
|
3
|
+
interface Props {
|
4
4
|
className: string;
|
5
5
|
name: string;
|
6
|
-
onClick: (evt:
|
6
|
+
onClick: (evt: React.MouseEvent) => void;
|
7
7
|
}
|
8
8
|
|
9
|
-
export default function RichTextToolbarButton(
|
10
|
-
props: RichTextToolbarButtonProps
|
11
|
-
) {
|
9
|
+
export default function RichTextToolbarButton(props: Props) {
|
12
10
|
return (
|
13
11
|
<a
|
14
12
|
title={props.name}
|
@@ -1,21 +1,28 @@
|
|
1
|
-
import React, { ChangeEvent, useState } from "react";
|
1
|
+
import React, { ChangeEvent, MouseEvent, KeyboardEvent, useState } from "react";
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
import * as Tags from "../../types/Tags";
|
4
|
+
|
5
|
+
interface Props {
|
6
|
+
dispatch: (action: Tags.Action) => void;
|
5
7
|
}
|
6
8
|
|
7
|
-
export default function AddTagForm(props:
|
9
|
+
export default function AddTagForm(props: Props) {
|
8
10
|
const [tag, setTag] = useState("");
|
9
11
|
|
10
|
-
const submit = (
|
11
|
-
|
12
|
-
props.addTag(tag);
|
12
|
+
const submit = () => {
|
13
|
+
props.dispatch({ type: "addTag", payload: tag });
|
13
14
|
setTag("");
|
14
15
|
};
|
15
16
|
|
16
|
-
const
|
17
|
+
const handleSubmit = (evt: MouseEvent) => {
|
18
|
+
evt.preventDefault();
|
19
|
+
submit();
|
20
|
+
};
|
21
|
+
|
22
|
+
const handleKeyDown = (evt: KeyboardEvent<HTMLInputElement>) => {
|
17
23
|
if (evt.which === 13) {
|
18
|
-
|
24
|
+
evt.preventDefault();
|
25
|
+
submit();
|
19
26
|
}
|
20
27
|
};
|
21
28
|
|
@@ -24,17 +31,17 @@ export default function AddTagForm(props: AddTagFormProps) {
|
|
24
31
|
};
|
25
32
|
|
26
33
|
return (
|
27
|
-
<div className="add-tag-form">
|
34
|
+
<div className="add-tag-form inline-form">
|
28
35
|
<input
|
29
36
|
name="add-tag"
|
30
37
|
type="text"
|
31
|
-
className="add-tag"
|
38
|
+
className="add-tag tight"
|
32
39
|
value={tag}
|
33
40
|
onKeyDown={handleKeyDown}
|
34
41
|
onChange={handleChange}
|
35
42
|
placeholder="Add tag..."
|
36
43
|
/>
|
37
|
-
<button onClick={
|
44
|
+
<button onClick={handleSubmit} disabled={!tag}>
|
38
45
|
Add
|
39
46
|
</button>
|
40
47
|
</div>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import { allTags, isEnabled } from "./useTags";
|
4
|
+
|
5
|
+
import AddTagForm from "./AddTagForm";
|
6
|
+
import Tag from "./Tag";
|
7
|
+
import * as Tags from "../../types/Tags";
|
8
|
+
|
9
|
+
interface Props {
|
10
|
+
name: string;
|
11
|
+
state: Tags.State;
|
12
|
+
dispatch: (action: Tags.Action) => void;
|
13
|
+
}
|
14
|
+
|
15
|
+
export default function Editor(props: Props) {
|
16
|
+
const { name, state, dispatch } = props;
|
17
|
+
|
18
|
+
return (
|
19
|
+
<div className="tag-editor">
|
20
|
+
<input type="hidden" name={name} value={JSON.stringify(state.enabled)} />
|
21
|
+
{allTags(state).map((t) => (
|
22
|
+
<Tag
|
23
|
+
key={t}
|
24
|
+
tag={t}
|
25
|
+
enabled={isEnabled(t, state)}
|
26
|
+
dispatch={dispatch}
|
27
|
+
/>
|
28
|
+
))}
|
29
|
+
<AddTagForm dispatch={dispatch} />
|
30
|
+
</div>
|
31
|
+
);
|
32
|
+
}
|
@@ -1,14 +1,16 @@
|
|
1
1
|
import React from "react";
|
2
2
|
|
3
|
-
|
3
|
+
import * as Tags from "../../types/Tags";
|
4
|
+
|
5
|
+
interface Props {
|
4
6
|
enabled: boolean;
|
5
7
|
tag: string;
|
6
|
-
|
8
|
+
dispatch: (action: Tags.Action) => void;
|
7
9
|
}
|
8
10
|
|
9
|
-
export default function Tag(props:
|
11
|
+
export default function Tag(props: Props) {
|
10
12
|
const handleChange = () => {
|
11
|
-
props.
|
13
|
+
props.dispatch({ type: "toggleTag", payload: props.tag });
|
12
14
|
};
|
13
15
|
|
14
16
|
const classes = ["tag"];
|