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.
- 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 +672 -379
- 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 +1 -1
- 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 +2 -2
- data/app/controllers/admin/pages_controller.rb +22 -42
- 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 +0 -2
- 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 +2 -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 -12
- 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/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 +4 -4
- data/app/models/page.rb +0 -3
- data/app/models/user.rb +2 -46
- data/app/policies/page_policy.rb +6 -2
- 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/news/_sidebar.html.erb +2 -4
- data/app/views/admin/news/index.html.erb +0 -1
- 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/sessions/new.html.erb +9 -11
- data/app/views/admin/users/_access_control.html.erb +5 -1
- data/app/views/admin/users/_list.html.erb +12 -7
- data/app/views/layouts/admin/_header.html.erb +2 -4
- data/app/views/layouts/admin/_page_header.html.erb +1 -2
- data/app/views/layouts/admin.html.erb +1 -1
- data/config/locales/en.yml +0 -4
- data/config/routes.rb +3 -7
- data/db/migrate/20240126160700_add_2fa_fields.rb +5 -1
- 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/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 +95 -29
- data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -27
- data/app/controllers/admin/categories_controller.rb +0 -56
- data/app/controllers/concerns/pages_core/admin/persistent_params.rb +0 -75
- 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/MainController.ts +0 -74
- data/app/javascript/controllers/PageOptionsController.js +0 -67
- data/app/models/category.rb +0 -22
- data/app/models/concerns/pages_core/has_otp.rb +0 -27
- data/app/models/page_category.rb +0 -6
- 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/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -0,0 +1,26 @@
|
|
1
|
+
import { useState } from "react";
|
2
|
+
|
3
|
+
import * as Images from "../../types/Images";
|
4
|
+
|
5
|
+
import { useDragCollection } from "../drag";
|
6
|
+
|
7
|
+
export default function useImageGrid(
|
8
|
+
records: Images.Record[],
|
9
|
+
enablePrimary = false
|
10
|
+
): Images.GridState {
|
11
|
+
const primaryRecords = enablePrimary
|
12
|
+
? records.filter((r) => r.primary).slice(0, 1)
|
13
|
+
: [];
|
14
|
+
const imageRecords = records.filter((r) => primaryRecords.indexOf(r) === -1);
|
15
|
+
|
16
|
+
const primary = useDragCollection(primaryRecords);
|
17
|
+
const images = useDragCollection(imageRecords);
|
18
|
+
const [deleted, setDeleted] = useState<Images.Record[]>([]);
|
19
|
+
|
20
|
+
return {
|
21
|
+
primary: primary,
|
22
|
+
images: images,
|
23
|
+
deleted: deleted,
|
24
|
+
setDeleted: setDeleted
|
25
|
+
};
|
26
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as Images from "../types/Images";
|
4
|
+
import useImageGrid from "./ImageGrid/useImageGrid";
|
5
|
+
import Grid from "./ImageGrid/Grid";
|
6
|
+
|
7
|
+
interface Props extends Images.GridOptions {
|
8
|
+
records: Images.Record[];
|
9
|
+
}
|
10
|
+
|
11
|
+
export default function ImageGrid(props: Props) {
|
12
|
+
const state = useImageGrid(props.records, props.showEmbed);
|
13
|
+
|
14
|
+
return <Grid state={state} {...props} />;
|
15
|
+
}
|
@@ -1,21 +1,28 @@
|
|
1
|
-
import React, { useState } from "react";
|
2
|
-
|
3
|
-
import FileUploadButton from "./FileUploadButton";
|
1
|
+
import React, { DragEvent, MouseEvent, useState } from "react";
|
2
|
+
|
4
3
|
import useToastStore from "../stores/useToastStore";
|
5
|
-
import { ImageResource, Locale } from "../types";
|
6
4
|
import { post } from "../lib/request";
|
5
|
+
import * as Images from "../types/Images";
|
6
|
+
import { Locale } from "../types";
|
7
7
|
|
8
|
-
|
8
|
+
import EditableImage from "./EditableImage";
|
9
|
+
import FileUploadButton from "./FileUploadButton";
|
9
10
|
|
10
|
-
interface
|
11
|
+
interface Props {
|
11
12
|
locale: string;
|
12
13
|
locales: { [index: string]: Locale };
|
13
|
-
image:
|
14
|
+
image: Images.Resource;
|
14
15
|
src: string;
|
15
16
|
width: number;
|
16
17
|
caption: boolean;
|
17
18
|
attr: string;
|
18
|
-
alternative
|
19
|
+
alternative?: string;
|
20
|
+
onChange?: (state: State) => void;
|
21
|
+
}
|
22
|
+
|
23
|
+
interface State {
|
24
|
+
image?: Images.Resource;
|
25
|
+
src?: string;
|
19
26
|
}
|
20
27
|
|
21
28
|
function getFiles(dt: DataTransfer): File[] {
|
@@ -34,14 +41,23 @@ function getFiles(dt: DataTransfer): File[] {
|
|
34
41
|
return files;
|
35
42
|
}
|
36
43
|
|
37
|
-
export default function ImageUploader(props:
|
44
|
+
export default function ImageUploader(props: Props) {
|
38
45
|
const [uploading, setUploading] = useState(false);
|
39
46
|
const [dragover, setDragover] = useState(false);
|
40
|
-
const [
|
41
|
-
|
47
|
+
const [state, setState] = useState<State>({
|
48
|
+
image: props.image,
|
49
|
+
src: props.src
|
50
|
+
});
|
51
|
+
const { image, src } = props.onChange ? props : state;
|
52
|
+
|
42
53
|
const error = useToastStore((state) => state.error);
|
43
54
|
|
44
|
-
const
|
55
|
+
const update = (image: Images.Resource | null, src?: string) => {
|
56
|
+
const handler = props.onChange || setState;
|
57
|
+
handler({ image: image, src: src || null });
|
58
|
+
};
|
59
|
+
|
60
|
+
const handleDragOver = (evt: DragEvent) => {
|
45
61
|
evt.preventDefault();
|
46
62
|
setDragover(true);
|
47
63
|
};
|
@@ -50,7 +66,7 @@ export default function ImageUploader(props: ImageUploaderProps) {
|
|
50
66
|
setDragover(false);
|
51
67
|
};
|
52
68
|
|
53
|
-
const handleDragEnd = (evt:
|
69
|
+
const handleDragEnd = (evt: DragEvent) => {
|
54
70
|
if ("dataTransfer" in evt) {
|
55
71
|
if ("items" in evt.dataTransfer && "remove" in evt.dataTransfer.items) {
|
56
72
|
for (let i = 0; i < evt.dataTransfer.items.length; i++) {
|
@@ -63,7 +79,7 @@ export default function ImageUploader(props: ImageUploaderProps) {
|
|
63
79
|
setDragover(false);
|
64
80
|
};
|
65
81
|
|
66
|
-
const handleDrop = (evt:
|
82
|
+
const handleDrop = (evt: DragEvent) => {
|
67
83
|
let files: File[] = [];
|
68
84
|
if ("dataTransfer" in evt) {
|
69
85
|
files = getFiles(evt.dataTransfer);
|
@@ -74,10 +90,9 @@ export default function ImageUploader(props: ImageUploaderProps) {
|
|
74
90
|
}
|
75
91
|
};
|
76
92
|
|
77
|
-
const handleRemove = (evt:
|
93
|
+
const handleRemove = (evt: MouseEvent) => {
|
78
94
|
evt.preventDefault();
|
79
|
-
|
80
|
-
setSrc(null);
|
95
|
+
update(null);
|
81
96
|
};
|
82
97
|
|
83
98
|
const receiveFiles = (files: File[]) => {
|
@@ -108,8 +123,7 @@ export default function ImageUploader(props: ImageUploaderProps) {
|
|
108
123
|
|
109
124
|
const data = new FormData();
|
110
125
|
|
111
|
-
|
112
|
-
setSrc(null);
|
126
|
+
update(null);
|
113
127
|
setDragover(false);
|
114
128
|
setUploading(true);
|
115
129
|
|
@@ -118,13 +132,12 @@ export default function ImageUploader(props: ImageUploaderProps) {
|
|
118
132
|
data.append(`image[alternative][${l}]`, props.alternative || "");
|
119
133
|
});
|
120
134
|
|
121
|
-
void post("/admin/images.json", data).then((response:
|
135
|
+
void post("/admin/images.json", data).then((response: Images.Response) => {
|
122
136
|
setUploading(false);
|
123
137
|
if ("status" in response && response.status === "error") {
|
124
138
|
error(`Error uploading image: ${response.error}`);
|
125
139
|
} else if ("thumbnail_url" in response) {
|
126
|
-
|
127
|
-
setImage(response);
|
140
|
+
update(response, response.thumbnail_url);
|
128
141
|
}
|
129
142
|
});
|
130
143
|
};
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
interface LabelledFieldProps {
|
4
|
+
label: string;
|
5
|
+
children: React.ReactNode;
|
6
|
+
htmlFor?: string;
|
7
|
+
description?: string;
|
8
|
+
errors?: string[];
|
9
|
+
}
|
10
|
+
|
11
|
+
export default function LabelledField(props: LabelledFieldProps) {
|
12
|
+
const { htmlFor, description, label, errors, children } = props;
|
13
|
+
|
14
|
+
const classNames = ["field"];
|
15
|
+
if (errors && errors.length > 0) {
|
16
|
+
classNames.push("field-with-errors");
|
17
|
+
}
|
18
|
+
|
19
|
+
return (
|
20
|
+
<div className={classNames.join(" ")}>
|
21
|
+
<label htmlFor={htmlFor}>
|
22
|
+
{label}
|
23
|
+
{errors && (
|
24
|
+
<React.Fragment>
|
25
|
+
{" "}
|
26
|
+
<span className="error">{errors[errors.length - 1]}</span>
|
27
|
+
</React.Fragment>
|
28
|
+
)}
|
29
|
+
</label>
|
30
|
+
{description && <p className="description">{description}</p>}
|
31
|
+
{children}
|
32
|
+
</div>
|
33
|
+
);
|
34
|
+
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, { useEffect } from "react";
|
1
|
+
import React, { MouseEvent, useEffect } from "react";
|
2
2
|
|
3
3
|
import useModalStore from "../stores/useModalStore";
|
4
4
|
|
@@ -6,7 +6,7 @@ export default function Modal() {
|
|
6
6
|
const component = useModalStore((state) => state.component);
|
7
7
|
const close = useModalStore((state) => state.close);
|
8
8
|
|
9
|
-
const handleClose = (evt:
|
9
|
+
const handleClose = (evt: KeyboardEvent | MouseEvent) => {
|
10
10
|
evt.stopPropagation();
|
11
11
|
evt.preventDefault();
|
12
12
|
close();
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import React, { ChangeEvent } from "react";
|
2
|
+
|
3
|
+
import * as Template from "../../types/Template";
|
4
|
+
|
5
|
+
import LabelledField from "../LabelledField";
|
6
|
+
import RichTextArea from "../RichTextArea";
|
7
|
+
|
8
|
+
interface Props {
|
9
|
+
block: Template.Block;
|
10
|
+
errors: string[];
|
11
|
+
onChange: (value: string) => void;
|
12
|
+
lang: string;
|
13
|
+
dir: string;
|
14
|
+
value: string;
|
15
|
+
}
|
16
|
+
|
17
|
+
export default function Block(props: Props) {
|
18
|
+
const { block, errors, onChange, lang, dir, value } = props;
|
19
|
+
|
20
|
+
const handleChange = (
|
21
|
+
evt: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLSelectElement>
|
22
|
+
) => {
|
23
|
+
onChange(evt.target.value);
|
24
|
+
};
|
25
|
+
|
26
|
+
const id = `page_${block.name}`;
|
27
|
+
|
28
|
+
const commonOptions = {
|
29
|
+
id: id,
|
30
|
+
name: `page[${block.name}]`,
|
31
|
+
value: value
|
32
|
+
};
|
33
|
+
|
34
|
+
const textFieldOptions = {
|
35
|
+
...commonOptions,
|
36
|
+
className: ["rich", block.class].join(" ").trim(),
|
37
|
+
lang: lang,
|
38
|
+
dir: dir,
|
39
|
+
placeholder: block.placeholder
|
40
|
+
};
|
41
|
+
|
42
|
+
let field: React.ReactNode;
|
43
|
+
if (block.type == "select") {
|
44
|
+
const options = block.options;
|
45
|
+
|
46
|
+
// Ensure the current value is part of the options
|
47
|
+
if (options.map((o) => o[1]).indexOf(value) === -1) {
|
48
|
+
options.push([value, value]);
|
49
|
+
}
|
50
|
+
|
51
|
+
field = (
|
52
|
+
<select onChange={handleChange} {...commonOptions}>
|
53
|
+
{options.map((opt) => (
|
54
|
+
<option key={opt[1]} value={opt[1]}>
|
55
|
+
{opt[0]}
|
56
|
+
</option>
|
57
|
+
))}
|
58
|
+
</select>
|
59
|
+
);
|
60
|
+
} else if (block.size == "field") {
|
61
|
+
field = <input type="text" onChange={handleChange} {...textFieldOptions} />;
|
62
|
+
} else {
|
63
|
+
field = (
|
64
|
+
<RichTextArea
|
65
|
+
onChange={onChange}
|
66
|
+
rows={block.size == "large" ? 15 : 5}
|
67
|
+
{...textFieldOptions}
|
68
|
+
/>
|
69
|
+
);
|
70
|
+
}
|
71
|
+
|
72
|
+
return (
|
73
|
+
<LabelledField
|
74
|
+
htmlFor={id}
|
75
|
+
label={block.title}
|
76
|
+
description={block.description}
|
77
|
+
errors={errors}>
|
78
|
+
{field}
|
79
|
+
</LabelledField>
|
80
|
+
);
|
81
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
import * as Tags from "../../types/Tags";
|
5
|
+
import { MaybeLocalizedValue } from "../../types";
|
6
|
+
|
7
|
+
import { blockValue, errorsOn } from "./usePage";
|
8
|
+
import LabelledField from "../LabelledField";
|
9
|
+
import { default as TagEditor } from "../TagEditor/Editor";
|
10
|
+
import Block from "./Block";
|
11
|
+
import Dates from "./Dates";
|
12
|
+
|
13
|
+
interface Props {
|
14
|
+
state: PageEditor.State;
|
15
|
+
dispatch: (action: PageEditor.Action) => void;
|
16
|
+
tagsState: Tags.State;
|
17
|
+
tagsDispatch: (action: Tags.Action) => void;
|
18
|
+
}
|
19
|
+
|
20
|
+
export default function Content(props: Props) {
|
21
|
+
const { state, dispatch, tagsState, tagsDispatch } = props;
|
22
|
+
|
23
|
+
const { page, locale, inputDir, templateConfig } = state;
|
24
|
+
|
25
|
+
const handleChange = (attr: string) => (value: MaybeLocalizedValue) => {
|
26
|
+
dispatch({ type: "updateBlocks", payload: { [attr]: value } });
|
27
|
+
};
|
28
|
+
|
29
|
+
return (
|
30
|
+
<React.Fragment>
|
31
|
+
{templateConfig.blocks.map((b) => (
|
32
|
+
<Block
|
33
|
+
key={b.name}
|
34
|
+
block={b}
|
35
|
+
errors={errorsOn(page, b.name)}
|
36
|
+
dir={inputDir}
|
37
|
+
lang={locale}
|
38
|
+
onChange={handleChange(b.name)}
|
39
|
+
value={blockValue(state, b)}
|
40
|
+
/>
|
41
|
+
))}
|
42
|
+
{templateConfig.dates && <Dates state={state} dispatch={dispatch} />}
|
43
|
+
{templateConfig.tags && (
|
44
|
+
<LabelledField label="Tags">
|
45
|
+
<TagEditor
|
46
|
+
name="page[serialized_tags]"
|
47
|
+
state={tagsState}
|
48
|
+
dispatch={tagsDispatch}
|
49
|
+
/>
|
50
|
+
</LabelledField>
|
51
|
+
)}
|
52
|
+
</React.Fragment>
|
53
|
+
);
|
54
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
|
5
|
+
import DateRangeSelect from "../DateRangeSelect";
|
6
|
+
|
7
|
+
interface Props {
|
8
|
+
state: PageEditor.State;
|
9
|
+
dispatch: (action: PageEditor.Action) => void;
|
10
|
+
}
|
11
|
+
|
12
|
+
export default function Dates(props: Props) {
|
13
|
+
const { state, dispatch } = props;
|
14
|
+
const { datesEnabled, page } = state;
|
15
|
+
|
16
|
+
const toggleAllDay = () => {
|
17
|
+
dispatch({ type: "update", payload: { all_day: !page.all_day } });
|
18
|
+
};
|
19
|
+
|
20
|
+
const toggleDatesEnabled = () => {
|
21
|
+
dispatch({ type: "setDatesEnabled", payload: !datesEnabled });
|
22
|
+
};
|
23
|
+
|
24
|
+
const setDate = (attr: "starts_at" | "ends_at") => (date: Date) => {
|
25
|
+
dispatch({ type: "update", payload: { [attr]: date } });
|
26
|
+
};
|
27
|
+
|
28
|
+
return (
|
29
|
+
<div className="page-dates field">
|
30
|
+
<input
|
31
|
+
type="hidden"
|
32
|
+
name="page[all_day]"
|
33
|
+
value={datesEnabled && page.all_day ? "1" : "0"}
|
34
|
+
/>
|
35
|
+
<label>Dates</label>
|
36
|
+
<div className="toggles">
|
37
|
+
<label className="has-dates-toggle">
|
38
|
+
<input
|
39
|
+
type="checkbox"
|
40
|
+
checked={datesEnabled}
|
41
|
+
onChange={toggleDatesEnabled}
|
42
|
+
/>
|
43
|
+
Enabled
|
44
|
+
</label>
|
45
|
+
<label className={!datesEnabled && "disabled"}>
|
46
|
+
<input
|
47
|
+
type="checkbox"
|
48
|
+
disabled={!datesEnabled}
|
49
|
+
checked={page.all_day}
|
50
|
+
onChange={toggleAllDay}
|
51
|
+
/>
|
52
|
+
All day event
|
53
|
+
</label>
|
54
|
+
</div>
|
55
|
+
<DateRangeSelect
|
56
|
+
objectName="page"
|
57
|
+
startsAt={page.starts_at}
|
58
|
+
setStartsAt={setDate("starts_at")}
|
59
|
+
endsAt={page.ends_at}
|
60
|
+
setEndsAt={setDate("ends_at")}
|
61
|
+
disabled={!datesEnabled}
|
62
|
+
disableTime={page.all_day}
|
63
|
+
/>
|
64
|
+
</div>
|
65
|
+
);
|
66
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as Attachments from "../../types/Attachments";
|
4
|
+
import { Locale } from "../../types";
|
5
|
+
|
6
|
+
import List from "../Attachments/List";
|
7
|
+
|
8
|
+
interface Props {
|
9
|
+
locale: string;
|
10
|
+
locales: { [index: string]: Locale };
|
11
|
+
state: Attachments.State;
|
12
|
+
}
|
13
|
+
|
14
|
+
export default function Files(props: Props) {
|
15
|
+
const { locale, locales, state } = props;
|
16
|
+
|
17
|
+
return (
|
18
|
+
<div className="page-files">
|
19
|
+
<List
|
20
|
+
attribute="page[page_files_attributes]"
|
21
|
+
showEmbed={true}
|
22
|
+
locale={locale}
|
23
|
+
locales={locales}
|
24
|
+
state={state}
|
25
|
+
/>
|
26
|
+
</div>
|
27
|
+
);
|
28
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import { csrfToken } from "../../lib/request";
|
4
|
+
import * as PageEditor from "../../types/PageEditor";
|
5
|
+
|
6
|
+
interface FormProps {
|
7
|
+
state: PageEditor.State;
|
8
|
+
children: React.ReactNode;
|
9
|
+
}
|
10
|
+
|
11
|
+
function pageUrl(state: PageEditor.State): string {
|
12
|
+
if (state.page.id) {
|
13
|
+
return `/admin/${state.locale}/pages/${state.page.id}`;
|
14
|
+
} else {
|
15
|
+
return `/admin/${state.locale}/pages`;
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
export default function Form(props: FormProps) {
|
20
|
+
const { state, children } = props;
|
21
|
+
const { page } = state;
|
22
|
+
|
23
|
+
return (
|
24
|
+
<form
|
25
|
+
className="edit-page main-wrapper"
|
26
|
+
method="post"
|
27
|
+
acceptCharset="UTF-8"
|
28
|
+
action={pageUrl(state)}>
|
29
|
+
{page.id && (
|
30
|
+
<input type="hidden" name="_method" value="put" autoComplete="off" />
|
31
|
+
)}
|
32
|
+
<input
|
33
|
+
type="hidden"
|
34
|
+
autoComplete="off"
|
35
|
+
name="authenticity_token"
|
36
|
+
value={csrfToken()}
|
37
|
+
/>
|
38
|
+
{children}
|
39
|
+
</form>
|
40
|
+
);
|
41
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import { GridState } from "../../types/Images";
|
4
|
+
import { Locale } from "../../types";
|
5
|
+
|
6
|
+
import Grid from "../ImageGrid/Grid";
|
7
|
+
|
8
|
+
interface Props {
|
9
|
+
locale: string;
|
10
|
+
locales: { [index: string]: Locale };
|
11
|
+
state: GridState;
|
12
|
+
}
|
13
|
+
|
14
|
+
export default function Images(props: Props) {
|
15
|
+
return (
|
16
|
+
<div className="page-images">
|
17
|
+
<Grid
|
18
|
+
attribute="page[page_images_attributes]"
|
19
|
+
primaryAttribute="page[image_id]"
|
20
|
+
enablePrimary={true}
|
21
|
+
showEmbed={true}
|
22
|
+
locale={props.locale}
|
23
|
+
locales={props.locales}
|
24
|
+
state={props.state}
|
25
|
+
/>
|
26
|
+
</div>
|
27
|
+
);
|
28
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
|
5
|
+
interface Props {
|
6
|
+
state: PageEditor.State;
|
7
|
+
dispatch: (action: PageEditor.Action) => void;
|
8
|
+
}
|
9
|
+
|
10
|
+
export default function LocaleLinks(props: Props) {
|
11
|
+
const { state, dispatch } = props;
|
12
|
+
const { locale, locales } = state;
|
13
|
+
|
14
|
+
const handleClick = (newLocale: string) => (evt: React.MouseEvent) => {
|
15
|
+
evt.preventDefault();
|
16
|
+
dispatch({ type: "setLocale", payload: newLocale });
|
17
|
+
};
|
18
|
+
|
19
|
+
if (!locales) {
|
20
|
+
return "";
|
21
|
+
}
|
22
|
+
|
23
|
+
return (
|
24
|
+
<div className="links">
|
25
|
+
{Object.keys(locales).map((l) => (
|
26
|
+
<a
|
27
|
+
key={l}
|
28
|
+
className={locale == l ? "current" : ""}
|
29
|
+
href="#"
|
30
|
+
onClick={handleClick(l)}>
|
31
|
+
{locales[l].name}
|
32
|
+
</a>
|
33
|
+
))}
|
34
|
+
</div>
|
35
|
+
);
|
36
|
+
}
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import React from "react";
|
2
|
+
|
3
|
+
import * as PageEditor from "../../types/PageEditor";
|
4
|
+
import * as Pages from "../../types/Pages";
|
5
|
+
import { MaybeLocalizedValue } from "../../types";
|
6
|
+
|
7
|
+
import { blockValue, errorsOn } from "./usePage";
|
8
|
+
import Block from "./Block";
|
9
|
+
import PathSegment from "./PathSegment";
|
10
|
+
import LabelledField from "../LabelledField";
|
11
|
+
import ImageUploader from "../ImageUploader";
|
12
|
+
|
13
|
+
interface Props {
|
14
|
+
state: PageEditor.State;
|
15
|
+
dispatch: (action: PageEditor.Action) => void;
|
16
|
+
}
|
17
|
+
|
18
|
+
export default function Metadata(props: Props) {
|
19
|
+
const { state, dispatch } = props;
|
20
|
+
|
21
|
+
const { page, locale, locales, inputDir, templateConfig } = state;
|
22
|
+
|
23
|
+
const handleChange = (attr: string) => (value: MaybeLocalizedValue) => {
|
24
|
+
dispatch({ type: "updateBlocks", payload: { [attr]: value } });
|
25
|
+
};
|
26
|
+
|
27
|
+
const handleMetaImage = (value: Pages.MetaImage) => {
|
28
|
+
dispatch({ type: "update", payload: { meta_image: value } });
|
29
|
+
};
|
30
|
+
|
31
|
+
return (
|
32
|
+
<React.Fragment>
|
33
|
+
<PathSegment state={state} dispatch={dispatch} />
|
34
|
+
<LabelledField
|
35
|
+
htmlFor="page_meta_image_id"
|
36
|
+
label="Image"
|
37
|
+
description={
|
38
|
+
"Image displayed when sharing on social media. " +
|
39
|
+
"Will fall back to the primary image if absent. " +
|
40
|
+
"Recommended size is at least 1200x630."
|
41
|
+
}
|
42
|
+
errors={errorsOn(page, "meta_image_id")}>
|
43
|
+
<ImageUploader
|
44
|
+
attr="page[meta_image_id]"
|
45
|
+
locale={locale}
|
46
|
+
locales={locales}
|
47
|
+
image={page.meta_image.image}
|
48
|
+
src={page.meta_image.src}
|
49
|
+
onChange={handleMetaImage}
|
50
|
+
width={250}
|
51
|
+
caption={false}
|
52
|
+
/>
|
53
|
+
</LabelledField>
|
54
|
+
{templateConfig.metadata_blocks.map((b) => (
|
55
|
+
<Block
|
56
|
+
key={b.name}
|
57
|
+
block={b}
|
58
|
+
errors={errorsOn(page, b.name)}
|
59
|
+
dir={inputDir}
|
60
|
+
lang={locale}
|
61
|
+
onChange={handleChange(b.name)}
|
62
|
+
value={blockValue(state, b)}
|
63
|
+
/>
|
64
|
+
))}
|
65
|
+
</React.Fragment>
|
66
|
+
);
|
67
|
+
}
|