pages_core 3.12.0 → 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 (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" });