pages_core 3.14.0 → 3.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +19 -8
  4. data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
  5. data/app/assets/builds/pages_core/admin.css +672 -379
  6. data/app/assets/fonts/Inter-Black.woff2 +0 -0
  7. data/app/assets/fonts/Inter-BlackItalic.woff2 +0 -0
  8. data/app/assets/fonts/Inter-Bold.woff2 +0 -0
  9. data/app/assets/fonts/Inter-BoldItalic.woff2 +0 -0
  10. data/app/assets/fonts/Inter-ExtraBold.woff2 +0 -0
  11. data/app/assets/fonts/Inter-ExtraBoldItalic.woff2 +0 -0
  12. data/app/assets/fonts/Inter-ExtraLight.woff2 +0 -0
  13. data/app/assets/fonts/Inter-ExtraLightItalic.woff2 +0 -0
  14. data/app/assets/fonts/Inter-Italic.woff2 +0 -0
  15. data/app/assets/fonts/Inter-Light.woff2 +0 -0
  16. data/app/assets/fonts/Inter-LightItalic.woff2 +0 -0
  17. data/app/assets/fonts/Inter-Medium.woff2 +0 -0
  18. data/app/assets/fonts/Inter-MediumItalic.woff2 +0 -0
  19. data/app/assets/fonts/Inter-Regular.woff2 +0 -0
  20. data/app/assets/fonts/Inter-SemiBold.woff2 +0 -0
  21. data/app/assets/fonts/Inter-SemiBoldItalic.woff2 +0 -0
  22. data/app/assets/fonts/Inter-Thin.woff2 +0 -0
  23. data/app/assets/fonts/Inter-ThinItalic.woff2 +0 -0
  24. data/app/assets/fonts/InterDisplay-Black.woff2 +0 -0
  25. data/app/assets/fonts/InterDisplay-BlackItalic.woff2 +0 -0
  26. data/app/assets/fonts/InterDisplay-Bold.woff2 +0 -0
  27. data/app/assets/fonts/InterDisplay-BoldItalic.woff2 +0 -0
  28. data/app/assets/fonts/InterDisplay-ExtraBold.woff2 +0 -0
  29. data/app/assets/fonts/InterDisplay-ExtraBoldItalic.woff2 +0 -0
  30. data/app/assets/fonts/InterDisplay-ExtraLight.woff2 +0 -0
  31. data/app/assets/fonts/InterDisplay-ExtraLightItalic.woff2 +0 -0
  32. data/app/assets/fonts/InterDisplay-Italic.woff2 +0 -0
  33. data/app/assets/fonts/InterDisplay-Light.woff2 +0 -0
  34. data/app/assets/fonts/InterDisplay-LightItalic.woff2 +0 -0
  35. data/app/assets/fonts/InterDisplay-Medium.woff2 +0 -0
  36. data/app/assets/fonts/InterDisplay-MediumItalic.woff2 +0 -0
  37. data/app/assets/fonts/InterDisplay-Regular.woff2 +0 -0
  38. data/app/assets/fonts/InterDisplay-SemiBold.woff2 +0 -0
  39. data/app/assets/fonts/InterDisplay-SemiBoldItalic.woff2 +0 -0
  40. data/app/assets/fonts/InterDisplay-Thin.woff2 +0 -0
  41. data/app/assets/fonts/InterDisplay-ThinItalic.woff2 +0 -0
  42. data/app/assets/fonts/InterVariable-Italic.woff2 +0 -0
  43. data/app/assets/fonts/InterVariable.woff2 +0 -0
  44. data/app/assets/stylesheets/pages_core/admin/components/archive.css +1 -1
  45. data/app/assets/stylesheets/pages_core/admin/components/attachments.css +22 -34
  46. data/app/assets/stylesheets/pages_core/admin/components/base.css +1 -68
  47. data/app/assets/stylesheets/pages_core/admin/components/forms.css +107 -48
  48. data/app/assets/stylesheets/pages_core/admin/components/header.css +56 -58
  49. data/app/assets/stylesheets/pages_core/admin/components/image_editor.css +35 -24
  50. data/app/assets/stylesheets/pages_core/admin/components/image_grid.css +28 -27
  51. data/app/assets/stylesheets/pages_core/admin/components/image_uploader.css +5 -5
  52. data/app/assets/stylesheets/pages_core/admin/components/layout.css +7 -1
  53. data/app/assets/stylesheets/pages_core/admin/components/list_table.css +24 -15
  54. data/app/assets/stylesheets/pages_core/admin/components/page_tree.css +63 -104
  55. data/app/assets/stylesheets/pages_core/admin/components/pagination.css +12 -13
  56. data/app/assets/stylesheets/pages_core/admin/components/search.css +1 -16
  57. data/app/assets/stylesheets/pages_core/admin/components/sidebar.css +5 -11
  58. data/app/assets/stylesheets/pages_core/admin/components/tag_editor.css +22 -36
  59. data/app/assets/stylesheets/pages_core/admin/components/toast.css +1 -2
  60. data/app/assets/stylesheets/pages_core/admin/components/toolbar.css +10 -10
  61. data/app/assets/stylesheets/pages_core/admin/components/totp.css +1 -1
  62. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +37 -51
  63. data/app/assets/stylesheets/pages_core/admin/global/fonts.css +271 -0
  64. data/app/assets/stylesheets/pages_core/admin/global/typography.css +109 -0
  65. data/app/assets/stylesheets/pages_core/admin/vars.css +1 -3
  66. data/app/assets/stylesheets/pages_core/admin.postcss.css +1 -0
  67. data/app/controllers/admin/account_recoveries_controller.rb +2 -2
  68. data/app/controllers/admin/pages_controller.rb +22 -42
  69. data/app/controllers/concerns/pages_core/error_reporting.rb +1 -1
  70. data/app/controllers/concerns/pages_core/page_parameters.rb +29 -0
  71. data/app/controllers/concerns/pages_core/policies_helper.rb +1 -1
  72. data/app/controllers/concerns/pages_core/preview_pages_controller.rb +20 -20
  73. data/app/controllers/pages_core/admin_controller.rb +0 -2
  74. data/app/controllers/pages_core/frontend/pages_controller.rb +2 -6
  75. data/app/formatters/pages_core/html_formatter.rb +2 -4
  76. data/app/helpers/admin/menu_helper.rb +5 -4
  77. data/app/helpers/admin/pages_helper.rb +1 -21
  78. data/app/helpers/pages_core/admin/admin_helper.rb +2 -3
  79. data/app/helpers/pages_core/admin/content_tabs_helper.rb +1 -2
  80. data/app/helpers/pages_core/admin/labelled_field_helper.rb +1 -1
  81. data/app/helpers/pages_core/frontend_helper.rb +1 -1
  82. data/app/helpers/pages_core/images_helper.rb +10 -8
  83. data/app/helpers/pages_core/labelled_form_builder.rb +2 -7
  84. data/app/helpers/pages_core/page_path_helper.rb +1 -1
  85. data/app/javascript/components/Attachments/Attachment.tsx +20 -18
  86. data/app/javascript/components/Attachments/AttachmentEditor.tsx +11 -9
  87. data/app/javascript/components/{Attachments.jsx → Attachments/List.tsx} +58 -63
  88. data/app/javascript/components/Attachments/useAttachments.ts +15 -0
  89. data/app/javascript/components/Attachments.tsx +14 -0
  90. data/app/javascript/components/DateRangeSelect.tsx +105 -0
  91. data/app/javascript/components/DateTimeSelect.tsx +136 -0
  92. data/app/javascript/components/EditableImage.tsx +11 -9
  93. data/app/javascript/components/FileUploadButton.tsx +7 -7
  94. data/app/javascript/components/ImageCropper/FocalPoint.tsx +9 -12
  95. data/app/javascript/components/ImageCropper/Image.tsx +10 -8
  96. data/app/javascript/components/ImageCropper/Toolbar.tsx +11 -12
  97. data/app/javascript/components/ImageCropper/useCrop.ts +24 -53
  98. data/app/javascript/components/ImageCropper.tsx +10 -15
  99. data/app/javascript/components/ImageEditor/Form.tsx +12 -8
  100. data/app/javascript/components/ImageEditor.tsx +12 -7
  101. data/app/javascript/components/ImageGrid/DragElement.tsx +9 -12
  102. data/app/javascript/components/{ImageGrid.jsx → ImageGrid/Grid.tsx} +62 -71
  103. data/app/javascript/components/ImageGrid/GridImage.tsx +22 -23
  104. data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -2
  105. data/app/javascript/components/ImageGrid/useImageGrid.ts +26 -0
  106. data/app/javascript/components/ImageGrid.tsx +15 -0
  107. data/app/javascript/components/ImageUploader.tsx +35 -22
  108. data/app/javascript/components/LabelledField.tsx +34 -0
  109. data/app/javascript/components/Modal.tsx +2 -2
  110. data/app/javascript/components/PageForm/Block.tsx +81 -0
  111. data/app/javascript/components/PageForm/Content.tsx +54 -0
  112. data/app/javascript/components/PageForm/Dates.tsx +66 -0
  113. data/app/javascript/components/PageForm/Files.tsx +28 -0
  114. data/app/javascript/components/PageForm/Form.tsx +41 -0
  115. data/app/javascript/components/PageForm/Images.tsx +28 -0
  116. data/app/javascript/components/PageForm/LocaleLinks.tsx +36 -0
  117. data/app/javascript/components/PageForm/Metadata.tsx +67 -0
  118. data/app/javascript/components/PageForm/Options.tsx +180 -0
  119. data/app/javascript/components/PageForm/PageDescription.tsx +48 -0
  120. data/app/javascript/components/PageForm/PathSegment.tsx +65 -0
  121. data/app/javascript/components/PageForm/TabPanel.tsx +21 -0
  122. data/app/javascript/components/PageForm/Tabs.tsx +33 -0
  123. data/app/javascript/components/PageForm/UnconfiguredContent.tsx +42 -0
  124. data/app/javascript/components/PageForm/pageParams.ts +95 -0
  125. data/app/javascript/components/PageForm/preview.ts +23 -0
  126. data/app/javascript/components/PageForm/usePage.ts +169 -0
  127. data/app/javascript/components/PageForm/useTabs.ts +46 -0
  128. data/app/javascript/components/PageForm.tsx +163 -0
  129. data/app/javascript/components/PageImages.tsx +7 -9
  130. data/app/javascript/components/PageTree/Draggable.tsx +40 -39
  131. data/app/javascript/components/PageTree/Node.tsx +62 -56
  132. data/app/javascript/components/PageTree/PageName.tsx +28 -0
  133. data/app/javascript/components/PageTree.tsx +65 -53
  134. data/app/javascript/components/{RichTextArea.jsx → RichTextArea.tsx} +98 -79
  135. data/app/javascript/components/RichTextToolbarButton.tsx +4 -6
  136. data/app/javascript/components/TagEditor/AddTagForm.tsx +19 -12
  137. data/app/javascript/components/TagEditor/Editor.tsx +32 -0
  138. data/app/javascript/components/TagEditor/Tag.tsx +6 -4
  139. data/app/javascript/components/TagEditor/useTags.ts +58 -0
  140. data/app/javascript/components/TagEditor.tsx +8 -58
  141. data/app/javascript/components/Toast.tsx +3 -3
  142. data/app/javascript/components/drag/draggedOrder.ts +22 -14
  143. data/app/javascript/components/drag/useDragCollection.ts +35 -30
  144. data/app/javascript/components/drag/useDragUploader.ts +32 -21
  145. data/app/javascript/components/drag/useDraggable.ts +7 -6
  146. data/app/javascript/components/drag.ts +0 -1
  147. data/app/javascript/components.ts +1 -3
  148. data/app/javascript/features/RichText.tsx +2 -3
  149. data/app/javascript/features/contentTabs.ts +79 -0
  150. data/app/javascript/index.ts +5 -12
  151. data/app/javascript/lib/Tree.ts +31 -45
  152. data/app/javascript/lib/request.ts +11 -11
  153. data/app/javascript/stores/useToastStore.ts +1 -1
  154. data/app/javascript/types/Attachments.ts +29 -0
  155. data/app/javascript/types/Crop.ts +36 -0
  156. data/app/javascript/types/Drag.ts +34 -0
  157. data/app/javascript/types/Images.ts +47 -0
  158. data/app/javascript/types/PageEditor.ts +26 -0
  159. data/app/javascript/types/Pages.ts +75 -0
  160. data/app/javascript/types/Tags.ts +9 -0
  161. data/app/javascript/types/Template.ts +24 -0
  162. data/app/javascript/types/Trees.ts +19 -0
  163. data/app/javascript/types.ts +2 -25
  164. data/app/models/attachment.rb +1 -1
  165. data/app/models/concerns/pages_core/authenticable_user.rb +63 -0
  166. data/app/models/concerns/pages_core/emailable.rb +16 -0
  167. data/app/models/concerns/pages_core/page_model/templateable.rb +2 -16
  168. data/app/models/invite.rb +2 -6
  169. data/app/models/otp_secret.rb +4 -4
  170. data/app/models/page.rb +0 -3
  171. data/app/models/user.rb +2 -46
  172. data/app/policies/page_policy.rb +6 -2
  173. data/app/resources/admin/page_resource.rb +95 -0
  174. data/app/resources/admin/page_tree_resource.rb +27 -0
  175. data/app/resources/admin/template_configuration_resource.rb +50 -0
  176. data/app/views/admin/news/_sidebar.html.erb +2 -4
  177. data/app/views/admin/news/index.html.erb +0 -1
  178. data/app/views/admin/pages/_form.html.erb +10 -30
  179. data/app/views/admin/pages/_search_bar.html.erb +1 -1
  180. data/app/views/admin/pages/edit.html.erb +1 -57
  181. data/app/views/admin/pages/index.html.erb +1 -1
  182. data/app/views/admin/pages/new.html.erb +1 -44
  183. data/app/views/admin/sessions/new.html.erb +9 -11
  184. data/app/views/admin/users/_access_control.html.erb +5 -1
  185. data/app/views/admin/users/_list.html.erb +12 -7
  186. data/app/views/layouts/admin/_header.html.erb +2 -4
  187. data/app/views/layouts/admin/_page_header.html.erb +1 -2
  188. data/app/views/layouts/admin.html.erb +1 -1
  189. data/config/locales/en.yml +0 -4
  190. data/config/routes.rb +3 -7
  191. data/db/migrate/20240126160700_add_2fa_fields.rb +5 -1
  192. data/db/migrate/20240131140700_change_email_to_citext.rb +18 -0
  193. data/db/migrate/20240201160700_remove_persistent_data.rb +7 -0
  194. data/db/migrate/20240508145300_remove_categories.rb +21 -0
  195. data/lib/pages_core/configuration/base.rb +2 -2
  196. data/lib/pages_core/templates/configuration.rb +1 -1
  197. data/lib/pages_core/templates/configuration_proxy.rb +2 -2
  198. data/lib/pages_core/templates/template_configuration.rb +11 -1
  199. data/lib/pages_core/templates.rb +6 -4
  200. data/lib/pages_core/version.rb +1 -1
  201. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +6 -7
  202. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +17 -12
  203. data/lib/rails/generators/pages_core/rspec/rspec_generator.rb +0 -2
  204. data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +3 -4
  205. metadata +95 -29
  206. data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -27
  207. data/app/controllers/admin/categories_controller.rb +0 -56
  208. data/app/controllers/concerns/pages_core/admin/persistent_params.rb +0 -75
  209. data/app/helpers/pages_core/admin/page_blocks_helper.rb +0 -66
  210. data/app/helpers/pages_core/admin/page_json_helper.rb +0 -23
  211. data/app/javascript/components/DateRangeSelect.jsx +0 -225
  212. data/app/javascript/components/PageDates.jsx +0 -73
  213. data/app/javascript/components/PageFiles.jsx +0 -25
  214. data/app/javascript/components/PageTree/types.ts +0 -15
  215. data/app/javascript/components/drag/types.ts +0 -28
  216. data/app/javascript/controllers/EditPageController.ts +0 -22
  217. data/app/javascript/controllers/MainController.ts +0 -74
  218. data/app/javascript/controllers/PageOptionsController.js +0 -67
  219. data/app/models/category.rb +0 -22
  220. data/app/models/concerns/pages_core/has_otp.rb +0 -27
  221. data/app/models/page_category.rb +0 -6
  222. data/app/views/admin/pages/_edit_content.html.erb +0 -19
  223. data/app/views/admin/pages/_edit_files.html.erb +0 -4
  224. data/app/views/admin/pages/_edit_images.html.erb +0 -4
  225. data/app/views/admin/pages/_edit_metadata.html.erb +0 -35
  226. data/app/views/admin/pages/_edit_options.html.erb +0 -91
  227. data/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -1,31 +1,27 @@
1
- import React, { useState } from "react";
2
- import PropTypes from "prop-types";
3
- import Attachment from "./Attachments/Attachment";
4
- import Placeholder from "./Attachments/Placeholder";
5
- import FileUploadButton from "./FileUploadButton";
6
- import { post } from "../lib/request";
7
-
8
- import {
9
- createDraggable,
10
- draggedOrder,
11
- useDragCollection,
12
- useDragUploader
13
- } from "./drag";
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 Attachments(props) {
20
- const collection = useDragCollection(props.records);
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
- let name = {};
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
- let data = new FormData();
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((json) => {
44
- collection.dispatch({
45
- type: "update",
46
- payload: {
47
- ...draggable,
48
- record: { attachment: json, uploading: false }
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
- [...collection.draggables.map((d) => d.record), ...deleted].indexOf(
82
- record
83
- ) + 1
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 = (draggable) => (attachment) => {
92
- const { record } = draggable;
93
- const updated = {
94
- ...draggable,
95
- record: {
96
- ...record,
97
- attachment: { ...record.attachment, ...attachment }
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={true}
159
+ value="true"
157
160
  />
158
161
  </span>
159
162
  ))}
@@ -168,11 +171,3 @@ export default function Attachments(props) {
168
171
  </div>
169
172
  );
170
173
  }
171
-
172
- Attachments.propTypes = {
173
- attribute: PropTypes.string,
174
- locale: PropTypes.string,
175
- locales: PropTypes.object,
176
- records: PropTypes.array,
177
- showEmbed: PropTypes.bool
178
- };
@@ -0,0 +1,15 @@
1
+ import { useState } from "react";
2
+ import { useDragCollection } from "../drag";
3
+ import * as Attachments from "../../types/Attachments";
4
+
5
+ export default function useAttachments(
6
+ records: Attachments.Record[]
7
+ ): Attachments.State {
8
+ const [deleted, setDeleted] = useState<Attachments.Record[]>([]);
9
+
10
+ return {
11
+ collection: useDragCollection(records),
12
+ deleted: deleted,
13
+ setDeleted: setDeleted
14
+ };
15
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import useAttachments from "./Attachments/useAttachments";
3
+ import List from "./Attachments/List";
4
+ import * as Attachment from "../types/Attachments";
5
+
6
+ interface Props extends Attachment.Options {
7
+ records: Attachment.Record[];
8
+ }
9
+
10
+ export default function Attachments(props: Props) {
11
+ const state = useAttachments(props.records);
12
+
13
+ return <List state={state} {...props} />;
14
+ }
@@ -0,0 +1,105 @@
1
+ import React, { useEffect, useState } from "react";
2
+
3
+ import DateTimeSelect from "./DateTimeSelect";
4
+
5
+ interface Props {
6
+ objectName: string;
7
+ startsAt: Date | string;
8
+ endsAt: Date | string;
9
+ setStartsAt?: (date: Date) => void;
10
+ setEndsAt?: (date: Date) => void;
11
+ disabled?: boolean;
12
+ disableTime?: boolean;
13
+ }
14
+
15
+ function defaultDate(offset = 0): Date {
16
+ const coeff = 1000 * 60 * 60;
17
+ return new Date(
18
+ Math.round(new Date().getTime() / coeff) * coeff +
19
+ coeff +
20
+ 1000 * 60 * offset
21
+ );
22
+ }
23
+
24
+ function parseDate(str: Date | string): Date {
25
+ if (!str) {
26
+ return null;
27
+ } else if (typeof str === "string") {
28
+ return new Date(str);
29
+ } else {
30
+ return str;
31
+ }
32
+ }
33
+
34
+ export default function DateRangeSelect(props: Props) {
35
+ const { disabled, disableTime, objectName } = props;
36
+
37
+ const [uncontrolledStartsAt, setUncontrolledStartsAt] = useState(
38
+ parseDate(props.startsAt)
39
+ );
40
+
41
+ const [uncontrolledEndsAt, setUncontrolledEndsAt] = useState(
42
+ parseDate(props.endsAt) || defaultDate(60)
43
+ );
44
+
45
+ const startsAt = parseDate(
46
+ props.setStartsAt ? props.startsAt : uncontrolledStartsAt
47
+ );
48
+ const setStartsAt = props.setStartsAt || setUncontrolledStartsAt;
49
+
50
+ const endsAt = parseDate(props.setEndsAt ? props.endsAt : uncontrolledEndsAt);
51
+ const setEndsAt = props.setEndsAt || setUncontrolledEndsAt;
52
+
53
+ const setDates = (start: Date, end: Date) => {
54
+ if (end < start) {
55
+ end = start;
56
+ }
57
+ setStartsAt(start);
58
+ setEndsAt(end);
59
+ };
60
+
61
+ const changeStartsAt = (newDate: Date) => {
62
+ setDates(
63
+ newDate,
64
+ new Date(endsAt.getTime() + (newDate.getTime() - startsAt.getTime()))
65
+ );
66
+ };
67
+
68
+ const changeEndsAt = (newDate: Date) => {
69
+ setDates(startsAt, newDate);
70
+ };
71
+
72
+ useEffect(() => {
73
+ if (!startsAt || !endsAt) {
74
+ setDates(startsAt || defaultDate(), endsAt || defaultDate(60));
75
+ }
76
+ }, [startsAt, endsAt]);
77
+
78
+ return (
79
+ <div className="date-range-select">
80
+ {startsAt && (
81
+ <div className="date">
82
+ <DateTimeSelect
83
+ name={objectName + "[starts_at]"}
84
+ disabled={disabled}
85
+ disableTime={disableTime}
86
+ onChange={changeStartsAt}
87
+ value={startsAt}
88
+ />
89
+ </div>
90
+ )}
91
+ <span className="to">to</span>
92
+ {endsAt && (
93
+ <div className="date">
94
+ <DateTimeSelect
95
+ name={objectName + "[ends_at]"}
96
+ disabled={disabled}
97
+ disableTime={disableTime}
98
+ onChange={changeEndsAt}
99
+ value={endsAt}
100
+ />
101
+ </div>
102
+ )}
103
+ </div>
104
+ );
105
+ }
@@ -0,0 +1,136 @@
1
+ import React, { useEffect, useState } from "react";
2
+
3
+ interface DateTimeSelectProps {
4
+ name: string;
5
+ onChange: (date: Date) => void;
6
+ value: Date;
7
+ disabled?: boolean;
8
+ disableTime?: boolean;
9
+ }
10
+
11
+ interface ModifyOptions {
12
+ year?: number;
13
+ month?: number;
14
+ date?: number;
15
+ time?: string;
16
+ }
17
+
18
+ function modifyDate(original: Date, options: ModifyOptions = {}): Date {
19
+ const newDate = new Date(original);
20
+ if ("year" in options) {
21
+ newDate.setFullYear(options.year);
22
+ }
23
+ if ("month" in options) {
24
+ newDate.setMonth(options.month);
25
+ }
26
+ if ("date" in options) {
27
+ newDate.setDate(options.date);
28
+ }
29
+ if ("time" in options && options.time.match(/^[\d]{1,2}(:[\d]{1,2})?$/)) {
30
+ newDate.setHours(parseInt(options.time.split(":")[0], 10));
31
+ newDate.setMinutes(parseInt(options.time.split(":")[1], 10) || 0);
32
+ }
33
+ return newDate;
34
+ }
35
+
36
+ function timeToString(time: Date): string {
37
+ return time.toTimeString().slice(0, 5);
38
+ }
39
+
40
+ // Returns an array with years from 2000 to 10 years from now.
41
+ function yearOptions(): number[] {
42
+ const start = 2000;
43
+ const years: number[] = [];
44
+ for (let i = start; i <= new Date().getFullYear() + 11; i++) {
45
+ years.push(i);
46
+ }
47
+ return years;
48
+ }
49
+
50
+ function monthOptions(): string[] {
51
+ return [
52
+ "January",
53
+ "February",
54
+ "March",
55
+ "April",
56
+ "May",
57
+ "June",
58
+ "July",
59
+ "August",
60
+ "September",
61
+ "October",
62
+ "November",
63
+ "December"
64
+ ];
65
+ }
66
+
67
+ function dayOptions(): number[] {
68
+ const numbers: number[] = [];
69
+ for (let i = 1; i <= 31; i++) {
70
+ numbers.push(i);
71
+ }
72
+ return numbers;
73
+ }
74
+
75
+ export default function DateTimeSelect(props: DateTimeSelectProps) {
76
+ const { name, disabled, disableTime, onChange, value } = props;
77
+
78
+ const [timeString, setTimeString] = useState(timeToString(value));
79
+
80
+ useEffect(() => {
81
+ setTimeString(timeToString(value));
82
+ }, [value]);
83
+
84
+ const handleChange = (options = {}) => {
85
+ onChange(modifyDate(value, options));
86
+ };
87
+
88
+ return (
89
+ <div className="date-select">
90
+ {name && (
91
+ <input type="hidden" name={name} value={!disabled && value.toJSON()} />
92
+ )}
93
+ <select
94
+ value={value.getMonth()}
95
+ onChange={(e) => handleChange({ month: e.target.value })}
96
+ disabled={disabled}>
97
+ {monthOptions().map((m, i) => (
98
+ <option key={i} value={i}>
99
+ {m}
100
+ </option>
101
+ ))}
102
+ </select>
103
+ <select
104
+ value={value.getDate()}
105
+ onChange={(e) => handleChange({ date: e.target.value })}
106
+ disabled={disabled}>
107
+ {dayOptions().map((d) => (
108
+ <option key={d} value={d}>
109
+ {d}
110
+ </option>
111
+ ))}
112
+ </select>
113
+ <select
114
+ value={value.getFullYear()}
115
+ onChange={(e) => handleChange({ year: e.target.value })}
116
+ disabled={disabled}>
117
+ {yearOptions().map((y) => (
118
+ <option key={y} value={y}>
119
+ {y}
120
+ </option>
121
+ ))}
122
+ </select>
123
+ {!disableTime && (
124
+ <input
125
+ className="time"
126
+ type="text"
127
+ size={5}
128
+ disabled={disabled}
129
+ value={timeString}
130
+ onChange={(e) => setTimeString(e.target.value)}
131
+ onBlur={(e) => handleChange({ time: e.target.value })}
132
+ />
133
+ )}
134
+ </div>
135
+ );
136
+ }
@@ -1,20 +1,22 @@
1
- import React, { useState } from "react";
2
- import ImageEditor from "./ImageEditor";
1
+ import React, { MouseEvent, useState } from "react";
2
+
3
3
  import useModalStore from "../stores/useModalStore";
4
+ import * as Images from "../types/Images";
5
+ import { Locale } from "../types";
4
6
 
5
- import { Locale, ImageResource } from "../types";
7
+ import ImageEditor from "./ImageEditor";
6
8
 
7
- interface EditableImageProps {
8
- image: ImageResource;
9
+ interface Props {
10
+ image: Images.Resource;
9
11
  src: string;
10
12
  caption: boolean;
11
13
  locale: string;
12
14
  locales: Record<string, Locale>;
13
15
  width: number;
14
- onUpdate?: (newImage: ImageResource, src: string) => void;
16
+ onUpdate?: (newImage: Images.Resource, src: string) => void;
15
17
  }
16
18
 
17
- export default function EditableImage(props: EditableImageProps) {
19
+ export default function EditableImage(props: Props) {
18
20
  const [image, setImage] = useState(props.image);
19
21
  const [src, setSrc] = useState(props.src);
20
22
 
@@ -26,7 +28,7 @@ export default function EditableImage(props: EditableImageProps) {
26
28
  return Math.round((height / width) * props.width);
27
29
  };
28
30
 
29
- const updateImage = (updatedImage: ImageResource, src: string) => {
31
+ const updateImage = (updatedImage: Images.Resource, src: string) => {
30
32
  const newImage = { ...image, ...updatedImage };
31
33
  setSrc(src);
32
34
  setImage(newImage);
@@ -35,7 +37,7 @@ export default function EditableImage(props: EditableImageProps) {
35
37
  }
36
38
  };
37
39
 
38
- const handleClick = (evt: Event) => {
40
+ const handleClick = (evt: MouseEvent) => {
39
41
  evt.preventDefault();
40
42
  openModal(
41
43
  <ImageEditor
@@ -1,13 +1,13 @@
1
- import React, { ChangeEvent, useRef } from "react";
1
+ import React, { ChangeEvent, MouseEvent, useRef } from "react";
2
2
 
3
- interface FileUploadButtonProps {
3
+ interface Props {
4
4
  callback: (files: File[]) => void;
5
- type: string;
6
- multiple: boolean;
7
- multiline: boolean;
5
+ type?: string;
6
+ multiline?: boolean;
7
+ multiple?: boolean;
8
8
  }
9
9
 
10
- export default function FileUploadButton(props: FileUploadButtonProps) {
10
+ export default function FileUploadButton(props: Props) {
11
11
  const inputRef = useRef<HTMLInputElement>();
12
12
 
13
13
  const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
@@ -21,7 +21,7 @@ export default function FileUploadButton(props: FileUploadButtonProps) {
21
21
  }
22
22
  };
23
23
 
24
- const triggerDialog = (evt: Event) => {
24
+ const triggerDialog = (evt: MouseEvent) => {
25
25
  evt.preventDefault();
26
26
  inputRef.current.click();
27
27
  };
@@ -1,14 +1,11 @@
1
- import React, { useRef, useState } from "react";
1
+ import React, { MouseEvent, TouchEvent, useRef, useState } from "react";
2
2
 
3
- interface Position {
4
- x: number;
5
- y: number;
6
- }
3
+ import * as Crop from "../../types/Crop";
7
4
 
8
- interface FocalPointProps {
5
+ interface Props {
9
6
  x: number;
10
7
  y: number;
11
- onChange: (pos: Position) => void;
8
+ onChange: (pos: Crop.Position) => void;
12
9
  width: number;
13
10
  height: number;
14
11
  }
@@ -23,11 +20,11 @@ function clamp(val: number, min: number, max: number): number {
23
20
  }
24
21
  }
25
22
 
26
- export default function FocalPoint(props: FocalPointProps) {
23
+ export default function FocalPoint(props: Props) {
27
24
  const { width, height, onChange } = props;
28
25
 
29
26
  const [dragging, setDragging] = useState(false);
30
- const [position, setPosition] = useState<Position>({
27
+ const [position, setPosition] = useState<Crop.Position>({
31
28
  x: props.x,
32
29
  y: props.y
33
30
  });
@@ -35,7 +32,7 @@ export default function FocalPoint(props: FocalPointProps) {
35
32
  const containerRef = useRef<HTMLDivElement>();
36
33
  const pointRef = useRef<HTMLDivElement>();
37
34
 
38
- const dragStart = (evt: Event) => {
35
+ const dragStart = (evt: MouseEvent | TouchEvent) => {
39
36
  evt.preventDefault();
40
37
  evt.stopPropagation();
41
38
  if (evt.target == pointRef.current) {
@@ -50,7 +47,7 @@ export default function FocalPoint(props: FocalPointProps) {
50
47
  }
51
48
  };
52
49
 
53
- const drag = (evt: TouchEvent | MouseEvent) => {
50
+ const drag = (evt: MouseEvent | TouchEvent) => {
54
51
  if (dragging) {
55
52
  let x: number, y: number;
56
53
  const containerSize = containerRef.current.getBoundingClientRect();
@@ -59,7 +56,7 @@ export default function FocalPoint(props: FocalPointProps) {
59
56
  if ("touches" in evt && evt.type == "touchmove") {
60
57
  x = evt.touches[0].clientX - (containerSize.x || containerSize.left);
61
58
  y = evt.touches[0].clientY - (containerSize.y || containerSize.top);
62
- } else {
59
+ } else if ("clientX" in evt) {
63
60
  x = evt.clientX - (containerSize.x || containerSize.left);
64
61
  y = evt.clientY - (containerSize.y || containerSize.top);
65
62
  }
@@ -1,19 +1,21 @@
1
1
  import React from "react";
2
2
  import ReactCrop from "react-image-crop";
3
3
 
4
- import { cropSize, CropSize, CropState, Position, Size } from "./useCrop";
4
+ import * as Crop from "../../types/Crop";
5
+
6
+ import { cropSize } from "./useCrop";
5
7
  import FocalPoint from "./FocalPoint";
6
8
 
7
- interface ImageProps {
8
- containerSize: Size;
9
+ interface Props {
10
+ containerSize: Crop.Size;
9
11
  croppedImage: string;
10
- cropState: CropState;
11
- focalPoint: Position;
12
- setCrop: (crop: CropSize) => void;
13
- setFocal: (focal: Position) => void;
12
+ cropState: Crop.State;
13
+ focalPoint: Crop.Position;
14
+ setCrop: (crop: Crop.CropSize) => void;
15
+ setFocal: (focal: Crop.Position) => void;
14
16
  }
15
17
 
16
- export default function Image(props: ImageProps) {
18
+ export default function Image(props: Props) {
17
19
  const imageSize = () => {
18
20
  const { image, cropping, crop_width, crop_height } = props.cropState;
19
21
  if (cropping) {
@@ -1,22 +1,22 @@
1
- import React from "react";
1
+ import React, { MouseEvent } from "react";
2
2
 
3
- import { ImageResource } from "../../types";
4
- import { CropState } from "./useCrop";
3
+ import * as Crop from "../../types/Crop";
4
+ import * as Images from "../../types/Images";
5
5
 
6
6
  type Ratio = number | null;
7
7
 
8
- interface ToolbarProps {
9
- cropState: CropState;
10
- image: ImageResource;
8
+ interface Props {
9
+ cropState: Crop.State;
10
+ image: Images.Resource;
11
11
  setAspect: (Ratio) => void;
12
- toggleCrop: (evt: Event) => void;
13
- toggleFocal: (evt: Event) => void;
12
+ toggleCrop: (evt: MouseEvent) => void;
13
+ toggleFocal: (evt: MouseEvent) => void;
14
14
  }
15
15
 
16
- export default function Toolbar(props: ToolbarProps) {
16
+ export default function Toolbar(props: Props) {
17
17
  const { cropping } = props.cropState;
18
18
 
19
- const aspectRatios = [
19
+ const aspectRatios: Array<[string, Ratio]> = [
20
20
  ["Free", null],
21
21
  ["1:1", 1],
22
22
  ["3:2", 3 / 2],
@@ -28,7 +28,7 @@ export default function Toolbar(props: ToolbarProps) {
28
28
  ["16:9", 16 / 9]
29
29
  ];
30
30
 
31
- const updateAspect = (ratio: Ratio) => (evt: Event) => {
31
+ const updateAspect = (ratio: Ratio) => (evt: MouseEvent) => {
32
32
  evt.preventDefault();
33
33
  props.setAspect(ratio);
34
34
  };
@@ -61,7 +61,6 @@ export default function Toolbar(props: ToolbarProps) {
61
61
  href={props.image.original_url}
62
62
  className="button"
63
63
  title="Download original image"
64
- disabled={cropping}
65
64
  download={props.image.filename}
66
65
  onClick={(evt) => cropping && evt.preventDefault()}>
67
66
  <i className="fa-solid fa-download" />