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.
Files changed (213) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +1 -1
  4. data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
  5. data/app/assets/builds/pages_core/admin.css +378 -253
  6. data/app/assets/builds/pages_core/mailer.css +41 -6
  7. data/app/assets/builds/pages_core_fonts/121b837e.woff2 +0 -0
  8. data/app/assets/builds/pages_core_fonts/216e5c23.woff2 +0 -0
  9. data/app/assets/builds/pages_core_fonts/3017b52f.woff2 +0 -0
  10. data/app/assets/builds/pages_core_fonts/489746b9.woff2 +0 -0
  11. data/app/assets/builds/pages_core_fonts/49775483.woff2 +0 -0
  12. data/app/assets/builds/pages_core_fonts/49c9e472.woff2 +0 -0
  13. data/app/assets/builds/pages_core_fonts/4a119645.woff2 +0 -0
  14. data/app/assets/builds/pages_core_fonts/5d56d7a8.woff2 +0 -0
  15. data/app/assets/builds/pages_core_fonts/61ea75a6.woff2 +0 -0
  16. data/app/assets/builds/pages_core_fonts/62cbb778.woff2 +0 -0
  17. data/app/assets/builds/pages_core_fonts/647d26c.woff2 +0 -0
  18. data/app/assets/builds/pages_core_fonts/67764053.woff2 +0 -0
  19. data/app/assets/builds/pages_core_fonts/6bb0fd00.woff2 +0 -0
  20. data/app/assets/builds/pages_core_fonts/6c0194a2.woff2 +0 -0
  21. data/app/assets/builds/pages_core_fonts/71423409.woff2 +0 -0
  22. data/app/assets/builds/pages_core_fonts/7584e61d.woff2 +0 -0
  23. data/app/assets/builds/pages_core_fonts/77bcfa1c.woff2 +0 -0
  24. data/app/assets/builds/pages_core_fonts/7aca0cc5.woff2 +0 -0
  25. data/app/assets/builds/pages_core_fonts/9a09533f.woff2 +0 -0
  26. data/app/assets/builds/pages_core_fonts/a51f5bc8.woff2 +0 -0
  27. data/app/assets/builds/pages_core_fonts/a80b2975.woff2 +0 -0
  28. data/app/assets/builds/pages_core_fonts/a891f617.woff2 +0 -0
  29. data/app/assets/builds/pages_core_fonts/ad6083f3.woff2 +0 -0
  30. data/app/assets/builds/pages_core_fonts/b29a61ff.woff2 +0 -0
  31. data/app/assets/builds/{fonts/6569749d.ttf → pages_core_fonts/b30b0656.ttf} +0 -0
  32. data/app/assets/builds/pages_core_fonts/b3a5f48c.woff2 +0 -0
  33. data/app/assets/builds/pages_core_fonts/bc73ee06.woff2 +0 -0
  34. data/app/assets/builds/pages_core_fonts/c38c6d45.woff2 +0 -0
  35. data/app/assets/builds/pages_core_fonts/c5ce0b1f.woff2 +0 -0
  36. data/app/assets/builds/pages_core_fonts/c8d53904.woff2 +0 -0
  37. data/app/assets/builds/pages_core_fonts/ce13c169.woff2 +0 -0
  38. data/app/assets/builds/pages_core_fonts/d43bd0d5.woff2 +0 -0
  39. data/app/assets/builds/pages_core_fonts/e1c7d368.woff2 +0 -0
  40. data/app/assets/builds/pages_core_fonts/e1e8175d.woff2 +0 -0
  41. data/app/assets/builds/pages_core_fonts/e318f796.woff2 +0 -0
  42. data/app/assets/builds/{fonts/ee32bc60.ttf → pages_core_fonts/e7acb7d9.ttf} +0 -0
  43. data/app/assets/builds/pages_core_fonts/ee5514c6.woff2 +0 -0
  44. data/app/assets/builds/pages_core_fonts/f4e495e2.woff2 +0 -0
  45. data/app/assets/builds/pages_core_fonts/f736ec65.woff2 +0 -0
  46. data/app/assets/builds/pages_core_fonts/f741c7ba.woff2 +0 -0
  47. data/app/assets/builds/pages_core_fonts/f7767345.woff2 +0 -0
  48. data/app/assets/builds/pages_core_fonts/fe9eb751.woff2 +0 -0
  49. data/app/assets/stylesheets/pages_core/admin/components/forms.css +2 -2
  50. data/app/assets/stylesheets/pages_core/admin/components/header.css +1 -1
  51. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +1 -1
  52. data/app/assets/stylesheets/pages_core/admin/global/fonts.css +38 -38
  53. data/app/controllers/{pages_core → admin}/admin_controller.rb +1 -3
  54. data/app/controllers/attachments_controller.rb +40 -0
  55. data/app/controllers/concerns/pages_core/document_title_controller.rb +16 -0
  56. data/app/controllers/concerns/pages_core/page_parameters.rb +1 -1
  57. data/app/controllers/concerns/pages_core/pages/preview_controller.rb +49 -0
  58. data/app/controllers/concerns/pages_core/pages/rss_controller.rb +43 -0
  59. data/app/controllers/errors_controller.rb +2 -0
  60. data/app/controllers/images_controller.rb +13 -0
  61. data/app/controllers/pages_core/frontend/pages_controller.rb +3 -4
  62. data/app/controllers/pages_core/frontend_controller.rb +6 -1
  63. data/app/controllers/pages_core/sitemaps_controller.rb +21 -52
  64. data/app/helpers/pages_core/admin/image_uploads_helper.rb +1 -1
  65. data/app/helpers/pages_core/application_helper.rb +0 -3
  66. data/app/helpers/pages_core/attachments_helper.rb +0 -10
  67. data/app/helpers/pages_core/feed_tags_helper.rb +31 -0
  68. data/app/helpers/pages_core/frontend_helper.rb +3 -0
  69. data/app/helpers/pages_core/head_tags_helper.rb +80 -70
  70. data/app/helpers/pages_core/page_path_helper.rb +1 -12
  71. data/app/javascript/components/Attachments/Attachment.tsx +3 -3
  72. data/app/javascript/components/Attachments/AttachmentEditor.tsx +5 -5
  73. data/app/javascript/components/Attachments/Deleted.tsx +28 -0
  74. data/app/javascript/components/Attachments/List.tsx +11 -24
  75. data/app/javascript/components/Attachments/Placeholder.tsx +0 -2
  76. data/app/javascript/components/Attachments.tsx +2 -3
  77. data/app/javascript/components/DateRangeSelect.tsx +13 -10
  78. data/app/javascript/components/DateTimeSelect.tsx +11 -11
  79. data/app/javascript/components/EditableImage.tsx +3 -3
  80. data/app/javascript/components/FileUploadButton.tsx +3 -3
  81. data/app/javascript/components/ImageCropper/FocalPoint.tsx +10 -14
  82. data/app/javascript/components/ImageCropper/Image.tsx +19 -25
  83. data/app/javascript/components/ImageCropper/Toolbar.tsx +27 -26
  84. data/app/javascript/components/ImageCropper/useContainerSize.ts +25 -0
  85. data/app/javascript/components/ImageCropper/useCrop.ts +28 -13
  86. data/app/javascript/components/ImageCropper/useImageCropperContext.ts +13 -0
  87. data/app/javascript/components/ImageCropper.tsx +24 -83
  88. data/app/javascript/components/ImageEditor/Form.tsx +25 -28
  89. data/app/javascript/components/ImageEditor/useImageEditor.ts +63 -0
  90. data/app/javascript/components/ImageEditor/useImageEditorContext.ts +14 -0
  91. data/app/javascript/components/ImageEditor.tsx +28 -42
  92. data/app/javascript/components/ImageGrid/Deleted.tsx +28 -0
  93. data/app/javascript/components/ImageGrid/DragElement.tsx +5 -5
  94. data/app/javascript/components/ImageGrid/FilePlaceholder.tsx +0 -2
  95. data/app/javascript/components/ImageGrid/Grid.tsx +15 -24
  96. data/app/javascript/components/ImageGrid/GridImage.tsx +4 -4
  97. data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -4
  98. data/app/javascript/components/ImageGrid.tsx +2 -4
  99. data/app/javascript/components/ImageUploader.tsx +5 -5
  100. data/app/javascript/components/LabelledField.tsx +6 -6
  101. data/app/javascript/components/Modal.tsx +16 -13
  102. data/app/javascript/components/PageForm/Block.tsx +3 -3
  103. data/app/javascript/components/PageForm/Content.tsx +11 -15
  104. data/app/javascript/components/PageForm/Dates.tsx +3 -11
  105. data/app/javascript/components/PageForm/Files.tsx +2 -4
  106. data/app/javascript/components/PageForm/Form.tsx +3 -9
  107. data/app/javascript/components/PageForm/Images.tsx +2 -4
  108. data/app/javascript/components/PageForm/LocaleLinks.tsx +4 -11
  109. data/app/javascript/components/PageForm/Metadata.tsx +8 -13
  110. data/app/javascript/components/PageForm/Options.tsx +28 -11
  111. data/app/javascript/components/PageForm/PageDescription.tsx +7 -14
  112. data/app/javascript/components/PageForm/PathSegment.tsx +5 -10
  113. data/app/javascript/components/PageForm/TabPanel.tsx +3 -6
  114. data/app/javascript/components/PageForm/Tabs.tsx +2 -4
  115. data/app/javascript/components/PageForm/UnconfiguredContent.tsx +7 -12
  116. data/app/javascript/components/PageForm/pageParams.ts +3 -2
  117. data/app/javascript/components/PageForm/usePage.ts +1 -46
  118. data/app/javascript/components/PageForm/usePageFormContext.ts +8 -0
  119. data/app/javascript/components/PageForm/useTabs.ts +1 -1
  120. data/app/javascript/components/PageForm/utils.ts +49 -0
  121. data/app/javascript/components/PageForm.tsx +52 -48
  122. data/app/javascript/components/PageImages.tsx +1 -3
  123. data/app/javascript/components/PageTree/Button.tsx +25 -0
  124. data/app/javascript/components/PageTree/CollapseArrow.tsx +34 -0
  125. data/app/javascript/components/PageTree/CollapsedLabel.tsx +21 -0
  126. data/app/javascript/components/PageTree/EditPageName.tsx +68 -0
  127. data/app/javascript/components/PageTree/Node.tsx +143 -413
  128. data/app/javascript/components/PageTree/PageName.tsx +6 -4
  129. data/app/javascript/components/PageTree/StatusLabel.tsx +10 -0
  130. data/app/javascript/components/PageTree/tree.ts +268 -0
  131. data/app/javascript/components/PageTree/usePageTree.ts +268 -0
  132. data/app/javascript/components/PageTree/usePageTreeContext.ts +13 -0
  133. data/app/javascript/components/PageTree.tsx +194 -214
  134. data/app/javascript/components/{RichTextToolbarButton.tsx → RichTextArea/ToolbarButton.tsx} +3 -5
  135. data/app/javascript/components/RichTextArea/actions.ts +106 -0
  136. data/app/javascript/components/RichTextArea/useMaybeControlledValue.ts +14 -0
  137. data/app/javascript/components/RichTextArea.tsx +91 -209
  138. data/app/javascript/components/TagEditor/AddTagForm.tsx +2 -2
  139. data/app/javascript/components/TagEditor/Editor.tsx +3 -5
  140. data/app/javascript/components/TagEditor/Tag.tsx +3 -5
  141. data/app/javascript/components/TagEditor/useTags.ts +7 -4
  142. data/app/javascript/components/TagEditor.tsx +2 -4
  143. data/app/javascript/components/Toast.tsx +5 -5
  144. data/app/javascript/components/drag/draggedOrder.ts +6 -6
  145. data/app/javascript/components/drag/useDragCollection.ts +21 -25
  146. data/app/javascript/components/drag/useDragUploader.ts +20 -18
  147. data/app/javascript/components/drag/useDraggable.ts +3 -3
  148. data/app/javascript/features/RichText.tsx +0 -1
  149. data/app/javascript/features/contentTabs.ts +2 -2
  150. data/app/javascript/stores/useModalStore.ts +1 -1
  151. data/app/javascript/stores/useToastStore.ts +2 -2
  152. data/app/javascript/types/Attachments.ts +11 -11
  153. data/app/javascript/types/Crop.ts +16 -12
  154. data/app/javascript/types/Drag.ts +21 -23
  155. data/app/javascript/types/Images.ts +8 -8
  156. data/app/javascript/types/PageEditor.ts +11 -4
  157. data/app/javascript/types/Pages.ts +22 -27
  158. data/app/javascript/types/Tags.ts +5 -6
  159. data/app/javascript/types/Template.ts +4 -4
  160. data/app/javascript/types.ts +2 -2
  161. data/app/models/attachment.rb +5 -9
  162. data/app/models/autopublisher.rb +1 -1
  163. data/app/models/concerns/pages_core/page_model/redirectable.rb +1 -2
  164. data/app/models/concerns/pages_core/page_model/searchable.rb +1 -1
  165. data/app/models/concerns/pages_core/page_model/status.rb +2 -4
  166. data/app/models/concerns/pages_core/searchable_document.rb +2 -4
  167. data/app/models/image.rb +0 -15
  168. data/app/models/page_builder.rb +4 -6
  169. data/app/resources/admin/page_resource.rb +2 -2
  170. data/app/resources/export/page_resource.rb +1 -1
  171. data/app/services/pages_core/invite_service.rb +1 -2
  172. data/app/views/layouts/admin.html.erb +1 -0
  173. data/app/views/pages_core/sitemaps/index.xml.builder +10 -0
  174. data/config/routes.rb +4 -3
  175. data/db/migrate/20240917142300_add_skip_index_to_pages.rb +7 -0
  176. data/lib/pages_core/engine.rb +15 -17
  177. data/lib/pages_core/sitemap.rb +58 -0
  178. data/lib/pages_core/templates/configuration_proxy.rb +3 -3
  179. data/lib/pages_core.rb +7 -4
  180. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +2 -2
  181. data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +13 -5
  182. data/lib/rails/generators/pages_core/frontend/templates/postcss.config.js +2 -6
  183. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.css +3 -1
  184. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.css +2 -3
  185. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/animation.css +1 -1
  186. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.css +6 -5
  187. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/fonts.css +1 -1
  188. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/grid.css +9 -6
  189. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.css +42 -26
  190. data/lib/rails/generators/pages_core/install/templates/application_controller.rb +0 -6
  191. data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +1 -1
  192. metadata +81 -49
  193. data/app/assets/builds/fonts/7b7db107.woff2 +0 -0
  194. data/app/assets/builds/fonts/921961e9.woff2 +0 -0
  195. data/app/controller_dummies/admin/admin_controller.rb +0 -6
  196. data/app/controller_dummies/application_controller.rb +0 -6
  197. data/app/controller_dummies/attachments_controller.rb +0 -4
  198. data/app/controller_dummies/frontend_controller.rb +0 -4
  199. data/app/controller_dummies/images_controller.rb +0 -4
  200. data/app/controller_dummies/page_files_controller.rb +0 -4
  201. data/app/controller_dummies/pages_controller.rb +0 -4
  202. data/app/controller_dummies/sitemaps_controller.rb +0 -4
  203. data/app/controllers/concerns/pages_core/preview_pages_controller.rb +0 -47
  204. data/app/controllers/concerns/pages_core/rss_controller.rb +0 -41
  205. data/app/controllers/pages_core/attachments_controller.rb +0 -42
  206. data/app/controllers/pages_core/frontend/page_files_controller.rb +0 -25
  207. data/app/controllers/pages_core/images_controller.rb +0 -15
  208. data/app/helpers/pages_core/meta_tags_helper.rb +0 -96
  209. data/app/helpers/pages_core/open_graph_tags_helper.rb +0 -49
  210. data/app/javascript/components/PageTree/Draggable.tsx +0 -338
  211. data/app/javascript/lib/Tree.ts +0 -305
  212. data/app/javascript/types/Trees.ts +0 -19
  213. 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 * as Images from "../../types/Images";
7
- import { Locale } from "../../types";
4
+ import useImageEditorContext from "./useImageEditorContext";
8
5
 
9
- interface Props {
10
- alternative: Record<string, string>;
11
- caption: Record<string, string>;
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(props: Props) {
22
- const { alternative, caption, image, locale, locales } = props;
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
- props.setLocale(evt.target.value);
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={(e) =>
72
- props.updateLocalization("alternative", e.target.value)
73
- }
72
+ onChange={handleChangeAlternative}
74
73
  />
75
74
  </div>
76
- {props.showCaption && (
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={(e) =>
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={props.save}>
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
- interface Props {
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, dispatch, croppedImage] = useCrop(props.image);
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 closeModal = useModalStore((state) => state.close);
25
+ const [state, dispatch, options] = useImageEditor(props);
31
26
 
32
- const updateLocalization = (
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 save = (evt: MouseEvent) => {
29
+ const handleSave = async (evt: React.MouseEvent) => {
43
30
  evt.preventDefault();
44
31
  evt.stopPropagation();
45
32
 
46
- const data = { ...localizations, ...cropParams(cropState) };
47
- void putJson(`/admin/images/${props.image.id}`, { image: data });
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
- <div className="image-editor">
57
- <ImageCropper
58
- croppedImage={croppedImage}
59
- cropState={cropState}
60
- dispatch={dispatch}
61
- />
62
- {!cropState.cropping && (
63
- <Form
64
- alternative={localizations.alternative}
65
- caption={localizations.caption}
66
- image={props.image}
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
- </div>
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 React, { RefObject } from "react";
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
- interface Props {
6
+ type Props = {
7
7
  container: RefObject<HTMLDivElement>;
8
- draggable: Drag.Item<Images.Record>;
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,3 @@
1
- import React from "react";
2
-
3
1
  export default function FilePlaceholder() {
4
2
  return (
5
3
  <div className="grid-image" key="file-placeholder">
@@ -1,5 +1,6 @@
1
- import React, { useRef } from "react";
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
- interface Props extends Images.GridOptions {
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
- ): [Drag.Item<Images.Record>, Drag.Item<Images.Record>[]] {
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<Images.Record>(
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: { ...draggable, record: { image: json } } as Drag.Draggable
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.Item<Images.Record>,
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
- <div className="deleted">
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 React, { MouseEvent, useEffect, useState } from "react";
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
- interface Props {
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,8 +1,6 @@
1
- import React from "react";
2
-
3
- interface Props {
1
+ type Props = {
4
2
  src: string;
5
- }
3
+ };
6
4
 
7
5
  export default function Placeholder(props: Props) {
8
6
  if (props.src) {
@@ -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
- interface Props extends Images.GridOptions {
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 React, { DragEvent, MouseEvent, useState } from "react";
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
- interface Props {
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
- interface State {
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 React from "react";
1
+ import { Fragment } from "react";
2
2
 
3
- interface LabelledFieldProps {
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: LabelledFieldProps) {
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
- <React.Fragment>
24
+ <Fragment>
25
25
  {" "}
26
26
  <span className="error">{errors[errors.length - 1]}</span>
27
- </React.Fragment>
27
+ </Fragment>
28
28
  )}
29
29
  </label>
30
30
  {description && <p className="description">{description}</p>}
@@ -1,4 +1,4 @@
1
- import React, { MouseEvent, useEffect } from "react";
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 = (evt: KeyboardEvent | MouseEvent) => {
10
- evt.stopPropagation();
11
- evt.preventDefault();
12
- close();
13
- };
14
-
15
- const handleKeypress = (evt: KeyboardEvent) => {
16
- if (component && (evt.key == "Escape" || evt.keyCode === 27)) {
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 React, { ChangeEvent } from "react";
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
- interface Props {
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 React from "react";
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 "./usePage";
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
- interface Props {
14
- state: PageEditor.State;
15
- dispatch: (action: PageEditor.Action) => void;
12
+ type Props = {
16
13
  tagsState: Tags.State;
17
- tagsDispatch: (action: Tags.Action) => void;
18
- }
19
-
20
- export default function Content(props: Props) {
21
- const { state, dispatch, tagsState, tagsDispatch } = props;
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
- <React.Fragment>
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 state={state} dispatch={dispatch} />}
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
- </React.Fragment>
48
+ </Fragment>
53
49
  );
54
50
  }