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,233 +1,213 @@
1
- import React, { Component } from "react";
1
+ import { useCallback, useEffect, useState } from "react";
2
2
 
3
- import Tree from "../lib/Tree";
4
- import { postJson, putJson } from "../lib/request";
5
- import * as Trees from "../types/Trees";
6
3
  import * as Pages from "../types/Pages";
7
-
8
- import Draggable from "./PageTree/Draggable";
9
-
10
- type CollapsedState = Record<number, boolean>;
11
-
12
- interface ParentMap {
13
- [index: number]: Pages.TreeNode[];
14
- }
15
-
16
- interface Props {
4
+ import * as Tree from "./PageTree/tree";
5
+ import usePageTree, {
6
+ State,
7
+ movePage,
8
+ visibleChildNodes
9
+ } from "./PageTree/usePageTree";
10
+ import { PageTreeContext } from "./PageTree/usePageTreeContext";
11
+ import Node, { paddingLeft } from "./PageTree/Node";
12
+
13
+ type DragState = {
14
+ id: Tree.Id;
15
+ x: number;
16
+ y: number;
17
+ w: number;
18
+ h: number;
19
+ scrollTop: number;
20
+ scrollLeft: number;
21
+ startX: number;
22
+ startY: number;
23
+ offsetX: number;
24
+ offsetY: number;
25
+ tree: State;
26
+ };
27
+
28
+ type Props = {
17
29
  dir: string;
18
30
  locale: string;
19
- pages: Pages.TreeNode[];
31
+ pages: Pages.TreeResource[];
20
32
  permissions: string[];
21
- }
22
-
23
- interface State {
24
- tree: Tree<Pages.TreeNode>;
25
- }
26
-
27
- function collapsedState(): CollapsedState {
28
- if (
29
- window &&
30
- window.localStorage &&
31
- typeof window.localStorage.collapsedPages != "undefined"
32
- ) {
33
- return JSON.parse(
34
- window.localStorage.getItem("collapsedPages")
35
- ) as CollapsedState;
33
+ };
34
+
35
+ function prevAddButtonCount(state: State, id: Tree.Id) {
36
+ let count = 0;
37
+ const parentNodes = Tree.parents(state, id);
38
+
39
+ let pointer = Tree.getNodeByTop(state, state.nodes[id].top - 1);
40
+ while (pointer) {
41
+ if (
42
+ parentNodes.indexOf(pointer.id) == -1 &&
43
+ !pointer.collapsed &&
44
+ visibleChildNodes(state, pointer.id).length > 0
45
+ ) {
46
+ count += 1;
47
+ }
48
+ pointer = Tree.getNodeByTop(state, pointer.top - 1);
36
49
  }
37
- return {};
50
+ return count;
38
51
  }
39
52
 
40
- export default class PageTree extends Component<Props, State> {
41
- constructor(props: Props) {
42
- super(props);
53
+ export default function PageTree({ dir, locale, pages, permissions }: Props) {
54
+ const [state, dispatch] = usePageTree(pages, locale, dir, permissions);
55
+
56
+ const [dragging, setDragging] = useState(false);
57
+ const [dragState, setDragState] = useState<DragState | null>(null);
58
+
59
+ const getDraggingDom = () => {
60
+ if (dragging) {
61
+ const dragStateStyles = {
62
+ top: dragState.y,
63
+ left: dragState.x,
64
+ width: dragState.w
65
+ };
66
+
67
+ return (
68
+ <div className="draggable" style={dragStateStyles}>
69
+ <Node id={dragState.id} />
70
+ </div>
71
+ );
72
+ }
73
+ };
43
74
 
44
- this.state = { tree: this.buildTree(props.pages, props.locale) };
45
- }
75
+ const dragStart = (id: Tree.Id, dom: HTMLDivElement, e: React.MouseEvent) => {
76
+ // Only drag on left click
77
+ if (e.button !== 0) {
78
+ return;
79
+ }
46
80
 
47
- applyCollapsed(tree: Tree<Pages.TreeNode>) {
48
- const depth = (t: Tree, index: Trees.Index) => {
49
- let depth = 0;
50
- let pointer = t.getIndex(index.parent);
51
- while (pointer) {
52
- depth += 1;
53
- pointer = t.getIndex(pointer.parent);
81
+ setDragState({
82
+ id: id,
83
+ w: dom.offsetWidth,
84
+ h: dom.offsetHeight,
85
+ x: dom.offsetLeft,
86
+ y: dom.offsetTop,
87
+ scrollTop: document.body.scrollTop,
88
+ scrollLeft: document.body.scrollLeft,
89
+ startX: dom.offsetLeft,
90
+ startY: dom.offsetTop,
91
+ offsetX: e.clientX,
92
+ offsetY: e.clientY,
93
+ tree: { ...state }
94
+ });
95
+ };
96
+
97
+ const drag = useCallback(
98
+ (e: MouseEvent) => {
99
+ if (!dragState) {
100
+ return;
101
+ } else if (!dragging) {
102
+ const distance =
103
+ Math.abs(e.clientX - dragState.offsetX) +
104
+ Math.abs(e.clientY - dragState.offsetY);
105
+ if (distance >= 15) {
106
+ setDragging(true);
107
+ } else {
108
+ return null;
109
+ }
54
110
  }
55
- return depth;
56
- };
57
111
 
58
- const walk = (id: Trees.Id) => {
59
- const index = tree.getIndex(id);
60
- const node = index.node;
61
- if (node.id && node.id in collapsedState()) {
62
- node.collapsed = collapsedState()[node.id];
63
- } else if (node.news_page) {
64
- node.collapsed = true;
65
- } else if (depth(tree, index) > 1) {
66
- node.collapsed = true;
112
+ let tree = dragState.tree;
113
+ let node = tree.nodes[dragState.id];
114
+
115
+ const pos = {
116
+ x:
117
+ dragState.startX +
118
+ e.clientX -
119
+ dragState.offsetX +
120
+ (document.body.scrollLeft - dragState.scrollLeft),
121
+ y:
122
+ dragState.startY +
123
+ e.clientY -
124
+ dragState.offsetY +
125
+ (document.body.scrollTop - dragState.scrollTop)
126
+ };
127
+
128
+ const move = (target: Tree.Id, placement: Tree.MovePlacement) => {
129
+ tree = {
130
+ ...tree,
131
+ ...Tree.indexPositions(
132
+ Tree.moveRelative(tree, node.id, target, placement)
133
+ )
134
+ };
135
+ node = tree.nodes[dragState.id];
136
+ };
137
+
138
+ const diffX = pos.x - paddingLeft / 2 - (node.left - 2) * paddingLeft;
139
+ const diffY =
140
+ pos.y -
141
+ dragState.h / 2 -
142
+ (node.top - 2 + prevAddButtonCount(tree, dragState.id)) * dragState.h;
143
+
144
+ if (diffX < 0) {
145
+ // left
146
+ if (node.parent && !Tree.nextSibling(tree, node.id)) {
147
+ move(node.parent, "after");
148
+ }
149
+ } else if (diffX > paddingLeft) {
150
+ // right
151
+ const prev = Tree.prevSibling(tree, node.id);
152
+ if (prev && !prev.collapsed) {
153
+ move(prev.id, "append");
154
+ }
67
155
  }
68
- if (index.children && index.children.length) {
69
- index.children.forEach((c) => walk(c));
70
- }
71
- };
72
- walk(1);
73
- }
74
-
75
- createPage(index: Trees.Index<Pages.TreeNode>, attributes: Pages.TreeItem) {
76
- void postJson(`/admin/${this.props.locale}/pages.json`, {
77
- page: attributes
78
- }).then((response: Pages.TreeItem) => this.updateNode(index, response));
79
- }
80
156
 
81
- buildTree(pages: Pages.TreeNode[], locale: string) {
82
- // Build tree
83
- const parentMap: ParentMap = pages.reduce(
84
- (m: ParentMap, page: Pages.TreeNode) => {
85
- const id = page.parent_page_id || 0;
86
- m[id] = [...(m[id] || []), page];
87
- return m;
88
- },
89
- {}
90
- );
91
-
92
- pages.forEach((p) => {
93
- p.children = parentMap[p.id] || [];
94
- });
95
-
96
- const tree = new Tree<Pages.TreeNode>({
97
- blocks: {
98
- name: { [locale]: "All Pages" }
99
- },
100
- permissions: this.props.permissions,
101
- root: true,
102
- children: parentMap[0],
103
- collapsed: false
104
- });
105
- this.applyCollapsed(tree);
106
- tree.updateNodesPosition();
107
- return tree;
108
- }
109
-
110
- movePage(
111
- index: Trees.Index<Pages.TreeNode>,
112
- parent: Trees.Index<Pages.TreeNode>,
113
- position: number
114
- ) {
115
- const data = {
116
- parent_id: parent.node.id,
117
- position: position
118
- };
119
- const url = `/admin/${this.props.locale}/pages/${index.node.id}/move.json`;
120
- this.performUpdate(index, url, data);
121
- }
122
-
123
- performUpdate(
124
- index: Trees.Index<Pages.TreeNode>,
125
- url: string,
126
- data: Record<string, unknown>
127
- ) {
128
- void putJson(url, data).then((response: Pages.TreeItem) =>
129
- this.updateNode(index, response)
130
- );
131
- }
132
-
133
- render() {
134
- const addChild = (id: Trees.Id, attributes: Pages.TreeNode) => {
135
- const tree = this.state.tree;
136
- const index = tree.append(attributes, id);
137
- this.reorderChildren(id);
138
- this.setCollapsed(id, false);
139
- this.createPage(index, attributes);
140
- this.setState({ tree: tree });
141
- };
142
-
143
- const movedPage = (id: Trees.Id) => {
144
- const tree = this.state.tree;
145
- const index = tree.getIndex(id);
146
- this.reorderChildren(index.parent);
147
-
148
- const parent = tree.getIndex(index.parent);
149
- const position = parent.children.indexOf(id) + 1;
150
-
151
- this.movePage(index, parent, position);
152
- this.setState({ tree: tree });
153
- };
154
-
155
- const toggleCollapsed = (id: Trees.Id) => {
156
- const tree = this.state.tree;
157
- const node = tree.getIndex(id).node;
158
- this.setCollapsed(id, !node.collapsed);
159
- this.setState({ tree: tree });
160
- };
161
-
162
- const updatePage = (id: Trees.Id, attributes: Pages.TreeItem) => {
163
- const tree = this.state.tree;
164
- const index = tree.getIndex(id);
165
- const url = `/admin/${this.props.locale}/pages/${index.node.id}.json`;
166
- this.updateNode(index, attributes);
167
-
168
- const data: Record<string, unknown> = { ...attributes };
169
- if ("blocks" in attributes && "name" in attributes.blocks) {
170
- data.name = attributes.blocks.name[this.props.locale];
157
+ if (diffY < 0 - dragState.h * 0.5) {
158
+ // up
159
+ move(Tree.getNodeByTop(tree, node.top - 1).id, "before");
160
+ } else if (diffY > dragState.h * 1.5) {
161
+ // down
162
+ const below =
163
+ Tree.nextSibling(tree, node.id) ||
164
+ Tree.getNodeByTop(tree, node.id + node.height);
165
+
166
+ if (below && below.parent !== node.id) {
167
+ if (below.childNodes.length > 0 && !below.collapsed) {
168
+ move(below.id, "prepend");
169
+ } else {
170
+ move(below.id, "after");
171
+ }
172
+ }
171
173
  }
172
- this.performUpdate(index, url, { page: data });
173
- };
174
174
 
175
- const updateTree = (tree: Tree<Pages.TreeNode>) => {
176
- this.setState({ tree: tree });
177
- };
175
+ setDragState({ ...dragState, ...pos, tree: tree });
176
+ },
177
+ [dragging, dragState]
178
+ );
178
179
 
179
- return (
180
- <Draggable
181
- tree={this.state.tree}
182
- addChild={addChild}
183
- movedPage={movedPage}
184
- toggleCollapsed={toggleCollapsed}
185
- updatePage={updatePage}
186
- updateTree={updateTree}
187
- locale={this.props.locale}
188
- dir={this.props.dir}
189
- />
190
- );
191
- }
192
-
193
- reorderChildren(id: Trees.Id) {
194
- const tree = this.state.tree;
195
- const index = this.state.tree.getIndex(id);
196
- const node = index.node;
197
- if (!node.news_page) {
198
- return;
180
+ const dragEnd = useCallback(() => {
181
+ if (dragging) {
182
+ movePage(dragState.tree, dragState.id, dispatch);
183
+ setDragging(false);
199
184
  }
200
- index.children = index.children.sort(function (a, b) {
201
- const aNode = tree.getIndex(a).node;
202
- const bNode = tree.getIndex(b).node;
203
- if (aNode.pinned == bNode.pinned) {
204
- return (
205
- new Date(bNode.published_at).getTime() -
206
- new Date(aNode.published_at).getTime()
207
- );
208
- } else {
209
- return aNode.pinned ? -1 : 1;
210
- }
211
- });
212
- tree.updateNodesPosition();
213
- }
214
-
215
- setCollapsed(id: Trees.Id, value: boolean) {
216
- const node = this.state.tree.getIndex(id).node;
217
- node.collapsed = value;
218
- this.storeCollapsed(id, node.collapsed);
219
- this.state.tree.updateNodesPosition();
220
- }
221
-
222
- storeCollapsed(id: Trees.Id, newState: boolean) {
223
- const node = this.state.tree.getIndex(id).node;
224
- const store = collapsedState();
225
- store[node.id] = newState;
226
- window.localStorage.collapsedPages = JSON.stringify(store);
227
- }
228
-
229
- updateNode(index: Trees.Index<Pages.TreeNode>, attributes: Pages.TreeItem) {
230
- index.node = { ...index.node, ...attributes };
231
- this.setState({ tree: this.state.tree });
232
- }
185
+ setDragState(null);
186
+ }, [dragging, dragState, dispatch]);
187
+
188
+ useEffect(() => {
189
+ window.addEventListener("mousemove", drag);
190
+ window.addEventListener("mouseup", dragEnd);
191
+ return () => {
192
+ window.removeEventListener("mousemove", drag);
193
+ window.removeEventListener("mouseup", dragEnd);
194
+ };
195
+ }, [drag, dragEnd]);
196
+
197
+ return (
198
+ <PageTreeContext.Provider
199
+ value={{
200
+ state: (dragging && dragState.tree) || state,
201
+ dispatch: dispatch
202
+ }}>
203
+ <div className="page-tree">
204
+ {getDraggingDom()}
205
+ <Node
206
+ id={state.rootId}
207
+ onDragStart={dragStart}
208
+ dragging={dragging && dragState.id}
209
+ />
210
+ </div>
211
+ </PageTreeContext.Provider>
212
+ );
233
213
  }
@@ -1,12 +1,10 @@
1
- import React, { MouseEvent } from "react";
2
-
3
- interface Props {
1
+ type Props = {
4
2
  className: string;
5
3
  name: string;
6
4
  onClick: (evt: React.MouseEvent) => void;
7
- }
5
+ };
8
6
 
9
- export default function RichTextToolbarButton(props: Props) {
7
+ export default function ToolbarButton(props: Props) {
10
8
  return (
11
9
  <a
12
10
  title={props.name}
@@ -0,0 +1,106 @@
1
+ type Replacement = [string, string, string];
2
+
3
+ export type ActionFn = (str: string) => Replacement;
4
+
5
+ type Action = {
6
+ name: string;
7
+ className: string;
8
+ fn: ActionFn;
9
+ hotkey?: string;
10
+ };
11
+
12
+ function strToList(str: string, prefix: string): string {
13
+ return str
14
+ .split("\n")
15
+ .map((line) => prefix + " " + line)
16
+ .join("\n");
17
+ }
18
+
19
+ function relativeUrl(str: string): string {
20
+ let url: URL = null;
21
+
22
+ if (!str.match(/^https:\/\//) || !document || !document.location) {
23
+ return str;
24
+ }
25
+
26
+ try {
27
+ url = new URL(str);
28
+ } catch (error) {
29
+ console.log("Error parsing URL: ", error);
30
+ }
31
+
32
+ if (
33
+ url &&
34
+ url.hostname === document.location.hostname &&
35
+ (document.location.port || "80") === (url.port || "80")
36
+ ) {
37
+ return url.pathname;
38
+ }
39
+ return str;
40
+ }
41
+
42
+ function emailLink(selection: string): Replacement {
43
+ const address = prompt("Enter email address", "");
44
+ const name = selection.length > 0 ? selection : address;
45
+ return ['"', name, `":mailto:${address}`];
46
+ }
47
+
48
+ function link(selection: string): Replacement {
49
+ const name = selection.length > 0 ? selection : "Link text";
50
+ const url = prompt("Enter link URL", "");
51
+ if (url) {
52
+ return ['"', name, `":${relativeUrl(url)}`];
53
+ } else {
54
+ return ["", name, ""];
55
+ }
56
+ }
57
+
58
+ export const simpleActions: Action[] = [
59
+ {
60
+ name: "bold",
61
+ className: "bold",
62
+ hotkey: "b",
63
+ fn: (str: string) => ["<b>", str, "</b>"]
64
+ },
65
+ {
66
+ name: "italic",
67
+ className: "italic",
68
+ hotkey: "i",
69
+ fn: (str: string) => ["<i>", str, "</i>"]
70
+ }
71
+ ];
72
+
73
+ export const advancedActions: Action[] = [
74
+ {
75
+ name: "Heading 2",
76
+ className: "header h2",
77
+ fn: (str: string) => ["h2. ", str, ""]
78
+ },
79
+ {
80
+ name: "Heading 3",
81
+ className: "header h3",
82
+ fn: (str: string) => ["h3. ", str, ""]
83
+ },
84
+ {
85
+ name: "Heading 4",
86
+ className: "header h4",
87
+ fn: (str: string) => ["h4. ", str, ""]
88
+ },
89
+ {
90
+ name: "Blockquote",
91
+ className: "quote-left",
92
+ fn: (str: string) => ["bq. ", str, ""]
93
+ },
94
+ {
95
+ name: "List",
96
+ className: "list-ul",
97
+ fn: (str: string) => ["", strToList(str, "*"), ""]
98
+ },
99
+ {
100
+ name: "Ordered list",
101
+ className: "list-ol",
102
+ fn: (str: string) => ["", strToList(str, "#"), ""]
103
+ },
104
+ { name: "Link", className: "link", fn: link },
105
+ { name: "Email link", className: "envelope", fn: emailLink }
106
+ ];
@@ -0,0 +1,14 @@
1
+ import { useState } from "react";
2
+
3
+ export default function useMaybeControlledValue<T>(
4
+ initialValue: T,
5
+ onChange?: (nextValue: T) => void
6
+ ): [T, (nextValue: T) => void] {
7
+ const [value, setValue] = useState(initialValue);
8
+
9
+ if (onChange) {
10
+ return [initialValue, onChange];
11
+ } else {
12
+ return [value, setValue];
13
+ }
14
+ }