pages_core 3.14.0 → 3.15.0

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +19 -8
  4. data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
  5. data/app/assets/builds/pages_core/admin.css +672 -379
  6. data/app/assets/fonts/Inter-Black.woff2 +0 -0
  7. data/app/assets/fonts/Inter-BlackItalic.woff2 +0 -0
  8. data/app/assets/fonts/Inter-Bold.woff2 +0 -0
  9. data/app/assets/fonts/Inter-BoldItalic.woff2 +0 -0
  10. data/app/assets/fonts/Inter-ExtraBold.woff2 +0 -0
  11. data/app/assets/fonts/Inter-ExtraBoldItalic.woff2 +0 -0
  12. data/app/assets/fonts/Inter-ExtraLight.woff2 +0 -0
  13. data/app/assets/fonts/Inter-ExtraLightItalic.woff2 +0 -0
  14. data/app/assets/fonts/Inter-Italic.woff2 +0 -0
  15. data/app/assets/fonts/Inter-Light.woff2 +0 -0
  16. data/app/assets/fonts/Inter-LightItalic.woff2 +0 -0
  17. data/app/assets/fonts/Inter-Medium.woff2 +0 -0
  18. data/app/assets/fonts/Inter-MediumItalic.woff2 +0 -0
  19. data/app/assets/fonts/Inter-Regular.woff2 +0 -0
  20. data/app/assets/fonts/Inter-SemiBold.woff2 +0 -0
  21. data/app/assets/fonts/Inter-SemiBoldItalic.woff2 +0 -0
  22. data/app/assets/fonts/Inter-Thin.woff2 +0 -0
  23. data/app/assets/fonts/Inter-ThinItalic.woff2 +0 -0
  24. data/app/assets/fonts/InterDisplay-Black.woff2 +0 -0
  25. data/app/assets/fonts/InterDisplay-BlackItalic.woff2 +0 -0
  26. data/app/assets/fonts/InterDisplay-Bold.woff2 +0 -0
  27. data/app/assets/fonts/InterDisplay-BoldItalic.woff2 +0 -0
  28. data/app/assets/fonts/InterDisplay-ExtraBold.woff2 +0 -0
  29. data/app/assets/fonts/InterDisplay-ExtraBoldItalic.woff2 +0 -0
  30. data/app/assets/fonts/InterDisplay-ExtraLight.woff2 +0 -0
  31. data/app/assets/fonts/InterDisplay-ExtraLightItalic.woff2 +0 -0
  32. data/app/assets/fonts/InterDisplay-Italic.woff2 +0 -0
  33. data/app/assets/fonts/InterDisplay-Light.woff2 +0 -0
  34. data/app/assets/fonts/InterDisplay-LightItalic.woff2 +0 -0
  35. data/app/assets/fonts/InterDisplay-Medium.woff2 +0 -0
  36. data/app/assets/fonts/InterDisplay-MediumItalic.woff2 +0 -0
  37. data/app/assets/fonts/InterDisplay-Regular.woff2 +0 -0
  38. data/app/assets/fonts/InterDisplay-SemiBold.woff2 +0 -0
  39. data/app/assets/fonts/InterDisplay-SemiBoldItalic.woff2 +0 -0
  40. data/app/assets/fonts/InterDisplay-Thin.woff2 +0 -0
  41. data/app/assets/fonts/InterDisplay-ThinItalic.woff2 +0 -0
  42. data/app/assets/fonts/InterVariable-Italic.woff2 +0 -0
  43. data/app/assets/fonts/InterVariable.woff2 +0 -0
  44. data/app/assets/stylesheets/pages_core/admin/components/archive.css +1 -1
  45. data/app/assets/stylesheets/pages_core/admin/components/attachments.css +22 -34
  46. data/app/assets/stylesheets/pages_core/admin/components/base.css +1 -68
  47. data/app/assets/stylesheets/pages_core/admin/components/forms.css +107 -48
  48. data/app/assets/stylesheets/pages_core/admin/components/header.css +56 -58
  49. data/app/assets/stylesheets/pages_core/admin/components/image_editor.css +35 -24
  50. data/app/assets/stylesheets/pages_core/admin/components/image_grid.css +28 -27
  51. data/app/assets/stylesheets/pages_core/admin/components/image_uploader.css +5 -5
  52. data/app/assets/stylesheets/pages_core/admin/components/layout.css +7 -1
  53. data/app/assets/stylesheets/pages_core/admin/components/list_table.css +24 -15
  54. data/app/assets/stylesheets/pages_core/admin/components/page_tree.css +63 -104
  55. data/app/assets/stylesheets/pages_core/admin/components/pagination.css +12 -13
  56. data/app/assets/stylesheets/pages_core/admin/components/search.css +1 -16
  57. data/app/assets/stylesheets/pages_core/admin/components/sidebar.css +5 -11
  58. data/app/assets/stylesheets/pages_core/admin/components/tag_editor.css +22 -36
  59. data/app/assets/stylesheets/pages_core/admin/components/toast.css +1 -2
  60. data/app/assets/stylesheets/pages_core/admin/components/toolbar.css +10 -10
  61. data/app/assets/stylesheets/pages_core/admin/components/totp.css +1 -1
  62. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +37 -51
  63. data/app/assets/stylesheets/pages_core/admin/global/fonts.css +271 -0
  64. data/app/assets/stylesheets/pages_core/admin/global/typography.css +109 -0
  65. data/app/assets/stylesheets/pages_core/admin/vars.css +1 -3
  66. data/app/assets/stylesheets/pages_core/admin.postcss.css +1 -0
  67. data/app/controllers/admin/account_recoveries_controller.rb +2 -2
  68. data/app/controllers/admin/pages_controller.rb +22 -42
  69. data/app/controllers/concerns/pages_core/error_reporting.rb +1 -1
  70. data/app/controllers/concerns/pages_core/page_parameters.rb +29 -0
  71. data/app/controllers/concerns/pages_core/policies_helper.rb +1 -1
  72. data/app/controllers/concerns/pages_core/preview_pages_controller.rb +20 -20
  73. data/app/controllers/pages_core/admin_controller.rb +0 -2
  74. data/app/controllers/pages_core/frontend/pages_controller.rb +2 -6
  75. data/app/formatters/pages_core/html_formatter.rb +2 -4
  76. data/app/helpers/admin/menu_helper.rb +5 -4
  77. data/app/helpers/admin/pages_helper.rb +1 -21
  78. data/app/helpers/pages_core/admin/admin_helper.rb +2 -3
  79. data/app/helpers/pages_core/admin/content_tabs_helper.rb +1 -2
  80. data/app/helpers/pages_core/admin/labelled_field_helper.rb +1 -1
  81. data/app/helpers/pages_core/frontend_helper.rb +1 -1
  82. data/app/helpers/pages_core/images_helper.rb +10 -8
  83. data/app/helpers/pages_core/labelled_form_builder.rb +2 -7
  84. data/app/helpers/pages_core/page_path_helper.rb +1 -1
  85. data/app/javascript/components/Attachments/Attachment.tsx +20 -18
  86. data/app/javascript/components/Attachments/AttachmentEditor.tsx +11 -9
  87. data/app/javascript/components/{Attachments.jsx → Attachments/List.tsx} +58 -63
  88. data/app/javascript/components/Attachments/useAttachments.ts +15 -0
  89. data/app/javascript/components/Attachments.tsx +14 -0
  90. data/app/javascript/components/DateRangeSelect.tsx +105 -0
  91. data/app/javascript/components/DateTimeSelect.tsx +136 -0
  92. data/app/javascript/components/EditableImage.tsx +11 -9
  93. data/app/javascript/components/FileUploadButton.tsx +7 -7
  94. data/app/javascript/components/ImageCropper/FocalPoint.tsx +9 -12
  95. data/app/javascript/components/ImageCropper/Image.tsx +10 -8
  96. data/app/javascript/components/ImageCropper/Toolbar.tsx +11 -12
  97. data/app/javascript/components/ImageCropper/useCrop.ts +24 -53
  98. data/app/javascript/components/ImageCropper.tsx +10 -15
  99. data/app/javascript/components/ImageEditor/Form.tsx +12 -8
  100. data/app/javascript/components/ImageEditor.tsx +12 -7
  101. data/app/javascript/components/ImageGrid/DragElement.tsx +9 -12
  102. data/app/javascript/components/{ImageGrid.jsx → ImageGrid/Grid.tsx} +62 -71
  103. data/app/javascript/components/ImageGrid/GridImage.tsx +22 -23
  104. data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -2
  105. data/app/javascript/components/ImageGrid/useImageGrid.ts +26 -0
  106. data/app/javascript/components/ImageGrid.tsx +15 -0
  107. data/app/javascript/components/ImageUploader.tsx +35 -22
  108. data/app/javascript/components/LabelledField.tsx +34 -0
  109. data/app/javascript/components/Modal.tsx +2 -2
  110. data/app/javascript/components/PageForm/Block.tsx +81 -0
  111. data/app/javascript/components/PageForm/Content.tsx +54 -0
  112. data/app/javascript/components/PageForm/Dates.tsx +66 -0
  113. data/app/javascript/components/PageForm/Files.tsx +28 -0
  114. data/app/javascript/components/PageForm/Form.tsx +41 -0
  115. data/app/javascript/components/PageForm/Images.tsx +28 -0
  116. data/app/javascript/components/PageForm/LocaleLinks.tsx +36 -0
  117. data/app/javascript/components/PageForm/Metadata.tsx +67 -0
  118. data/app/javascript/components/PageForm/Options.tsx +180 -0
  119. data/app/javascript/components/PageForm/PageDescription.tsx +48 -0
  120. data/app/javascript/components/PageForm/PathSegment.tsx +65 -0
  121. data/app/javascript/components/PageForm/TabPanel.tsx +21 -0
  122. data/app/javascript/components/PageForm/Tabs.tsx +33 -0
  123. data/app/javascript/components/PageForm/UnconfiguredContent.tsx +42 -0
  124. data/app/javascript/components/PageForm/pageParams.ts +95 -0
  125. data/app/javascript/components/PageForm/preview.ts +23 -0
  126. data/app/javascript/components/PageForm/usePage.ts +169 -0
  127. data/app/javascript/components/PageForm/useTabs.ts +46 -0
  128. data/app/javascript/components/PageForm.tsx +163 -0
  129. data/app/javascript/components/PageImages.tsx +7 -9
  130. data/app/javascript/components/PageTree/Draggable.tsx +40 -39
  131. data/app/javascript/components/PageTree/Node.tsx +62 -56
  132. data/app/javascript/components/PageTree/PageName.tsx +28 -0
  133. data/app/javascript/components/PageTree.tsx +65 -53
  134. data/app/javascript/components/{RichTextArea.jsx → RichTextArea.tsx} +98 -79
  135. data/app/javascript/components/RichTextToolbarButton.tsx +4 -6
  136. data/app/javascript/components/TagEditor/AddTagForm.tsx +19 -12
  137. data/app/javascript/components/TagEditor/Editor.tsx +32 -0
  138. data/app/javascript/components/TagEditor/Tag.tsx +6 -4
  139. data/app/javascript/components/TagEditor/useTags.ts +58 -0
  140. data/app/javascript/components/TagEditor.tsx +8 -58
  141. data/app/javascript/components/Toast.tsx +3 -3
  142. data/app/javascript/components/drag/draggedOrder.ts +22 -14
  143. data/app/javascript/components/drag/useDragCollection.ts +35 -30
  144. data/app/javascript/components/drag/useDragUploader.ts +32 -21
  145. data/app/javascript/components/drag/useDraggable.ts +7 -6
  146. data/app/javascript/components/drag.ts +0 -1
  147. data/app/javascript/components.ts +1 -3
  148. data/app/javascript/features/RichText.tsx +2 -3
  149. data/app/javascript/features/contentTabs.ts +79 -0
  150. data/app/javascript/index.ts +5 -12
  151. data/app/javascript/lib/Tree.ts +31 -45
  152. data/app/javascript/lib/request.ts +11 -11
  153. data/app/javascript/stores/useToastStore.ts +1 -1
  154. data/app/javascript/types/Attachments.ts +29 -0
  155. data/app/javascript/types/Crop.ts +36 -0
  156. data/app/javascript/types/Drag.ts +34 -0
  157. data/app/javascript/types/Images.ts +47 -0
  158. data/app/javascript/types/PageEditor.ts +26 -0
  159. data/app/javascript/types/Pages.ts +75 -0
  160. data/app/javascript/types/Tags.ts +9 -0
  161. data/app/javascript/types/Template.ts +24 -0
  162. data/app/javascript/types/Trees.ts +19 -0
  163. data/app/javascript/types.ts +2 -25
  164. data/app/models/attachment.rb +1 -1
  165. data/app/models/concerns/pages_core/authenticable_user.rb +63 -0
  166. data/app/models/concerns/pages_core/emailable.rb +16 -0
  167. data/app/models/concerns/pages_core/page_model/templateable.rb +2 -16
  168. data/app/models/invite.rb +2 -6
  169. data/app/models/otp_secret.rb +4 -4
  170. data/app/models/page.rb +0 -3
  171. data/app/models/user.rb +2 -46
  172. data/app/policies/page_policy.rb +6 -2
  173. data/app/resources/admin/page_resource.rb +95 -0
  174. data/app/resources/admin/page_tree_resource.rb +27 -0
  175. data/app/resources/admin/template_configuration_resource.rb +50 -0
  176. data/app/views/admin/news/_sidebar.html.erb +2 -4
  177. data/app/views/admin/news/index.html.erb +0 -1
  178. data/app/views/admin/pages/_form.html.erb +10 -30
  179. data/app/views/admin/pages/_search_bar.html.erb +1 -1
  180. data/app/views/admin/pages/edit.html.erb +1 -57
  181. data/app/views/admin/pages/index.html.erb +1 -1
  182. data/app/views/admin/pages/new.html.erb +1 -44
  183. data/app/views/admin/sessions/new.html.erb +9 -11
  184. data/app/views/admin/users/_access_control.html.erb +5 -1
  185. data/app/views/admin/users/_list.html.erb +12 -7
  186. data/app/views/layouts/admin/_header.html.erb +2 -4
  187. data/app/views/layouts/admin/_page_header.html.erb +1 -2
  188. data/app/views/layouts/admin.html.erb +1 -1
  189. data/config/locales/en.yml +0 -4
  190. data/config/routes.rb +3 -7
  191. data/db/migrate/20240126160700_add_2fa_fields.rb +5 -1
  192. data/db/migrate/20240131140700_change_email_to_citext.rb +18 -0
  193. data/db/migrate/20240201160700_remove_persistent_data.rb +7 -0
  194. data/db/migrate/20240508145300_remove_categories.rb +21 -0
  195. data/lib/pages_core/configuration/base.rb +2 -2
  196. data/lib/pages_core/templates/configuration.rb +1 -1
  197. data/lib/pages_core/templates/configuration_proxy.rb +2 -2
  198. data/lib/pages_core/templates/template_configuration.rb +11 -1
  199. data/lib/pages_core/templates.rb +6 -4
  200. data/lib/pages_core/version.rb +1 -1
  201. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/gridOverlay.ts +6 -7
  202. data/lib/rails/generators/pages_core/frontend/templates/javascript/lib/responsiveEmbeds.ts +17 -12
  203. data/lib/rails/generators/pages_core/rspec/rspec_generator.rb +0 -2
  204. data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +3 -4
  205. metadata +95 -29
  206. data/app/assets/stylesheets/pages_core/admin/components/login.css +0 -27
  207. data/app/controllers/admin/categories_controller.rb +0 -56
  208. data/app/controllers/concerns/pages_core/admin/persistent_params.rb +0 -75
  209. data/app/helpers/pages_core/admin/page_blocks_helper.rb +0 -66
  210. data/app/helpers/pages_core/admin/page_json_helper.rb +0 -23
  211. data/app/javascript/components/DateRangeSelect.jsx +0 -225
  212. data/app/javascript/components/PageDates.jsx +0 -73
  213. data/app/javascript/components/PageFiles.jsx +0 -25
  214. data/app/javascript/components/PageTree/types.ts +0 -15
  215. data/app/javascript/components/drag/types.ts +0 -28
  216. data/app/javascript/controllers/EditPageController.ts +0 -22
  217. data/app/javascript/controllers/MainController.ts +0 -74
  218. data/app/javascript/controllers/PageOptionsController.js +0 -67
  219. data/app/models/category.rb +0 -22
  220. data/app/models/concerns/pages_core/has_otp.rb +0 -27
  221. data/app/models/page_category.rb +0 -6
  222. data/app/views/admin/pages/_edit_content.html.erb +0 -19
  223. data/app/views/admin/pages/_edit_files.html.erb +0 -4
  224. data/app/views/admin/pages/_edit_images.html.erb +0 -4
  225. data/app/views/admin/pages/_edit_metadata.html.erb +0 -35
  226. data/app/views/admin/pages/_edit_options.html.erb +0 -91
  227. data/lib/rails/generators/pages_core/rspec/templates/mailer_macros.rb +0 -11
@@ -50,42 +50,27 @@
50
50
  SOFTWARE.
51
51
  */
52
52
 
53
- export type TreeId = number | string;
54
- type MovePlacement = "before" | "after" | "prepend" | "append";
55
-
56
- export interface TreeNode {
57
- children: TreeNode[];
58
- collapsed: boolean;
59
- }
53
+ import * as Trees from "../types/Trees";
60
54
 
61
- export interface TreeIndex<T extends TreeNode = TreeNode> {
62
- id: number;
63
- node: T;
64
- children: TreeIndex<T>[];
65
- parent: TreeIndex<T>;
66
- next: TreeIndex<T> | null;
67
- prev: TreeIndex<T> | null;
68
- top: number;
69
- height: number;
70
- }
55
+ type MovePlacement = "before" | "after" | "prepend" | "append";
71
56
 
72
- function indexName(id: number | string): string {
57
+ function indexName(id: Trees.Id): string {
73
58
  return `${id}`;
74
59
  }
75
60
 
76
- export default class Tree<N extends TreeNode = TreeNode> {
61
+ export default class Tree<N extends Trees.Node = Trees.Node> {
77
62
  cnt: number;
78
63
  obj: N;
79
- indexes: Record<string, TreeIndex<N>>;
64
+ indexes: Record<string, Trees.Index<N>>;
80
65
 
81
66
  constructor(obj: N) {
82
67
  this.cnt = 1;
83
- this.obj = obj || { children: [] };
68
+ this.obj = obj || ({ children: [] } as N);
84
69
  this.indexes = {};
85
70
  this.build(this.obj);
86
71
  }
87
72
 
88
- build(obj: N): TreeIndex<N> {
73
+ build(obj: N): Trees.Index<N> {
89
74
  const indexes = this.indexes;
90
75
  const startId = this.cnt;
91
76
 
@@ -93,12 +78,13 @@ export default class Tree<N extends TreeNode = TreeNode> {
93
78
  indexes[indexName(this.cnt)] = index;
94
79
  this.cnt++;
95
80
 
96
- const walk = (objs: TreeIndex<N>[], parent: TreeIndex<N>) => {
97
- const children: TreeIndex<N>[] = [];
98
- objs.forEach((obj) => {
99
- const index: TreeIndex<N> = {};
100
- index.id = this.cnt;
101
- index.node = obj;
81
+ const walk = (objs: Trees.Node[], parent: Trees.Index<N>) => {
82
+ const children: Trees.Id[] = [];
83
+ objs.forEach((obj: N) => {
84
+ const index: Trees.Index<N> = {
85
+ id: this.cnt,
86
+ node: obj
87
+ };
102
88
 
103
89
  if (parent) {
104
90
  index.parent = parent.id;
@@ -132,12 +118,12 @@ export default class Tree<N extends TreeNode = TreeNode> {
132
118
  return index;
133
119
  }
134
120
 
135
- getIndex(id: TreeId): TreeIndex<N> {
121
+ getIndex(id: Trees.Id) {
136
122
  return this.indexes[indexName(id)];
137
123
  }
138
124
 
139
- removeIndex(index: TreeIndex<N>) {
140
- const del = (index: TreeIndex<N>) => {
125
+ removeIndex(index: Trees.Index<N>) {
126
+ const del = (index: Trees.Index<N>) => {
141
127
  delete this.indexes[indexName(index.id)];
142
128
  if (index.children && index.children.length) {
143
129
  index.children.forEach((child) => {
@@ -148,11 +134,11 @@ export default class Tree<N extends TreeNode = TreeNode> {
148
134
  del(index);
149
135
  }
150
136
 
151
- get(id: TreeId): N {
137
+ get(id: Trees.Id) {
152
138
  return this.getIndex(id).node;
153
139
  }
154
140
 
155
- remove(id: TreeId): N {
141
+ remove(id: Trees.Id) {
156
142
  const index = this.getIndex(id);
157
143
  const node = this.get(id);
158
144
 
@@ -168,7 +154,7 @@ export default class Tree<N extends TreeNode = TreeNode> {
168
154
  return node;
169
155
  }
170
156
 
171
- updateChildren(children: TreeIndex<N>[]) {
157
+ updateChildren(children: Trees.Id[]) {
172
158
  children.forEach((id, i) => {
173
159
  const index = this.getIndex(id);
174
160
  index.prev = index.next = null;
@@ -181,7 +167,7 @@ export default class Tree<N extends TreeNode = TreeNode> {
181
167
  });
182
168
  }
183
169
 
184
- insert(obj: N, parentId: TreeId, i: number): TreeIndex<N> {
170
+ insert(obj: N, parentId: Trees.Id, i: number) {
185
171
  const parentIndex = this.getIndex(parentId);
186
172
  const parentNode = this.get(parentId);
187
173
 
@@ -202,25 +188,25 @@ export default class Tree<N extends TreeNode = TreeNode> {
202
188
  return index;
203
189
  }
204
190
 
205
- insertBefore(obj: N, destId: TreeId): TreeIndex<N> {
191
+ insertBefore(obj: N, destId: Trees.Id) {
206
192
  const destIndex = this.getIndex(destId);
207
193
  const parentId = destIndex.parent;
208
194
  const i = this.getIndex(parentId).children.indexOf(destId);
209
195
  return this.insert(obj, parentId, i);
210
196
  }
211
197
 
212
- insertAfter(obj: N, destId: TreeId): TreeIndex<N> {
198
+ insertAfter(obj: N, destId: Trees.Id) {
213
199
  const destIndex = this.getIndex(destId);
214
200
  const parentId = destIndex.parent;
215
201
  const i = this.getIndex(parentId).children.indexOf(destId);
216
202
  return this.insert(obj, parentId, i + 1);
217
203
  }
218
204
 
219
- prepend(obj: N, destId: TreeId): TreeIndex<N> {
205
+ prepend(obj: N, destId: Trees.Id) {
220
206
  return this.insert(obj, destId, 0);
221
207
  }
222
208
 
223
- append(obj: N, destId: TreeId): TreeIndex<N> {
209
+ append(obj: N, destId: Trees.Id) {
224
210
  const destIndex = this.getIndex(destId);
225
211
  destIndex.children = destIndex.children || [];
226
212
  return this.insert(obj, destId, destIndex.children.length);
@@ -237,13 +223,13 @@ export default class Tree<N extends TreeNode = TreeNode> {
237
223
  root.left = left++;
238
224
 
239
225
  const walk = (
240
- children: TreeIndex<N>[],
241
- parent: TreeIndex<N>,
226
+ children: Trees.Id[],
227
+ parent: Trees.Index<N>,
242
228
  left: number,
243
229
  collapsed: boolean
244
230
  ) => {
245
231
  let height = 1;
246
- children.forEach((id: TreeId) => {
232
+ children.forEach((id: Trees.Id) => {
247
233
  const node = this.getIndex(id);
248
234
  if (collapsed) {
249
235
  node.top = null;
@@ -276,13 +262,13 @@ export default class Tree<N extends TreeNode = TreeNode> {
276
262
  }
277
263
  }
278
264
 
279
- move(fromId: TreeId, toId: TreeId, placement: MovePlacement): TreeIndex<N> {
265
+ move(fromId: Trees.Id, toId: Trees.Id, placement: MovePlacement) {
280
266
  if (fromId === toId || toId === 1) {
281
267
  return;
282
268
  }
283
269
 
284
270
  const obj = this.remove(fromId);
285
- let index: TreeIndex<N>;
271
+ let index: Trees.Index<N>;
286
272
 
287
273
  if (placement === "before") {
288
274
  index = this.insertBefore(obj, toId);
@@ -306,7 +292,7 @@ export default class Tree<N extends TreeNode = TreeNode> {
306
292
  }
307
293
  }
308
294
 
309
- getNodeByTop(top) {
295
+ getNodeByTop(top: Trees.Id) {
310
296
  const indexes = this.indexes;
311
297
  for (const id in indexes) {
312
298
  if (Object.prototype.hasOwnProperty.call(indexes, id)) {
@@ -19,24 +19,24 @@ function jsonFetchOptions() {
19
19
  }
20
20
 
21
21
  export async function postJson(url: string, data: Record<string, unknown>) {
22
- const options = { ...jsonFetchOptions(), method: "POST" };
23
- if (data) {
24
- options.body = JSON.stringify(data);
25
- }
26
- const response = await fetch(url, options);
22
+ const response = await fetch(url, {
23
+ ...jsonFetchOptions(),
24
+ method: "POST",
25
+ body: data ? JSON.stringify(data) : null
26
+ });
27
27
  return response.json();
28
28
  }
29
29
 
30
30
  export async function putJson(url: string, data: Record<string, unknown>) {
31
- const options = { ...jsonFetchOptions(), method: "PUT" };
32
- if (data) {
33
- options.body = JSON.stringify(data);
34
- }
35
- const response = await fetch(url, options);
31
+ const response = await fetch(url, {
32
+ ...jsonFetchOptions(),
33
+ method: "PUT",
34
+ body: data ? JSON.stringify(data) : null
35
+ });
36
36
  return response.json();
37
37
  }
38
38
 
39
- export async function post(url: string, data: Record<string, unknown>) {
39
+ export async function post(url: string, data: FormData) {
40
40
  const response = await fetch(url, {
41
41
  method: "POST",
42
42
  body: data,
@@ -1,6 +1,6 @@
1
1
  import { create } from "zustand";
2
2
 
3
- export interface Toast {
3
+ interface Toast {
4
4
  type: string;
5
5
  message: string;
6
6
  }
@@ -0,0 +1,29 @@
1
+ import * as Drag from "./Drag";
2
+ import { Locale, LocalizedValue } from "../types";
3
+
4
+ export interface Resource {
5
+ id: number | null;
6
+ name: LocalizedValue;
7
+ description: LocalizedValue;
8
+ url: string;
9
+ filename: string;
10
+ }
11
+
12
+ export interface Record extends Drag.DraggableRecord {
13
+ id?: number;
14
+ attachment: Resource;
15
+ uploading?: boolean;
16
+ }
17
+
18
+ export interface Options {
19
+ attribute: string;
20
+ locale: string;
21
+ locales: { [index: string]: Locale };
22
+ showEmbed: boolean;
23
+ }
24
+
25
+ export interface State {
26
+ collection: Drag.Collection<Record>;
27
+ deleted: Record[];
28
+ setDeleted: (records: Record[]) => void;
29
+ }
@@ -0,0 +1,36 @@
1
+ import * as Images from "./Images";
2
+
3
+ export interface Position {
4
+ x: number;
5
+ y: number;
6
+ }
7
+
8
+ export interface Size {
9
+ width: number;
10
+ height: number;
11
+ }
12
+
13
+ export interface Params {
14
+ crop_start_x: number;
15
+ crop_start_y: number;
16
+ crop_width: number;
17
+ crop_height: number;
18
+ crop_gravity_x: number;
19
+ crop_gravity_y: number;
20
+ }
21
+
22
+ export interface State extends Params {
23
+ aspect: number | null;
24
+ cropping: boolean;
25
+ image: Images.Resource;
26
+ }
27
+
28
+ export interface CropSize extends Position, Size {
29
+ aspect?: number;
30
+ }
31
+
32
+ export type Action =
33
+ | { type: "completeCrop" | "startCrop" | "toggleFocal" }
34
+ | { type: "setCrop"; payload: CropSize }
35
+ | { type: "setAspect"; payload: number }
36
+ | { type: "setFocal"; payload: Position };
@@ -0,0 +1,34 @@
1
+ export type DraggableRecord = Record<string, unknown>;
2
+
3
+ export interface Draggable<T = DraggableRecord> {
4
+ record: T;
5
+ ref: React.MutableRefObject<HTMLDivElement>;
6
+ rect: DOMRect | null;
7
+ handle: string;
8
+ }
9
+
10
+ export type Item<T = DraggableRecord> = Draggable<T> | "Files";
11
+
12
+ export type CollectionAction<T = DraggableRecord> =
13
+ | {
14
+ type: "append" | "prepend" | "insertFiles" | "replace" | "reorder";
15
+ payload: Item<T>[];
16
+ }
17
+ | { type: "update"; payload: Item<T> }
18
+ | { type: "remove"; payload: Draggable<T> }
19
+ | { type: "updatePositions"; payload?: Draggable<T> };
20
+
21
+ export interface Collection<T = DraggableRecord> {
22
+ ref: React.MutableRefObject<HTMLDivElement>;
23
+ draggables: Item<T>[];
24
+ dispatch: (CollectionAction) => void;
25
+ }
26
+
27
+ export interface Position {
28
+ x: number | null;
29
+ y: number | null;
30
+ }
31
+
32
+ export interface State<T = DraggableRecord> extends Position {
33
+ dragging: Item<T> | false;
34
+ }
@@ -0,0 +1,47 @@
1
+ import * as Drag from "./Drag";
2
+ import { Locale, LocalizedValue } from "../types";
3
+
4
+ export interface Resource {
5
+ id: number | null;
6
+ alternative: LocalizedValue;
7
+ caption: LocalizedValue;
8
+ content_type: string;
9
+ filename: string;
10
+ crop_start_x: number | null;
11
+ crop_start_y: number | null;
12
+ crop_width: number | null;
13
+ crop_height: number | null;
14
+ crop_gravity_x: number;
15
+ crop_gravity_y: number;
16
+ real_width: number;
17
+ real_height: number;
18
+ original_url: string;
19
+ thumbnail_url: string;
20
+ uncropped_url: string;
21
+ }
22
+
23
+ export interface Record extends Drag.DraggableRecord {
24
+ id?: number;
25
+ image: Resource;
26
+ primary?: boolean;
27
+ src?: string;
28
+ file?: File;
29
+ }
30
+
31
+ export type Response = Resource | { status: "error"; error: string };
32
+
33
+ export interface GridOptions {
34
+ attribute: string;
35
+ enablePrimary: boolean;
36
+ locale: string;
37
+ locales: { [index: string]: Locale };
38
+ primaryAttribute: string;
39
+ showEmbed: boolean;
40
+ }
41
+
42
+ export interface GridState {
43
+ primary: Drag.Collection<Record>;
44
+ images: Drag.Collection<Record>;
45
+ deleted: Record[];
46
+ setDeleted: (records: Record[]) => void;
47
+ }
@@ -0,0 +1,26 @@
1
+ import * as Pages from "./Pages";
2
+ import * as Template from "./Template";
3
+ import { Locale } from "../types";
4
+
5
+ export type Action =
6
+ | { type: "setPage"; payload: Pages.SerializedResource }
7
+ | { type: "setDatesEnabled"; payload: boolean }
8
+ | { type: "setLocale"; payload: string }
9
+ | { type: "update"; payload: Partial<Pages.Resource> }
10
+ | { type: "updateBlocks"; payload: Partial<Pages.Blocks> };
11
+
12
+ export interface State<T = Pages.Resource> {
13
+ locale: string;
14
+ locales: { [index: string]: Locale };
15
+ page: T;
16
+ templates: Template.Config[];
17
+ datesEnabled?: boolean;
18
+ inputDir?: "ltr" | "rtl";
19
+ templateConfig?: Template.Config;
20
+ }
21
+
22
+ export interface Tab {
23
+ id: string;
24
+ name: string;
25
+ enabled: boolean;
26
+ }
@@ -0,0 +1,75 @@
1
+ import * as Attachments from "./Attachments";
2
+ import * as Images from "./Images";
3
+ import * as Trees from "./Trees";
4
+ import { LocalizedValue, MaybeLocalizedValue } from "../types";
5
+
6
+ export type Author = [name: string, id: number];
7
+
8
+ export interface Ancestor {
9
+ id: number;
10
+ name: LocalizedValue;
11
+ path_segment: LocalizedValue;
12
+ }
13
+
14
+ export interface StatusLabels {
15
+ [index: number]: string;
16
+ }
17
+
18
+ export interface TreeResource {
19
+ id?: number;
20
+ blocks: Blocks;
21
+ news_page: boolean;
22
+ parent_page_id: number;
23
+ permissions: string[];
24
+ pinned: boolean;
25
+ published_at: Date;
26
+ status: number;
27
+ }
28
+
29
+ export interface Blocks {
30
+ [index: string]: MaybeLocalizedValue;
31
+ name: LocalizedValue;
32
+ }
33
+
34
+ export interface MetaImage {
35
+ src?: string;
36
+ image?: Images.Resource;
37
+ }
38
+
39
+ export interface Resource extends TreeResource {
40
+ all_day: boolean;
41
+ ancestors: Ancestor[];
42
+ blocks: Blocks;
43
+ enabled_tags: string[];
44
+ ends_at: Date;
45
+ errors: { attribute: string; message: string }[];
46
+ feed_enabled: boolean;
47
+ meta_image: MetaImage;
48
+ page_files: Attachments.Record[];
49
+ page_images: Images.Record[];
50
+ path_segment: MaybeLocalizedValue;
51
+ redirect_to: string;
52
+ starts_at: Date;
53
+ tags_and_suggestions: string[];
54
+ template: string;
55
+ unique_name: string;
56
+ urls: LocalizedValue;
57
+ user_id: number;
58
+ }
59
+
60
+ export interface SerializedResource
61
+ extends Omit<Resource, "published_at" | "starts_at" | "ends_at"> {
62
+ published_at: string;
63
+ starts_at: string;
64
+ ends_at: string;
65
+ }
66
+
67
+ export interface TreeItem extends Partial<TreeResource> {
68
+ blocks: Blocks;
69
+ editing?: boolean;
70
+ permissions?: string[];
71
+ }
72
+
73
+ export interface TreeNode extends TreeItem, Trees.Node {
74
+ children: TreeNode[];
75
+ }
@@ -0,0 +1,9 @@
1
+ export interface Action {
2
+ type: "addTag" | "toggleTag";
3
+ payload: string;
4
+ }
5
+
6
+ export interface State {
7
+ enabled: string[];
8
+ tags: string[];
9
+ }
@@ -0,0 +1,24 @@
1
+ export interface Block {
2
+ name: string;
3
+ title: string;
4
+ description?: string;
5
+ optional: boolean;
6
+ enforced: boolean;
7
+ size: string;
8
+ class?: string;
9
+ localized?: boolean;
10
+ placeholder?: string;
11
+ options?: [string, string][];
12
+ type?: string;
13
+ }
14
+
15
+ export interface Config {
16
+ name: string;
17
+ template_name: string;
18
+ blocks: Block[];
19
+ metadata_blocks: Block[];
20
+ images: boolean;
21
+ dates: boolean;
22
+ tags: boolean;
23
+ files: boolean;
24
+ }
@@ -0,0 +1,19 @@
1
+ export type Id = number | string;
2
+
3
+ export interface Node {
4
+ children: Array<Node>;
5
+ collapsed: boolean;
6
+ root?: boolean;
7
+ }
8
+
9
+ export interface Index<T extends Node = Node> {
10
+ id: number;
11
+ node: T;
12
+ children?: Id[];
13
+ parent?: Id;
14
+ top?: number;
15
+ height?: number;
16
+ next?: Id;
17
+ prev?: Id;
18
+ left?: number;
19
+ }
@@ -3,28 +3,5 @@ export interface Locale {
3
3
  dir: "ltr" | "rtl";
4
4
  }
5
5
 
6
- export interface AttachmentResource {
7
- id: number | null;
8
- name: Record<string, string>;
9
- description: Record<string, string>;
10
- url: string;
11
- }
12
-
13
- export interface ImageResource {
14
- id: number | null;
15
- alternative: Record<string, string>;
16
- caption: Record<string, string>;
17
- content_type: string;
18
- filename: string;
19
- crop_start_x: number | null;
20
- crop_start_y: number | null;
21
- crop_width: number | null;
22
- crop_height: number | null;
23
- crop_gravity_x: number;
24
- crop_gravity_y: number;
25
- real_width: number;
26
- real_height: number;
27
- original_url: string;
28
- thumbnail_url: string;
29
- uncropped_url: string;
30
- }
6
+ export type LocalizedValue = Record<string, string>;
7
+ export type MaybeLocalizedValue = LocalizedValue | string;
@@ -61,7 +61,7 @@ class Attachment < ApplicationRecord
61
61
  end
62
62
 
63
63
  def filename_extension?
64
- filename =~ /\./
64
+ filename.include?(".")
65
65
  end
66
66
 
67
67
  # Includes a timestamp fingerprint in the URL param, so
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PagesCore
4
+ module AuthenticableUser
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_secure_password
9
+
10
+ validates(:otp_secret, presence: true, if: :otp_enabled?)
11
+ validates(
12
+ :password,
13
+ length: {
14
+ minimum: 8,
15
+ maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED,
16
+ allow_blank: true
17
+ }
18
+ )
19
+
20
+ after_initialize { |u| u.session_token ||= u.class.random_session_token }
21
+ before_validation :update_session_token
22
+ end
23
+
24
+ module ClassMethods
25
+ def authenticate(email, password:)
26
+ User.find_by(email:).try(:authenticate, password)
27
+ end
28
+
29
+ def random_session_token
30
+ SecureRandom.hex(32)
31
+ end
32
+ end
33
+
34
+ def can_login?
35
+ activated?
36
+ end
37
+
38
+ def recovery_codes=(codes)
39
+ self.hashed_recovery_codes = codes.map do |c|
40
+ BCrypt::Password.create(c, cost: 8)
41
+ end
42
+ end
43
+
44
+ def use_recovery_code!(code)
45
+ valid_hashes = hashed_recovery_codes.select do |c|
46
+ BCrypt::Password.new(c) == code
47
+ end
48
+ return false unless valid_hashes.any?
49
+
50
+ update(hashed_recovery_codes: hashed_recovery_codes - valid_hashes)
51
+ true
52
+ end
53
+
54
+ private
55
+
56
+ def update_session_token
57
+ return unless password_digest_changed? ||
58
+ otp_enabled_changed?
59
+
60
+ self.session_token = self.class.random_session_token
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PagesCore
4
+ module Emailable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ validates :email,
9
+ presence: true,
10
+ format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i },
11
+ uniqueness: { case_sensitive: false }
12
+
13
+ normalizes :email, with: ->(email) { email.strip }
14
+ end
15
+ end
16
+ end