pages_core 3.15.3 → 3.15.5
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 +1 -1
- data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
- data/app/assets/builds/pages_core/admin.css +378 -253
- data/app/assets/builds/pages_core/mailer.css +41 -6
- data/app/assets/builds/pages_core_fonts/121b837e.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/216e5c23.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/3017b52f.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/489746b9.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/49775483.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/49c9e472.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/4a119645.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/5d56d7a8.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/61ea75a6.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/62cbb778.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/647d26c.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/67764053.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/6bb0fd00.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/6c0194a2.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/71423409.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/7584e61d.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/77bcfa1c.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/7aca0cc5.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/9a09533f.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/a51f5bc8.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/a80b2975.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/a891f617.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/ad6083f3.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/b29a61ff.woff2 +0 -0
- data/app/assets/builds/{fonts/6569749d.ttf → pages_core_fonts/b30b0656.ttf} +0 -0
- data/app/assets/builds/pages_core_fonts/b3a5f48c.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/bc73ee06.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/c38c6d45.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/c5ce0b1f.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/c8d53904.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/ce13c169.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/d43bd0d5.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/e1c7d368.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/e1e8175d.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/e318f796.woff2 +0 -0
- data/app/assets/builds/{fonts/ee32bc60.ttf → pages_core_fonts/e7acb7d9.ttf} +0 -0
- data/app/assets/builds/pages_core_fonts/ee5514c6.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/f4e495e2.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/f736ec65.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/f741c7ba.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/f7767345.woff2 +0 -0
- data/app/assets/builds/pages_core_fonts/fe9eb751.woff2 +0 -0
- data/app/assets/stylesheets/pages_core/admin/components/forms.css +2 -2
- data/app/assets/stylesheets/pages_core/admin/components/header.css +1 -1
- data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +1 -1
- data/app/assets/stylesheets/pages_core/admin/global/fonts.css +38 -38
- data/app/controllers/{pages_core → admin}/admin_controller.rb +1 -3
- data/app/controllers/attachments_controller.rb +40 -0
- data/app/controllers/concerns/pages_core/document_title_controller.rb +16 -0
- data/app/controllers/concerns/pages_core/page_parameters.rb +1 -1
- data/app/controllers/concerns/pages_core/pages/preview_controller.rb +49 -0
- data/app/controllers/concerns/pages_core/pages/rss_controller.rb +43 -0
- data/app/controllers/errors_controller.rb +2 -0
- data/app/controllers/images_controller.rb +13 -0
- data/app/controllers/pages_core/frontend/pages_controller.rb +3 -4
- data/app/controllers/pages_core/frontend_controller.rb +6 -1
- data/app/controllers/pages_core/sitemaps_controller.rb +21 -52
- data/app/helpers/pages_core/admin/image_uploads_helper.rb +1 -1
- data/app/helpers/pages_core/application_helper.rb +0 -3
- data/app/helpers/pages_core/attachments_helper.rb +0 -10
- data/app/helpers/pages_core/feed_tags_helper.rb +31 -0
- data/app/helpers/pages_core/frontend_helper.rb +3 -0
- data/app/helpers/pages_core/head_tags_helper.rb +80 -70
- data/app/helpers/pages_core/page_path_helper.rb +1 -12
- data/app/javascript/components/Attachments/Attachment.tsx +3 -3
- data/app/javascript/components/Attachments/AttachmentEditor.tsx +5 -5
- data/app/javascript/components/Attachments/Deleted.tsx +28 -0
- data/app/javascript/components/Attachments/List.tsx +11 -24
- data/app/javascript/components/Attachments/Placeholder.tsx +0 -2
- data/app/javascript/components/Attachments.tsx +2 -3
- data/app/javascript/components/DateRangeSelect.tsx +13 -10
- data/app/javascript/components/DateTimeSelect.tsx +11 -11
- data/app/javascript/components/EditableImage.tsx +3 -3
- data/app/javascript/components/FileUploadButton.tsx +3 -3
- data/app/javascript/components/ImageCropper/FocalPoint.tsx +10 -14
- data/app/javascript/components/ImageCropper/Image.tsx +19 -25
- data/app/javascript/components/ImageCropper/Toolbar.tsx +27 -26
- data/app/javascript/components/ImageCropper/useContainerSize.ts +25 -0
- data/app/javascript/components/ImageCropper/useCrop.ts +28 -13
- data/app/javascript/components/ImageCropper/useImageCropperContext.ts +13 -0
- data/app/javascript/components/ImageCropper.tsx +24 -83
- data/app/javascript/components/ImageEditor/Form.tsx +25 -28
- data/app/javascript/components/ImageEditor/useImageEditor.ts +63 -0
- data/app/javascript/components/ImageEditor/useImageEditorContext.ts +14 -0
- data/app/javascript/components/ImageEditor.tsx +28 -42
- data/app/javascript/components/ImageGrid/Deleted.tsx +28 -0
- data/app/javascript/components/ImageGrid/DragElement.tsx +5 -5
- data/app/javascript/components/ImageGrid/FilePlaceholder.tsx +0 -2
- data/app/javascript/components/ImageGrid/Grid.tsx +15 -24
- data/app/javascript/components/ImageGrid/GridImage.tsx +4 -4
- data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -4
- data/app/javascript/components/ImageGrid.tsx +2 -4
- data/app/javascript/components/ImageUploader.tsx +5 -5
- data/app/javascript/components/LabelledField.tsx +6 -6
- data/app/javascript/components/Modal.tsx +16 -13
- data/app/javascript/components/PageForm/Block.tsx +3 -3
- data/app/javascript/components/PageForm/Content.tsx +11 -15
- data/app/javascript/components/PageForm/Dates.tsx +3 -11
- data/app/javascript/components/PageForm/Files.tsx +2 -4
- data/app/javascript/components/PageForm/Form.tsx +3 -9
- data/app/javascript/components/PageForm/Images.tsx +2 -4
- data/app/javascript/components/PageForm/LocaleLinks.tsx +4 -11
- data/app/javascript/components/PageForm/Metadata.tsx +8 -13
- data/app/javascript/components/PageForm/Options.tsx +28 -11
- data/app/javascript/components/PageForm/PageDescription.tsx +7 -14
- data/app/javascript/components/PageForm/PathSegment.tsx +5 -10
- data/app/javascript/components/PageForm/TabPanel.tsx +3 -6
- data/app/javascript/components/PageForm/Tabs.tsx +2 -4
- data/app/javascript/components/PageForm/UnconfiguredContent.tsx +7 -12
- data/app/javascript/components/PageForm/pageParams.ts +3 -2
- data/app/javascript/components/PageForm/usePage.ts +1 -46
- data/app/javascript/components/PageForm/usePageFormContext.ts +8 -0
- data/app/javascript/components/PageForm/useTabs.ts +1 -1
- data/app/javascript/components/PageForm/utils.ts +49 -0
- data/app/javascript/components/PageForm.tsx +52 -48
- data/app/javascript/components/PageImages.tsx +1 -3
- data/app/javascript/components/PageTree/Button.tsx +25 -0
- data/app/javascript/components/PageTree/CollapseArrow.tsx +34 -0
- data/app/javascript/components/PageTree/CollapsedLabel.tsx +21 -0
- data/app/javascript/components/PageTree/EditPageName.tsx +68 -0
- data/app/javascript/components/PageTree/Node.tsx +143 -413
- data/app/javascript/components/PageTree/PageName.tsx +6 -4
- data/app/javascript/components/PageTree/StatusLabel.tsx +10 -0
- data/app/javascript/components/PageTree/tree.ts +268 -0
- data/app/javascript/components/PageTree/usePageTree.ts +268 -0
- data/app/javascript/components/PageTree/usePageTreeContext.ts +13 -0
- data/app/javascript/components/PageTree.tsx +194 -214
- data/app/javascript/components/{RichTextToolbarButton.tsx → RichTextArea/ToolbarButton.tsx} +3 -5
- data/app/javascript/components/RichTextArea/actions.ts +106 -0
- data/app/javascript/components/RichTextArea/useMaybeControlledValue.ts +14 -0
- data/app/javascript/components/RichTextArea.tsx +91 -209
- data/app/javascript/components/TagEditor/AddTagForm.tsx +2 -2
- data/app/javascript/components/TagEditor/Editor.tsx +3 -5
- data/app/javascript/components/TagEditor/Tag.tsx +3 -5
- data/app/javascript/components/TagEditor/useTags.ts +7 -4
- data/app/javascript/components/TagEditor.tsx +2 -4
- data/app/javascript/components/Toast.tsx +5 -5
- data/app/javascript/components/drag/draggedOrder.ts +6 -6
- data/app/javascript/components/drag/useDragCollection.ts +21 -25
- data/app/javascript/components/drag/useDragUploader.ts +20 -18
- data/app/javascript/components/drag/useDraggable.ts +3 -3
- data/app/javascript/features/RichText.tsx +0 -1
- data/app/javascript/features/contentTabs.ts +2 -2
- data/app/javascript/stores/useModalStore.ts +1 -1
- data/app/javascript/stores/useToastStore.ts +2 -2
- data/app/javascript/types/Attachments.ts +11 -11
- data/app/javascript/types/Crop.ts +16 -12
- data/app/javascript/types/Drag.ts +21 -23
- data/app/javascript/types/Images.ts +8 -8
- data/app/javascript/types/PageEditor.ts +11 -4
- data/app/javascript/types/Pages.ts +22 -27
- data/app/javascript/types/Tags.ts +5 -6
- data/app/javascript/types/Template.ts +4 -4
- data/app/javascript/types.ts +2 -2
- data/app/models/attachment.rb +5 -9
- data/app/models/autopublisher.rb +1 -1
- data/app/models/concerns/pages_core/page_model/redirectable.rb +1 -2
- data/app/models/concerns/pages_core/page_model/searchable.rb +1 -1
- data/app/models/concerns/pages_core/page_model/status.rb +2 -4
- data/app/models/concerns/pages_core/searchable_document.rb +2 -4
- data/app/models/image.rb +0 -15
- data/app/models/page_builder.rb +4 -6
- data/app/resources/admin/page_resource.rb +2 -2
- data/app/resources/export/page_resource.rb +1 -1
- data/app/services/pages_core/invite_service.rb +1 -2
- data/app/views/layouts/admin.html.erb +1 -0
- data/app/views/pages_core/sitemaps/index.xml.builder +10 -0
- data/config/routes.rb +4 -3
- data/db/migrate/20240917142300_add_skip_index_to_pages.rb +7 -0
- data/lib/pages_core/engine.rb +15 -17
- data/lib/pages_core/sitemap.rb +58 -0
- data/lib/pages_core/templates/configuration_proxy.rb +3 -3
- data/lib/pages_core.rb +7 -4
- data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +2 -2
- data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +13 -5
- data/lib/rails/generators/pages_core/frontend/templates/postcss.config.js +2 -6
- data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.css +3 -1
- data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.css +2 -3
- data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/animation.css +1 -1
- data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.css +6 -5
- data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/fonts.css +1 -1
- data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/grid.css +9 -6
- data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.css +42 -26
- data/lib/rails/generators/pages_core/install/templates/application_controller.rb +0 -6
- data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +1 -1
- metadata +81 -49
- data/app/assets/builds/fonts/7b7db107.woff2 +0 -0
- data/app/assets/builds/fonts/921961e9.woff2 +0 -0
- data/app/controller_dummies/admin/admin_controller.rb +0 -6
- data/app/controller_dummies/application_controller.rb +0 -6
- data/app/controller_dummies/attachments_controller.rb +0 -4
- data/app/controller_dummies/frontend_controller.rb +0 -4
- data/app/controller_dummies/images_controller.rb +0 -4
- data/app/controller_dummies/page_files_controller.rb +0 -4
- data/app/controller_dummies/pages_controller.rb +0 -4
- data/app/controller_dummies/sitemaps_controller.rb +0 -4
- data/app/controllers/concerns/pages_core/preview_pages_controller.rb +0 -47
- data/app/controllers/concerns/pages_core/rss_controller.rb +0 -41
- data/app/controllers/pages_core/attachments_controller.rb +0 -42
- data/app/controllers/pages_core/frontend/page_files_controller.rb +0 -25
- data/app/controllers/pages_core/images_controller.rb +0 -15
- data/app/helpers/pages_core/meta_tags_helper.rb +0 -96
- data/app/helpers/pages_core/open_graph_tags_helper.rb +0 -49
- data/app/javascript/components/PageTree/Draggable.tsx +0 -338
- data/app/javascript/lib/Tree.ts +0 -305
- data/app/javascript/types/Trees.ts +0 -19
- data/app/views/sitemaps/show.xml.builder +0 -11
@@ -1,37 +1,38 @@
|
|
1
|
-
import React, { ChangeEvent, MouseEvent } from "react";
|
2
|
-
|
3
1
|
import useModalStore from "../../stores/useModalStore";
|
4
2
|
import useToastStore from "../../stores/useToastStore";
|
5
3
|
import copyToClipboard, { copySupported } from "../../lib/copyToClipboard";
|
6
|
-
import
|
7
|
-
import { Locale } from "../../types";
|
4
|
+
import useImageEditorContext from "./useImageEditorContext";
|
8
5
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
image: Images.Resource;
|
13
|
-
locale: string;
|
14
|
-
locales: Record<string, Locale>;
|
15
|
-
setLocale: (locale: string) => void;
|
16
|
-
save: (evt: MouseEvent) => void;
|
17
|
-
showCaption: boolean;
|
18
|
-
updateLocalization: (name: "alternative" | "caption", value: string) => void;
|
19
|
-
}
|
6
|
+
type Props = {
|
7
|
+
onSave: (evt: React.MouseEvent) => void;
|
8
|
+
};
|
20
9
|
|
21
|
-
export default function Form(
|
22
|
-
const {
|
10
|
+
export default function Form({ onSave }: Props) {
|
11
|
+
const { state, dispatch, options } = useImageEditorContext();
|
12
|
+
const { alternative, caption, locale } = state;
|
13
|
+
const { image, locales } = options;
|
23
14
|
|
24
15
|
const closeModal = useModalStore((state) => state.close);
|
25
16
|
const notice = useToastStore((state) => state.notice);
|
26
17
|
|
27
|
-
const copyEmbedCode = (evt: MouseEvent) => {
|
18
|
+
const copyEmbedCode = (evt: React.MouseEvent) => {
|
28
19
|
evt.preventDefault();
|
29
20
|
copyToClipboard(`[image:${image.id}]`);
|
30
21
|
notice("Embed code copied to clipboard");
|
31
22
|
};
|
32
23
|
|
33
|
-
const handleChangeLocale = (evt: ChangeEvent<HTMLSelectElement>) => {
|
34
|
-
|
24
|
+
const handleChangeLocale = (evt: React.ChangeEvent<HTMLSelectElement>) => {
|
25
|
+
dispatch({ type: "setLocale", payload: evt.target.value });
|
26
|
+
};
|
27
|
+
|
28
|
+
const handleChangeAlternative = (
|
29
|
+
evt: React.ChangeEvent<HTMLTextAreaElement>
|
30
|
+
) => {
|
31
|
+
dispatch({ type: "setAlternative", payload: evt.target.value });
|
32
|
+
};
|
33
|
+
|
34
|
+
const handleChangeCaption = (evt: React.ChangeEvent<HTMLTextAreaElement>) => {
|
35
|
+
dispatch({ type: "setCaption", payload: evt.target.value });
|
35
36
|
};
|
36
37
|
|
37
38
|
const inputDir = (locales && locales[locale] && locales[locale].dir) || "ltr";
|
@@ -68,27 +69,23 @@ export default function Form(props: Props) {
|
|
68
69
|
lang={locale}
|
69
70
|
dir={inputDir}
|
70
71
|
value={alternative[locale] || ""}
|
71
|
-
onChange={
|
72
|
-
props.updateLocalization("alternative", e.target.value)
|
73
|
-
}
|
72
|
+
onChange={handleChangeAlternative}
|
74
73
|
/>
|
75
74
|
</div>
|
76
|
-
{
|
75
|
+
{options.caption && (
|
77
76
|
<div className="field">
|
78
77
|
<label>Caption</label>
|
79
78
|
<textarea
|
80
79
|
lang={locale}
|
81
80
|
dir={inputDir}
|
82
|
-
onChange={
|
83
|
-
props.updateLocalization("caption", e.target.value)
|
84
|
-
}
|
81
|
+
onChange={handleChangeCaption}
|
85
82
|
value={caption[locale] || ""}
|
86
83
|
className="caption"
|
87
84
|
/>
|
88
85
|
</div>
|
89
86
|
)}
|
90
87
|
<div className="buttons">
|
91
|
-
<button className="primary" onClick={
|
88
|
+
<button className="primary" onClick={onSave}>
|
92
89
|
Save
|
93
90
|
</button>
|
94
91
|
<button onClick={closeModal}>Cancel</button>
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { useReducer } from "react";
|
2
|
+
import * as Images from "../../types/Images";
|
3
|
+
import { Locale, LocalizedValue } from "../../types";
|
4
|
+
|
5
|
+
export type Action = {
|
6
|
+
type: "setAlternative" | "setCaption" | "setLocale";
|
7
|
+
payload: string;
|
8
|
+
};
|
9
|
+
|
10
|
+
export type State = {
|
11
|
+
locale: string;
|
12
|
+
caption: LocalizedValue;
|
13
|
+
alternative: LocalizedValue;
|
14
|
+
};
|
15
|
+
|
16
|
+
export type Options = {
|
17
|
+
caption: boolean;
|
18
|
+
image: Images.Resource;
|
19
|
+
locales: Record<string, Locale>;
|
20
|
+
};
|
21
|
+
|
22
|
+
type Props = {
|
23
|
+
caption: boolean;
|
24
|
+
locale: string;
|
25
|
+
locales: Record<string, Locale>;
|
26
|
+
image: Images.Resource;
|
27
|
+
};
|
28
|
+
|
29
|
+
function reducer(state: State, action: Action): State {
|
30
|
+
switch (action.type) {
|
31
|
+
case "setAlternative":
|
32
|
+
return {
|
33
|
+
...state,
|
34
|
+
alternative: { ...state.alternative, [state.locale]: action.payload }
|
35
|
+
};
|
36
|
+
case "setCaption":
|
37
|
+
return {
|
38
|
+
...state,
|
39
|
+
caption: { ...state.caption, [state.locale]: action.payload }
|
40
|
+
};
|
41
|
+
case "setLocale":
|
42
|
+
return { ...state, locale: action.payload };
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
export default function useImageEditor({
|
47
|
+
caption,
|
48
|
+
locale,
|
49
|
+
locales,
|
50
|
+
image
|
51
|
+
}: Props): [State, React.Dispatch<Action>, Options] {
|
52
|
+
const [state, dispatch] = useReducer(reducer, {
|
53
|
+
locale: locale,
|
54
|
+
caption: image.caption || {},
|
55
|
+
alternative: image.alternative || {}
|
56
|
+
});
|
57
|
+
const options = {
|
58
|
+
caption: caption,
|
59
|
+
locales: locales,
|
60
|
+
image: image
|
61
|
+
};
|
62
|
+
return [state, dispatch, options];
|
63
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import { createContext, useContext } from "react";
|
2
|
+
import { State, Action, Options } from "./useImageEditor";
|
3
|
+
|
4
|
+
type Context = {
|
5
|
+
state: State;
|
6
|
+
dispatch: React.Dispatch<Action>;
|
7
|
+
options: Options;
|
8
|
+
};
|
9
|
+
|
10
|
+
export const ImageEditorContext = createContext<Context>(null);
|
11
|
+
|
12
|
+
export default function useImageEditorContext() {
|
13
|
+
return useContext(ImageEditorContext);
|
14
|
+
}
|
@@ -1,5 +1,3 @@
|
|
1
|
-
import React, { MouseEvent, useState } from "react";
|
2
|
-
|
3
1
|
import useModalStore from "../stores/useModalStore";
|
4
2
|
import { putJson } from "../lib/request";
|
5
3
|
import * as Images from "../types/Images";
|
@@ -7,8 +5,10 @@ import { Locale } from "../types";
|
|
7
5
|
|
8
6
|
import ImageCropper, { useCrop, cropParams } from "./ImageCropper";
|
9
7
|
import Form from "./ImageEditor/Form";
|
8
|
+
import useImageEditor from "./ImageEditor/useImageEditor";
|
9
|
+
import { ImageEditorContext } from "./ImageEditor/useImageEditorContext";
|
10
10
|
|
11
|
-
|
11
|
+
type Props = {
|
12
12
|
image: Images.Resource;
|
13
13
|
caption: boolean;
|
14
14
|
locale: string;
|
@@ -17,34 +17,25 @@ interface Props {
|
|
17
17
|
data: Partial<Images.Resource>,
|
18
18
|
croppedImage: string | null
|
19
19
|
) => void;
|
20
|
-
}
|
20
|
+
};
|
21
21
|
|
22
22
|
export default function ImageEditor(props: Props) {
|
23
|
-
const [cropState,
|
24
|
-
const [locale, setLocale] = useState(props.locale);
|
25
|
-
const [localizations, setLocalizations] = useState({
|
26
|
-
caption: props.image.caption || {},
|
27
|
-
alternative: props.image.alternative || {}
|
28
|
-
});
|
23
|
+
const [cropState, cropDispatch, croppedImage] = useCrop(props.image);
|
29
24
|
|
30
|
-
const
|
25
|
+
const [state, dispatch, options] = useImageEditor(props);
|
31
26
|
|
32
|
-
const
|
33
|
-
name: "alternative" | "caption",
|
34
|
-
value: string
|
35
|
-
) => {
|
36
|
-
setLocalizations({
|
37
|
-
...localizations,
|
38
|
-
[name]: { ...localizations[name], [locale]: value }
|
39
|
-
});
|
40
|
-
};
|
27
|
+
const closeModal = useModalStore((state) => state.close);
|
41
28
|
|
42
|
-
const
|
29
|
+
const handleSave = async (evt: React.MouseEvent) => {
|
43
30
|
evt.preventDefault();
|
44
31
|
evt.stopPropagation();
|
45
32
|
|
46
|
-
const data = {
|
47
|
-
|
33
|
+
const data = {
|
34
|
+
...cropParams(cropState),
|
35
|
+
alternative: state.alternative,
|
36
|
+
caption: state.caption
|
37
|
+
};
|
38
|
+
await putJson(`/admin/images/${props.image.id}`, { image: data });
|
48
39
|
|
49
40
|
if (props.onUpdate) {
|
50
41
|
props.onUpdate(data, croppedImage);
|
@@ -53,25 +44,20 @@ export default function ImageEditor(props: Props) {
|
|
53
44
|
};
|
54
45
|
|
55
46
|
return (
|
56
|
-
<
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
<
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
locale={locale}
|
68
|
-
locales={props.locales}
|
69
|
-
setLocale={setLocale}
|
70
|
-
save={save}
|
71
|
-
showCaption={props.caption}
|
72
|
-
updateLocalization={updateLocalization}
|
47
|
+
<ImageEditorContext.Provider
|
48
|
+
value={{
|
49
|
+
state: state,
|
50
|
+
dispatch: dispatch,
|
51
|
+
options: options
|
52
|
+
}}>
|
53
|
+
<div className="image-editor">
|
54
|
+
<ImageCropper
|
55
|
+
croppedImage={croppedImage}
|
56
|
+
state={cropState}
|
57
|
+
dispatch={cropDispatch}
|
73
58
|
/>
|
74
|
-
|
75
|
-
|
59
|
+
{!cropState.cropping && <Form onSave={handleSave} />}
|
60
|
+
</div>
|
61
|
+
</ImageEditorContext.Provider>
|
76
62
|
);
|
77
63
|
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import * as Images from "../../types/Images";
|
2
|
+
|
3
|
+
type Props = {
|
4
|
+
attributeName: (record: Images.Record) => string;
|
5
|
+
deleted: Images.Record[];
|
6
|
+
};
|
7
|
+
|
8
|
+
export default function Deleted({ attributeName, deleted }: Props) {
|
9
|
+
return (
|
10
|
+
<div className="deleted">
|
11
|
+
{deleted.map((r) => (
|
12
|
+
<span className="deleted-image" key={r.id}>
|
13
|
+
<input name={`${attributeName(r)}[id]`} type="hidden" value={r.id} />
|
14
|
+
<input
|
15
|
+
name={`${attributeName(r)}[attachment_id]`}
|
16
|
+
type="hidden"
|
17
|
+
value={(r.image && r.image.id) || ""}
|
18
|
+
/>
|
19
|
+
<input
|
20
|
+
name={`${attributeName(r)}[_destroy]`}
|
21
|
+
type="hidden"
|
22
|
+
value={"true"}
|
23
|
+
/>
|
24
|
+
</span>
|
25
|
+
))}
|
26
|
+
</div>
|
27
|
+
);
|
28
|
+
}
|
@@ -1,13 +1,13 @@
|
|
1
|
-
import
|
1
|
+
import { RefObject } from "react";
|
2
2
|
|
3
3
|
import * as Drag from "../../types/Drag";
|
4
4
|
import * as Images from "../../types/Images";
|
5
5
|
|
6
|
-
|
6
|
+
type Props = {
|
7
7
|
container: RefObject<HTMLDivElement>;
|
8
|
-
draggable: Drag.
|
9
|
-
dragState: Drag.State
|
10
|
-
}
|
8
|
+
draggable: Drag.DraggableOrFiles<Images.Record>;
|
9
|
+
dragState: Drag.State<Images.Record>;
|
10
|
+
};
|
11
11
|
|
12
12
|
export default function DragElement(props: Props) {
|
13
13
|
const { draggable, dragState, container } = props;
|
@@ -1,5 +1,6 @@
|
|
1
|
-
import
|
1
|
+
import { useRef } from "react";
|
2
2
|
import FileUploadButton from "../FileUploadButton";
|
3
|
+
import Deleted from "./Deleted";
|
3
4
|
import DragElement from "./DragElement";
|
4
5
|
import FilePlaceholder from "./FilePlaceholder";
|
5
6
|
import GridImage from "./GridImage";
|
@@ -10,9 +11,9 @@ import * as Images from "../../types/Images";
|
|
10
11
|
|
11
12
|
import { createDraggable, collectionOrder, useDragUploader } from "../drag";
|
12
13
|
|
13
|
-
|
14
|
+
type Props = Images.GridOptions & {
|
14
15
|
state: Images.GridState;
|
15
|
-
}
|
16
|
+
};
|
16
17
|
|
17
18
|
function filterFiles(files: File[]): File[] {
|
18
19
|
const validMimeTypes = [
|
@@ -29,7 +30,10 @@ function draggedImageOrder(
|
|
29
30
|
primaryCollection: Drag.Collection<Images.Record>,
|
30
31
|
imagesCollection: Drag.Collection<Images.Record>,
|
31
32
|
dragState: Drag.State<Images.Record>
|
32
|
-
): [
|
33
|
+
): [
|
34
|
+
Drag.DraggableOrFiles<Images.Record>,
|
35
|
+
Drag.DraggableOrFiles<Images.Record>[]
|
36
|
+
] {
|
33
37
|
const [primary, ...rest] = collectionOrder(primaryCollection, dragState);
|
34
38
|
let images = [...rest, ...collectionOrder(imagesCollection, dragState)];
|
35
39
|
|
@@ -79,7 +83,7 @@ export default function Grid(props: Props) {
|
|
79
83
|
}
|
80
84
|
};
|
81
85
|
|
82
|
-
const [dragState, dragStart, listeners] = useDragUploader
|
86
|
+
const [dragState, dragStart, listeners] = useDragUploader(
|
83
87
|
[primary, images],
|
84
88
|
dragEnd
|
85
89
|
);
|
@@ -116,7 +120,10 @@ export default function Grid(props: Props) {
|
|
116
120
|
} else {
|
117
121
|
dispatchAll({
|
118
122
|
type: "update",
|
119
|
-
payload: {
|
123
|
+
payload: {
|
124
|
+
...draggable,
|
125
|
+
record: { image: json }
|
126
|
+
} as Drag.Draggable<Images.Record>
|
120
127
|
});
|
121
128
|
}
|
122
129
|
});
|
@@ -145,7 +152,7 @@ export default function Grid(props: Props) {
|
|
145
152
|
};
|
146
153
|
|
147
154
|
const renderImage = (
|
148
|
-
draggable: Drag.
|
155
|
+
draggable: Drag.DraggableOrFiles<Images.Record>,
|
149
156
|
isPrimary: boolean
|
150
157
|
) => {
|
151
158
|
const { dragging } = dragState;
|
@@ -256,23 +263,7 @@ export default function Grid(props: Props) {
|
|
256
263
|
{draggedImages.map((r) => renderImage(r, false))}
|
257
264
|
</div>
|
258
265
|
</div>
|
259
|
-
<
|
260
|
-
{deleted.map((r) => (
|
261
|
-
<span className="deleted-image" key={r.id}>
|
262
|
-
<input name={`${attrName(r)}[id]`} type="hidden" value={r.id} />
|
263
|
-
<input
|
264
|
-
name={`${attrName(r)}[attachment_id]`}
|
265
|
-
type="hidden"
|
266
|
-
value={(r.image && r.image.id) || ""}
|
267
|
-
/>
|
268
|
-
<input
|
269
|
-
name={`${attrName(r)}[_destroy]`}
|
270
|
-
type="hidden"
|
271
|
-
value={"true"}
|
272
|
-
/>
|
273
|
-
</span>
|
274
|
-
))}
|
275
|
-
</div>
|
266
|
+
<Deleted attributeName={attrName} deleted={deleted} />
|
276
267
|
</div>
|
277
268
|
);
|
278
269
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import { MouseEvent, useEffect, useState } from "react";
|
2
2
|
|
3
3
|
import copyToClipboard from "../../lib/copyToClipboard";
|
4
4
|
import useToastStore from "../../stores/useToastStore";
|
@@ -10,7 +10,7 @@ import { useDraggable } from "../drag";
|
|
10
10
|
import EditableImage from "../EditableImage";
|
11
11
|
import Placeholder from "./Placeholder";
|
12
12
|
|
13
|
-
|
13
|
+
type Props = {
|
14
14
|
draggable: Drag.Draggable<Images.Record>;
|
15
15
|
attributeName: string;
|
16
16
|
locale: string;
|
@@ -26,7 +26,7 @@ interface Props {
|
|
26
26
|
draggable: Drag.Draggable<Images.Record>
|
27
27
|
) => void;
|
28
28
|
onUpdate: (newImage: Images.Resource, src: string) => void;
|
29
|
-
}
|
29
|
+
};
|
30
30
|
|
31
31
|
export default function GridImage(props: Props) {
|
32
32
|
const { attributeName, draggable } = props;
|
@@ -45,7 +45,7 @@ export default function GridImage(props: Props) {
|
|
45
45
|
reader.onload = () => setSrc(reader.result as string);
|
46
46
|
reader.readAsDataURL(record.file);
|
47
47
|
}
|
48
|
-
}, []);
|
48
|
+
}, [record]);
|
49
49
|
|
50
50
|
const copyEmbed = (evt: MouseEvent) => {
|
51
51
|
evt.preventDefault();
|
@@ -1,12 +1,10 @@
|
|
1
|
-
import React from "react";
|
2
|
-
|
3
1
|
import * as Images from "../types/Images";
|
4
2
|
import useImageGrid from "./ImageGrid/useImageGrid";
|
5
3
|
import Grid from "./ImageGrid/Grid";
|
6
4
|
|
7
|
-
|
5
|
+
type Props = Images.GridOptions & {
|
8
6
|
records: Images.Record[];
|
9
|
-
}
|
7
|
+
};
|
10
8
|
|
11
9
|
export default function ImageGrid(props: Props) {
|
12
10
|
const state = useImageGrid(props.records, props.showEmbed);
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import { DragEvent, MouseEvent, useState } from "react";
|
2
2
|
|
3
3
|
import useToastStore from "../stores/useToastStore";
|
4
4
|
import { post } from "../lib/request";
|
@@ -8,7 +8,7 @@ import { Locale } from "../types";
|
|
8
8
|
import EditableImage from "./EditableImage";
|
9
9
|
import FileUploadButton from "./FileUploadButton";
|
10
10
|
|
11
|
-
|
11
|
+
type Props = {
|
12
12
|
locale: string;
|
13
13
|
locales: { [index: string]: Locale };
|
14
14
|
image: Images.Resource;
|
@@ -18,12 +18,12 @@ interface Props {
|
|
18
18
|
attr: string;
|
19
19
|
alternative?: string;
|
20
20
|
onChange?: (state: State) => void;
|
21
|
-
}
|
21
|
+
};
|
22
22
|
|
23
|
-
|
23
|
+
type State = {
|
24
24
|
image?: Images.Resource;
|
25
25
|
src?: string;
|
26
|
-
}
|
26
|
+
};
|
27
27
|
|
28
28
|
function getFiles(dt: DataTransfer): File[] {
|
29
29
|
const files: File[] = [];
|
@@ -1,14 +1,14 @@
|
|
1
|
-
import
|
1
|
+
import { Fragment } from "react";
|
2
2
|
|
3
|
-
|
3
|
+
type Props = {
|
4
4
|
label: string;
|
5
5
|
children: React.ReactNode;
|
6
6
|
htmlFor?: string;
|
7
7
|
description?: string;
|
8
8
|
errors?: string[];
|
9
|
-
}
|
9
|
+
};
|
10
10
|
|
11
|
-
export default function LabelledField(props:
|
11
|
+
export default function LabelledField(props: Props) {
|
12
12
|
const { htmlFor, description, label, errors, children } = props;
|
13
13
|
|
14
14
|
const classNames = ["field"];
|
@@ -21,10 +21,10 @@ export default function LabelledField(props: LabelledFieldProps) {
|
|
21
21
|
<label htmlFor={htmlFor}>
|
22
22
|
{label}
|
23
23
|
{errors && (
|
24
|
-
<
|
24
|
+
<Fragment>
|
25
25
|
{" "}
|
26
26
|
<span className="error">{errors[errors.length - 1]}</span>
|
27
|
-
</
|
27
|
+
</Fragment>
|
28
28
|
)}
|
29
29
|
</label>
|
30
30
|
{description && <p className="description">{description}</p>}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import
|
1
|
+
import { useCallback, MouseEvent, useEffect } from "react";
|
2
2
|
|
3
3
|
import useModalStore from "../stores/useModalStore";
|
4
4
|
|
@@ -6,17 +6,14 @@ 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 = (
|
10
|
-
evt
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
handleClose(evt);
|
18
|
-
}
|
19
|
-
};
|
9
|
+
const handleClose = useCallback(
|
10
|
+
(evt: KeyboardEvent | MouseEvent) => {
|
11
|
+
evt.stopPropagation();
|
12
|
+
evt.preventDefault();
|
13
|
+
close();
|
14
|
+
},
|
15
|
+
[close]
|
16
|
+
);
|
20
17
|
|
21
18
|
useEffect(() => {
|
22
19
|
if (component) {
|
@@ -27,11 +24,17 @@ export default function Modal() {
|
|
27
24
|
}, [component]);
|
28
25
|
|
29
26
|
useEffect(() => {
|
27
|
+
const handleKeypress = (evt: KeyboardEvent) => {
|
28
|
+
if (component && (evt.key == "Escape" || evt.keyCode === 27)) {
|
29
|
+
handleClose(evt);
|
30
|
+
}
|
31
|
+
};
|
32
|
+
|
30
33
|
window.addEventListener("keypress", handleKeypress);
|
31
34
|
return () => {
|
32
35
|
window.removeEventListener("keypress", handleKeypress);
|
33
36
|
};
|
34
|
-
}, []);
|
37
|
+
}, [component, handleClose]);
|
35
38
|
|
36
39
|
if (component) {
|
37
40
|
return (
|
@@ -1,18 +1,18 @@
|
|
1
|
-
import
|
1
|
+
import { ChangeEvent } from "react";
|
2
2
|
|
3
3
|
import * as Template from "../../types/Template";
|
4
4
|
|
5
5
|
import LabelledField from "../LabelledField";
|
6
6
|
import RichTextArea from "../RichTextArea";
|
7
7
|
|
8
|
-
|
8
|
+
type Props = {
|
9
9
|
block: Template.Block;
|
10
10
|
errors: string[];
|
11
11
|
onChange: (value: string) => void;
|
12
12
|
lang: string;
|
13
13
|
dir: string;
|
14
14
|
value: string;
|
15
|
-
}
|
15
|
+
};
|
16
16
|
|
17
17
|
export default function Block(props: Props) {
|
18
18
|
const { block, errors, onChange, lang, dir, value } = props;
|
@@ -1,25 +1,21 @@
|
|
1
|
-
import
|
2
|
-
|
3
|
-
import * as PageEditor from "../../types/PageEditor";
|
1
|
+
import { Fragment } from "react";
|
4
2
|
import * as Tags from "../../types/Tags";
|
5
3
|
import { MaybeLocalizedValue } from "../../types";
|
6
4
|
|
7
|
-
import { blockValue, errorsOn } from "./
|
5
|
+
import { blockValue, errorsOn } from "./utils";
|
6
|
+
import usePageFormContext from "./usePageFormContext";
|
8
7
|
import LabelledField from "../LabelledField";
|
9
8
|
import { default as TagEditor } from "../TagEditor/Editor";
|
10
9
|
import Block from "./Block";
|
11
10
|
import Dates from "./Dates";
|
12
11
|
|
13
|
-
|
14
|
-
state: PageEditor.State;
|
15
|
-
dispatch: (action: PageEditor.Action) => void;
|
12
|
+
type Props = {
|
16
13
|
tagsState: Tags.State;
|
17
|
-
tagsDispatch:
|
18
|
-
}
|
19
|
-
|
20
|
-
export default function Content(props: Props) {
|
21
|
-
const { state, dispatch, tagsState, tagsDispatch } = props;
|
14
|
+
tagsDispatch: React.Dispatch<Tags.Action>;
|
15
|
+
};
|
22
16
|
|
17
|
+
export default function Content({ tagsState, tagsDispatch }: Props) {
|
18
|
+
const { state, dispatch } = usePageFormContext();
|
23
19
|
const { page, locale, inputDir, templateConfig } = state;
|
24
20
|
|
25
21
|
const handleChange = (attr: string) => (value: MaybeLocalizedValue) => {
|
@@ -27,7 +23,7 @@ export default function Content(props: Props) {
|
|
27
23
|
};
|
28
24
|
|
29
25
|
return (
|
30
|
-
<
|
26
|
+
<Fragment>
|
31
27
|
{templateConfig.blocks.map((b) => (
|
32
28
|
<Block
|
33
29
|
key={b.name}
|
@@ -39,7 +35,7 @@ export default function Content(props: Props) {
|
|
39
35
|
value={blockValue(state, b)}
|
40
36
|
/>
|
41
37
|
))}
|
42
|
-
{templateConfig.dates && <Dates
|
38
|
+
{templateConfig.dates && <Dates />}
|
43
39
|
{templateConfig.tags && (
|
44
40
|
<LabelledField label="Tags">
|
45
41
|
<TagEditor
|
@@ -49,6 +45,6 @@ export default function Content(props: Props) {
|
|
49
45
|
/>
|
50
46
|
</LabelledField>
|
51
47
|
)}
|
52
|
-
</
|
48
|
+
</Fragment>
|
53
49
|
);
|
54
50
|
}
|