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,20 +1,20 @@
|
|
1
|
-
import React, { useRef
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import
|
6
|
-
import
|
7
|
-
import
|
8
|
-
import
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
}
|
1
|
+
import React, { useRef } from "react";
|
2
|
+
import FileUploadButton from "../FileUploadButton";
|
3
|
+
import DragElement from "./DragElement";
|
4
|
+
import FilePlaceholder from "./FilePlaceholder";
|
5
|
+
import GridImage from "./GridImage";
|
6
|
+
import useToastStore from "../../stores/useToastStore";
|
7
|
+
import { post } from "../../lib/request";
|
8
|
+
import * as Drag from "../../types/Drag";
|
9
|
+
import * as Images from "../../types/Images";
|
10
|
+
|
11
|
+
import { createDraggable, collectionOrder, useDragUploader } from "../drag";
|
12
|
+
|
13
|
+
interface Props extends Images.GridOptions {
|
14
|
+
state: Images.GridState;
|
15
|
+
}
|
16
16
|
|
17
|
-
function filterFiles(files) {
|
17
|
+
function filterFiles(files: File[]): File[] {
|
18
18
|
const validMimeTypes = [
|
19
19
|
"image/gif",
|
20
20
|
"image/jpeg",
|
@@ -25,7 +25,11 @@ function filterFiles(files) {
|
|
25
25
|
return files.filter((f) => validMimeTypes.indexOf(f.type) !== -1);
|
26
26
|
}
|
27
27
|
|
28
|
-
function draggedImageOrder(
|
28
|
+
function draggedImageOrder(
|
29
|
+
primaryCollection: Drag.Collection<Images.Record>,
|
30
|
+
imagesCollection: Drag.Collection<Images.Record>,
|
31
|
+
dragState: Drag.State<Images.Record>
|
32
|
+
): [Drag.Item<Images.Record>, Drag.Item<Images.Record>[]] {
|
29
33
|
const [primary, ...rest] = collectionOrder(primaryCollection, dragState);
|
30
34
|
let images = [...rest, ...collectionOrder(imagesCollection, dragState)];
|
31
35
|
|
@@ -45,29 +49,18 @@ function draggedImageOrder(primaryCollection, imagesCollection, dragState) {
|
|
45
49
|
return [primary, images];
|
46
50
|
}
|
47
51
|
|
48
|
-
function
|
49
|
-
const primary = props.
|
50
|
-
? props.records.filter((r) => r.primary).slice(0, 1)
|
51
|
-
: [];
|
52
|
-
|
53
|
-
return [primary, props.records.filter((r) => primary.indexOf(r) === -1)];
|
54
|
-
}
|
55
|
-
|
56
|
-
export default function ImageGrid(props) {
|
57
|
-
const [initPrimary, initImages] = initRecords(props);
|
58
|
-
const primary = useDragCollection(initPrimary);
|
59
|
-
const images = useDragCollection(initImages);
|
60
|
-
const [deleted, setDeleted] = useState([]);
|
61
|
-
const error = useToastStore((state) => state.error);
|
52
|
+
export default function Grid(props: Props) {
|
53
|
+
const { primary, images, deleted, setDeleted } = props.state;
|
62
54
|
|
63
55
|
const containerRef = useRef();
|
56
|
+
const error = useToastStore((state) => state.error);
|
64
57
|
|
65
58
|
const dispatchAll = (action) => {
|
66
59
|
primary.dispatch(action);
|
67
60
|
images.dispatch(action);
|
68
61
|
};
|
69
62
|
|
70
|
-
const dragEnd = (dragState
|
63
|
+
const dragEnd = (dragState: Drag.State<Images.Record>, files: File[]) => {
|
71
64
|
const [draggedPrimary, draggedImages] = draggedImageOrder(
|
72
65
|
primary,
|
73
66
|
images,
|
@@ -86,40 +79,44 @@ export default function ImageGrid(props) {
|
|
86
79
|
}
|
87
80
|
};
|
88
81
|
|
89
|
-
const [dragState, dragStart, listeners] = useDragUploader(
|
82
|
+
const [dragState, dragStart, listeners] = useDragUploader<Images.Record>(
|
90
83
|
[primary, images],
|
91
84
|
dragEnd
|
92
85
|
);
|
93
86
|
|
94
|
-
const position = (record) => {
|
87
|
+
const position = (record: Images.Record) => {
|
95
88
|
return (
|
96
89
|
[
|
97
|
-
...primary.draggables.map(
|
98
|
-
|
90
|
+
...primary.draggables.map(
|
91
|
+
(d: Drag.Draggable<Images.Record>) => d.record
|
92
|
+
),
|
93
|
+
...images.draggables.map(
|
94
|
+
(d: Drag.Draggable<Images.Record>) => d.record
|
95
|
+
),
|
99
96
|
...deleted
|
100
97
|
].indexOf(record) + 1
|
101
98
|
);
|
102
99
|
};
|
103
100
|
|
104
|
-
const attrName = (record) => {
|
101
|
+
const attrName = (record: Images.Record) => {
|
105
102
|
return `${props.attribute}[${position(record)}]`;
|
106
103
|
};
|
107
104
|
|
108
|
-
const uploadImage = (file) => {
|
105
|
+
const uploadImage = (file: File) => {
|
109
106
|
const draggable = createDraggable({ image: null, file: file });
|
110
107
|
|
111
|
-
|
108
|
+
const data = new FormData();
|
112
109
|
|
113
110
|
data.append("image[file]", file);
|
114
111
|
|
115
|
-
post("/admin/images.json", data).then((json) => {
|
116
|
-
if (json.status === "error") {
|
117
|
-
error(
|
112
|
+
void post("/admin/images.json", data).then((json: Images.Response) => {
|
113
|
+
if ("status" in json && json.status === "error") {
|
114
|
+
error(`Error uploading image: ${json.error}`);
|
118
115
|
dispatchAll({ type: "remove", payload: draggable });
|
119
116
|
} else {
|
120
117
|
dispatchAll({
|
121
118
|
type: "update",
|
122
|
-
payload: { ...draggable, record: { image: json } }
|
119
|
+
payload: { ...draggable, record: { image: json } } as Drag.Draggable
|
123
120
|
});
|
124
121
|
}
|
125
122
|
});
|
@@ -127,26 +124,30 @@ export default function ImageGrid(props) {
|
|
127
124
|
return draggable;
|
128
125
|
};
|
129
126
|
|
130
|
-
const update =
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
127
|
+
const update =
|
128
|
+
(draggable: Drag.Draggable<Images.Record>) => (image: Images.Resource) => {
|
129
|
+
const { record } = draggable;
|
130
|
+
const updated = {
|
131
|
+
...draggable,
|
132
|
+
record: {
|
133
|
+
...record,
|
134
|
+
image: { ...record.image, ...image }
|
135
|
+
}
|
136
|
+
};
|
137
|
+
dispatchAll({ type: "update", payload: updated });
|
138
138
|
};
|
139
|
-
dispatchAll({ type: "update", payload: updated });
|
140
|
-
};
|
141
139
|
|
142
|
-
const remove = (draggable) => () => {
|
140
|
+
const remove = (draggable: Drag.Draggable<Images.Record>) => () => {
|
143
141
|
dispatchAll({ type: "remove", payload: draggable });
|
144
142
|
if (draggable.record.id) {
|
145
143
|
setDeleted([...deleted, draggable.record]);
|
146
144
|
}
|
147
145
|
};
|
148
146
|
|
149
|
-
const renderImage = (
|
147
|
+
const renderImage = (
|
148
|
+
draggable: Drag.Item<Images.Record>,
|
149
|
+
isPrimary: boolean
|
150
|
+
) => {
|
150
151
|
const { dragging } = dragState;
|
151
152
|
|
152
153
|
if (draggable === "Files") {
|
@@ -172,7 +173,7 @@ export default function ImageGrid(props) {
|
|
172
173
|
);
|
173
174
|
};
|
174
175
|
|
175
|
-
const uploadPrimary = (files) => {
|
176
|
+
const uploadPrimary = (files: File[]) => {
|
176
177
|
const [first, ...rest] = filterFiles(files).map((f) => uploadImage(f));
|
177
178
|
if (first) {
|
178
179
|
images.dispatch({
|
@@ -183,14 +184,14 @@ export default function ImageGrid(props) {
|
|
183
184
|
}
|
184
185
|
};
|
185
186
|
|
186
|
-
const uploadAdditional = (files) => {
|
187
|
+
const uploadAdditional = (files: File[]) => {
|
187
188
|
images.dispatch({
|
188
189
|
type: "append",
|
189
190
|
payload: filterFiles(files).map((f) => uploadImage(f))
|
190
191
|
});
|
191
192
|
};
|
192
193
|
|
193
|
-
|
194
|
+
const classNames = ["image-grid"];
|
194
195
|
if (props.enablePrimary) {
|
195
196
|
classNames.push("with-primary-image");
|
196
197
|
}
|
@@ -221,7 +222,7 @@ export default function ImageGrid(props) {
|
|
221
222
|
type="hidden"
|
222
223
|
name={props.primaryAttribute}
|
223
224
|
value={
|
224
|
-
(draggedPrimary
|
225
|
+
(draggedPrimary !== "Files" &&
|
225
226
|
draggedPrimary.record.image &&
|
226
227
|
draggedPrimary.record.image.id) ||
|
227
228
|
""
|
@@ -260,14 +261,14 @@ export default function ImageGrid(props) {
|
|
260
261
|
<span className="deleted-image" key={r.id}>
|
261
262
|
<input name={`${attrName(r)}[id]`} type="hidden" value={r.id} />
|
262
263
|
<input
|
263
|
-
name={`${attrName(r)}[
|
264
|
+
name={`${attrName(r)}[attachment_id]`}
|
264
265
|
type="hidden"
|
265
266
|
value={(r.image && r.image.id) || ""}
|
266
267
|
/>
|
267
268
|
<input
|
268
269
|
name={`${attrName(r)}[_destroy]`}
|
269
270
|
type="hidden"
|
270
|
-
value={true}
|
271
|
+
value={"true"}
|
271
272
|
/>
|
272
273
|
</span>
|
273
274
|
))}
|
@@ -275,13 +276,3 @@ export default function ImageGrid(props) {
|
|
275
276
|
</div>
|
276
277
|
);
|
277
278
|
}
|
278
|
-
|
279
|
-
ImageGrid.propTypes = {
|
280
|
-
attribute: PropTypes.string,
|
281
|
-
locale: PropTypes.string,
|
282
|
-
locales: PropTypes.object,
|
283
|
-
records: PropTypes.array,
|
284
|
-
enablePrimary: PropTypes.bool,
|
285
|
-
primaryAttribute: PropTypes.string,
|
286
|
-
showEmbed: PropTypes.bool
|
287
|
-
};
|
@@ -1,21 +1,17 @@
|
|
1
|
-
import React, { useEffect, useState } from "react";
|
1
|
+
import React, { MouseEvent, useEffect, useState } from "react";
|
2
|
+
|
2
3
|
import copyToClipboard from "../../lib/copyToClipboard";
|
3
|
-
import EditableImage from "../EditableImage";
|
4
4
|
import useToastStore from "../../stores/useToastStore";
|
5
|
-
import
|
6
|
-
import
|
7
|
-
|
5
|
+
import * as Drag from "../../types/Drag";
|
6
|
+
import * as Images from "../../types/Images";
|
7
|
+
import { Locale } from "../../types";
|
8
8
|
import { useDraggable } from "../drag";
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
image: ImageResource;
|
13
|
-
src: string | null;
|
14
|
-
file: File | null;
|
15
|
-
}
|
10
|
+
import EditableImage from "../EditableImage";
|
11
|
+
import Placeholder from "./Placeholder";
|
16
12
|
|
17
|
-
interface
|
18
|
-
draggable:
|
13
|
+
interface Props {
|
14
|
+
draggable: Drag.Draggable<Images.Record>;
|
19
15
|
attributeName: string;
|
20
16
|
locale: string;
|
21
17
|
locales: { [index: string]: Locale };
|
@@ -25,36 +21,39 @@ interface GridImageProps {
|
|
25
21
|
primary: boolean;
|
26
22
|
position: number;
|
27
23
|
deleteImage: () => void;
|
28
|
-
startDrag: (
|
29
|
-
|
24
|
+
startDrag: (
|
25
|
+
evt: MouseEvent,
|
26
|
+
draggable: Drag.Draggable<Images.Record>
|
27
|
+
) => void;
|
28
|
+
onUpdate: (newImage: Images.Resource, src: string) => void;
|
30
29
|
}
|
31
30
|
|
32
|
-
export default function GridImage(props:
|
31
|
+
export default function GridImage(props: Props) {
|
33
32
|
const { attributeName, draggable } = props;
|
34
33
|
const record = draggable.record;
|
35
34
|
const image = record.image;
|
36
35
|
|
37
36
|
const notice = useToastStore((state) => state.notice);
|
38
37
|
|
39
|
-
const [src, setSrc] = useState(record.src || null);
|
38
|
+
const [src, setSrc] = useState<string>(record.src || null);
|
40
39
|
|
41
40
|
const dragAttrs = useDraggable(draggable, props.startDrag);
|
42
41
|
|
43
42
|
useEffect(() => {
|
44
|
-
if (record.file) {
|
43
|
+
if ("file" in record && record.file) {
|
45
44
|
const reader = new FileReader();
|
46
|
-
reader.onload = () => setSrc(reader.result);
|
45
|
+
reader.onload = () => setSrc(reader.result as string);
|
47
46
|
reader.readAsDataURL(record.file);
|
48
47
|
}
|
49
48
|
}, []);
|
50
49
|
|
51
|
-
const copyEmbed = (evt:
|
50
|
+
const copyEmbed = (evt: MouseEvent) => {
|
52
51
|
evt.preventDefault();
|
53
52
|
copyToClipboard(`[image:${image.id}]`);
|
54
53
|
notice("Embed code copied to clipboard");
|
55
54
|
};
|
56
55
|
|
57
|
-
const deleteImage = (evt:
|
56
|
+
const deleteImage = (evt: MouseEvent) => {
|
58
57
|
evt.preventDefault();
|
59
58
|
if (props.deleteImage) {
|
60
59
|
props.deleteImage();
|
@@ -65,7 +64,7 @@ export default function GridImage(props: GridImageProps) {
|
|
65
64
|
if (props.placeholder) {
|
66
65
|
classes.push("placeholder");
|
67
66
|
}
|
68
|
-
if (record
|
67
|
+
if ("file" in record) {
|
69
68
|
classes.push("uploading");
|
70
69
|
}
|
71
70
|
|
@@ -90,7 +89,7 @@ export default function GridImage(props: GridImageProps) {
|
|
90
89
|
<input
|
91
90
|
name={`${attributeName}[primary]`}
|
92
91
|
type="hidden"
|
93
|
-
value={props.primary}
|
92
|
+
value={props.primary ? "true" : "false"}
|
94
93
|
/>
|
95
94
|
)}
|
96
95
|
{!image && <Placeholder src={src} />}
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import React from "react";
|
2
2
|
|
3
|
-
interface
|
3
|
+
interface Props {
|
4
4
|
src: string;
|
5
5
|
}
|
6
6
|
|
7
|
-
export default function Placeholder(props:
|
7
|
+
export default function Placeholder(props: Props) {
|
8
8
|
if (props.src) {
|
9
9
|
return (
|
10
10
|
<div className="temp-image">
|
@@ -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
|
+
}
|