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
@@ -1,31 +1,27 @@
|
|
1
|
-
import React
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import
|
6
|
-
import
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
}
|
14
|
-
|
15
|
-
function filenameToName(str) {
|
1
|
+
import React from "react";
|
2
|
+
import Attachment from "./Attachment";
|
3
|
+
import Placeholder from "./Placeholder";
|
4
|
+
import FileUploadButton from "../FileUploadButton";
|
5
|
+
import { post } from "../../lib/request";
|
6
|
+
import * as Attachments from "../../types/Attachments";
|
7
|
+
import * as Drag from "../../types/Drag";
|
8
|
+
|
9
|
+
import { createDraggable, draggedOrder, useDragUploader } from "../drag";
|
10
|
+
|
11
|
+
interface Props extends Attachments.Options {
|
12
|
+
state: Attachments.State;
|
13
|
+
}
|
14
|
+
|
15
|
+
function filenameToName(str: string): string {
|
16
16
|
return str.replace(/\.[\w\d]+$/, "").replace(/_/g, " ");
|
17
17
|
}
|
18
18
|
|
19
|
-
export default function
|
20
|
-
const collection =
|
21
|
-
const locales =
|
22
|
-
props.locales && props.locales.length > 0
|
23
|
-
? Object.keys(props.locales)
|
24
|
-
: [props.locale];
|
25
|
-
const [deleted, setDeleted] = useState([]);
|
19
|
+
export default function List(props: Props) {
|
20
|
+
const { collection, deleted, setDeleted } = props.state;
|
21
|
+
const locales = props.locales ? Object.keys(props.locales) : [props.locale];
|
26
22
|
|
27
|
-
const uploadAttachment = (file) => {
|
28
|
-
|
23
|
+
const uploadAttachment = (file: File) => {
|
24
|
+
const name = {};
|
29
25
|
locales.forEach((l) => (name[l] = file.name));
|
30
26
|
|
31
27
|
const draggable = createDraggable({
|
@@ -33,34 +29,36 @@ export default function Attachments(props) {
|
|
33
29
|
uploading: true
|
34
30
|
});
|
35
31
|
|
36
|
-
|
32
|
+
const data = new FormData();
|
37
33
|
|
38
34
|
data.append("attachment[file]", file);
|
39
35
|
locales.forEach((l) => {
|
40
36
|
data.append(`attachment[name][${l}]`, filenameToName(file.name));
|
41
37
|
});
|
42
38
|
|
43
|
-
post("/admin/attachments.json", data).then(
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
39
|
+
void post("/admin/attachments.json", data).then(
|
40
|
+
(json: Attachments.Resource) => {
|
41
|
+
collection.dispatch({
|
42
|
+
type: "update",
|
43
|
+
payload: {
|
44
|
+
...draggable,
|
45
|
+
record: { attachment: json, uploading: false }
|
46
|
+
}
|
47
|
+
});
|
48
|
+
}
|
49
|
+
);
|
52
50
|
|
53
51
|
return draggable;
|
54
52
|
};
|
55
53
|
|
56
|
-
const receiveFiles = (files) => {
|
54
|
+
const receiveFiles = (files: File[]) => {
|
57
55
|
collection.dispatch({
|
58
56
|
type: "append",
|
59
57
|
payload: files.map((f) => uploadAttachment(f))
|
60
58
|
});
|
61
59
|
};
|
62
60
|
|
63
|
-
const dragEnd = (dragState, files) => {
|
61
|
+
const dragEnd = (dragState: Drag.State, files: File[]) => {
|
64
62
|
collection.dispatch({
|
65
63
|
type: "reorder",
|
66
64
|
payload: draggedOrder(collection, dragState)
|
@@ -71,43 +69,48 @@ export default function Attachments(props) {
|
|
71
69
|
});
|
72
70
|
};
|
73
71
|
|
74
|
-
const [dragState, dragStart, listeners] = useDragUploader(
|
72
|
+
const [dragState, dragStart, listeners] = useDragUploader<Attachments.Record>(
|
75
73
|
[collection],
|
76
74
|
dragEnd
|
77
75
|
);
|
78
76
|
|
79
|
-
const position = (record) => {
|
77
|
+
const position = (record: Attachments.Record) => {
|
80
78
|
return (
|
81
|
-
[
|
82
|
-
|
83
|
-
|
79
|
+
[
|
80
|
+
...collection.draggables.map(
|
81
|
+
(d: Drag.Draggable<Attachments.Record>) => d.record
|
82
|
+
),
|
83
|
+
...deleted
|
84
|
+
].indexOf(record) + 1
|
84
85
|
);
|
85
86
|
};
|
86
87
|
|
87
|
-
const attrName = (record) => {
|
88
|
+
const attrName = (record: Attachments.Record) => {
|
88
89
|
return `${props.attribute}[${position(record)}]`;
|
89
90
|
};
|
90
91
|
|
91
|
-
const update =
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
...
|
97
|
-
|
98
|
-
|
92
|
+
const update =
|
93
|
+
(draggable: Drag.Draggable<Attachments.Record>) =>
|
94
|
+
(attachment: Partial<Attachments.Resource>) => {
|
95
|
+
const { record } = draggable;
|
96
|
+
const updated = {
|
97
|
+
...draggable,
|
98
|
+
record: {
|
99
|
+
...record,
|
100
|
+
attachment: { ...record.attachment, ...attachment }
|
101
|
+
}
|
102
|
+
};
|
103
|
+
collection.dispatch({ type: "update", payload: updated });
|
99
104
|
};
|
100
|
-
collection.dispatch({ type: "update", payload: updated });
|
101
|
-
};
|
102
105
|
|
103
|
-
const remove = (draggable) => () => {
|
106
|
+
const remove = (draggable: Drag.Draggable<Attachments.Record>) => () => {
|
104
107
|
collection.dispatch({ type: "remove", payload: draggable });
|
105
108
|
if (draggable.record.id) {
|
106
109
|
setDeleted([...deleted, draggable.record]);
|
107
110
|
}
|
108
111
|
};
|
109
112
|
|
110
|
-
const attachment = (draggable) => {
|
113
|
+
const attachment = (draggable: Drag.Item<Attachments.Record>) => {
|
111
114
|
const { dragging } = dragState;
|
112
115
|
|
113
116
|
if (draggable === "Files") {
|
@@ -153,7 +156,7 @@ export default function Attachments(props) {
|
|
153
156
|
<input
|
154
157
|
name={`${attrName(r)}[_destroy]`}
|
155
158
|
type="hidden"
|
156
|
-
value=
|
159
|
+
value="true"
|
157
160
|
/>
|
158
161
|
</span>
|
159
162
|
))}
|
@@ -168,11 +171,3 @@ export default function Attachments(props) {
|
|
168
171
|
</div>
|
169
172
|
);
|
170
173
|
}
|
171
|
-
|
172
|
-
Attachments.propTypes = {
|
173
|
-
attribute: PropTypes.string,
|
174
|
-
locale: PropTypes.string,
|
175
|
-
locales: PropTypes.object,
|
176
|
-
records: PropTypes.array,
|
177
|
-
showEmbed: PropTypes.bool
|
178
|
-
};
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { useState } from "react";
|
2
|
+
import { useDragCollection } from "../drag";
|
3
|
+
import * as Attachments from "../../types/Attachments";
|
4
|
+
|
5
|
+
export default function useAttachments(
|
6
|
+
records: Attachments.Record[]
|
7
|
+
): Attachments.State {
|
8
|
+
const [deleted, setDeleted] = useState<Attachments.Record[]>([]);
|
9
|
+
|
10
|
+
return {
|
11
|
+
collection: useDragCollection(records),
|
12
|
+
deleted: deleted,
|
13
|
+
setDeleted: setDeleted
|
14
|
+
};
|
15
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import React from "react";
|
2
|
+
import useAttachments from "./Attachments/useAttachments";
|
3
|
+
import List from "./Attachments/List";
|
4
|
+
import * as Attachment from "../types/Attachments";
|
5
|
+
|
6
|
+
interface Props extends Attachment.Options {
|
7
|
+
records: Attachment.Record[];
|
8
|
+
}
|
9
|
+
|
10
|
+
export default function Attachments(props: Props) {
|
11
|
+
const state = useAttachments(props.records);
|
12
|
+
|
13
|
+
return <List state={state} {...props} />;
|
14
|
+
}
|
@@ -0,0 +1,105 @@
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
2
|
+
|
3
|
+
import DateTimeSelect from "./DateTimeSelect";
|
4
|
+
|
5
|
+
interface Props {
|
6
|
+
objectName: string;
|
7
|
+
startsAt: Date | string;
|
8
|
+
endsAt: Date | string;
|
9
|
+
setStartsAt?: (date: Date) => void;
|
10
|
+
setEndsAt?: (date: Date) => void;
|
11
|
+
disabled?: boolean;
|
12
|
+
disableTime?: boolean;
|
13
|
+
}
|
14
|
+
|
15
|
+
function defaultDate(offset = 0): Date {
|
16
|
+
const coeff = 1000 * 60 * 60;
|
17
|
+
return new Date(
|
18
|
+
Math.round(new Date().getTime() / coeff) * coeff +
|
19
|
+
coeff +
|
20
|
+
1000 * 60 * offset
|
21
|
+
);
|
22
|
+
}
|
23
|
+
|
24
|
+
function parseDate(str: Date | string): Date {
|
25
|
+
if (!str) {
|
26
|
+
return null;
|
27
|
+
} else if (typeof str === "string") {
|
28
|
+
return new Date(str);
|
29
|
+
} else {
|
30
|
+
return str;
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
export default function DateRangeSelect(props: Props) {
|
35
|
+
const { disabled, disableTime, objectName } = props;
|
36
|
+
|
37
|
+
const [uncontrolledStartsAt, setUncontrolledStartsAt] = useState(
|
38
|
+
parseDate(props.startsAt)
|
39
|
+
);
|
40
|
+
|
41
|
+
const [uncontrolledEndsAt, setUncontrolledEndsAt] = useState(
|
42
|
+
parseDate(props.endsAt) || defaultDate(60)
|
43
|
+
);
|
44
|
+
|
45
|
+
const startsAt = parseDate(
|
46
|
+
props.setStartsAt ? props.startsAt : uncontrolledStartsAt
|
47
|
+
);
|
48
|
+
const setStartsAt = props.setStartsAt || setUncontrolledStartsAt;
|
49
|
+
|
50
|
+
const endsAt = parseDate(props.setEndsAt ? props.endsAt : uncontrolledEndsAt);
|
51
|
+
const setEndsAt = props.setEndsAt || setUncontrolledEndsAt;
|
52
|
+
|
53
|
+
const setDates = (start: Date, end: Date) => {
|
54
|
+
if (end < start) {
|
55
|
+
end = start;
|
56
|
+
}
|
57
|
+
setStartsAt(start);
|
58
|
+
setEndsAt(end);
|
59
|
+
};
|
60
|
+
|
61
|
+
const changeStartsAt = (newDate: Date) => {
|
62
|
+
setDates(
|
63
|
+
newDate,
|
64
|
+
new Date(endsAt.getTime() + (newDate.getTime() - startsAt.getTime()))
|
65
|
+
);
|
66
|
+
};
|
67
|
+
|
68
|
+
const changeEndsAt = (newDate: Date) => {
|
69
|
+
setDates(startsAt, newDate);
|
70
|
+
};
|
71
|
+
|
72
|
+
useEffect(() => {
|
73
|
+
if (!startsAt || !endsAt) {
|
74
|
+
setDates(startsAt || defaultDate(), endsAt || defaultDate(60));
|
75
|
+
}
|
76
|
+
}, [startsAt, endsAt]);
|
77
|
+
|
78
|
+
return (
|
79
|
+
<div className="date-range-select">
|
80
|
+
{startsAt && (
|
81
|
+
<div className="date">
|
82
|
+
<DateTimeSelect
|
83
|
+
name={objectName + "[starts_at]"}
|
84
|
+
disabled={disabled}
|
85
|
+
disableTime={disableTime}
|
86
|
+
onChange={changeStartsAt}
|
87
|
+
value={startsAt}
|
88
|
+
/>
|
89
|
+
</div>
|
90
|
+
)}
|
91
|
+
<span className="to">to</span>
|
92
|
+
{endsAt && (
|
93
|
+
<div className="date">
|
94
|
+
<DateTimeSelect
|
95
|
+
name={objectName + "[ends_at]"}
|
96
|
+
disabled={disabled}
|
97
|
+
disableTime={disableTime}
|
98
|
+
onChange={changeEndsAt}
|
99
|
+
value={endsAt}
|
100
|
+
/>
|
101
|
+
</div>
|
102
|
+
)}
|
103
|
+
</div>
|
104
|
+
);
|
105
|
+
}
|
@@ -0,0 +1,136 @@
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
2
|
+
|
3
|
+
interface DateTimeSelectProps {
|
4
|
+
name: string;
|
5
|
+
onChange: (date: Date) => void;
|
6
|
+
value: Date;
|
7
|
+
disabled?: boolean;
|
8
|
+
disableTime?: boolean;
|
9
|
+
}
|
10
|
+
|
11
|
+
interface ModifyOptions {
|
12
|
+
year?: number;
|
13
|
+
month?: number;
|
14
|
+
date?: number;
|
15
|
+
time?: string;
|
16
|
+
}
|
17
|
+
|
18
|
+
function modifyDate(original: Date, options: ModifyOptions = {}): Date {
|
19
|
+
const newDate = new Date(original);
|
20
|
+
if ("year" in options) {
|
21
|
+
newDate.setFullYear(options.year);
|
22
|
+
}
|
23
|
+
if ("month" in options) {
|
24
|
+
newDate.setMonth(options.month);
|
25
|
+
}
|
26
|
+
if ("date" in options) {
|
27
|
+
newDate.setDate(options.date);
|
28
|
+
}
|
29
|
+
if ("time" in options && options.time.match(/^[\d]{1,2}(:[\d]{1,2})?$/)) {
|
30
|
+
newDate.setHours(parseInt(options.time.split(":")[0], 10));
|
31
|
+
newDate.setMinutes(parseInt(options.time.split(":")[1], 10) || 0);
|
32
|
+
}
|
33
|
+
return newDate;
|
34
|
+
}
|
35
|
+
|
36
|
+
function timeToString(time: Date): string {
|
37
|
+
return time.toTimeString().slice(0, 5);
|
38
|
+
}
|
39
|
+
|
40
|
+
// Returns an array with years from 2000 to 10 years from now.
|
41
|
+
function yearOptions(): number[] {
|
42
|
+
const start = 2000;
|
43
|
+
const years: number[] = [];
|
44
|
+
for (let i = start; i <= new Date().getFullYear() + 11; i++) {
|
45
|
+
years.push(i);
|
46
|
+
}
|
47
|
+
return years;
|
48
|
+
}
|
49
|
+
|
50
|
+
function monthOptions(): string[] {
|
51
|
+
return [
|
52
|
+
"January",
|
53
|
+
"February",
|
54
|
+
"March",
|
55
|
+
"April",
|
56
|
+
"May",
|
57
|
+
"June",
|
58
|
+
"July",
|
59
|
+
"August",
|
60
|
+
"September",
|
61
|
+
"October",
|
62
|
+
"November",
|
63
|
+
"December"
|
64
|
+
];
|
65
|
+
}
|
66
|
+
|
67
|
+
function dayOptions(): number[] {
|
68
|
+
const numbers: number[] = [];
|
69
|
+
for (let i = 1; i <= 31; i++) {
|
70
|
+
numbers.push(i);
|
71
|
+
}
|
72
|
+
return numbers;
|
73
|
+
}
|
74
|
+
|
75
|
+
export default function DateTimeSelect(props: DateTimeSelectProps) {
|
76
|
+
const { name, disabled, disableTime, onChange, value } = props;
|
77
|
+
|
78
|
+
const [timeString, setTimeString] = useState(timeToString(value));
|
79
|
+
|
80
|
+
useEffect(() => {
|
81
|
+
setTimeString(timeToString(value));
|
82
|
+
}, [value]);
|
83
|
+
|
84
|
+
const handleChange = (options = {}) => {
|
85
|
+
onChange(modifyDate(value, options));
|
86
|
+
};
|
87
|
+
|
88
|
+
return (
|
89
|
+
<div className="date-select">
|
90
|
+
{name && (
|
91
|
+
<input type="hidden" name={name} value={!disabled && value.toJSON()} />
|
92
|
+
)}
|
93
|
+
<select
|
94
|
+
value={value.getMonth()}
|
95
|
+
onChange={(e) => handleChange({ month: e.target.value })}
|
96
|
+
disabled={disabled}>
|
97
|
+
{monthOptions().map((m, i) => (
|
98
|
+
<option key={i} value={i}>
|
99
|
+
{m}
|
100
|
+
</option>
|
101
|
+
))}
|
102
|
+
</select>
|
103
|
+
<select
|
104
|
+
value={value.getDate()}
|
105
|
+
onChange={(e) => handleChange({ date: e.target.value })}
|
106
|
+
disabled={disabled}>
|
107
|
+
{dayOptions().map((d) => (
|
108
|
+
<option key={d} value={d}>
|
109
|
+
{d}
|
110
|
+
</option>
|
111
|
+
))}
|
112
|
+
</select>
|
113
|
+
<select
|
114
|
+
value={value.getFullYear()}
|
115
|
+
onChange={(e) => handleChange({ year: e.target.value })}
|
116
|
+
disabled={disabled}>
|
117
|
+
{yearOptions().map((y) => (
|
118
|
+
<option key={y} value={y}>
|
119
|
+
{y}
|
120
|
+
</option>
|
121
|
+
))}
|
122
|
+
</select>
|
123
|
+
{!disableTime && (
|
124
|
+
<input
|
125
|
+
className="time"
|
126
|
+
type="text"
|
127
|
+
size={5}
|
128
|
+
disabled={disabled}
|
129
|
+
value={timeString}
|
130
|
+
onChange={(e) => setTimeString(e.target.value)}
|
131
|
+
onBlur={(e) => handleChange({ time: e.target.value })}
|
132
|
+
/>
|
133
|
+
)}
|
134
|
+
</div>
|
135
|
+
);
|
136
|
+
}
|
@@ -1,20 +1,22 @@
|
|
1
|
-
import React, { useState } from "react";
|
2
|
-
|
1
|
+
import React, { MouseEvent, useState } from "react";
|
2
|
+
|
3
3
|
import useModalStore from "../stores/useModalStore";
|
4
|
+
import * as Images from "../types/Images";
|
5
|
+
import { Locale } from "../types";
|
4
6
|
|
5
|
-
import
|
7
|
+
import ImageEditor from "./ImageEditor";
|
6
8
|
|
7
|
-
interface
|
8
|
-
image:
|
9
|
+
interface Props {
|
10
|
+
image: Images.Resource;
|
9
11
|
src: string;
|
10
12
|
caption: boolean;
|
11
13
|
locale: string;
|
12
14
|
locales: Record<string, Locale>;
|
13
15
|
width: number;
|
14
|
-
onUpdate?: (newImage:
|
16
|
+
onUpdate?: (newImage: Images.Resource, src: string) => void;
|
15
17
|
}
|
16
18
|
|
17
|
-
export default function EditableImage(props:
|
19
|
+
export default function EditableImage(props: Props) {
|
18
20
|
const [image, setImage] = useState(props.image);
|
19
21
|
const [src, setSrc] = useState(props.src);
|
20
22
|
|
@@ -26,7 +28,7 @@ export default function EditableImage(props: EditableImageProps) {
|
|
26
28
|
return Math.round((height / width) * props.width);
|
27
29
|
};
|
28
30
|
|
29
|
-
const updateImage = (updatedImage:
|
31
|
+
const updateImage = (updatedImage: Images.Resource, src: string) => {
|
30
32
|
const newImage = { ...image, ...updatedImage };
|
31
33
|
setSrc(src);
|
32
34
|
setImage(newImage);
|
@@ -35,7 +37,7 @@ export default function EditableImage(props: EditableImageProps) {
|
|
35
37
|
}
|
36
38
|
};
|
37
39
|
|
38
|
-
const handleClick = (evt:
|
40
|
+
const handleClick = (evt: MouseEvent) => {
|
39
41
|
evt.preventDefault();
|
40
42
|
openModal(
|
41
43
|
<ImageEditor
|
@@ -1,13 +1,13 @@
|
|
1
|
-
import React, { ChangeEvent, useRef } from "react";
|
1
|
+
import React, { ChangeEvent, MouseEvent, useRef } from "react";
|
2
2
|
|
3
|
-
interface
|
3
|
+
interface Props {
|
4
4
|
callback: (files: File[]) => void;
|
5
|
-
type
|
6
|
-
|
7
|
-
|
5
|
+
type?: string;
|
6
|
+
multiline?: boolean;
|
7
|
+
multiple?: boolean;
|
8
8
|
}
|
9
9
|
|
10
|
-
export default function FileUploadButton(props:
|
10
|
+
export default function FileUploadButton(props: Props) {
|
11
11
|
const inputRef = useRef<HTMLInputElement>();
|
12
12
|
|
13
13
|
const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
@@ -21,7 +21,7 @@ export default function FileUploadButton(props: FileUploadButtonProps) {
|
|
21
21
|
}
|
22
22
|
};
|
23
23
|
|
24
|
-
const triggerDialog = (evt:
|
24
|
+
const triggerDialog = (evt: MouseEvent) => {
|
25
25
|
evt.preventDefault();
|
26
26
|
inputRef.current.click();
|
27
27
|
};
|
@@ -1,14 +1,11 @@
|
|
1
|
-
import React, { useRef, useState } from "react";
|
1
|
+
import React, { MouseEvent, TouchEvent, useRef, useState } from "react";
|
2
2
|
|
3
|
-
|
4
|
-
x: number;
|
5
|
-
y: number;
|
6
|
-
}
|
3
|
+
import * as Crop from "../../types/Crop";
|
7
4
|
|
8
|
-
interface
|
5
|
+
interface Props {
|
9
6
|
x: number;
|
10
7
|
y: number;
|
11
|
-
onChange: (pos: Position) => void;
|
8
|
+
onChange: (pos: Crop.Position) => void;
|
12
9
|
width: number;
|
13
10
|
height: number;
|
14
11
|
}
|
@@ -23,11 +20,11 @@ function clamp(val: number, min: number, max: number): number {
|
|
23
20
|
}
|
24
21
|
}
|
25
22
|
|
26
|
-
export default function FocalPoint(props:
|
23
|
+
export default function FocalPoint(props: Props) {
|
27
24
|
const { width, height, onChange } = props;
|
28
25
|
|
29
26
|
const [dragging, setDragging] = useState(false);
|
30
|
-
const [position, setPosition] = useState<Position>({
|
27
|
+
const [position, setPosition] = useState<Crop.Position>({
|
31
28
|
x: props.x,
|
32
29
|
y: props.y
|
33
30
|
});
|
@@ -35,7 +32,7 @@ export default function FocalPoint(props: FocalPointProps) {
|
|
35
32
|
const containerRef = useRef<HTMLDivElement>();
|
36
33
|
const pointRef = useRef<HTMLDivElement>();
|
37
34
|
|
38
|
-
const dragStart = (evt:
|
35
|
+
const dragStart = (evt: MouseEvent | TouchEvent) => {
|
39
36
|
evt.preventDefault();
|
40
37
|
evt.stopPropagation();
|
41
38
|
if (evt.target == pointRef.current) {
|
@@ -50,7 +47,7 @@ export default function FocalPoint(props: FocalPointProps) {
|
|
50
47
|
}
|
51
48
|
};
|
52
49
|
|
53
|
-
const drag = (evt:
|
50
|
+
const drag = (evt: MouseEvent | TouchEvent) => {
|
54
51
|
if (dragging) {
|
55
52
|
let x: number, y: number;
|
56
53
|
const containerSize = containerRef.current.getBoundingClientRect();
|
@@ -59,7 +56,7 @@ export default function FocalPoint(props: FocalPointProps) {
|
|
59
56
|
if ("touches" in evt && evt.type == "touchmove") {
|
60
57
|
x = evt.touches[0].clientX - (containerSize.x || containerSize.left);
|
61
58
|
y = evt.touches[0].clientY - (containerSize.y || containerSize.top);
|
62
|
-
} else {
|
59
|
+
} else if ("clientX" in evt) {
|
63
60
|
x = evt.clientX - (containerSize.x || containerSize.left);
|
64
61
|
y = evt.clientY - (containerSize.y || containerSize.top);
|
65
62
|
}
|
@@ -1,19 +1,21 @@
|
|
1
1
|
import React from "react";
|
2
2
|
import ReactCrop from "react-image-crop";
|
3
3
|
|
4
|
-
import
|
4
|
+
import * as Crop from "../../types/Crop";
|
5
|
+
|
6
|
+
import { cropSize } from "./useCrop";
|
5
7
|
import FocalPoint from "./FocalPoint";
|
6
8
|
|
7
|
-
interface
|
8
|
-
containerSize: Size;
|
9
|
+
interface Props {
|
10
|
+
containerSize: Crop.Size;
|
9
11
|
croppedImage: string;
|
10
|
-
cropState:
|
11
|
-
focalPoint: Position;
|
12
|
-
setCrop: (crop: CropSize) => void;
|
13
|
-
setFocal: (focal: Position) => void;
|
12
|
+
cropState: Crop.State;
|
13
|
+
focalPoint: Crop.Position;
|
14
|
+
setCrop: (crop: Crop.CropSize) => void;
|
15
|
+
setFocal: (focal: Crop.Position) => void;
|
14
16
|
}
|
15
17
|
|
16
|
-
export default function Image(props:
|
18
|
+
export default function Image(props: Props) {
|
17
19
|
const imageSize = () => {
|
18
20
|
const { image, cropping, crop_width, crop_height } = props.cropState;
|
19
21
|
if (cropping) {
|
@@ -1,22 +1,22 @@
|
|
1
|
-
import React from "react";
|
1
|
+
import React, { MouseEvent } from "react";
|
2
2
|
|
3
|
-
import
|
4
|
-
import
|
3
|
+
import * as Crop from "../../types/Crop";
|
4
|
+
import * as Images from "../../types/Images";
|
5
5
|
|
6
6
|
type Ratio = number | null;
|
7
7
|
|
8
|
-
interface
|
9
|
-
cropState:
|
10
|
-
image:
|
8
|
+
interface Props {
|
9
|
+
cropState: Crop.State;
|
10
|
+
image: Images.Resource;
|
11
11
|
setAspect: (Ratio) => void;
|
12
|
-
toggleCrop: (evt:
|
13
|
-
toggleFocal: (evt:
|
12
|
+
toggleCrop: (evt: MouseEvent) => void;
|
13
|
+
toggleFocal: (evt: MouseEvent) => void;
|
14
14
|
}
|
15
15
|
|
16
|
-
export default function Toolbar(props:
|
16
|
+
export default function Toolbar(props: Props) {
|
17
17
|
const { cropping } = props.cropState;
|
18
18
|
|
19
|
-
const aspectRatios = [
|
19
|
+
const aspectRatios: Array<[string, Ratio]> = [
|
20
20
|
["Free", null],
|
21
21
|
["1:1", 1],
|
22
22
|
["3:2", 3 / 2],
|
@@ -28,7 +28,7 @@ export default function Toolbar(props: ToolbarProps) {
|
|
28
28
|
["16:9", 16 / 9]
|
29
29
|
];
|
30
30
|
|
31
|
-
const updateAspect = (ratio: Ratio) => (evt:
|
31
|
+
const updateAspect = (ratio: Ratio) => (evt: MouseEvent) => {
|
32
32
|
evt.preventDefault();
|
33
33
|
props.setAspect(ratio);
|
34
34
|
};
|
@@ -61,7 +61,6 @@ export default function Toolbar(props: ToolbarProps) {
|
|
61
61
|
href={props.image.original_url}
|
62
62
|
className="button"
|
63
63
|
title="Download original image"
|
64
|
-
disabled={cropping}
|
65
64
|
download={props.image.filename}
|
66
65
|
onClick={(evt) => cropping && evt.preventDefault()}>
|
67
66
|
<i className="fa-solid fa-download" />
|