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,441 +1,171 @@
1
- /*
2
- Based on react-ui-tree
3
- https://github.com/pqx/react-ui-tree
1
+ import { Fragment, useRef } from "react";
4
2
 
5
- The MIT License (MIT)
6
-
7
- Copyright (c) 2015 pqx Limited
8
-
9
- Permission is hereby granted, free of charge, to any person obtaining a copy
10
- of this software and associated documentation files (the "Software"), to deal
11
- in the Software without restriction, including without limitation the rights
12
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
- copies of the Software, and to permit persons to whom the Software is
14
- furnished to do so, subject to the following conditions:
15
-
16
- The above copyright notice and this permission notice shall be included in all
17
- copies or substantial portions of the Software.
18
-
19
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
- SOFTWARE.
26
- */
27
-
28
- import React, {
29
- createRef,
30
- ChangeEvent,
31
- Component,
32
- CSSProperties,
33
- FormEvent,
34
- MouseEvent,
35
- RefObject
36
- } from "react";
37
-
38
- import Tree from "../../lib/Tree";
39
- import * as Trees from "../../types/Trees";
40
- import * as Pages from "../../types/Pages";
3
+ import usePageTreeContext from "./usePageTreeContext";
4
+ import * as Tree from "./tree";
41
5
 
6
+ import { addChild, updatePage, visibleChildNodes } from "./usePageTree";
7
+ import Button from "./Button";
8
+ import CollapseArrow from "./CollapseArrow";
9
+ import CollapsedLabel from "./CollapsedLabel";
42
10
  import PageName from "./PageName";
11
+ import StatusLabel from "./StatusLabel";
12
+ import EditPageName from "./EditPageName";
43
13
 
44
- interface Props {
45
- index: Trees.Index<Pages.TreeNode>;
46
- paddingLeft: number;
47
- tree: Tree<Pages.TreeNode>;
48
- addChild?: (index: Trees.Index<Pages.TreeNode>) => void;
49
- dir?: string;
50
- dragging?: Trees.Id;
51
- locale: string;
52
- onCollapse?: (id: Trees.Id) => void;
53
- onDragStart?: (id: number, element: HTMLDivElement, evt: MouseEvent) => void;
54
- updatePage?: (
55
- index: Trees.Index<Pages.TreeNode>,
56
- attributes: Partial<Pages.TreeItem>
57
- ) => void;
58
- }
59
-
60
- interface State {
61
- newName: string;
62
- }
63
-
64
- interface ButtonOptions {
65
- icon: string;
66
- className: string;
67
- onClick: (evt: MouseEvent) => void;
68
- }
69
-
70
- export default class Node extends Component<Props, State> {
71
- innerRef: RefObject<HTMLDivElement>;
72
-
73
- constructor(props: Props) {
74
- super(props);
75
- this.state = { newName: this.pageName() };
76
- this.innerRef = createRef<HTMLDivElement>();
77
- }
78
-
79
- permitted(action: string): boolean {
80
- return (
81
- this.node().permissions && this.node().permissions.indexOf(action) != -1
82
- );
83
- }
84
-
85
- actions() {
86
- const statusLabel = this.node().status != 2 ? "Publish" : "Hide";
87
- const statusIcon = this.node().status != 2 ? "check" : "ban";
88
-
89
- if (this.node().editing) {
90
- return null;
91
- }
92
-
93
- if (this.props.index.id === 1) {
94
- return (
95
- <span className="actions">
96
- <button
97
- type="button"
98
- className="add"
99
- onClick={() => this.props.addChild(this.props.index)}>
100
- <i className="fa-solid fa-plus icon" />
101
- Add child
102
- </button>
103
- </span>
104
- );
105
- } else {
106
- return (
107
- <span className="actions">
108
- {this.permitted("edit") &&
109
- this.button(statusLabel, {
110
- className: "toggle-status",
111
- icon: statusIcon,
112
- onClick: () => this.toggleStatus()
113
- })}
114
-
115
- {this.permitted("edit") &&
116
- this.button("Rename", {
117
- className: "edit",
118
- icon: "pencil",
119
- onClick: () => this.edit()
120
- })}
14
+ type Props = {
15
+ id: Tree.Id;
121
16
 
122
- {this.permitted("edit") &&
123
- this.button("Delete", {
124
- className: "delete",
125
- icon: "trash",
126
- onClick: () => this.deletePage()
127
- })}
17
+ dragging?: Tree.Id;
18
+ onDragStart?: (
19
+ id: number,
20
+ element: HTMLDivElement,
21
+ evt: React.MouseEvent | React.TouchEvent
22
+ ) => void;
23
+ };
128
24
 
129
- {this.permitted("create") &&
130
- this.button("Add child", {
131
- className: "add",
132
- icon: "plus",
133
- onClick: () => this.props.addChild(this.props.index)
134
- })}
135
- </span>
136
- );
137
- }
138
- }
25
+ export const paddingLeft = 20;
139
26
 
140
- addButton() {
141
- const node = this.node();
142
- const handleClick = () => {
143
- if (this.props.addChild) {
144
- this.props.addChild(this.props.index);
145
- }
146
- };
27
+ export default function Node(props: Props) {
28
+ const { state, dispatch } = usePageTreeContext();
29
+ const { id, dragging, onDragStart } = props;
30
+ const { dir, locale } = state;
31
+ const node = state.nodes[id];
32
+ const page = node.record;
33
+ const name = page.blocks.name[locale];
147
34
 
148
- if (
149
- !node.collapsed &&
150
- this.permitted("create") &&
151
- (node.root || this.visibleChildren().length > 0)
152
- ) {
153
- return this.button("Add page here", {
154
- className: "add add-inline transparent",
155
- icon: "plus",
156
- onClick: handleClick
157
- });
158
- }
159
- }
35
+ const innerRef = useRef<HTMLDivElement>();
160
36
 
161
- button(label: string, options: ButtonOptions) {
162
- const icon = "fa-solid fa-" + options.icon + " icon";
163
- return (
164
- <button
165
- type="button"
166
- className={options.className}
167
- onClick={options.onClick}>
168
- <i className={icon} />
169
- {label}
170
- </button>
171
- );
37
+ const classNames = ["node"];
38
+ if (id === dragging) {
39
+ classNames.push("placeholder");
172
40
  }
173
41
 
174
- childNodes() {
175
- const { index, tree, dragging, dir, locale } = this.props;
176
-
177
- if (index.children && index.children.length && !index.node.collapsed) {
178
- const childrenStyles: CSSProperties = {};
179
- if (index.node.collapsed) {
180
- childrenStyles.display = "none";
181
- }
182
- childrenStyles["paddingLeft"] = `${this.props.paddingLeft}px`;
42
+ const pageClassNames = ["page"];
43
+ let iconClass = "fa-regular fa-file icon";
183
44
 
184
- return (
185
- <div className="children" style={childrenStyles}>
186
- {index.children.map((child) => {
187
- const childIndex = tree.getIndex(child);
188
- return (
189
- <Node
190
- tree={tree}
191
- index={childIndex}
192
- key={childIndex.id}
193
- dragging={dragging}
194
- paddingLeft={this.props.paddingLeft}
195
- addChild={this.props.addChild}
196
- onCollapse={this.props.onCollapse}
197
- onDragStart={this.props.onDragStart}
198
- updatePage={this.props.updatePage}
199
- dir={dir}
200
- locale={locale}
201
- />
202
- );
203
- })}
204
- </div>
205
- );
45
+ if (!("root" in page)) {
46
+ pageClassNames.push(`status-${page.status}`);
47
+ if (page.news_page) {
48
+ iconClass = "fa-regular fa-file-lines page-icon";
49
+ } else if (page.pinned) {
50
+ iconClass = "fa-regular fa-flag page-icon";
206
51
  }
207
-
208
- return null;
209
52
  }
210
53
 
211
- collapseArrow() {
212
- const index = this.props.index;
54
+ const permitted = (action: string): boolean => {
55
+ return page.permissions && page.permissions.indexOf(action) !== -1;
56
+ };
213
57
 
214
- // Don't collapse the root node
215
- if (!index.parent) {
216
- return null;
217
- }
218
-
219
- const handleCollapse = (e: MouseEvent) => {
220
- e.stopPropagation();
221
- const nodeId = this.props.index.id;
222
- if (this.props.onCollapse) {
223
- this.props.onCollapse(nodeId);
224
- }
225
- };
226
-
227
- if (this.visibleChildren().length > 0) {
228
- const collapsed = index.node.collapsed;
229
- const classnames = ["collapse fa-solid fa-caret-right"];
230
-
231
- if (collapsed) {
232
- classnames.push("collapsed");
233
- } else {
234
- classnames.push("open");
235
- }
236
-
237
- return (
238
- <i
239
- className={classnames.join(" ")}
240
- onMouseDown={function (e) {
241
- e.stopPropagation();
242
- }}
243
- onClick={handleCollapse}
244
- />
245
- );
246
- }
58
+ const handleAddChild = () => {
59
+ addChild(state, id, dispatch);
60
+ };
247
61
 
248
- return null;
249
- }
250
-
251
- collapsedLabel() {
252
- if (
253
- this.node().collapsed &&
254
- this.node().children &&
255
- this.node().children.length > 0
256
- ) {
257
- const pluralized = this.node().children.length == 1 ? "item" : "items";
258
- return (
259
- <span className="collapsed-label">
260
- ({this.node().children.length} {pluralized})
261
- </span>
262
- );
263
- } else {
264
- return null;
62
+ const handleDragStart = (evt: React.MouseEvent | React.TouchEvent) => {
63
+ if (permitted("edit") && !page.editing && onDragStart) {
64
+ onDragStart(id, innerRef.current, evt);
265
65
  }
266
- }
66
+ };
267
67
 
268
- deletePage() {
68
+ const handleDelete = () => {
269
69
  if (confirm("Are you sure you want to delete this page?")) {
270
- this.updatePage({ status: 4 });
271
- }
272
- }
273
-
274
- edit() {
275
- this.updatePage({ editing: true });
276
- }
277
-
278
- editUrl(page: Pages.TreeNode) {
279
- return `/admin/${this.props.locale}/pages/${page.id}/edit`;
280
- }
281
-
282
- node(): Pages.TreeNode {
283
- return this.props.index.node;
284
- }
285
-
286
- pageName() {
287
- return this.node().blocks.name[this.props.locale];
288
- }
289
-
290
- render() {
291
- const props = this.props;
292
- const index = props.index;
293
- const dragging = props.dragging;
294
- const editing = this.node().editing;
295
- let classnames = "node";
296
-
297
- const node = editing ? this.renderEditNode() : this.renderNode();
298
-
299
- if (index.id === dragging) {
300
- classnames = "node placeholder";
70
+ updatePage(state, id, dispatch, { status: 4 });
301
71
  }
72
+ };
302
73
 
303
- const handleMouseDown = (e: MouseEvent) => {
304
- if (this.permitted("edit") && !editing && props.onDragStart) {
305
- props.onDragStart(props.index.id, this.innerRef.current, e);
306
- }
307
- };
74
+ const handleEdit = () => {
75
+ dispatch({ type: "update", id: id, payload: { editing: true } });
76
+ };
308
77
 
309
- if (this.node().status != 4) {
310
- return (
311
- <div className={classnames}>
312
- <div
313
- className="inner"
314
- ref={this.innerRef}
315
- onMouseDown={handleMouseDown}>
316
- {this.collapseArrow()}
317
- {node}
318
- </div>
319
- {this.childNodes()}
320
- {this.addButton()}
321
- </div>
322
- );
78
+ const handleToggleStatus = () => {
79
+ if ("status" in page && page.status != 2) {
80
+ updatePage(state, id, dispatch, { status: 2 });
323
81
  } else {
324
- return null;
325
- }
326
- }
327
-
328
- renderEditNode() {
329
- const { dir, locale } = this.props;
330
-
331
- const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
332
- this.setState({ newName: event.target.value });
333
- };
334
-
335
- const performEdit = (event: FormEvent) => {
336
- event.preventDefault();
337
- this.updatePage({
338
- blocks: { name: { [locale]: this.state.newName } },
339
- editing: false
340
- });
341
- };
342
-
343
- const cancelEdit = () => {
344
- this.setState({ newName: this.pageName() });
345
- this.updatePage({ editing: false });
346
- };
347
-
348
- return (
349
- <div className="page edit">
350
- <i className="fa-regular fa-file icon"></i>
351
- <form className="edit-name" onSubmit={performEdit}>
352
- <input
353
- className="tight"
354
- type="text"
355
- value={this.state.newName}
356
- dir={dir}
357
- lang={locale}
358
- autoFocus
359
- onChange={handleNameChange}
360
- />
361
- <button className="save primary" type="submit">
362
- <i className="fa-solid fa-cloud icon"></i>
363
- Save
364
- </button>
365
- {this.button("Cancel", {
366
- className: "cancel",
367
- icon: "ban",
368
- onClick: cancelEdit
369
- })}
370
- </form>
371
- </div>
372
- );
373
- }
374
-
375
- renderNode() {
376
- const index = this.props.index;
377
- const node = index.node;
378
-
379
- let className = "page";
380
-
381
- let iconClass = "fa-regular fa-file icon";
382
-
383
- if (typeof node.status != "undefined") {
384
- className = `page status-${this.node().status}`;
385
- }
386
-
387
- if (node.news_page) {
388
- iconClass = "fa-regular fa-file-lines page-icon";
389
- } else if (node.pinned) {
390
- iconClass = "fa-regular fa-flag page-icon";
391
- }
82
+ updatePage(state, id, dispatch, { status: 3 });
83
+ }
84
+ };
85
+
86
+ return (
87
+ <div className={classNames.join(" ")}>
88
+ <div className="inner" ref={innerRef} onMouseDown={handleDragStart}>
89
+ <CollapseArrow id={id} />
90
+
91
+ {!page.editing && (
92
+ <div className={pageClassNames.join(" ")}>
93
+ <i className={iconClass}></i>
94
+ <PageName
95
+ name={name}
96
+ dir={dir}
97
+ locale={locale}
98
+ editUrl={
99
+ "id" in page &&
100
+ permitted("edit") &&
101
+ `/admin/${locale}/pages/${page.id}/edit`
102
+ }
103
+ />
104
+ {"status" in page && <StatusLabel status={page.status} />}
105
+ <CollapsedLabel id={id} />
106
+ <span className="actions">
107
+ {!("root" in page) && (
108
+ <Fragment>
109
+ {permitted("edit") && (
110
+ <Button
111
+ label={page.status != 2 ? "Publish" : "Hide"}
112
+ className="toggle-status"
113
+ icon={page.status != 2 ? "check" : "ban"}
114
+ onClick={handleToggleStatus}
115
+ />
116
+ )}
117
+
118
+ {permitted("edit") && (
119
+ <Button
120
+ label="Rename"
121
+ className="edit"
122
+ icon="pencil"
123
+ onClick={handleEdit}
124
+ />
125
+ )}
126
+
127
+ {permitted("edit") && (
128
+ <Button
129
+ label="Delete"
130
+ className="delete"
131
+ icon="trash"
132
+ onClick={handleDelete}
133
+ />
134
+ )}
135
+ </Fragment>
136
+ )}
137
+ {permitted("create") && (
138
+ <Button
139
+ label="Add child"
140
+ className="add"
141
+ icon="plus"
142
+ onClick={handleAddChild}
143
+ />
144
+ )}
145
+ </span>
146
+ </div>
147
+ )}
392
148
 
393
- return (
394
- <div className={className}>
395
- <i className={iconClass}></i>
396
- <PageName
397
- name={this.pageName()}
398
- dir={this.props.dir}
399
- locale={this.props.locale}
400
- editUrl={node.id && this.permitted("edit") && this.editUrl(node)}
401
- />
402
- {this.statusLabel()}
403
- {this.collapsedLabel()}
404
- {this.actions()}
149
+ {page.editing && <EditPageName id={id} />}
405
150
  </div>
406
- );
407
- }
408
-
409
- statusLabel() {
410
- const labels = ["Draft", "Reviewed", "Published", "Hidden", "Deleted"];
411
- if (typeof this.node().status != "undefined" && this.node().status != 2) {
412
- return (
413
- <span className="status-label">({labels[this.node().status]})</span>
414
- );
415
- } else {
416
- return "";
417
- }
418
- }
419
151
 
420
- toggleStatus() {
421
- if (this.node().status != 2) {
422
- this.updatePage({ status: 2 });
423
- } else {
424
- this.updatePage({ status: 3 });
425
- }
426
- }
427
-
428
- updatePage(attributes: Partial<Pages.TreeItem>) {
429
- if (this.props.updatePage) {
430
- return this.props.updatePage(this.props.index, attributes);
431
- }
432
- }
433
-
434
- visibleChildren(): Pages.TreeNode[] {
435
- if (this.node().children) {
436
- return this.node().children.filter((p) => p.status != 4);
437
- } else {
438
- return [];
439
- }
440
- }
152
+ {!node.collapsed && visibleChildNodes(state, id).length > 0 && (
153
+ <Fragment>
154
+ <div className="children" style={{ paddingLeft: `${paddingLeft}px` }}>
155
+ {visibleChildNodes(state, id).map((childId) => {
156
+ return <Node {...props} id={childId} key={childId} />;
157
+ })}
158
+ </div>
159
+ {permitted("create") && (
160
+ <Button
161
+ label="Add page here"
162
+ className="add add-inline transparent"
163
+ icon="plus"
164
+ onClick={handleAddChild}
165
+ />
166
+ )}
167
+ </Fragment>
168
+ )}
169
+ </div>
170
+ );
441
171
  }
@@ -1,6 +1,4 @@
1
- import React from "react";
2
-
3
- interface Props {
1
+ type Props = {
4
2
  name: string;
5
3
  locale: string;
6
4
  dir?: string;
@@ -16,9 +14,13 @@ export default function PageName(props: Props) {
16
14
  </span>
17
15
  );
18
16
 
17
+ const preventDrag = (evt: React.MouseEvent) => {
18
+ evt.preventDefault();
19
+ };
20
+
19
21
  if (editUrl) {
20
22
  return (
21
- <a href={editUrl} className="name">
23
+ <a href={editUrl} className="name" onDragStart={preventDrag}>
22
24
  {span}
23
25
  </a>
24
26
  );
@@ -0,0 +1,10 @@
1
+ type Props = {
2
+ status: number;
3
+ }
4
+
5
+ export default function StatusLabel({ status }: Props) {
6
+ const labels = ["Draft", "Reviewed", "Published", "Hidden", "Deleted"];
7
+ if (status != 2) {
8
+ return <span className="status-label">({labels[status]})</span>;
9
+ }
10
+ }