pages_core 3.13.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 (257) 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 +704 -388
  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 +26 -0
  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 +87 -0
  68. data/app/controllers/admin/invites_controller.rb +3 -2
  69. data/app/controllers/admin/otp_secrets_controller.rb +45 -0
  70. data/app/controllers/admin/pages_controller.rb +22 -42
  71. data/app/controllers/admin/recovery_codes_controller.rb +32 -0
  72. data/app/controllers/admin/sessions_controller.rb +65 -0
  73. data/app/controllers/admin/users_controller.rb +2 -8
  74. data/app/controllers/concerns/pages_core/authentication.rb +12 -10
  75. data/app/controllers/concerns/pages_core/error_reporting.rb +1 -1
  76. data/app/controllers/concerns/pages_core/page_parameters.rb +29 -0
  77. data/app/controllers/concerns/pages_core/policies_helper.rb +1 -1
  78. data/app/controllers/concerns/pages_core/preview_pages_controller.rb +20 -20
  79. data/app/controllers/pages_core/admin_controller.rb +1 -3
  80. data/app/controllers/pages_core/frontend/pages_controller.rb +2 -6
  81. data/app/formatters/pages_core/html_formatter.rb +2 -4
  82. data/app/helpers/admin/menu_helper.rb +5 -4
  83. data/app/helpers/admin/pages_helper.rb +1 -21
  84. data/app/helpers/pages_core/admin/admin_helper.rb +13 -3
  85. data/app/helpers/pages_core/admin/content_tabs_helper.rb +1 -2
  86. data/app/helpers/pages_core/admin/labelled_field_helper.rb +1 -1
  87. data/app/helpers/pages_core/frontend_helper.rb +1 -1
  88. data/app/helpers/pages_core/images_helper.rb +10 -8
  89. data/app/helpers/pages_core/labelled_form_builder.rb +2 -7
  90. data/app/helpers/pages_core/page_path_helper.rb +1 -1
  91. data/app/javascript/components/Attachments/Attachment.tsx +20 -18
  92. data/app/javascript/components/Attachments/AttachmentEditor.tsx +11 -9
  93. data/app/javascript/components/{Attachments.jsx → Attachments/List.tsx} +58 -63
  94. data/app/javascript/components/Attachments/useAttachments.ts +15 -0
  95. data/app/javascript/components/Attachments.tsx +14 -0
  96. data/app/javascript/components/DateRangeSelect.tsx +105 -0
  97. data/app/javascript/components/DateTimeSelect.tsx +136 -0
  98. data/app/javascript/components/EditableImage.tsx +11 -9
  99. data/app/javascript/components/FileUploadButton.tsx +7 -7
  100. data/app/javascript/components/ImageCropper/FocalPoint.tsx +9 -12
  101. data/app/javascript/components/ImageCropper/Image.tsx +10 -8
  102. data/app/javascript/components/ImageCropper/Toolbar.tsx +11 -12
  103. data/app/javascript/components/ImageCropper/useCrop.ts +24 -53
  104. data/app/javascript/components/ImageCropper.tsx +10 -15
  105. data/app/javascript/components/ImageEditor/Form.tsx +12 -8
  106. data/app/javascript/components/ImageEditor.tsx +12 -7
  107. data/app/javascript/components/ImageGrid/DragElement.tsx +9 -12
  108. data/app/javascript/components/{ImageGrid.jsx → ImageGrid/Grid.tsx} +62 -71
  109. data/app/javascript/components/ImageGrid/GridImage.tsx +22 -23
  110. data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -2
  111. data/app/javascript/components/ImageGrid/useImageGrid.ts +26 -0
  112. data/app/javascript/components/ImageGrid.tsx +15 -0
  113. data/app/javascript/components/ImageUploader.tsx +35 -22
  114. data/app/javascript/components/LabelledField.tsx +34 -0
  115. data/app/javascript/components/Modal.tsx +2 -2
  116. data/app/javascript/components/PageForm/Block.tsx +81 -0
  117. data/app/javascript/components/PageForm/Content.tsx +54 -0
  118. data/app/javascript/components/PageForm/Dates.tsx +66 -0
  119. data/app/javascript/components/PageForm/Files.tsx +28 -0
  120. data/app/javascript/components/PageForm/Form.tsx +41 -0
  121. data/app/javascript/components/PageForm/Images.tsx +28 -0
  122. data/app/javascript/components/PageForm/LocaleLinks.tsx +36 -0
  123. data/app/javascript/components/PageForm/Metadata.tsx +67 -0
  124. data/app/javascript/components/PageForm/Options.tsx +180 -0
  125. data/app/javascript/components/PageForm/PageDescription.tsx +48 -0
  126. data/app/javascript/components/PageForm/PathSegment.tsx +65 -0
  127. data/app/javascript/components/PageForm/TabPanel.tsx +21 -0
  128. data/app/javascript/components/PageForm/Tabs.tsx +33 -0
  129. data/app/javascript/components/PageForm/UnconfiguredContent.tsx +42 -0
  130. data/app/javascript/components/PageForm/pageParams.ts +95 -0
  131. data/app/javascript/components/PageForm/preview.ts +23 -0
  132. data/app/javascript/components/PageForm/usePage.ts +169 -0
  133. data/app/javascript/components/PageForm/useTabs.ts +46 -0
  134. data/app/javascript/components/PageForm.tsx +163 -0
  135. data/app/javascript/components/PageImages.tsx +7 -9
  136. data/app/javascript/components/PageTree/Draggable.tsx +40 -39
  137. data/app/javascript/components/PageTree/Node.tsx +62 -56
  138. data/app/javascript/components/PageTree/PageName.tsx +28 -0
  139. data/app/javascript/components/PageTree.tsx +65 -53
  140. data/app/javascript/components/{RichTextArea.jsx → RichTextArea.tsx} +98 -79
  141. data/app/javascript/components/RichTextToolbarButton.tsx +4 -6
  142. data/app/javascript/components/TagEditor/AddTagForm.tsx +19 -12
  143. data/app/javascript/components/TagEditor/Editor.tsx +32 -0
  144. data/app/javascript/components/TagEditor/Tag.tsx +6 -4
  145. data/app/javascript/components/TagEditor/useTags.ts +58 -0
  146. data/app/javascript/components/TagEditor.tsx +8 -58
  147. data/app/javascript/components/Toast.tsx +3 -3
  148. data/app/javascript/components/drag/draggedOrder.ts +22 -14
  149. data/app/javascript/components/drag/useDragCollection.ts +35 -30
  150. data/app/javascript/components/drag/useDragUploader.ts +32 -21
  151. data/app/javascript/components/drag/useDraggable.ts +7 -6
  152. data/app/javascript/components/drag.ts +0 -1
  153. data/app/javascript/components.ts +1 -3
  154. data/app/javascript/features/RichText.tsx +2 -3
  155. data/app/javascript/features/contentTabs.ts +79 -0
  156. data/app/javascript/index.ts +5 -14
  157. data/app/javascript/lib/Tree.ts +31 -45
  158. data/app/javascript/lib/request.ts +11 -11
  159. data/app/javascript/stores/useToastStore.ts +1 -1
  160. data/app/javascript/types/Attachments.ts +29 -0
  161. data/app/javascript/types/Crop.ts +36 -0
  162. data/app/javascript/types/Drag.ts +34 -0
  163. data/app/javascript/types/Images.ts +47 -0
  164. data/app/javascript/types/PageEditor.ts +26 -0
  165. data/app/javascript/types/Pages.ts +75 -0
  166. data/app/javascript/types/Tags.ts +9 -0
  167. data/app/javascript/types/Template.ts +24 -0
  168. data/app/javascript/types/Trees.ts +19 -0
  169. data/app/javascript/types.ts +2 -25
  170. data/app/mailers/admin_mailer.rb +2 -2
  171. data/app/models/attachment.rb +1 -1
  172. data/app/models/concerns/pages_core/authenticable_user.rb +63 -0
  173. data/app/models/concerns/pages_core/emailable.rb +16 -0
  174. data/app/models/concerns/pages_core/page_model/templateable.rb +2 -16
  175. data/app/models/invite.rb +2 -6
  176. data/app/models/otp_secret.rb +101 -0
  177. data/app/models/page.rb +0 -3
  178. data/app/models/user.rb +2 -68
  179. data/app/policies/page_policy.rb +6 -2
  180. data/app/policies/user_policy.rb +4 -0
  181. data/app/resources/admin/page_resource.rb +95 -0
  182. data/app/resources/admin/page_tree_resource.rb +27 -0
  183. data/app/resources/admin/template_configuration_resource.rb +50 -0
  184. data/app/views/admin/account_recoveries/new.html.erb +22 -0
  185. data/app/views/admin/account_recoveries/show.html.erb +37 -0
  186. data/app/views/admin/invites/show.html.erb +1 -1
  187. data/app/views/admin/news/_sidebar.html.erb +2 -4
  188. data/app/views/admin/news/index.html.erb +0 -1
  189. data/app/views/admin/otp_secrets/create.html.erb +7 -0
  190. data/app/views/admin/otp_secrets/new.html.erb +60 -0
  191. data/app/views/admin/pages/_form.html.erb +10 -30
  192. data/app/views/admin/pages/_search_bar.html.erb +1 -1
  193. data/app/views/admin/pages/edit.html.erb +1 -57
  194. data/app/views/admin/pages/index.html.erb +1 -1
  195. data/app/views/admin/pages/new.html.erb +1 -44
  196. data/app/views/admin/recovery_codes/_codes.html.erb +14 -0
  197. data/app/views/admin/recovery_codes/create.html.erb +7 -0
  198. data/app/views/admin/recovery_codes/new.html.erb +11 -0
  199. data/app/views/admin/sessions/_otp_form.html.erb +13 -0
  200. data/app/views/admin/sessions/new.html.erb +31 -0
  201. data/app/views/admin/sessions/verify_otp.html.erb +19 -0
  202. data/app/views/admin/users/_access_control.html.erb +5 -1
  203. data/app/views/admin/users/_list.html.erb +12 -7
  204. data/app/views/admin/users/edit.html.erb +31 -1
  205. data/app/views/admin/users/new.html.erb +1 -1
  206. data/app/views/admin_mailer/account_recovery.text.erb +10 -0
  207. data/app/views/layouts/admin/_header.html.erb +3 -5
  208. data/app/views/layouts/admin/_page_header.html.erb +1 -2
  209. data/app/views/layouts/admin/_toast.html.erb +12 -0
  210. data/app/views/layouts/admin.html.erb +2 -2
  211. data/config/locales/en.yml +11 -7
  212. data/config/routes.rb +13 -12
  213. data/db/migrate/20240126160700_add_2fa_fields.rb +26 -0
  214. data/db/migrate/20240129201300_remove_password_reset_tokens.rb +13 -0
  215. data/db/migrate/20240131140700_change_email_to_citext.rb +18 -0
  216. data/db/migrate/20240201160700_remove_persistent_data.rb +7 -0
  217. data/db/migrate/20240508145300_remove_categories.rb +21 -0
  218. data/lib/pages_core/configuration/base.rb +2 -2
  219. data/lib/pages_core/templates/configuration.rb +1 -1
  220. data/lib/pages_core/templates/configuration_proxy.rb +2 -2
  221. data/lib/pages_core/templates/template_configuration.rb +11 -1
  222. data/lib/pages_core/templates.rb +6 -4
  223. data/lib/pages_core/version.rb +1 -1
  224. data/lib/pages_core.rb +6 -0
  225. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +6 -7
  226. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +17 -12
  227. data/lib/rails/generators/pages_core/rspec/rspec_generator.rb +0 -2
  228. data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +3 -4
  229. metadata +143 -35
  230. data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -33
  231. data/app/controllers/admin/categories_controller.rb +0 -56
  232. data/app/controllers/admin/password_resets_controller.rb +0 -85
  233. data/app/controllers/concerns/pages_core/admin/persistent_params.rb +0 -75
  234. data/app/controllers/sessions_controller.rb +0 -27
  235. data/app/helpers/pages_core/admin/page_blocks_helper.rb +0 -66
  236. data/app/helpers/pages_core/admin/page_json_helper.rb +0 -23
  237. data/app/javascript/components/DateRangeSelect.jsx +0 -225
  238. data/app/javascript/components/PageDates.jsx +0 -73
  239. data/app/javascript/components/PageFiles.jsx +0 -25
  240. data/app/javascript/components/PageTree/types.ts +0 -15
  241. data/app/javascript/components/drag/types.ts +0 -28
  242. data/app/javascript/controllers/EditPageController.ts +0 -22
  243. data/app/javascript/controllers/LoginController.ts +0 -32
  244. data/app/javascript/controllers/MainController.ts +0 -74
  245. data/app/javascript/controllers/PageOptionsController.js +0 -67
  246. data/app/models/category.rb +0 -22
  247. data/app/models/page_category.rb +0 -6
  248. data/app/models/password_reset_token.rb +0 -34
  249. data/app/views/admin/pages/_edit_content.html.erb +0 -19
  250. data/app/views/admin/pages/_edit_files.html.erb +0 -4
  251. data/app/views/admin/pages/_edit_images.html.erb +0 -4
  252. data/app/views/admin/pages/_edit_metadata.html.erb +0 -35
  253. data/app/views/admin/pages/_edit_options.html.erb +0 -91
  254. data/app/views/admin/password_resets/show.html.erb +0 -21
  255. data/app/views/admin/users/login.html.erb +0 -65
  256. data/app/views/admin_mailer/password_reset.text.erb +0 -11
  257. data/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -0,0 +1,58 @@
1
+ import { useReducer } from "react";
2
+
3
+ import * as Tags from "../../types/Tags";
4
+
5
+ function onlyUnique(value: string, index: number, self: string[]) {
6
+ return self.indexOf(value) === index;
7
+ }
8
+
9
+ export function allTags(state: Tags.State) {
10
+ return [...state.tags, ...state.enabled].filter(onlyUnique);
11
+ }
12
+
13
+ function normalize(tag: string, state: Tags.State) {
14
+ return (
15
+ allTags(state).filter((t) => t.toLowerCase() == tag.toLowerCase())[0] || tag
16
+ );
17
+ }
18
+
19
+ export function isEnabled(tag: string, state: Tags.State) {
20
+ return (
21
+ state.enabled.map((t) => t.toLowerCase()).indexOf(tag.toLowerCase()) !== -1
22
+ );
23
+ }
24
+
25
+ function toggle(tag: string, state: Tags.State) {
26
+ if (isEnabled(tag, state)) {
27
+ return { ...state, enabled: state.enabled.filter((t) => t !== tag) };
28
+ } else {
29
+ return { ...state, enabled: [...state.enabled, tag] };
30
+ }
31
+ }
32
+
33
+ function reducer(state: Tags.State, action: Tags.Action) {
34
+ const { type, payload } = action;
35
+ const normalized = normalize(payload, state);
36
+ switch (type) {
37
+ case "addTag":
38
+ return {
39
+ tags: [...state.tags, normalized].filter(onlyUnique),
40
+ enabled: [...state.enabled, normalized].filter(onlyUnique)
41
+ };
42
+ case "toggleTag":
43
+ return toggle(normalized, state);
44
+ default:
45
+ return state;
46
+ }
47
+ }
48
+
49
+ export default function useTags(
50
+ initTags: string[],
51
+ initEnabled: string[]
52
+ ): [Tags.State, (action: Tags.Action) => void] {
53
+ const [state, dispatch] = useReducer(reducer, {
54
+ tags: initTags,
55
+ enabled: initEnabled
56
+ });
57
+ return [state, dispatch];
58
+ }
@@ -1,65 +1,15 @@
1
- import React, { useState } from "react";
1
+ import React from "react";
2
2
 
3
- import AddTagForm from "./TagEditor/AddTagForm";
4
- import Tag from "./TagEditor/Tag";
3
+ import useTags from "./TagEditor/useTags";
4
+ import Editor from "./TagEditor/Editor";
5
+ import * as Tags from "../types/Tags";
5
6
 
6
- interface TagEditorProps {
7
+ interface Props extends Tags.State {
7
8
  name: string;
8
- enabled: string[];
9
- tags: string[];
10
9
  }
11
10
 
12
- function onlyUnique(value: string, index: number, self: string[]): number {
13
- return self.indexOf(value) === index;
14
- }
15
-
16
- export default function TagEditor(props: TagEditorProps) {
17
- const [tags, setTags] = useState(props.tags);
18
- const [enabled, setEnabled] = useState(props.enabled);
19
-
20
- const tagList = [...tags, ...enabled].filter(onlyUnique);
21
-
22
- const normalize = (tag: string): string => {
23
- return (
24
- tagList.filter((t) => t.toLowerCase() == tag.toLowerCase())[0] || tag
25
- );
26
- };
27
-
28
- const tagEnabled = (tag: string): boolean => {
29
- return (
30
- enabled.map((t) => t.toLowerCase()).indexOf(tag.toLowerCase()) !== -1
31
- );
32
- };
33
-
34
- const toggleEnabled = (tag: string) => {
35
- const normalized = normalize(tag);
36
-
37
- if (tagEnabled(normalized)) {
38
- setEnabled(enabled.filter((t) => t !== normalized));
39
- } else {
40
- setEnabled([...enabled, normalized]);
41
- }
42
- };
43
-
44
- const addTag = (tag: string) => {
45
- const normalized = normalize(tag);
46
-
47
- setTags([...tags, normalized].filter(onlyUnique));
48
- setEnabled([...enabled, normalized].filter(onlyUnique));
49
- };
11
+ export default function TagEditor(props: Props) {
12
+ const [state, dispatch] = useTags(props.tags, props.enabled);
50
13
 
51
- return (
52
- <div className="tag-editor clearfix">
53
- <input type="hidden" name={props.name} value={JSON.stringify(enabled)} />
54
- {tagList.map((t) => (
55
- <Tag
56
- key={t}
57
- tag={t}
58
- enabled={tagEnabled(t)}
59
- toggleEnabled={toggleEnabled}
60
- />
61
- ))}
62
- <AddTagForm addTag={addTag} />
63
- </div>
64
- );
14
+ return <Editor name={props.name} state={state} dispatch={dispatch} />;
65
15
  }
@@ -2,15 +2,15 @@ import React, { useEffect, useRef, useState } from "react";
2
2
 
3
3
  import useToastStore from "../stores/useToastStore";
4
4
 
5
- interface ToastProps {
5
+ interface Props {
6
6
  error: string;
7
7
  notice: string;
8
8
  }
9
9
 
10
- export default function Toast(props: ToastProps) {
10
+ export default function Toast(props: Props) {
11
11
  const [fadeout, setFadeout] = useState(false);
12
12
  const { toasts, error, notice, next } = useToastStore((state) => state);
13
- const timerRef = useRef<number | null>(null);
13
+ const timerRef = useRef<ReturnType<typeof setTimeout>>(null);
14
14
 
15
15
  const toast = toasts[0];
16
16
 
@@ -1,11 +1,14 @@
1
- import { Draggable, DragCollection, DragState } from "./types";
1
+ import * as Drag from "../../types/Drag";
2
2
 
3
- function hovering(
4
- dragState: DragState,
5
- target: Draggable | React.MutableRefObject<HTMLDivElement>
6
- ): boolean {
3
+ function hovering<T>(
4
+ dragState: Drag.State<T>,
5
+ target: Drag.Item<T> | React.MutableRefObject<HTMLDivElement>
6
+ ) {
7
7
  const { x, y } = dragState;
8
8
  let rect: DOMRect;
9
+ if (typeof target === "string") {
10
+ return false;
11
+ }
9
12
  if ("rect" in target) {
10
13
  rect = target.rect;
11
14
  } else if ("current" in target && target.current) {
@@ -16,10 +19,10 @@ function hovering(
16
19
  return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
17
20
  }
18
21
 
19
- export function collectionOrder(
20
- collection: DragCollection,
21
- dragState: DragState
22
- ): Draggable[] {
22
+ export function collectionOrder<T>(
23
+ collection: Drag.Collection<T>,
24
+ dragState: Drag.State<T>
25
+ ): Array<Drag.Item<T>> {
23
26
  const { draggables, ref } = collection;
24
27
  const { dragging } = dragState;
25
28
 
@@ -27,7 +30,12 @@ export function collectionOrder(
27
30
  return draggables;
28
31
  }
29
32
 
30
- let ordered = draggables.filter((d) => d.handle !== dragging.handle);
33
+ let ordered: Drag.Item<T>[] = draggables;
34
+ if (typeof dragging !== "string") {
35
+ ordered = draggables
36
+ .filter((d) => typeof d !== "string")
37
+ .filter((d: Drag.Draggable<T>) => d.handle !== dragging.handle);
38
+ }
31
39
  if (hovering(dragState, ref)) {
32
40
  const hovered = ordered.filter((d) => hovering(dragState, d))[0];
33
41
  if (hovered) {
@@ -41,10 +49,10 @@ export function collectionOrder(
41
49
  return ordered;
42
50
  }
43
51
 
44
- export default function draggedOrder(
45
- collection: DragCollection,
46
- dragState: DragState
47
- ): Draggable[] {
52
+ export default function draggedOrder<T>(
53
+ collection: Drag.Collection<T>,
54
+ dragState: Drag.State<T>
55
+ ) {
48
56
  let ordered = collectionOrder(collection, dragState);
49
57
 
50
58
  if (dragState.dragging && ordered.indexOf(dragState.dragging) === -1) {
@@ -1,25 +1,21 @@
1
1
  import { createRef, useEffect, useReducer, useRef } from "react";
2
2
  import { uniqueId } from "lodash";
3
3
 
4
- import {
5
- Draggable,
6
- DraggableRecord,
7
- DragCollectionAction,
8
- DragCollection
9
- } from "./types";
4
+ import * as Drag from "../../types/Drag";
10
5
 
11
- type Draggables = Draggable[];
12
-
13
- function getPosition(draggable: Draggable) {
14
- if (draggable && draggable.ref && draggable.ref.current) {
6
+ function getPosition<T>(draggable: Drag.Draggable<T>) {
7
+ if (draggable && "ref" in draggable && draggable.ref.current) {
15
8
  return draggable.ref.current.getBoundingClientRect();
16
9
  } else {
17
10
  return null;
18
11
  }
19
12
  }
20
13
 
21
- function hideDraggable(draggable: Draggable | null, callback: () => void) {
22
- if (draggable && draggable.ref && draggable.ref.current) {
14
+ function hideDraggable<T>(
15
+ draggable: Drag.Draggable<T> | null,
16
+ callback: () => Drag.Draggable<T>[]
17
+ ) {
18
+ if (draggable && "ref" in draggable && draggable.ref.current) {
23
19
  const prevDisplay = draggable.ref.current.style.display;
24
20
  draggable.ref.current.style.display = "none";
25
21
  const result = callback();
@@ -30,7 +26,10 @@ function hideDraggable(draggable: Draggable | null, callback: () => void) {
30
26
  }
31
27
  }
32
28
 
33
- function insertFiles(state: Draggable[], files: Draggable[]): Draggable[] {
29
+ function insertFiles<T>(
30
+ state: Drag.Item<T>[],
31
+ files: Drag.Item<T>[]
32
+ ): Drag.Item<T>[] {
34
33
  const index = state.indexOf("Files");
35
34
  if (index === -1 || !files) {
36
35
  return state;
@@ -39,29 +38,33 @@ function insertFiles(state: Draggable[], files: Draggable[]): Draggable[] {
39
38
  }
40
39
  }
41
40
 
42
- function dragCollectionReducer(
43
- state: Draggable[],
44
- action: DragCollectionAction
45
- ): Draggable[] {
41
+ function dragCollectionReducer<T = Drag.DraggableRecord>(
42
+ state: Drag.Item<T>[],
43
+ action: Drag.CollectionAction<T>
44
+ ): Drag.Item<T>[] {
46
45
  switch (action.type) {
47
46
  case "append":
48
- return [...state, ...(action.payload as Draggable[])];
47
+ return [...state, ...action.payload];
49
48
  case "prepend":
50
- return [...(action.payload as Draggable[]), ...state];
49
+ return [...action.payload, ...state];
51
50
  case "insertFiles":
52
51
  return insertFiles(state, action.payload);
53
52
  case "update":
54
- return state.map((d) => {
55
- return d.handle === action.payload.handle ? action.payload : d;
53
+ return state.map((d: Drag.Draggable<T>) => {
54
+ return d.handle === (action.payload as Drag.Draggable<T>).handle
55
+ ? action.payload
56
+ : d;
56
57
  });
57
58
  case "updatePositions":
58
59
  return hideDraggable(action.payload, () => {
59
- return state.map((d) => {
60
+ return state.map((d: Drag.Draggable<T>) => {
60
61
  return { ...d, rect: getPosition(d) };
61
62
  });
62
63
  });
63
64
  case "remove":
64
- return state.filter((d) => d.handle !== action.payload.handle);
65
+ return state.filter(
66
+ (d: Drag.Draggable<T>) => d.handle !== action.payload.handle
67
+ );
65
68
  case "replace":
66
69
  return action.payload;
67
70
  case "reorder":
@@ -71,7 +74,9 @@ function dragCollectionReducer(
71
74
  }
72
75
  }
73
76
 
74
- export function createDraggable(record: Record<string, unknown>): Draggable {
77
+ export function createDraggable<T = Drag.DraggableRecord>(
78
+ record: T
79
+ ): Drag.Draggable<T> {
75
80
  return {
76
81
  record: record,
77
82
  rect: null,
@@ -80,13 +85,13 @@ export function createDraggable(record: Record<string, unknown>): Draggable {
80
85
  };
81
86
  }
82
87
 
83
- export default function useDragCollection(
84
- records: DraggableRecord[]
85
- ): DragCollection {
86
- const containerRef = useRef<HTMLElement>(null);
87
- const [draggables, dispatch] = useReducer(dragCollectionReducer, [], () =>
88
+ export default function useDragCollection<T = Drag.DraggableRecord>(
89
+ records: Array<T>
90
+ ): Drag.Collection<T> {
91
+ const containerRef = useRef<HTMLDivElement>(null);
92
+ const [draggables, dispatch] = useReducer(dragCollectionReducer<T>, [], () =>
88
93
  records.map((r) => createDraggable(r))
89
- ) as [Draggables, (Draggables) => Draggable[]];
94
+ );
90
95
 
91
96
  useEffect(() => {
92
97
  dispatch({ type: "updatePositions" });
@@ -1,16 +1,20 @@
1
1
  import { useEffect, useState } from "react";
2
2
 
3
- import { Draggable, DragCollection, DragState, Position } from "./types";
3
+ import * as Drag from "../../types/Drag";
4
4
 
5
- function containsFiles(evt: Event) {
5
+ type AnyTouchEvent =
6
+ | MouseEvent
7
+ | TouchEvent
8
+ | React.MouseEvent
9
+ | React.TouchEvent;
10
+
11
+ function containsFiles(evt: AnyTouchEvent) {
6
12
  if ("dataTransfer" in evt) {
7
- const dataTransfer: DataTransfer = evt.dataTransfer;
8
- if ("types" in dataTransfer) {
9
- const types = dataTransfer.types;
10
- for (let i = 0; i < types.length; i++) {
11
- if (types[i] === "Files" || types[i] === "application/x-moz-file") {
12
- return true;
13
- }
13
+ const dataTransfer = evt.dataTransfer as DataTransfer;
14
+ const types = dataTransfer.types;
15
+ for (let i = 0; i < types.length; i++) {
16
+ if (types[i] === "Files" || types[i] === "application/x-moz-file") {
17
+ return true;
14
18
  }
15
19
  }
16
20
  }
@@ -33,7 +37,7 @@ function getFiles(dt: DataTransfer): File[] {
33
37
  return files;
34
38
  }
35
39
 
36
- function mousePosition(evt: TouchEvent | MouseEvent): Position {
40
+ function mousePosition(evt: AnyTouchEvent): Drag.Position {
37
41
  let x: number | null, y: number | null;
38
42
  if ("touches" in evt && evt.type == "touchmove") {
39
43
  x = evt.touches[0].clientX;
@@ -45,11 +49,18 @@ function mousePosition(evt: TouchEvent | MouseEvent): Position {
45
49
  return { x: x, y: y };
46
50
  }
47
51
 
48
- export default function useDragUploader(
49
- collections: DragCollection[],
50
- onDragEnd: (dragState: DragState, files: File[]) => void
51
- ) {
52
- const initialState: DragState = {
52
+ export default function useDragUploader<T>(
53
+ collections: Drag.Collection<T>[],
54
+ onDragEnd: (dragState: Drag.State<T>, files: File[]) => void
55
+ ): [
56
+ Drag.State<T>,
57
+ (evt: AnyTouchEvent, draggable: Drag.Item<T>) => void,
58
+ {
59
+ onDragOver: (evt: AnyTouchEvent) => void;
60
+ onDrop: (evt: AnyTouchEvent) => void;
61
+ }
62
+ ] {
63
+ const initialState: Drag.State<T> = {
53
64
  dragging: false,
54
65
  x: null,
55
66
  y: null
@@ -57,18 +68,18 @@ export default function useDragUploader(
57
68
 
58
69
  const [dragState, setDragState] = useState(initialState);
59
70
 
60
- const updatePositions = (dragging: Draggable | null) => {
71
+ const updatePositions = (dragging?: Drag.Draggable<T> | string) => {
61
72
  collections.forEach((c) => {
62
73
  c.dispatch({ type: "updatePositions", payload: dragging });
63
74
  });
64
75
  };
65
76
 
66
- const startDrag = (evt: Event, draggable: Draggable) => {
77
+ const startDrag = (evt: AnyTouchEvent, draggable: Drag.Item<T>) => {
67
78
  updatePositions(draggable);
68
79
  setDragState({ dragging: draggable, ...mousePosition(evt) });
69
80
  };
70
81
 
71
- const drag = (evt: Event) => {
82
+ const drag = (evt: AnyTouchEvent) => {
72
83
  if (dragState.dragging) {
73
84
  evt.stopPropagation();
74
85
  evt.preventDefault();
@@ -80,14 +91,14 @@ export default function useDragUploader(
80
91
  }
81
92
  };
82
93
 
83
- const dragEnd = (evt: Event) => {
94
+ const dragEnd = (evt: AnyTouchEvent) => {
84
95
  if (dragState.dragging) {
85
96
  const prevDragState = dragState;
86
97
  let files: File[] = [];
87
98
  evt.stopPropagation();
88
99
  evt.preventDefault();
89
100
  if ("dataTransfer" in evt && dragState.dragging == "Files") {
90
- files = getFiles(evt.dataTransfer);
101
+ files = getFiles(evt.dataTransfer as DataTransfer);
91
102
  }
92
103
  setDragState({ dragging: false, x: null, y: null });
93
104
  onDragEnd(prevDragState, files);
@@ -95,7 +106,7 @@ export default function useDragUploader(
95
106
  }
96
107
  };
97
108
 
98
- const dragLeave = (evt: Event) => {
109
+ const dragLeave = (evt: AnyTouchEvent) => {
99
110
  if (dragState.dragging === "Files") {
100
111
  evt.preventDefault();
101
112
  evt.stopPropagation();
@@ -1,13 +1,14 @@
1
- import React, { useEffect, useRef } from "react";
2
- import { Draggable } from "./types";
1
+ import React, { DragEvent, useEffect, useRef } from "react";
3
2
 
4
- export default function useDraggable(
5
- draggable: Draggable,
6
- startDrag: (evt: React.MouseEvent, draggable: Draggable) => void
3
+ import * as Drag from "../../types/Drag";
4
+
5
+ export default function useDraggable<T = Drag.DraggableRecord>(
6
+ draggable: Drag.Draggable<T>,
7
+ startDrag: (evt: React.MouseEvent, draggable: Drag.Draggable<T>) => void
7
8
  ) {
8
9
  const ref = useRef<HTMLDivElement>(null);
9
10
 
10
- const handleDrag = (evt: Event) => {
11
+ const handleDrag = (evt: DragEvent) => {
11
12
  evt.preventDefault();
12
13
  evt.stopPropagation();
13
14
  startDrag(evt, draggable);
@@ -1,4 +1,3 @@
1
- export { Draggable, DragState } from "./drag/types";
2
1
  export {
3
2
  default as useDragCollection,
4
3
  createDraggable
@@ -6,9 +6,7 @@ export { default as ImageCropper } from "./components/ImageCropper";
6
6
  export { default as ImageGrid } from "./components/ImageGrid";
7
7
  export { default as ImageUploader } from "./components/ImageUploader";
8
8
  export { default as Modal } from "./components/Modal";
9
- export { default as PageDates } from "./components/PageDates";
10
- export { default as PageFiles } from "./components/PageFiles";
11
- export { default as PageImages } from "./components/PageImages";
9
+ export { default as PageForm } from "./components/PageForm";
12
10
  export { default as PageTree } from "./components/PageTree";
13
11
  export { default as RichTextArea } from "./components/RichTextArea";
14
12
  export { default as TagEditor } from "./components/TagEditor";
@@ -6,7 +6,7 @@ import readyHandler from "../lib/readyHandler";
6
6
  class RichText {
7
7
  apply() {
8
8
  const elems = document.querySelectorAll("textarea.rich");
9
- elems.forEach((elem) => {
9
+ elems.forEach((elem: HTMLTextAreaElement) => {
10
10
  this.enhance(elem);
11
11
  });
12
12
  }
@@ -20,8 +20,7 @@ class RichText {
20
20
  name={elem.name}
21
21
  rows={elem.rows}
22
22
  id={elem.id}
23
- />,
24
- container
23
+ />
25
24
  );
26
25
  elem.parentNode.removeChild(elem);
27
26
  }
@@ -0,0 +1,79 @@
1
+ import readyHandler from "../lib/readyHandler";
2
+
3
+ interface WindowState {
4
+ tabId: string;
5
+ }
6
+
7
+ function applyTabs() {
8
+ const tabLinks: HTMLAnchorElement[] = Array.from(
9
+ document.querySelectorAll(".content-tabs li a")
10
+ );
11
+ const tabs: HTMLDivElement[] = Array.from(
12
+ document.querySelectorAll(".content-tab")
13
+ );
14
+
15
+ const showTab = (tab: string | null) => {
16
+ tabLinks.forEach((l) => {
17
+ const parent = l.parentNode as HTMLUListElement | null;
18
+ if (parent) {
19
+ if (l.dataset.tab == tab) {
20
+ parent.classList.add("current");
21
+ } else {
22
+ parent.classList.remove("current");
23
+ }
24
+ }
25
+ });
26
+
27
+ tabs.forEach((t) => {
28
+ if (t.dataset.tab == tab) {
29
+ t.classList.remove("hidden");
30
+ } else {
31
+ t.classList.add("hidden");
32
+ }
33
+ });
34
+ };
35
+
36
+ const changeTab = (evt: MouseEvent) => {
37
+ evt.preventDefault();
38
+ const link = evt.target as HTMLAnchorElement;
39
+ if ("tab" in link.dataset) {
40
+ const tab = link.dataset.tab;
41
+ showTab(tab);
42
+ history.pushState(
43
+ { tabId: tab },
44
+ "",
45
+ `${window.location.pathname}#${tab}`
46
+ );
47
+ }
48
+ };
49
+
50
+ const stateHandler = (evt: PopStateEvent) => {
51
+ if (evt.state && "tabId" in evt.state) {
52
+ const { tabId } = evt.state as WindowState;
53
+ showTab(tabId);
54
+ }
55
+ };
56
+
57
+ if (tabLinks.length > 0 && tabs.length > 0) {
58
+ const tabNames = tabs.map((t) => t.dataset.tab);
59
+
60
+ tabLinks.forEach((l) => l.addEventListener("click", changeTab));
61
+
62
+ let initTab: string = null;
63
+ const tabExpression = /#(.*)$/;
64
+
65
+ if (document.location.toString().match(tabExpression)) {
66
+ const id = document.location.toString().match(tabExpression)[1];
67
+ if (tabNames.indexOf(id) !== -1) {
68
+ initTab = id;
69
+ }
70
+ }
71
+
72
+ showTab(initTab || tabs[0].dataset.tab);
73
+ window.addEventListener("popstate", stateHandler);
74
+ }
75
+ }
76
+
77
+ export default function contentTabs() {
78
+ readyHandler.ready(applyTabs);
79
+ }
@@ -1,33 +1,24 @@
1
- import { start as startRails } from "@rails/ujs";
2
- import { Application } from "@hotwired/stimulus";
1
+ import Rails from "@rails/ujs";
3
2
  import "react_ujs";
4
3
  import { FC } from "react";
5
4
 
6
5
  import * as Components from "./components";
7
6
 
8
- import EditPageController from "./controllers/EditPageController";
9
- import MainController from "./controllers/MainController";
10
- import LoginController from "./controllers/LoginController";
11
- import PageOptionsController from "./controllers/PageOptionsController";
12
-
13
7
  import RichText from "./features/RichText";
8
+ import contentTabs from "./features/contentTabs";
14
9
 
15
10
  export function registerComponent(name: string, component: FC) {
16
11
  window[name] = component;
17
12
  }
18
13
 
19
14
  export default function startPages() {
20
- startRails();
15
+ Rails.start();
21
16
  for (const name in Components) {
22
17
  registerComponent(name, Components[name] as FC);
23
18
  }
24
- RichText.start();
25
19
 
26
- const application = Application.start();
27
- application.register("edit-page", EditPageController);
28
- application.register("main", MainController);
29
- application.register("login", LoginController);
30
- application.register("page-options", PageOptionsController);
20
+ RichText.start();
21
+ contentTabs();
31
22
  }
32
23
 
33
24
  export * from "./components";