pages_core 3.12.1 → 3.12.2

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +59 -14
  4. data/app/assets/builds/pages_core/admin-dist.js.map +7 -0
  5. data/app/assets/builds/pages_core/admin.css +39 -0
  6. data/app/assets/stylesheets/pages_core/admin/components/search.css +27 -0
  7. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +6 -0
  8. data/app/controllers/admin/pages_controller.rb +12 -11
  9. data/app/controllers/concerns/pages_core/rss_controller.rb +17 -1
  10. data/app/controllers/pages_core/admin_controller.rb +6 -0
  11. data/app/controllers/pages_core/frontend/pages_controller.rb +9 -5
  12. data/app/controllers/pages_core/sitemaps_controller.rb +3 -5
  13. data/app/helpers/admin/pages_helper.rb +32 -0
  14. data/app/javascript/admin-dist.ts +2 -0
  15. data/app/javascript/components/Attachments/{Attachment.jsx → Attachment.tsx} +42 -33
  16. data/app/javascript/components/Attachments/{AttachmentEditor.jsx → AttachmentEditor.tsx} +23 -23
  17. data/app/javascript/components/{EditableImage.jsx → EditableImage.tsx} +27 -24
  18. data/app/javascript/components/{FileUploadButton.jsx → FileUploadButton.tsx} +15 -16
  19. data/app/javascript/components/ImageCropper/FocalPoint.tsx +94 -0
  20. data/app/javascript/components/ImageCropper/{Image.jsx → Image.tsx} +13 -14
  21. data/app/javascript/components/ImageCropper/{Toolbar.jsx → Toolbar.tsx} +16 -12
  22. data/app/javascript/components/ImageCropper/{useCrop.js → useCrop.ts} +80 -37
  23. data/app/javascript/components/{ImageCropper.jsx → ImageCropper.tsx} +17 -15
  24. data/app/javascript/components/ImageEditor/{Form.jsx → Form.tsx} +24 -23
  25. data/app/javascript/components/{ImageEditor.jsx → ImageEditor.tsx} +17 -15
  26. data/app/javascript/components/ImageGrid/{DragElement.jsx → DragElement.tsx} +12 -10
  27. data/app/javascript/components/ImageGrid/{GridImage.jsx → GridImage.tsx} +40 -30
  28. data/app/javascript/components/ImageGrid/{Placeholder.jsx → Placeholder.tsx} +5 -6
  29. data/app/javascript/components/ImageGrid.jsx +3 -4
  30. data/app/javascript/components/{ImageUploader.jsx → ImageUploader.tsx} +46 -41
  31. data/app/javascript/components/Modal.tsx +48 -0
  32. data/app/javascript/components/PageImages.tsx +28 -0
  33. data/app/javascript/components/{PageTreeDraggable.jsx → PageTree/Draggable.tsx} +79 -57
  34. data/app/javascript/components/{PageTreeNode.jsx → PageTree/Node.tsx} +79 -70
  35. data/app/javascript/components/PageTree/types.ts +15 -0
  36. data/app/javascript/components/PageTree.tsx +206 -0
  37. data/app/javascript/components/RichTextToolbarButton.tsx +17 -0
  38. data/app/javascript/components/TagEditor/{AddTagForm.jsx → AddTagForm.tsx} +9 -10
  39. data/app/javascript/components/TagEditor/{Tag.jsx → Tag.tsx} +8 -9
  40. data/app/javascript/components/{TagEditor.jsx → TagEditor.tsx} +12 -13
  41. data/app/javascript/components/Toast.tsx +61 -0
  42. data/app/javascript/components/drag/{draggedOrder.js → draggedOrder.ts} +22 -12
  43. data/app/javascript/components/drag/types.ts +28 -0
  44. data/app/javascript/components/drag/{useDragCollection.js → useDragCollection.ts} +40 -22
  45. data/app/javascript/components/drag/{useDragUploader.js → useDragUploader.ts} +34 -25
  46. data/app/javascript/components/drag/useDraggable.ts +21 -0
  47. data/app/javascript/components/{drag.js → drag.ts} +1 -0
  48. data/app/javascript/controllers/{EditPageController.js → EditPageController.ts} +3 -1
  49. data/app/javascript/controllers/{LoginController.js → LoginController.ts} +7 -3
  50. data/app/javascript/controllers/{MainController.js → MainController.ts} +19 -14
  51. data/app/javascript/{index.js → index.ts} +8 -7
  52. data/app/javascript/lib/{Tree.js → Tree.ts} +106 -85
  53. data/app/javascript/lib/{copyToClipboard.js → copyToClipboard.ts} +1 -1
  54. data/app/javascript/lib/{readyHandler.js → readyHandler.ts} +4 -2
  55. data/app/javascript/lib/{request.js → request.ts} +11 -5
  56. data/app/javascript/stores/useModalStore.ts +15 -0
  57. data/app/javascript/stores/useToastStore.ts +26 -0
  58. data/app/javascript/stores.ts +2 -0
  59. data/app/javascript/types.ts +30 -0
  60. data/app/policies/page_policy.rb +4 -0
  61. data/app/views/admin/calendars/_sidebar.html.erb +3 -0
  62. data/app/views/admin/news/_sidebar.html.erb +3 -0
  63. data/app/views/admin/pages/_list_item.html.erb +4 -22
  64. data/app/views/admin/pages/_search_bar.html.erb +12 -0
  65. data/app/views/admin/pages/index.html.erb +3 -0
  66. data/app/views/admin/pages/search.html.erb +54 -0
  67. data/app/views/feeds/pages.rss.builder +3 -9
  68. data/config/routes.rb +1 -0
  69. data/lib/pages_core/configuration/pages.rb +0 -1
  70. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +33 -17
  71. data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +0 -1
  72. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +40 -0
  73. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +68 -0
  74. data/lib/rails/generators/pages_core/frontend/templates/postcss.config.js +17 -0
  75. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.postcss.css +4 -0
  76. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.css +24 -0
  77. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/layout.css +21 -0
  78. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.css +5 -0
  79. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/animation.css +5 -0
  80. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.css +18 -0
  81. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/fonts.css +6 -0
  82. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/grid.css +65 -0
  83. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.css +131 -0
  84. data/lib/rails/generators/pages_core/install/templates/pages_initializer.rb +0 -3
  85. metadata +68 -62
  86. data/app/javascript/admin-dist.js +0 -2
  87. data/app/javascript/components/ImageCropper/FocalPoint.jsx +0 -93
  88. data/app/javascript/components/Modal.jsx +0 -59
  89. data/app/javascript/components/PageImages.jsx +0 -25
  90. data/app/javascript/components/PageTree.jsx +0 -196
  91. data/app/javascript/components/RichTextToolbarButton.jsx +0 -20
  92. data/app/javascript/components/Toast.jsx +0 -72
  93. data/app/javascript/components/drag/useDraggable.js +0 -17
  94. data/app/javascript/stores/ModalStore.jsx +0 -12
  95. data/app/javascript/stores/ToastStore.jsx +0 -14
  96. data/app/javascript/stores.js +0 -2
  97. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/GridOverlay.js +0 -66
  98. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/ResponsiveEmbeds.js +0 -72
  99. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.sass.scss +0 -15
  100. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.scss +0 -12
  101. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.scss +0 -26
  102. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/breakpoints.scss +0 -42
  103. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/clearfix.scss +0 -7
  104. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/fonts.scss +0 -32
  105. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid.scss +0 -168
  106. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid_overlay.scss +0 -44
  107. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.scss +0 -8
  108. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.scss +0 -90
  109. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/vendor/normalize.css +0 -349
  110. /data/app/javascript/components/Attachments/{Placeholder.jsx → Placeholder.tsx} +0 -0
  111. /data/app/javascript/components/ImageGrid/{FilePlaceholder.jsx → FilePlaceholder.tsx} +0 -0
  112. /data/app/javascript/{components.js → components.ts} +0 -0
  113. /data/app/javascript/{hooks.js → hooks.ts} +0 -0
@@ -25,24 +25,50 @@
25
25
  SOFTWARE.
26
26
  */
27
27
 
28
- import React from "react";
29
- import PropTypes from "prop-types";
28
+ import React, { createRef, ChangeEvent, Component, RefObject } from "react";
29
+ import Tree, { TreeId, TreeIndex } from "../../lib/Tree";
30
+ import { Attributes, PageNode } from "./types";
31
+
32
+ interface NodeProps {
33
+ addChild: (index: TreeIndex) => void,
34
+ dir: string,
35
+ dragging: number,
36
+ index: TreeIndex<PageNode>,
37
+ locale: string,
38
+ onCollapse: (id: TreeId) => void,
39
+ onDragStart: (id: number, element: HTMLDivElement, evt: Event) => void,
40
+ paddingLeft: number,
41
+ tree: Tree<PageNode>,
42
+ updatePage: (index: TreeIndex, attributes: Attributes) => void
43
+ }
44
+
45
+ interface NodeState {
46
+ newName: string
47
+ }
30
48
 
31
- export default class PageTreeNode extends React.Component {
32
- constructor(props) {
49
+ interface ButtonOptions {
50
+ icon: string,
51
+ className: string,
52
+ onClick: (evt: Event) => void
53
+ }
54
+
55
+ export default class Node extends Component<NodeProps, NodeState> {
56
+ innerRef: RefObject<HTMLDivElement>;
57
+
58
+ constructor(props: NodeProps) {
33
59
  super(props);
34
60
  this.state = { newName: props.index.node.name };
35
- this.innerRef = React.createRef();
61
+ this.innerRef = createRef<HTMLDivElement>();
36
62
  }
37
63
 
38
- permitted(action) {
64
+ permitted(action: string): boolean {
39
65
  return this.node().permissions &&
40
66
  this.node().permissions.indexOf(action) != -1;
41
67
  }
42
68
 
43
69
  actions() {
44
- let statusLabel = (this.node().status != 2) ? "Publish" : "Hide";
45
- let statusIcon = (this.node().status != 2) ? "check" : "ban";
70
+ const statusLabel = (this.node().status != 2) ? "Publish" : "Hide";
71
+ const statusIcon = (this.node().status != 2) ? "check" : "ban";
46
72
 
47
73
  if (this.node().editing) {
48
74
  return null;
@@ -91,11 +117,10 @@ export default class PageTreeNode extends React.Component {
91
117
  }
92
118
 
93
119
  addButton() {
94
- let self = this;
95
- let node = this.node();
96
- let handleClick = function () {
97
- if (self.props.addChild) {
98
- self.props.addChild(self.props.index);
120
+ const node = this.node();
121
+ const handleClick = () => {
122
+ if (this.props.addChild) {
123
+ this.props.addChild(this.props.index);
99
124
  }
100
125
  };
101
126
 
@@ -112,8 +137,8 @@ export default class PageTreeNode extends React.Component {
112
137
  }
113
138
  }
114
139
 
115
- button(label, options) {
116
- let icon = "fa-solid fa-" + options.icon + " icon";
140
+ button(label: string, options: ButtonOptions) {
141
+ const icon = "fa-solid fa-" + options.icon + " icon";
117
142
  return (
118
143
  <button type="button"
119
144
  className={options.className}
@@ -128,18 +153,18 @@ export default class PageTreeNode extends React.Component {
128
153
  const { index, tree, dragging, dir, locale } = this.props;
129
154
 
130
155
  if (index.children && index.children.length && !index.node.collapsed) {
131
- var childrenStyles = {};
156
+ const childrenStyles = {};
132
157
  if (index.node.collapsed) {
133
158
  childrenStyles.display = "none";
134
159
  }
135
- childrenStyles["paddingLeft"] = this.props.paddingLeft + "px";
160
+ childrenStyles["paddingLeft"] = `${this.props.paddingLeft}px`;
136
161
 
137
162
  return (
138
163
  <div className="children" style={childrenStyles}>
139
164
  {index.children.map((child) => {
140
- var childIndex = tree.getIndex(child);
165
+ const childIndex = tree.getIndex(child);
141
166
  return (
142
- <PageTreeNode
167
+ <Node
143
168
  tree={tree}
144
169
  index={childIndex}
145
170
  key={childIndex.id}
@@ -161,25 +186,24 @@ export default class PageTreeNode extends React.Component {
161
186
  }
162
187
 
163
188
  collapseArrow() {
164
- let index = this.props.index;
165
- let self = this;
189
+ const index = this.props.index;
166
190
 
167
191
  // Don't collapse the root node
168
192
  if (!index.parent) {
169
193
  return null;
170
194
  }
171
195
 
172
- let handleCollapse = function (e) {
196
+ const handleCollapse = (e: Event) => {
173
197
  e.stopPropagation();
174
- let nodeId = self.props.index.id;
175
- if (self.props.onCollapse) {
176
- self.props.onCollapse(nodeId);
198
+ const nodeId = this.props.index.id;
199
+ if (this.props.onCollapse) {
200
+ this.props.onCollapse(nodeId);
177
201
  }
178
202
  };
179
203
 
180
204
  if (this.visibleChildren().length > 0) {
181
- let collapsed = index.node.collapsed;
182
- var classnames = null;
205
+ const collapsed = index.node.collapsed;
206
+ let classnames = "";
183
207
 
184
208
  if (collapsed) {
185
209
  classnames = "collapse fa-solid fa-caret-right";
@@ -201,7 +225,7 @@ export default class PageTreeNode extends React.Component {
201
225
  if (this.node().collapsed &&
202
226
  this.node().children &&
203
227
  this.node().children.length > 0) {
204
- let pluralized = (this.node().children.length == 1) ? "item" : "items";
228
+ const pluralized = (this.node().children.length == 1) ? "item" : "items";
205
229
  return (
206
230
  <span className="collapsed-label">
207
231
  ({this.node().children.length} {pluralized})
@@ -222,11 +246,11 @@ export default class PageTreeNode extends React.Component {
222
246
  this.updatePage({editing: true});
223
247
  }
224
248
 
225
- editUrl(page) {
249
+ editUrl(page: PageNode) {
226
250
  return(`/admin/${page.locale}/pages/${page.param}/edit`);
227
251
  }
228
252
 
229
- node() {
253
+ node(): PageNode {
230
254
  return this.props.index.node;
231
255
  }
232
256
 
@@ -242,22 +266,21 @@ export default class PageTreeNode extends React.Component {
242
266
  }
243
267
 
244
268
  render() {
245
- let self = this;
246
- let props = this.props;
247
- let index = props.index;
248
- let dragging = props.dragging;
249
- let editing = this.node().editing;
250
- var classnames = "node";
269
+ const props = this.props;
270
+ const index = props.index;
271
+ const dragging = props.dragging;
272
+ const editing = this.node().editing;
273
+ let classnames = "node";
251
274
 
252
- var node = editing ? this.renderEditNode() : this.renderNode();
275
+ const node = editing ? this.renderEditNode() : this.renderNode();
253
276
 
254
277
  if (index.id === dragging) {
255
278
  classnames = "node placeholder";
256
279
  }
257
280
 
258
- let handleMouseDown = function (e) {
259
- if (self.permitted("edit") && !editing && props.onDragStart) {
260
- props.onDragStart(props.index.id, self.innerRef.current, e);
281
+ const handleMouseDown = (e: Event) => {
282
+ if (this.permitted("edit") && !editing && props.onDragStart) {
283
+ props.onDragStart(props.index.id, this.innerRef.current, e);
261
284
  }
262
285
  };
263
286
 
@@ -281,23 +304,22 @@ export default class PageTreeNode extends React.Component {
281
304
 
282
305
  renderEditNode() {
283
306
  const { dir, locale } = this.props;
284
- let self = this;
285
307
 
286
- let handleNameChange = function(event) {
287
- self.setState({newName: event.target.value});
308
+ const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
309
+ this.setState({ newName: event.target.value });
288
310
  };
289
311
 
290
- let performEdit = function(event) {
312
+ const performEdit = (event: Event) => {
291
313
  event.preventDefault();
292
- self.updatePage({
293
- name: self.state.newName,
314
+ this.updatePage({
315
+ name: this.state.newName,
294
316
  editing: false
295
317
  });
296
318
  };
297
319
 
298
- let cancelEdit = function() {
299
- self.setState({newName: self.node().name});
300
- self.updatePage({editing: false});
320
+ const cancelEdit = () => {
321
+ this.setState({ newName: this.node().name });
322
+ this.updatePage({ editing: false });
301
323
  };
302
324
 
303
325
  return (
@@ -325,13 +347,13 @@ export default class PageTreeNode extends React.Component {
325
347
  }
326
348
 
327
349
  renderNode() {
328
- let index = this.props.index;
329
- let node = index.node;
350
+ const index = this.props.index;
351
+ const node = index.node;
330
352
 
331
- var pageName = <span className="name">{this.pageName()}</span>;
332
- var className = "page";
353
+ let pageName = <span className="name">{this.pageName()}</span>;
354
+ let className = "page";
333
355
 
334
- var iconClass = "fa-regular fa-file icon";
356
+ let iconClass = "fa-regular fa-file icon";
335
357
 
336
358
  if (typeof(node.status) != "undefined") {
337
359
  className = `page status-${this.node().status}`;
@@ -361,7 +383,7 @@ export default class PageTreeNode extends React.Component {
361
383
  }
362
384
 
363
385
  statusLabel() {
364
- let labels = ["Draft", "Reviewed", "Published", "Hidden", "Deleted"];
386
+ const labels = ["Draft", "Reviewed", "Published", "Hidden", "Deleted"];
365
387
  if (typeof(this.node().status) != "undefined" && this.node().status != 2) {
366
388
  return (
367
389
  <span className="status-label">
@@ -381,13 +403,13 @@ export default class PageTreeNode extends React.Component {
381
403
  }
382
404
  }
383
405
 
384
- updatePage(attributes) {
406
+ updatePage(attributes: Attributes) {
385
407
  if (this.props.updatePage) {
386
408
  return this.props.updatePage(this.props.index, attributes);
387
409
  }
388
410
  }
389
411
 
390
- visibleChildren() {
412
+ visibleChildren(): PageNode[] {
391
413
  if (this.node().children) {
392
414
  return this.node().children.filter(p => p.status != 4);
393
415
  } else {
@@ -395,16 +417,3 @@ export default class PageTreeNode extends React.Component {
395
417
  }
396
418
  }
397
419
  }
398
-
399
- PageTreeNode.propTypes = {
400
- addChild: PropTypes.func,
401
- dragging: PropTypes.number,
402
- index: PropTypes.object,
403
- onCollapse: PropTypes.func,
404
- onDragStart: PropTypes.func,
405
- paddingLeft: PropTypes.number,
406
- tree: PropTypes.object,
407
- updatePage: PropTypes.func,
408
- locale: PropTypes.string,
409
- dir: PropTypes.string,
410
- };
@@ -0,0 +1,15 @@
1
+ import { TreeNode } from "../../lib/Tree";
2
+
3
+ export type Attributes = Record<string, unknown>;
4
+
5
+ export interface PageNode extends TreeNode {
6
+ id: number | null,
7
+ children: PageNode[],
8
+ editing: boolean,
9
+ locale: string,
10
+ name: string,
11
+ param: string,
12
+ permissions: string[],
13
+ published_at: string,
14
+ status: string
15
+ }
@@ -0,0 +1,206 @@
1
+ import React, { Component } from "react";
2
+ import Tree, { TreeId, TreeIndex } from "../lib/Tree";
3
+ import { postJson, putJson } from "../lib/request";
4
+ import { Attributes, PageNode } from "./PageTree/types";
5
+ import Draggable from "./PageTree/Draggable";
6
+
7
+ interface Page extends Record<string, unknown> {
8
+ parent_page_id: number | null
9
+ }
10
+
11
+ type CollapsedState = Record<number, boolean>;
12
+
13
+ interface ParentMap {
14
+ [index: number]: Page[]
15
+ }
16
+
17
+ interface PageTreeProps {
18
+ dir: string,
19
+ locale: string,
20
+ pages: Page[],
21
+ permissions: string[]
22
+ }
23
+
24
+ interface PageTreeState {
25
+ tree: Tree<PageNode>
26
+ }
27
+
28
+ function collapsedState(): CollapsedState {
29
+ if (window && window.localStorage &&
30
+ typeof(window.localStorage.collapsedPages) != "undefined") {
31
+ return JSON.parse(window.localStorage.getItem("collapsedPages")) as CollapsedState;
32
+ }
33
+ return {};
34
+ }
35
+
36
+ export default class PageTree extends Component<PageTreeProps, PageTreeState> {
37
+ constructor(props: PageTreeProps) {
38
+ super(props);
39
+
40
+ this.state = { tree: this.buildTree(props.pages) };
41
+ }
42
+
43
+ applyCollapsed(tree: Tree<PageNode>) {
44
+ const depth = (t: Tree, index: TreeIndex) => {
45
+ let depth = 0;
46
+ let pointer = t.getIndex(index.parent);
47
+ while (pointer) {
48
+ depth += 1;
49
+ pointer = t.getIndex(pointer.parent);
50
+ }
51
+ return depth;
52
+ };
53
+
54
+ const walk = (id: TreeId) => {
55
+ const index = tree.getIndex(id);
56
+ const node = index.node;
57
+ if (node.id && node.id in collapsedState()) {
58
+ node.collapsed = collapsedState()[node.id];
59
+ } else if (node.news_page) {
60
+ node.collapsed = true;
61
+ } else if (depth(tree, index) > 1) {
62
+ node.collapsed = true;
63
+ }
64
+ if (index.children && index.children.length) {
65
+ index.children.forEach(c => walk(c));
66
+ }
67
+ };
68
+ walk(1);
69
+ }
70
+
71
+ createPage(index: TreeIndex<PageNode>, attributes: Attributes) {
72
+ void postJson(`/admin/${index.node.locale}/pages.json`, { page: attributes })
73
+ .then((response: Attributes) => this.updateNode(index, response));
74
+ }
75
+
76
+ buildTree(pages: Page[]) {
77
+ // Build tree
78
+ const parentMap: ParentMap = pages.reduce((m: ParentMap, page: Page) => {
79
+ const id = page.parent_page_id || 0;
80
+ m[id] = [...(m[id] || []), page];
81
+ return m;
82
+ }, {});
83
+
84
+ pages.forEach((p: Page) => { p.children = parentMap[p.id] || []; });
85
+
86
+ const tree = new Tree({
87
+ name: "All Pages",
88
+ locale: this.props.locale,
89
+ permissions: this.props.permissions,
90
+ root: true,
91
+ children: parentMap[0]
92
+ });
93
+ this.applyCollapsed(tree);
94
+ tree.updateNodesPosition();
95
+ return tree;
96
+ }
97
+
98
+ movePage(index: TreeIndex<PageNode>, parent: TreeIndex<PageNode>, position: number) {
99
+ const data = {
100
+ parent_id: parent.node.id,
101
+ position: position
102
+ };
103
+ const url = `/admin/${index.node.locale}/pages/${index.node.id}/move.json`;
104
+ this.performUpdate(index, url, data);
105
+ }
106
+
107
+ performUpdate(index: TreeIndex, url: string, data: Attributes) {
108
+ void putJson(url, data)
109
+ .then((response: Page) => this.updateNode(index, response));
110
+ }
111
+
112
+ render() {
113
+ const addChild = (id: TreeId, attributes: Attributes) => {
114
+ const tree = this.state.tree;
115
+ const index = tree.append(attributes, id);
116
+ this.reorderChildren(id);
117
+ this.setCollapsed(id, false);
118
+ this.createPage(index, attributes);
119
+ this.setState({tree: tree});
120
+ };
121
+
122
+ const movedPage = (id: TreeId) => {
123
+ const tree = this.state.tree;
124
+ const index = tree.getIndex(id);
125
+ this.reorderChildren(index.parent);
126
+
127
+ const parent = tree.getIndex(index.parent);
128
+ const position = parent.children.indexOf(id) + 1;
129
+
130
+ this.movePage(index, parent, position);
131
+ this.setState({ tree: tree });
132
+ };
133
+
134
+ const toggleCollapsed = (id: TreeId) => {
135
+ const tree = this.state.tree;
136
+ const node = tree.getIndex(id).node;
137
+ this.setCollapsed(id, !node.collapsed);
138
+ this.setState({tree: tree});
139
+ };
140
+
141
+ const updatePage = (id: TreeId, attributes: Attributes) => {
142
+ const tree = this.state.tree;
143
+ const index = tree.getIndex(id);
144
+ const url = `/admin/${index.node.locale}/pages/${index.node.id}.json`;
145
+ this.updateNode(index, attributes);
146
+ this.performUpdate(index, url, { page: attributes });
147
+ };
148
+
149
+ const updateTree = (tree: Tree) => {
150
+ this.setState({ tree: tree });
151
+ };
152
+
153
+ return(
154
+ <Draggable tree={this.state.tree}
155
+ addChild={addChild}
156
+ movedPage={movedPage}
157
+ toggleCollapsed={toggleCollapsed}
158
+ updatePage={updatePage}
159
+ updateTree={updateTree}
160
+ locale={this.props.locale}
161
+ dir={this.props.dir} />
162
+ );
163
+ }
164
+
165
+ reorderChildren(id: TreeId) {
166
+ const tree = this.state.tree;
167
+ const index = this.state.tree.getIndex(id);
168
+ const node = index.node;
169
+ if (!node.news_page) {
170
+ return;
171
+ }
172
+ index.children = index.children.sort(function (a, b) {
173
+ const aNode = tree.getIndex(a).node;
174
+ const bNode = tree.getIndex(b).node;
175
+ if (aNode.pinned == bNode.pinned) {
176
+ return new Date(bNode.published_at) - new Date(aNode.published_at);
177
+ } else {
178
+ return aNode.pinned ? -1 : 1;
179
+ }
180
+ });
181
+ tree.updateNodesPosition();
182
+ }
183
+
184
+ setCollapsed(id: TreeId, value: boolean) {
185
+ const node = this.state.tree.getIndex(id).node;
186
+ node.collapsed = value;
187
+ this.storeCollapsed(id, node.collapsed);
188
+ this.state.tree.updateNodesPosition();
189
+ }
190
+
191
+ storeCollapsed(id: TreeId, newState: boolean) {
192
+ const node = this.state.tree.getIndex(id).node;
193
+ const store = collapsedState();
194
+ store[node.id] = newState;
195
+ window.localStorage.collapsedPages = JSON.stringify(store);
196
+ }
197
+
198
+ updateNode(index: TreeIndex, attributes: Attributes) {
199
+ for (const attr in attributes) {
200
+ if (Object.prototype.hasOwnProperty.call(attributes, attr)) {
201
+ index.node[attr] = attributes[attr];
202
+ }
203
+ }
204
+ this.setState({ tree: this.state.tree });
205
+ }
206
+ }
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+
3
+ interface RichTextToolbarButtonProps {
4
+ className: string,
5
+ name: string,
6
+ onClick: (evt: Event) => void
7
+ }
8
+
9
+ export default function RichTextToolbarButton(props: RichTextToolbarButtonProps) {
10
+ return (
11
+ <a title={props.name}
12
+ className={"button " + props.className}
13
+ onClick={props.onClick}>
14
+ <i className={"fa-solid fa-" + props.className} />
15
+ </a>
16
+ );
17
+ }
@@ -1,22 +1,25 @@
1
- import React, { useState } from "react";
2
- import PropTypes from "prop-types";
1
+ import React, { ChangeEvent, useState } from "react";
3
2
 
4
- export default function AddTagForm(props) {
3
+ interface AddTagFormProps {
4
+ addTag: (string) => void
5
+ }
6
+
7
+ export default function AddTagForm(props: AddTagFormProps) {
5
8
  const [tag, setTag] = useState("");
6
9
 
7
- const submit = (evt) => {
10
+ const submit = (evt: Event) => {
8
11
  evt.preventDefault();
9
12
  props.addTag(tag);
10
13
  setTag("");
11
14
  };
12
15
 
13
- const handleKeyDown = (evt) => {
16
+ const handleKeyDown = (evt: Event) => {
14
17
  if (evt.which === 13) {
15
18
  submit(evt);
16
19
  }
17
20
  };
18
21
 
19
- const handleChange = (evt) => {
22
+ const handleChange = (evt: ChangeEvent<HTMLInputElement>) => {
20
23
  setTag(evt.target.value);
21
24
  };
22
25
 
@@ -36,7 +39,3 @@ export default function AddTagForm(props) {
36
39
  </div>
37
40
  );
38
41
  }
39
-
40
- AddTagForm.propTypes = {
41
- addTag: PropTypes.func
42
- };
@@ -1,12 +1,17 @@
1
1
  import React from "react";
2
- import PropTypes from "prop-types";
3
2
 
4
- export default function Tag(props) {
3
+ interface TagProps {
4
+ enabled: boolean,
5
+ tag: string,
6
+ toggleEnabled: (string) => void
7
+ }
8
+
9
+ export default function Tag(props: TagProps): JSX.Element {
5
10
  const handleChange = () => {
6
11
  props.toggleEnabled(props.tag);
7
12
  };
8
13
 
9
- let classes = ["tag"];
14
+ const classes = ["tag"];
10
15
  if (props.enabled) {
11
16
  classes.push("enabled");
12
17
  }
@@ -24,9 +29,3 @@ export default function Tag(props) {
24
29
  </span>
25
30
  );
26
31
  }
27
-
28
- Tag.propTypes = {
29
- enabled: PropTypes.bool,
30
- tag: PropTypes.string,
31
- toggleEnabled: PropTypes.func
32
- };
@@ -1,30 +1,35 @@
1
1
  import React, { useState } from "react";
2
- import PropTypes from "prop-types";
3
2
 
4
3
  import AddTagForm from "./TagEditor/AddTagForm";
5
4
  import Tag from "./TagEditor/Tag";
6
5
 
7
- function onlyUnique(value, index, self) {
6
+ interface TagEditorProps {
7
+ name: string,
8
+ enabled: string[],
9
+ tags: string[]
10
+ }
11
+
12
+ function onlyUnique(value: string, index: number, self: string[]): number {
8
13
  return self.indexOf(value) === index;
9
14
  }
10
15
 
11
- export default function TagEditor(props) {
16
+ export default function TagEditor(props: TagEditorProps) {
12
17
  const [tags, setTags] = useState(props.tags);
13
18
  const [enabled, setEnabled] = useState(props.enabled);
14
19
 
15
20
  const tagList = [...tags, ...enabled].filter(onlyUnique);
16
21
 
17
- const normalize = (tag) => {
22
+ const normalize = (tag: string): string => {
18
23
  return (
19
24
  tagList.filter(t => t.toLowerCase() == tag.toLowerCase())[0] || tag
20
25
  );
21
26
  };
22
27
 
23
- const tagEnabled = (tag) => {
28
+ const tagEnabled = (tag: string): boolean => {
24
29
  return enabled.map(t => t.toLowerCase()).indexOf(tag.toLowerCase()) !== -1;
25
30
  };
26
31
 
27
- const toggleEnabled = (tag) => {
32
+ const toggleEnabled = (tag: string) => {
28
33
  const normalized = normalize(tag);
29
34
 
30
35
  if (tagEnabled(normalized)) {
@@ -34,7 +39,7 @@ export default function TagEditor(props) {
34
39
  }
35
40
  };
36
41
 
37
- const addTag = (tag) => {
42
+ const addTag = (tag: string) => {
38
43
  const normalized = normalize(tag);
39
44
 
40
45
  setTags([...tags, normalized].filter(onlyUnique));
@@ -53,9 +58,3 @@ export default function TagEditor(props) {
53
58
  </div>
54
59
  );
55
60
  }
56
-
57
- TagEditor.propTypes = {
58
- name: PropTypes.string,
59
- enabled: PropTypes.array,
60
- tags: PropTypes.array
61
- };