pages_core 3.12.0 → 3.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (199) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/fonts/661557ef.ttf +0 -0
  4. data/app/assets/builds/fonts/a18fc2d2.woff2 +0 -0
  5. data/app/assets/builds/fonts/b2c7b78f.woff2 +0 -0
  6. data/app/assets/builds/fonts/ceddc204.ttf +0 -0
  7. data/app/assets/builds/pages_core/admin-dist.js +60 -14
  8. data/app/assets/builds/pages_core/admin-dist.js.map +7 -0
  9. data/app/assets/builds/pages_core/admin.css +9272 -0
  10. data/app/assets/images/pages/admin/angle-down-solid.svg +1 -0
  11. data/app/assets/images/pages/admin/icon.svg +1 -0
  12. data/app/assets/stylesheets/pages_core/admin/components/archive.css +6 -0
  13. data/app/assets/stylesheets/{pages/admin/components/attachments.scss → pages_core/admin/components/attachments.css} +35 -28
  14. data/app/assets/stylesheets/{pages/admin.scss → pages_core/admin/components/base.css} +125 -123
  15. data/app/assets/stylesheets/pages_core/admin/components/forms.css +223 -0
  16. data/app/assets/stylesheets/{pages/admin/components/header.scss → pages_core/admin/components/header.css} +76 -46
  17. data/app/assets/stylesheets/{pages/admin/components/image_editor.scss → pages_core/admin/components/image_editor.css} +42 -31
  18. data/app/assets/stylesheets/{pages/admin/components/image_grid.scss → pages_core/admin/components/image_grid.css} +76 -64
  19. data/app/assets/stylesheets/{pages/admin/components/image_uploader.scss → pages_core/admin/components/image_uploader.css} +12 -12
  20. data/app/assets/stylesheets/{pages/admin/components/layout.scss → pages_core/admin/components/layout.css} +13 -9
  21. data/app/assets/stylesheets/pages_core/admin/components/links.css +40 -0
  22. data/app/assets/stylesheets/pages_core/admin/components/list_table.css +66 -0
  23. data/app/assets/stylesheets/{pages/admin/components/login.scss → pages_core/admin/components/login.css} +6 -5
  24. data/app/assets/stylesheets/{pages/admin/components/modal.scss → pages_core/admin/components/modal.css} +10 -12
  25. data/app/assets/stylesheets/{pages/admin/components/page_tree.scss → pages_core/admin/components/page_tree.css} +54 -55
  26. data/app/assets/stylesheets/{pages/admin/components/pagination.scss → pages_core/admin/components/pagination.css} +17 -17
  27. data/app/assets/stylesheets/pages_core/admin/components/search.css +27 -0
  28. data/app/assets/stylesheets/{pages/admin/components/sidebar.scss → pages_core/admin/components/sidebar.css} +8 -7
  29. data/app/assets/stylesheets/{pages/admin/components/tag_editor.scss → pages_core/admin/components/tag_editor.css} +10 -15
  30. data/app/assets/stylesheets/{pages/admin/components/textarea.scss → pages_core/admin/components/textarea.css} +1 -1
  31. data/app/assets/stylesheets/{pages/admin/components/toast.scss → pages_core/admin/components/toast.css} +5 -3
  32. data/app/assets/stylesheets/{pages/admin/components/toolbar.scss → pages_core/admin/components/toolbar.css} +56 -29
  33. data/app/assets/stylesheets/{pages/admin/controllers/pages.scss → pages_core/admin/controllers/pages.css} +69 -52
  34. data/app/assets/stylesheets/pages_core/admin/controllers/users.css +3 -0
  35. data/app/assets/stylesheets/pages_core/admin/vars.css +34 -0
  36. data/app/assets/stylesheets/pages_core/admin.postcss.css +9 -0
  37. data/app/controllers/admin/pages_controller.rb +12 -11
  38. data/app/controllers/concerns/pages_core/rss_controller.rb +17 -1
  39. data/app/controllers/pages_core/admin_controller.rb +6 -0
  40. data/app/controllers/pages_core/frontend/pages_controller.rb +9 -5
  41. data/app/controllers/pages_core/sitemaps_controller.rb +3 -5
  42. data/app/formatters/pages_core/image_embedder.rb +5 -27
  43. data/app/helpers/admin/calendars_helper.rb +8 -0
  44. data/app/helpers/admin/news_helper.rb +13 -0
  45. data/app/helpers/admin/pages_helper.rb +32 -0
  46. data/app/helpers/pages_core/admin/admin_helper.rb +11 -54
  47. data/app/helpers/pages_core/admin/deprecated_admin_helper.rb +40 -0
  48. data/app/helpers/pages_core/images_helper.rb +37 -0
  49. data/app/javascript/admin-dist.ts +2 -0
  50. data/app/javascript/components/Attachments/{Attachment.jsx → Attachment.tsx} +44 -35
  51. data/app/javascript/components/Attachments/{AttachmentEditor.jsx → AttachmentEditor.tsx} +23 -23
  52. data/app/javascript/components/{EditableImage.jsx → EditableImage.tsx} +28 -25
  53. data/app/javascript/components/{FileUploadButton.jsx → FileUploadButton.tsx} +15 -16
  54. data/app/javascript/components/ImageCropper/FocalPoint.tsx +94 -0
  55. data/app/javascript/components/ImageCropper/{Image.jsx → Image.tsx} +13 -14
  56. data/app/javascript/components/ImageCropper/{Toolbar.jsx → Toolbar.tsx} +19 -15
  57. data/app/javascript/components/ImageCropper/{useCrop.js → useCrop.ts} +80 -37
  58. data/app/javascript/components/{ImageCropper.jsx → ImageCropper.tsx} +17 -15
  59. data/app/javascript/components/ImageEditor/{Form.jsx → Form.tsx} +24 -23
  60. data/app/javascript/components/{ImageEditor.jsx → ImageEditor.tsx} +17 -15
  61. data/app/javascript/components/ImageGrid/{DragElement.jsx → DragElement.tsx} +12 -10
  62. data/app/javascript/components/ImageGrid/{GridImage.jsx → GridImage.tsx} +40 -30
  63. data/app/javascript/components/ImageGrid/{Placeholder.jsx → Placeholder.tsx} +5 -6
  64. data/app/javascript/components/ImageGrid.jsx +3 -4
  65. data/app/javascript/components/{ImageUploader.jsx → ImageUploader.tsx} +46 -41
  66. data/app/javascript/components/Modal.tsx +48 -0
  67. data/app/javascript/components/PageImages.tsx +28 -0
  68. data/app/javascript/components/{PageTreeDraggable.jsx → PageTree/Draggable.tsx} +79 -57
  69. data/app/javascript/components/{PageTreeNode.jsx → PageTree/Node.tsx} +86 -77
  70. data/app/javascript/components/PageTree/types.ts +15 -0
  71. data/app/javascript/components/PageTree.tsx +206 -0
  72. data/app/javascript/components/RichTextToolbarButton.tsx +17 -0
  73. data/app/javascript/components/TagEditor/{AddTagForm.jsx → AddTagForm.tsx} +9 -10
  74. data/app/javascript/components/TagEditor/{Tag.jsx → Tag.tsx} +8 -9
  75. data/app/javascript/components/{TagEditor.jsx → TagEditor.tsx} +12 -13
  76. data/app/javascript/components/Toast.tsx +61 -0
  77. data/app/javascript/components/drag/{draggedOrder.js → draggedOrder.ts} +22 -12
  78. data/app/javascript/components/drag/types.ts +28 -0
  79. data/app/javascript/components/drag/{useDragCollection.js → useDragCollection.ts} +40 -22
  80. data/app/javascript/components/drag/{useDragUploader.js → useDragUploader.ts} +34 -25
  81. data/app/javascript/components/drag/useDraggable.ts +21 -0
  82. data/app/javascript/components/{drag.js → drag.ts} +1 -0
  83. data/app/javascript/controllers/{EditPageController.js → EditPageController.ts} +3 -1
  84. data/app/javascript/controllers/{LoginController.js → LoginController.ts} +7 -3
  85. data/app/javascript/controllers/{MainController.js → MainController.ts} +19 -14
  86. data/app/javascript/{index.js → index.ts} +8 -7
  87. data/app/javascript/lib/{Tree.js → Tree.ts} +106 -85
  88. data/app/javascript/lib/{copyToClipboard.js → copyToClipboard.ts} +1 -1
  89. data/app/javascript/lib/{readyHandler.js → readyHandler.ts} +4 -2
  90. data/app/javascript/lib/{request.js → request.ts} +11 -5
  91. data/app/javascript/stores/useModalStore.ts +15 -0
  92. data/app/javascript/stores/useToastStore.ts +26 -0
  93. data/app/javascript/stores.ts +2 -0
  94. data/app/javascript/types.ts +30 -0
  95. data/app/mailers/admin_mailer.rb +1 -0
  96. data/app/models/invite.rb +8 -0
  97. data/app/policies/page_policy.rb +4 -0
  98. data/app/views/admin/calendars/_sidebar.html.erb +50 -0
  99. data/app/views/admin/calendars/show.html.erb +15 -53
  100. data/app/views/admin/invites/new.html.erb +2 -8
  101. data/app/views/admin/invites/show.html.erb +2 -4
  102. data/app/views/admin/news/_sidebar.html.erb +51 -0
  103. data/app/views/admin/news/index.html.erb +21 -56
  104. data/app/views/admin/pages/_list_item.html.erb +4 -22
  105. data/app/views/admin/pages/_search_bar.html.erb +12 -0
  106. data/app/views/admin/pages/deleted.html.erb +10 -8
  107. data/app/views/admin/pages/edit.html.erb +20 -11
  108. data/app/views/admin/pages/index.html.erb +10 -8
  109. data/app/views/admin/pages/new.html.erb +10 -14
  110. data/app/views/admin/pages/search.html.erb +54 -0
  111. data/app/views/admin/password_resets/show.html.erb +3 -5
  112. data/app/views/admin/users/deactivated.html.erb +6 -7
  113. data/app/views/admin/users/edit.html.erb +7 -9
  114. data/app/views/admin/users/index.html.erb +3 -6
  115. data/app/views/admin/users/login.html.erb +4 -5
  116. data/app/views/admin/users/new.html.erb +2 -4
  117. data/app/views/admin/users/new_password.html.erb +4 -5
  118. data/app/views/admin/users/show.html.erb +11 -9
  119. data/app/views/errors/401.html.erb +2 -1
  120. data/app/views/errors/403.html.erb +2 -1
  121. data/app/views/errors/404.html.erb +1 -3
  122. data/app/views/errors/405.html.erb +2 -1
  123. data/app/views/errors/422.html.erb +2 -1
  124. data/app/views/errors/500.html.erb +2 -3
  125. data/app/views/feeds/pages.rss.builder +3 -9
  126. data/app/views/layouts/admin/_header.html.erb +1 -2
  127. data/app/views/layouts/admin/_page_header.html.erb +4 -4
  128. data/app/views/layouts/admin.html.erb +3 -3
  129. data/app/views/layouts/errors.html.erb +127 -4
  130. data/config/routes.rb +1 -0
  131. data/lib/pages_core/configuration/pages.rb +0 -1
  132. data/lib/pages_core/engine.rb +4 -3
  133. data/lib/pages_core.rb +0 -1
  134. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +33 -17
  135. data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +0 -1
  136. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +40 -0
  137. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +68 -0
  138. data/lib/rails/generators/pages_core/frontend/templates/postcss.config.js +17 -0
  139. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.postcss.css +4 -0
  140. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.css +24 -0
  141. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/layout.css +21 -0
  142. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.css +5 -0
  143. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/animation.css +5 -0
  144. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.css +18 -0
  145. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/fonts.css +6 -0
  146. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/grid.css +65 -0
  147. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.css +131 -0
  148. data/lib/rails/generators/pages_core/install/templates/pages_initializer.rb +0 -3
  149. metadata +104 -255
  150. data/app/assets/images/pages/admin/icon.png +0 -0
  151. data/app/assets/images/pages/admin/image-editor-bg.png +0 -0
  152. data/app/assets/images/pages/admin/list-table-pin-blue.gif +0 -0
  153. data/app/assets/images/pages/admin/list-table-pin-disabled.gif +0 -0
  154. data/app/assets/images/pages/admin/list-table-pin-green.gif +0 -0
  155. data/app/assets/images/pages/admin/list-table-pin-red.gif +0 -0
  156. data/app/assets/images/pages/admin/list-table-pin-yellow.gif +0 -0
  157. data/app/assets/images/pages/admin/loading-modal.gif +0 -0
  158. data/app/assets/images/pages/feed-icon-14x14.png +0 -0
  159. data/app/assets/stylesheets/pages/admin/components/archive.scss +0 -6
  160. data/app/assets/stylesheets/pages/admin/components/buttons.scss +0 -23
  161. data/app/assets/stylesheets/pages/admin/components/forms.scss +0 -169
  162. data/app/assets/stylesheets/pages/admin/components/links.scss +0 -43
  163. data/app/assets/stylesheets/pages/admin/components/list_table.scss +0 -61
  164. data/app/assets/stylesheets/pages/admin/controllers/users.scss +0 -3
  165. data/app/assets/stylesheets/pages/admin/mixins/breakpoints.scss +0 -21
  166. data/app/assets/stylesheets/pages/admin/mixins/clearfix.scss +0 -7
  167. data/app/assets/stylesheets/pages/admin/mixins/gradients.scss +0 -7
  168. data/app/assets/stylesheets/pages/admin/vars.scss +0 -30
  169. data/app/assets/stylesheets/pages/errors.css +0 -128
  170. data/app/javascript/admin-dist.js +0 -2
  171. data/app/javascript/components/ImageCropper/FocalPoint.jsx +0 -93
  172. data/app/javascript/components/Modal.jsx +0 -59
  173. data/app/javascript/components/PageImages.jsx +0 -25
  174. data/app/javascript/components/PageTree.jsx +0 -196
  175. data/app/javascript/components/RichTextToolbarButton.jsx +0 -20
  176. data/app/javascript/components/Toast.jsx +0 -72
  177. data/app/javascript/components/drag/useDraggable.js +0 -17
  178. data/app/javascript/stores/ModalStore.jsx +0 -12
  179. data/app/javascript/stores/ToastStore.jsx +0 -14
  180. data/app/javascript/stores.js +0 -2
  181. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/GridOverlay.js +0 -66
  182. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/ResponsiveEmbeds.js +0 -72
  183. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/application.sass.scss +0 -15
  184. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.scss +0 -12
  185. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.scss +0 -26
  186. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/breakpoints.scss +0 -42
  187. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/clearfix.scss +0 -7
  188. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/fonts.scss +0 -32
  189. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid.scss +0 -168
  190. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/framework/grid_overlay.scss +0 -44
  191. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.scss +0 -8
  192. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.scss +0 -90
  193. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/vendor/normalize.css +0 -349
  194. data/vendor/assets/stylesheets/ReactCrop.css +0 -167
  195. /data/app/assets/stylesheets/{pages/admin/components/tabs.scss → pages_core/admin/components/tabs.css} +0 -0
  196. /data/app/javascript/components/Attachments/{Placeholder.jsx → Placeholder.tsx} +0 -0
  197. /data/app/javascript/components/ImageGrid/{FilePlaceholder.jsx → FilePlaceholder.tsx} +0 -0
  198. /data/app/javascript/{components.js → components.ts} +0 -0
  199. /data/app/javascript/{hooks.js → hooks.ts} +0 -0
@@ -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
- };
@@ -0,0 +1,61 @@
1
+ import React, { useEffect, useRef, useState } from "react";
2
+
3
+ import useToastStore from "../stores/useToastStore";
4
+
5
+ interface ToastProps {
6
+ error: string,
7
+ notice: string
8
+ }
9
+
10
+ export default function Toast(props: ToastProps) {
11
+ const [fadeout, setFadeout] = useState(false);
12
+ const { toasts, error, notice, next } = useToastStore((state) => state);
13
+ const timerRef = useRef<number | null>(null);
14
+
15
+ const toast = toasts[0];
16
+
17
+ useEffect(() => {
18
+ if (props.error) {
19
+ error(props.error);
20
+ }
21
+ if (props.notice) {
22
+ notice(props.notice);
23
+ }
24
+ }, [props.error, props.notice]);
25
+
26
+ useEffect(() => {
27
+ setFadeout(false);
28
+ if (toast && !timerRef.current) {
29
+ timerRef.current = setTimeout(() => {
30
+ setFadeout(true);
31
+ timerRef.current = setTimeout(() => {
32
+ timerRef.current = null;
33
+ setFadeout(false);
34
+ next();
35
+ }, 500);
36
+ }, 4000);
37
+ }
38
+ return () => {
39
+ clearTimeout(timerRef.current);
40
+ };
41
+ }, [toast]);
42
+
43
+ const classNames = ["toast"];
44
+
45
+ if (toast) {
46
+ classNames.push(toast.type);
47
+ if (fadeout) {
48
+ classNames.push("fadeout");
49
+ }
50
+ }
51
+
52
+ return (
53
+ <div className="toast-wrapper" aria-live="polite">
54
+ {toast && (
55
+ <div className={classNames.join(" ")}>
56
+ {toast.message}
57
+ </div>
58
+ )}
59
+ </div>
60
+ );
61
+ }
@@ -1,9 +1,14 @@
1
- function hovering(dragState, target) {
2
- let { x, y } = dragState;
3
- var rect;
4
- if (target.rect) {
1
+ import { Draggable, DragCollection, DragState } from "./types";
2
+
3
+ function hovering(
4
+ dragState: DragState,
5
+ target: Draggable | React.MutableRefObject<HTMLDivElement>
6
+ ): boolean {
7
+ const { x, y } = dragState;
8
+ let rect: DOMRect;
9
+ if ("rect" in target) {
5
10
  rect = target.rect;
6
- } else if (target.current) {
11
+ } else if ("current" in target) {
7
12
  rect = target.current.getBoundingClientRect();
8
13
  } else {
9
14
  return false;
@@ -12,7 +17,9 @@ function hovering(dragState, target) {
12
17
  y >= rect.top && y <= rect.bottom);
13
18
  }
14
19
 
15
- export function collectionOrder(collection, dragState) {
20
+ export function collectionOrder(
21
+ collection: DragCollection, dragState: DragState
22
+ ): Draggable[] {
16
23
  const { draggables, ref } = collection;
17
24
  const { dragging } = dragState;
18
25
 
@@ -36,15 +43,18 @@ export function collectionOrder(collection, dragState) {
36
43
  return ordered;
37
44
  }
38
45
 
39
- export default function draggedOrder(collection, dragState) {
46
+ export default function draggedOrder(
47
+ collection: DragCollection, dragState: DragState
48
+ ): Draggable[] {
40
49
  let ordered = collectionOrder(collection, dragState);
41
50
 
42
51
  if (dragState.dragging && ordered.indexOf(dragState.dragging) === -1) {
43
- if (dragState.y < collection.ref.current.getBoundingClientRect().top) {
44
- ordered = [dragState.dragging, ...ordered];
45
- } else {
46
- ordered.push(dragState.dragging);
47
- }
52
+ if (collection.ref.current &&
53
+ dragState.y < collection.ref.current.getBoundingClientRect().top) {
54
+ ordered = [dragState.dragging, ...ordered];
55
+ } else {
56
+ ordered.push(dragState.dragging);
57
+ }
48
58
  }
49
59
 
50
60
  return ordered;
@@ -0,0 +1,28 @@
1
+ export type DraggableRecord = Record<string, unknown>;
2
+
3
+ export interface Draggable {
4
+ record: DraggableRecord,
5
+ ref: React.MutableRefObject<HTMLDivElement>,
6
+ rect: DOMRect | null,
7
+ handle: string
8
+ }
9
+
10
+ export interface DragCollectionAction {
11
+ type: string,
12
+ payload?: Draggable[] | Draggable | null
13
+ }
14
+
15
+ export interface DragCollection {
16
+ ref: React.MutableRefObject<HTMLDivElement>,
17
+ draggables: Draggable[],
18
+ dispatch: (DragCollectionAction) => void
19
+ }
20
+
21
+ export interface Position {
22
+ x: number | null,
23
+ y: number | null
24
+ }
25
+
26
+ export interface DragState extends Position {
27
+ dragging: Draggable | false
28
+ }
@@ -1,15 +1,24 @@
1
- import React, { useEffect, useReducer, useRef } from "react";
2
- import uniqueId from "lodash/uniqueId";
1
+ import { createRef, useEffect, useReducer, useRef } from "react";
2
+ import { uniqueId } from "lodash";
3
3
 
4
- function getPosition(draggable) {
5
- if (draggable.ref.current) {
4
+ import {
5
+ Draggable,
6
+ DraggableRecord,
7
+ DragCollectionAction,
8
+ DragCollection
9
+ } from "./types";
10
+
11
+ type Draggables = Draggable[];
12
+
13
+ function getPosition(draggable: Draggable) {
14
+ if (draggable && draggable.ref && draggable.ref.current) {
6
15
  return draggable.ref.current.getBoundingClientRect();
7
16
  } else {
8
17
  return null;
9
18
  }
10
19
  }
11
20
 
12
- function hideDraggable(draggable, callback) {
21
+ function hideDraggable(draggable: Draggable | null, callback: () => void) {
13
22
  if (draggable && draggable.ref && draggable.ref.current) {
14
23
  const prevDisplay = draggable.ref.current.style.display;
15
24
  draggable.ref.current.style.display = "none";
@@ -22,22 +31,29 @@ function hideDraggable(draggable, callback) {
22
31
  }
23
32
  }
24
33
 
25
- function dragCollectionReducer(state, action) {
34
+ function insertFiles(state: Draggable[], files: Draggable[]): Draggable[] {
35
+ const index = state.indexOf("Files");
36
+ if (index === -1 || !files) {
37
+ return state;
38
+ } else {
39
+ return [
40
+ ...state.slice(0, index),
41
+ ...files,
42
+ ...state.slice(index + 1)
43
+ ];
44
+ }
45
+ }
46
+
47
+ function dragCollectionReducer(
48
+ state: Draggable[], action: DragCollectionAction
49
+ ): Draggable[] {
26
50
  switch (action.type) {
27
51
  case "append":
28
- return [...state, ...action.payload];
52
+ return [...state, ...action.payload as Draggable[]];
29
53
  case "prepend":
30
- return [...action.payload, ...state];
54
+ return [...action.payload as Draggable[], ...state];
31
55
  case "insertFiles":
32
- var index = state.indexOf("Files");
33
-
34
- if (index === -1 || !action.payload) {
35
- return state;
36
- } else {
37
- return [...state.slice(0, index),
38
- ...action.payload,
39
- ...state.slice(index + 1)];
40
- }
56
+ return insertFiles(state, action.payload);
41
57
  case "update":
42
58
  return state.map(d => {
43
59
  return (d.handle === action.payload.handle) ? action.payload : d;
@@ -59,20 +75,22 @@ function dragCollectionReducer(state, action) {
59
75
  }
60
76
  }
61
77
 
62
- export function createDraggable(record) {
78
+ export function createDraggable(record: Record<string, unknown>): Draggable {
63
79
  return { record: record,
64
80
  rect: null,
65
- ref: React.createRef(),
81
+ ref: createRef(),
66
82
  handle: uniqueId("draggable") };
67
83
  }
68
84
 
69
- export default function useDragCollection(records) {
70
- const containerRef = useRef();
85
+ export default function useDragCollection(
86
+ records: DraggableRecord[]
87
+ ): DragCollection {
88
+ const containerRef = useRef<HTMLElement>(null);
71
89
  const [draggables, dispatch] = useReducer(
72
90
  dragCollectionReducer,
73
91
  [],
74
92
  () => records.map(r => createDraggable(r))
75
- );
93
+ ) as [Draggables, (Draggables) => Draggable[]];
76
94
 
77
95
  useEffect(() => {
78
96
  dispatch({ type: "updatePositions" });