pages_core 3.14.0 → 3.15.1
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/fonts/6569749d.ttf +0 -0
- data/app/assets/builds/fonts/7b7db107.woff2 +0 -0
- data/app/assets/builds/fonts/921961e9.woff2 +0 -0
- data/app/assets/builds/fonts/ee32bc60.ttf +0 -0
- 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 +699 -394
- data/app/assets/builds/pages_core/mailer.css +99 -0
- 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 +109 -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 → admin.css} +1 -0
- data/app/assets/stylesheets/pages_core/mailer.css +90 -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/attachments_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 +169 -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/mailers/admin_mailer.rb +5 -9
- data/app/models/attachment.rb +1 -1
- data/app/models/autopublisher.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/dated_page.rb +3 -3
- data/app/models/concerns/pages_core/page_model/templateable.rb +2 -16
- data/app/models/concerns/pages_core/taggable.rb +2 -19
- 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/admin_mailer/account_recovery.html.erb +20 -0
- data/app/views/admin_mailer/invite.html.erb +11 -0
- 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/app/views/layouts/pages_core/mailer.html.erb +11 -0
- 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/engine.rb +1 -0
- 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 +1 -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 +119 -36
- data/app/assets/builds/fonts/2a3059ad.ttf +0 -0
- data/app/assets/builds/fonts/47262711.woff2 +0 -0
- data/app/assets/builds/fonts/500ddeb0.woff2 +0 -0
- data/app/assets/builds/fonts/81221036.ttf +0 -0
- 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/app/views/admin_mailer/account_recovery.text.erb +0 -10
- data/app/views/admin_mailer/invite.text.erb +0 -7
- data/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -28,7 +28,8 @@ module PagesCore
|
|
28
28
|
# * <tt>:ratio</tt>: Ratio to constrain image by.
|
29
29
|
# * <tt>:size</tt>: Max size for image.
|
30
30
|
def image_figure(image, opts = {})
|
31
|
-
class_name = ["image", image_class_name(image
|
31
|
+
class_name = ["image", image_class_name(image, ratio: opts[:ratio]),
|
32
|
+
opts[:class_name]].compact
|
32
33
|
image_tag = image_figure_image_tag(image,
|
33
34
|
size: opts[:size],
|
34
35
|
ratio: opts[:ratio])
|
@@ -47,7 +48,8 @@ module PagesCore
|
|
47
48
|
# * <tt>:ratio</tt>: Ratio to constrain image by.
|
48
49
|
# * <tt>:sizes</tt>: Sizes attribute for image tag, default: "100vw".
|
49
50
|
def picture(image, opts = {})
|
50
|
-
class_name = ["image", image_class_name(image
|
51
|
+
class_name = ["image", image_class_name(image, ratio: opts[:ratio]),
|
52
|
+
opts[:class_name]].compact
|
51
53
|
pict = picture_tag(image, ratio: opts[:ratio], sizes: opts[:sizes])
|
52
54
|
content = opts[:link] ? image_link_to(pict, opts[:link]) : pict
|
53
55
|
tag.figure(content + image_caption(image, caption: opts[:caption]),
|
@@ -95,9 +97,11 @@ module PagesCore
|
|
95
97
|
Vector2d.new(v.y * ratio, v.y).fit(v)
|
96
98
|
end
|
97
99
|
|
98
|
-
def image_class_name(image)
|
99
|
-
|
100
|
-
|
100
|
+
def image_class_name(image, ratio: nil)
|
101
|
+
size = ratio ? fit_ratio(image.size, ratio) : image.size
|
102
|
+
|
103
|
+
return "square" if size.x == size.y
|
104
|
+
return "landscape" if size.x > size.y
|
101
105
|
|
102
106
|
"portrait"
|
103
107
|
end
|
@@ -120,9 +124,7 @@ module PagesCore
|
|
120
124
|
end
|
121
125
|
|
122
126
|
def image_widths(image)
|
123
|
-
[233, 350, 700, 1050, 1400, 2100, 2800].select
|
124
|
-
image.size.x >= w
|
125
|
-
end
|
127
|
+
[233, 350, 700, 1050, 1400, 2100, 2800].select { |w| image.size.x >= w }
|
126
128
|
end
|
127
129
|
|
128
130
|
def srcset(image, ratio: nil, format: nil)
|
@@ -30,15 +30,10 @@ module PagesCore
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def labelled_country_select(
|
33
|
-
attr, label = nil,
|
33
|
+
attr, label = nil, opts = {}, html_opts = {}
|
34
34
|
)
|
35
|
-
if priority.is_a?(Hash)
|
36
|
-
return labelled_field(attr, label, priority) do |options|
|
37
|
-
country_select(attr, options, opts, html_opts)
|
38
|
-
end
|
39
|
-
end
|
40
35
|
labelled_field(attr, label, opts) do |options|
|
41
|
-
country_select(attr,
|
36
|
+
country_select(attr, options, html_opts)
|
42
37
|
end
|
43
38
|
end
|
44
39
|
|
@@ -1,32 +1,31 @@
|
|
1
|
-
import React from "react";
|
1
|
+
import React, { MouseEvent } from "react";
|
2
2
|
import copyToClipboard from "../../lib/copyToClipboard";
|
3
3
|
import AttachmentEditor from "./AttachmentEditor";
|
4
4
|
import useModalStore from "../../stores/useModalStore";
|
5
5
|
import useToastStore from "../../stores/useToastStore";
|
6
|
-
import
|
6
|
+
import * as Attachments from "../../types/Attachments";
|
7
|
+
import * as Drag from "../../types/Drag";
|
8
|
+
import { Locale } from "../../types";
|
7
9
|
|
8
|
-
import { useDraggable
|
10
|
+
import { useDraggable } from "../drag";
|
9
11
|
|
10
|
-
interface
|
11
|
-
id: number | null;
|
12
|
-
attachment: AttachmentResource;
|
13
|
-
uploading: boolean;
|
14
|
-
}
|
15
|
-
|
16
|
-
interface AttachmentProps {
|
12
|
+
interface Props {
|
17
13
|
attributeName: string;
|
18
14
|
placeholder: boolean;
|
19
|
-
draggable:
|
15
|
+
draggable: Drag.Draggable<Attachments.Record>;
|
20
16
|
locale: string;
|
21
17
|
locales: { [index: string]: Locale };
|
22
18
|
deleteRecord: () => void;
|
23
19
|
showEmbed: boolean;
|
24
20
|
position: number;
|
25
|
-
onUpdate: (
|
26
|
-
startDrag: (
|
21
|
+
onUpdate: (attachment: Partial<Attachments.Resource>) => void;
|
22
|
+
startDrag: (
|
23
|
+
evt: MouseEvent,
|
24
|
+
draggable: Drag.Draggable<Attachments.Record>
|
25
|
+
) => void;
|
27
26
|
}
|
28
27
|
|
29
|
-
export default function Attachment(props:
|
28
|
+
export default function Attachment(props: Props) {
|
30
29
|
const { attributeName, draggable, locales, locale } = props;
|
31
30
|
const { record } = draggable;
|
32
31
|
const { attachment, uploading } = record;
|
@@ -34,15 +33,18 @@ export default function Attachment(props: AttachmentProps) {
|
|
34
33
|
const openModal = useModalStore((state) => state.open);
|
35
34
|
const notice = useToastStore((state) => state.notice);
|
36
35
|
|
37
|
-
const listeners = useDraggable(
|
36
|
+
const listeners = useDraggable<Attachments.Record>(
|
37
|
+
draggable,
|
38
|
+
props.startDrag
|
39
|
+
);
|
38
40
|
|
39
|
-
const copyEmbed = (evt:
|
41
|
+
const copyEmbed = (evt: MouseEvent) => {
|
40
42
|
evt.preventDefault();
|
41
43
|
copyToClipboard(`[attachment:${attachment.id}]`);
|
42
44
|
notice("Embed code copied to clipboard");
|
43
45
|
};
|
44
46
|
|
45
|
-
const deleteRecord = (evt:
|
47
|
+
const deleteRecord = (evt: MouseEvent) => {
|
46
48
|
evt.preventDefault();
|
47
49
|
if (props.deleteRecord) {
|
48
50
|
props.deleteRecord();
|
@@ -63,7 +65,7 @@ export default function Attachment(props: AttachmentProps) {
|
|
63
65
|
return null;
|
64
66
|
};
|
65
67
|
|
66
|
-
const editAttachment = (evt:
|
68
|
+
const editAttachment = (evt: MouseEvent) => {
|
67
69
|
evt.preventDefault();
|
68
70
|
openModal(
|
69
71
|
<AttachmentEditor
|
@@ -1,18 +1,19 @@
|
|
1
|
-
import React, { ChangeEvent, useState } from "react";
|
1
|
+
import React, { ChangeEvent, MouseEvent, useState } from "react";
|
2
2
|
import copyToClipboard, { copySupported } from "../../lib/copyToClipboard";
|
3
3
|
import useModalStore from "../../stores/useModalStore";
|
4
4
|
import useToastStore from "../../stores/useToastStore";
|
5
|
-
import { AttachmentResource, Locale } from "../../types";
|
6
5
|
import { putJson } from "../../lib/request";
|
6
|
+
import * as Attachments from "../../types/Attachments";
|
7
|
+
import { Locale } from "../../types";
|
7
8
|
|
8
|
-
interface
|
9
|
-
attachment:
|
9
|
+
interface Props {
|
10
|
+
attachment: Attachments.Resource;
|
10
11
|
locale: string;
|
11
12
|
locales: { [index: string]: Locale };
|
12
|
-
onUpdate: (
|
13
|
+
onUpdate: (attachment: Partial<Attachments.Resource>) => void;
|
13
14
|
}
|
14
15
|
|
15
|
-
export default function AttachmentEditor(props:
|
16
|
+
export default function AttachmentEditor(props: Props) {
|
16
17
|
const { attachment, locales } = props;
|
17
18
|
|
18
19
|
const [locale, setLocale] = useState(props.locale);
|
@@ -25,20 +26,21 @@ export default function AttachmentEditor(props: AttachmentEditorProps) {
|
|
25
26
|
const closeModal = useModalStore((state) => state.close);
|
26
27
|
|
27
28
|
const updateLocalization =
|
28
|
-
(name: "name" | "description") =>
|
29
|
+
(name: "name" | "description") =>
|
30
|
+
(evt: ChangeEvent<HTMLInputElement> | ChangeEvent<HTMLTextAreaElement>) => {
|
29
31
|
setLocalizations({
|
30
32
|
...localizations,
|
31
33
|
[name]: { ...localizations[name], [locale]: evt.target.value }
|
32
34
|
});
|
33
35
|
};
|
34
36
|
|
35
|
-
const copyEmbedCode = (evt:
|
37
|
+
const copyEmbedCode = (evt: MouseEvent) => {
|
36
38
|
evt.preventDefault();
|
37
39
|
copyToClipboard(`[attachment:${attachment.id}]`);
|
38
40
|
notice("Embed code copied to clipboard");
|
39
41
|
};
|
40
42
|
|
41
|
-
const save = (evt:
|
43
|
+
const save = (evt: MouseEvent) => {
|
42
44
|
evt.preventDefault();
|
43
45
|
evt.stopPropagation();
|
44
46
|
|
@@ -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
|
+
}
|