pages_core 3.15.3 → 3.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/assets/builds/pages_core/admin-dist.js +1 -1
  4. data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
  5. data/app/assets/builds/pages_core/admin.css +378 -253
  6. data/app/assets/builds/pages_core/mailer.css +41 -6
  7. data/app/assets/builds/pages_core_fonts/121b837e.woff2 +0 -0
  8. data/app/assets/builds/pages_core_fonts/216e5c23.woff2 +0 -0
  9. data/app/assets/builds/pages_core_fonts/3017b52f.woff2 +0 -0
  10. data/app/assets/builds/pages_core_fonts/489746b9.woff2 +0 -0
  11. data/app/assets/builds/pages_core_fonts/49775483.woff2 +0 -0
  12. data/app/assets/builds/pages_core_fonts/49c9e472.woff2 +0 -0
  13. data/app/assets/builds/pages_core_fonts/4a119645.woff2 +0 -0
  14. data/app/assets/builds/pages_core_fonts/5d56d7a8.woff2 +0 -0
  15. data/app/assets/builds/pages_core_fonts/61ea75a6.woff2 +0 -0
  16. data/app/assets/builds/pages_core_fonts/62cbb778.woff2 +0 -0
  17. data/app/assets/builds/pages_core_fonts/647d26c.woff2 +0 -0
  18. data/app/assets/builds/pages_core_fonts/67764053.woff2 +0 -0
  19. data/app/assets/builds/pages_core_fonts/6bb0fd00.woff2 +0 -0
  20. data/app/assets/builds/pages_core_fonts/6c0194a2.woff2 +0 -0
  21. data/app/assets/builds/pages_core_fonts/71423409.woff2 +0 -0
  22. data/app/assets/builds/pages_core_fonts/7584e61d.woff2 +0 -0
  23. data/app/assets/builds/pages_core_fonts/77bcfa1c.woff2 +0 -0
  24. data/app/assets/builds/pages_core_fonts/7aca0cc5.woff2 +0 -0
  25. data/app/assets/builds/pages_core_fonts/9a09533f.woff2 +0 -0
  26. data/app/assets/builds/pages_core_fonts/a51f5bc8.woff2 +0 -0
  27. data/app/assets/builds/pages_core_fonts/a80b2975.woff2 +0 -0
  28. data/app/assets/builds/pages_core_fonts/a891f617.woff2 +0 -0
  29. data/app/assets/builds/pages_core_fonts/ad6083f3.woff2 +0 -0
  30. data/app/assets/builds/pages_core_fonts/b29a61ff.woff2 +0 -0
  31. data/app/assets/builds/{fonts/6569749d.ttf → pages_core_fonts/b30b0656.ttf} +0 -0
  32. data/app/assets/builds/pages_core_fonts/b3a5f48c.woff2 +0 -0
  33. data/app/assets/builds/pages_core_fonts/bc73ee06.woff2 +0 -0
  34. data/app/assets/builds/pages_core_fonts/c38c6d45.woff2 +0 -0
  35. data/app/assets/builds/pages_core_fonts/c5ce0b1f.woff2 +0 -0
  36. data/app/assets/builds/pages_core_fonts/c8d53904.woff2 +0 -0
  37. data/app/assets/builds/pages_core_fonts/ce13c169.woff2 +0 -0
  38. data/app/assets/builds/pages_core_fonts/d43bd0d5.woff2 +0 -0
  39. data/app/assets/builds/pages_core_fonts/e1c7d368.woff2 +0 -0
  40. data/app/assets/builds/pages_core_fonts/e1e8175d.woff2 +0 -0
  41. data/app/assets/builds/pages_core_fonts/e318f796.woff2 +0 -0
  42. data/app/assets/builds/{fonts/ee32bc60.ttf → pages_core_fonts/e7acb7d9.ttf} +0 -0
  43. data/app/assets/builds/pages_core_fonts/ee5514c6.woff2 +0 -0
  44. data/app/assets/builds/pages_core_fonts/f4e495e2.woff2 +0 -0
  45. data/app/assets/builds/pages_core_fonts/f736ec65.woff2 +0 -0
  46. data/app/assets/builds/pages_core_fonts/f741c7ba.woff2 +0 -0
  47. data/app/assets/builds/pages_core_fonts/f7767345.woff2 +0 -0
  48. data/app/assets/builds/pages_core_fonts/fe9eb751.woff2 +0 -0
  49. data/app/assets/stylesheets/pages_core/admin/components/forms.css +2 -2
  50. data/app/assets/stylesheets/pages_core/admin/components/header.css +1 -1
  51. data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +1 -1
  52. data/app/assets/stylesheets/pages_core/admin/global/fonts.css +38 -38
  53. data/app/controllers/{pages_core → admin}/admin_controller.rb +1 -3
  54. data/app/controllers/attachments_controller.rb +40 -0
  55. data/app/controllers/concerns/pages_core/document_title_controller.rb +16 -0
  56. data/app/controllers/concerns/pages_core/page_parameters.rb +1 -1
  57. data/app/controllers/concerns/pages_core/pages/preview_controller.rb +49 -0
  58. data/app/controllers/concerns/pages_core/pages/rss_controller.rb +43 -0
  59. data/app/controllers/errors_controller.rb +2 -0
  60. data/app/controllers/images_controller.rb +13 -0
  61. data/app/controllers/pages_core/frontend/pages_controller.rb +3 -4
  62. data/app/controllers/pages_core/frontend_controller.rb +6 -1
  63. data/app/controllers/pages_core/sitemaps_controller.rb +21 -52
  64. data/app/helpers/pages_core/admin/image_uploads_helper.rb +1 -1
  65. data/app/helpers/pages_core/application_helper.rb +0 -3
  66. data/app/helpers/pages_core/attachments_helper.rb +0 -10
  67. data/app/helpers/pages_core/feed_tags_helper.rb +31 -0
  68. data/app/helpers/pages_core/frontend_helper.rb +3 -0
  69. data/app/helpers/pages_core/head_tags_helper.rb +80 -70
  70. data/app/helpers/pages_core/page_path_helper.rb +1 -12
  71. data/app/javascript/components/Attachments/Attachment.tsx +3 -3
  72. data/app/javascript/components/Attachments/AttachmentEditor.tsx +5 -5
  73. data/app/javascript/components/Attachments/Deleted.tsx +28 -0
  74. data/app/javascript/components/Attachments/List.tsx +11 -24
  75. data/app/javascript/components/Attachments/Placeholder.tsx +0 -2
  76. data/app/javascript/components/Attachments.tsx +2 -3
  77. data/app/javascript/components/DateRangeSelect.tsx +13 -10
  78. data/app/javascript/components/DateTimeSelect.tsx +11 -11
  79. data/app/javascript/components/EditableImage.tsx +3 -3
  80. data/app/javascript/components/FileUploadButton.tsx +3 -3
  81. data/app/javascript/components/ImageCropper/FocalPoint.tsx +10 -14
  82. data/app/javascript/components/ImageCropper/Image.tsx +19 -25
  83. data/app/javascript/components/ImageCropper/Toolbar.tsx +27 -26
  84. data/app/javascript/components/ImageCropper/useContainerSize.ts +25 -0
  85. data/app/javascript/components/ImageCropper/useCrop.ts +28 -13
  86. data/app/javascript/components/ImageCropper/useImageCropperContext.ts +13 -0
  87. data/app/javascript/components/ImageCropper.tsx +24 -83
  88. data/app/javascript/components/ImageEditor/Form.tsx +25 -28
  89. data/app/javascript/components/ImageEditor/useImageEditor.ts +63 -0
  90. data/app/javascript/components/ImageEditor/useImageEditorContext.ts +14 -0
  91. data/app/javascript/components/ImageEditor.tsx +28 -42
  92. data/app/javascript/components/ImageGrid/Deleted.tsx +28 -0
  93. data/app/javascript/components/ImageGrid/DragElement.tsx +5 -5
  94. data/app/javascript/components/ImageGrid/FilePlaceholder.tsx +0 -2
  95. data/app/javascript/components/ImageGrid/Grid.tsx +15 -24
  96. data/app/javascript/components/ImageGrid/GridImage.tsx +4 -4
  97. data/app/javascript/components/ImageGrid/Placeholder.tsx +2 -4
  98. data/app/javascript/components/ImageGrid.tsx +2 -4
  99. data/app/javascript/components/ImageUploader.tsx +5 -5
  100. data/app/javascript/components/LabelledField.tsx +6 -6
  101. data/app/javascript/components/Modal.tsx +16 -13
  102. data/app/javascript/components/PageForm/Block.tsx +3 -3
  103. data/app/javascript/components/PageForm/Content.tsx +11 -15
  104. data/app/javascript/components/PageForm/Dates.tsx +3 -11
  105. data/app/javascript/components/PageForm/Files.tsx +2 -4
  106. data/app/javascript/components/PageForm/Form.tsx +3 -9
  107. data/app/javascript/components/PageForm/Images.tsx +2 -4
  108. data/app/javascript/components/PageForm/LocaleLinks.tsx +4 -11
  109. data/app/javascript/components/PageForm/Metadata.tsx +8 -13
  110. data/app/javascript/components/PageForm/Options.tsx +28 -11
  111. data/app/javascript/components/PageForm/PageDescription.tsx +7 -14
  112. data/app/javascript/components/PageForm/PathSegment.tsx +5 -10
  113. data/app/javascript/components/PageForm/TabPanel.tsx +3 -6
  114. data/app/javascript/components/PageForm/Tabs.tsx +2 -4
  115. data/app/javascript/components/PageForm/UnconfiguredContent.tsx +7 -12
  116. data/app/javascript/components/PageForm/pageParams.ts +3 -2
  117. data/app/javascript/components/PageForm/usePage.ts +1 -46
  118. data/app/javascript/components/PageForm/usePageFormContext.ts +8 -0
  119. data/app/javascript/components/PageForm/useTabs.ts +1 -1
  120. data/app/javascript/components/PageForm/utils.ts +49 -0
  121. data/app/javascript/components/PageForm.tsx +52 -48
  122. data/app/javascript/components/PageImages.tsx +1 -3
  123. data/app/javascript/components/PageTree/Button.tsx +25 -0
  124. data/app/javascript/components/PageTree/CollapseArrow.tsx +34 -0
  125. data/app/javascript/components/PageTree/CollapsedLabel.tsx +21 -0
  126. data/app/javascript/components/PageTree/EditPageName.tsx +68 -0
  127. data/app/javascript/components/PageTree/Node.tsx +143 -413
  128. data/app/javascript/components/PageTree/PageName.tsx +6 -4
  129. data/app/javascript/components/PageTree/StatusLabel.tsx +10 -0
  130. data/app/javascript/components/PageTree/tree.ts +268 -0
  131. data/app/javascript/components/PageTree/usePageTree.ts +268 -0
  132. data/app/javascript/components/PageTree/usePageTreeContext.ts +13 -0
  133. data/app/javascript/components/PageTree.tsx +194 -214
  134. data/app/javascript/components/{RichTextToolbarButton.tsx → RichTextArea/ToolbarButton.tsx} +3 -5
  135. data/app/javascript/components/RichTextArea/actions.ts +106 -0
  136. data/app/javascript/components/RichTextArea/useMaybeControlledValue.ts +14 -0
  137. data/app/javascript/components/RichTextArea.tsx +91 -209
  138. data/app/javascript/components/TagEditor/AddTagForm.tsx +2 -2
  139. data/app/javascript/components/TagEditor/Editor.tsx +3 -5
  140. data/app/javascript/components/TagEditor/Tag.tsx +3 -5
  141. data/app/javascript/components/TagEditor/useTags.ts +7 -4
  142. data/app/javascript/components/TagEditor.tsx +2 -4
  143. data/app/javascript/components/Toast.tsx +5 -5
  144. data/app/javascript/components/drag/draggedOrder.ts +6 -6
  145. data/app/javascript/components/drag/useDragCollection.ts +21 -25
  146. data/app/javascript/components/drag/useDragUploader.ts +20 -18
  147. data/app/javascript/components/drag/useDraggable.ts +3 -3
  148. data/app/javascript/features/RichText.tsx +0 -1
  149. data/app/javascript/features/contentTabs.ts +2 -2
  150. data/app/javascript/stores/useModalStore.ts +1 -1
  151. data/app/javascript/stores/useToastStore.ts +2 -2
  152. data/app/javascript/types/Attachments.ts +11 -11
  153. data/app/javascript/types/Crop.ts +16 -12
  154. data/app/javascript/types/Drag.ts +21 -23
  155. data/app/javascript/types/Images.ts +8 -8
  156. data/app/javascript/types/PageEditor.ts +11 -4
  157. data/app/javascript/types/Pages.ts +22 -27
  158. data/app/javascript/types/Tags.ts +5 -6
  159. data/app/javascript/types/Template.ts +4 -4
  160. data/app/javascript/types.ts +2 -2
  161. data/app/models/attachment.rb +5 -9
  162. data/app/models/autopublisher.rb +1 -1
  163. data/app/models/concerns/pages_core/page_model/redirectable.rb +1 -2
  164. data/app/models/concerns/pages_core/page_model/searchable.rb +1 -1
  165. data/app/models/concerns/pages_core/page_model/status.rb +2 -4
  166. data/app/models/concerns/pages_core/searchable_document.rb +2 -4
  167. data/app/models/image.rb +0 -15
  168. data/app/models/page_builder.rb +4 -6
  169. data/app/resources/admin/page_resource.rb +2 -2
  170. data/app/resources/export/page_resource.rb +1 -1
  171. data/app/services/pages_core/invite_service.rb +1 -2
  172. data/app/views/layouts/admin.html.erb +1 -0
  173. data/app/views/pages_core/sitemaps/index.xml.builder +10 -0
  174. data/config/routes.rb +4 -3
  175. data/db/migrate/20240917142300_add_skip_index_to_pages.rb +7 -0
  176. data/lib/pages_core/engine.rb +15 -17
  177. data/lib/pages_core/sitemap.rb +58 -0
  178. data/lib/pages_core/templates/configuration_proxy.rb +3 -3
  179. data/lib/pages_core.rb +7 -4
  180. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +2 -2
  181. data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +13 -5
  182. data/lib/rails/generators/pages_core/frontend/templates/postcss.config.js +2 -6
  183. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/components/base.css +3 -1
  184. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/config.css +2 -3
  185. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/animation.css +1 -1
  186. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/colors.css +6 -5
  187. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/fonts.css +1 -1
  188. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/grid.css +9 -6
  189. data/lib/rails/generators/pages_core/frontend/templates/stylesheets/global/typography.css +42 -26
  190. data/lib/rails/generators/pages_core/install/templates/application_controller.rb +0 -6
  191. data/lib/rails/generators/pages_core/rspec/templates/rails_helper.rb +1 -1
  192. metadata +81 -49
  193. data/app/assets/builds/fonts/7b7db107.woff2 +0 -0
  194. data/app/assets/builds/fonts/921961e9.woff2 +0 -0
  195. data/app/controller_dummies/admin/admin_controller.rb +0 -6
  196. data/app/controller_dummies/application_controller.rb +0 -6
  197. data/app/controller_dummies/attachments_controller.rb +0 -4
  198. data/app/controller_dummies/frontend_controller.rb +0 -4
  199. data/app/controller_dummies/images_controller.rb +0 -4
  200. data/app/controller_dummies/page_files_controller.rb +0 -4
  201. data/app/controller_dummies/pages_controller.rb +0 -4
  202. data/app/controller_dummies/sitemaps_controller.rb +0 -4
  203. data/app/controllers/concerns/pages_core/preview_pages_controller.rb +0 -47
  204. data/app/controllers/concerns/pages_core/rss_controller.rb +0 -41
  205. data/app/controllers/pages_core/attachments_controller.rb +0 -42
  206. data/app/controllers/pages_core/frontend/page_files_controller.rb +0 -25
  207. data/app/controllers/pages_core/images_controller.rb +0 -15
  208. data/app/helpers/pages_core/meta_tags_helper.rb +0 -96
  209. data/app/helpers/pages_core/open_graph_tags_helper.rb +0 -49
  210. data/app/javascript/components/PageTree/Draggable.tsx +0 -338
  211. data/app/javascript/lib/Tree.ts +0 -305
  212. data/app/javascript/types/Trees.ts +0 -19
  213. data/app/views/sitemaps/show.xml.builder +0 -11
@@ -1,129 +1,69 @@
1
- import React, { createRef, ChangeEvent, Component, RefObject } from "react";
2
- import RichTextToolbarButton from "./RichTextToolbarButton";
1
+ import { useRef, ChangeEvent } from "react";
2
+ import ToolbarButton from "./RichTextArea/ToolbarButton";
3
+ import useMaybeControlledValue from "./RichTextArea/useMaybeControlledValue";
3
4
 
4
- interface Props {
5
+ import {
6
+ ActionFn,
7
+ simpleActions,
8
+ advancedActions
9
+ } from "./RichTextArea/actions";
10
+
11
+ type Props = {
5
12
  id: string;
6
13
  name: string;
7
14
  value: string;
8
- rows: number;
15
+ rows?: number;
9
16
  className?: string;
10
17
  simple?: boolean;
11
18
  lang?: string;
12
19
  dir?: string;
13
20
  onChange?: (str: string) => void;
14
- }
15
-
16
- interface State {
17
- value: string;
18
- rows: number;
19
- }
20
-
21
- type Replacement = [string, string, string];
22
- type ActionFn = (str: string) => Replacement;
23
-
24
- interface Action {
25
- name: string;
26
- className: string;
27
- fn: ActionFn;
28
- hotkey?: string;
29
- }
30
-
31
- export default class RichTextArea extends Component<Props, State> {
32
- inputRef: RefObject<HTMLTextAreaElement>;
33
-
34
- constructor(props: Props) {
35
- super(props);
36
- this.state = {
37
- value: props.value || "",
38
- rows: props.rows || 5
39
- };
40
- this.inputRef = createRef<HTMLTextAreaElement>();
41
- }
42
-
43
- actions = () => {
44
- const simple: Action[] = [
45
- {
46
- name: "bold",
47
- className: "bold",
48
- hotkey: "b",
49
- fn: (str: string) => ["<b>", str, "</b>"]
50
- },
51
- {
52
- name: "italic",
53
- className: "italic",
54
- hotkey: "i",
55
- fn: (str: string) => ["<i>", str, "</i>"]
56
- }
57
- ];
58
-
59
- const advanced: Action[] = [
60
- {
61
- name: "Heading 2",
62
- className: "header h2",
63
- fn: (str: string) => ["h2. ", str, ""]
64
- },
65
- {
66
- name: "Heading 3",
67
- className: "header h3",
68
- fn: (str: string) => ["h3. ", str, ""]
69
- },
70
- {
71
- name: "Heading 4",
72
- className: "header h4",
73
- fn: (str: string) => ["h4. ", str, ""]
74
- },
75
- {
76
- name: "Blockquote",
77
- className: "quote-left",
78
- fn: (str: string) => ["bq. ", str, ""]
79
- },
80
- {
81
- name: "List",
82
- className: "list-ul",
83
- fn: (str: string) => ["", this.strToList(str, "*"), ""]
84
- },
85
- {
86
- name: "Ordered list",
87
- className: "list-ol",
88
- fn: (str: string) => ["", this.strToList(str, "#"), ""]
89
- },
90
- { name: "Link", className: "link", fn: this.link },
91
- { name: "Email link", className: "envelope", fn: this.emailLink }
92
- ];
93
-
94
- return this.props.simple ? simple : [...simple, ...advanced];
95
- };
96
-
97
- applyAction(fn: ActionFn) {
98
- const [prefix, replacement, postfix] = fn(this.getSelection());
99
- this.replaceSelection(prefix, replacement, postfix);
100
- }
101
-
102
- emailLink = (selection: string): Replacement => {
103
- const address = prompt("Enter email address", "");
104
- const name = selection.length > 0 ? selection : address;
105
- return ['"', name, `":mailto:${address}`];
21
+ };
22
+
23
+ export default function RichTextArea({
24
+ id,
25
+ name,
26
+ value: initialValue,
27
+ rows,
28
+ className,
29
+ simple,
30
+ lang,
31
+ dir,
32
+ onChange
33
+ }: Props) {
34
+ const [value, setValue] = useMaybeControlledValue(initialValue, onChange);
35
+
36
+ const inputRef = useRef<HTMLTextAreaElement>(null);
37
+
38
+ const actions = simple
39
+ ? simpleActions
40
+ : [...simpleActions, ...advancedActions];
41
+
42
+ const applyAction = (fn: ActionFn) => {
43
+ const [prefix, replacement, postfix] = fn(getSelection());
44
+ replaceSelection(prefix, replacement, postfix);
106
45
  };
107
46
 
108
- getSelection = (): string => {
109
- const { selectionStart, selectionEnd, value } = this.inputRef.current;
110
- return value.substr(selectionStart, selectionEnd - selectionStart);
47
+ const getSelection = (): string => {
48
+ const textarea = inputRef.current;
49
+ const { selectionStart, selectionEnd, value } = textarea;
50
+ return value.substring(selectionStart, selectionEnd);
111
51
  };
112
52
 
113
- handleChange = (evt: ChangeEvent<HTMLTextAreaElement>) => {
114
- this.updateValue(evt.target.value);
53
+ const handleChange = (evt: ChangeEvent<HTMLTextAreaElement>) => {
54
+ setValue(evt.target.value);
115
55
  };
116
56
 
117
- handleKeyPress = (evt: React.KeyboardEvent) => {
57
+ const handleKeyPress = (evt: React.KeyboardEvent) => {
118
58
  let key: string;
119
- if (evt.which >= 65 && evt.which <= 90) {
120
- key = String.fromCharCode(evt.keyCode).toLowerCase();
121
- } else if (evt.keyCode === 13) {
59
+ if (evt.key >= "A" && evt.key <= "Z") {
60
+ key = evt.key.toLowerCase();
61
+ } else if (evt.key === "Enter") {
122
62
  key = "enter";
123
63
  }
124
64
 
125
65
  const hotkeys: Record<string, ActionFn> = {};
126
- this.actions().forEach((a) => {
66
+ actions.forEach((a) => {
127
67
  if (a.hotkey) {
128
68
  hotkeys[a.hotkey] = a.fn;
129
69
  }
@@ -131,133 +71,75 @@ export default class RichTextArea extends Component<Props, State> {
131
71
 
132
72
  if ((evt.metaKey || evt.ctrlKey) && key in hotkeys) {
133
73
  evt.preventDefault();
134
- this.applyAction(hotkeys[key]);
74
+ applyAction(hotkeys[key]);
135
75
  }
136
76
  };
137
77
 
138
- link = (selection: string): Replacement => {
139
- const name = selection.length > 0 ? selection : "Link text";
140
- const url = prompt("Enter link URL", "");
141
- if (url) {
142
- return ['"', name, `":${this.relativeUrl(url)}`];
143
- } else {
144
- return ["", name, ""];
145
- }
146
- };
147
-
148
- localeOptions() {
78
+ const localeOptions = () => {
149
79
  const opts: React.HTMLProps<HTMLTextAreaElement> = {};
150
80
 
151
- if (this.props.lang) {
152
- opts.lang = this.props.lang;
81
+ if (lang) {
82
+ opts.lang = lang;
153
83
  }
154
84
 
155
- if (this.props.dir) {
156
- opts.dir = this.props.dir;
85
+ if (dir) {
86
+ opts.dir = dir;
157
87
  }
158
88
 
159
89
  return opts;
160
- }
161
-
162
- relativeUrl(str: string): string {
163
- let url: URL = null;
164
-
165
- if (!str.match(/^https:\/\//) || !document || !document.location) {
166
- return str;
167
- }
168
-
169
- try {
170
- url = new URL(str);
171
- } catch (error) {
172
- console.log("Error parsing URL: ", error);
173
- }
174
-
175
- if (
176
- url &&
177
- url.hostname == document.location.hostname &&
178
- (document.location.port || "80") == (url.port || "80")
179
- ) {
180
- return url.pathname;
181
- }
182
- return str;
183
- }
184
-
185
- render() {
186
- const { rows } = this.state;
187
- const { id, name } = this.props;
188
- const value = this.getValue();
189
-
190
- const clickHandler = (fn: ActionFn) => (evt: React.MouseEvent) => {
191
- evt.preventDefault();
192
- this.applyAction(fn);
193
- };
194
-
195
- return (
196
- <div className="rich-text-area">
197
- <div className="rich-text toolbar">
198
- {this.actions().map((a) => (
199
- <RichTextToolbarButton
200
- key={a.name}
201
- name={a.name}
202
- className={a.className}
203
- onClick={clickHandler(a.fn)}
204
- />
205
- ))}
206
- </div>
207
- <textarea
208
- className={this.props.className || "rich"}
209
- ref={this.inputRef}
210
- id={id}
211
- name={name}
212
- value={value}
213
- rows={rows}
214
- onChange={this.handleChange}
215
- onKeyDown={this.handleKeyPress}
216
- {...this.localeOptions()}
217
- />
218
- </div>
219
- );
220
- }
90
+ };
221
91
 
222
- replaceSelection = (prefix: string, replacement: string, postfix: string) => {
223
- const textarea = this.inputRef.current;
92
+ const replaceSelection = (
93
+ prefix: string,
94
+ replacement: string,
95
+ postfix: string
96
+ ) => {
97
+ const textarea = inputRef.current;
224
98
  const { selectionStart, selectionEnd, value } = textarea;
225
99
 
226
100
  textarea.value =
227
- value.substr(0, selectionStart) +
101
+ value.substring(0, selectionStart) +
228
102
  prefix +
229
103
  replacement +
230
104
  postfix +
231
- value.substr(selectionEnd, value.length);
105
+ value.substring(selectionEnd);
232
106
 
233
107
  textarea.focus({ preventScroll: true });
234
108
  textarea.setSelectionRange(
235
109
  selectionStart + prefix.length,
236
110
  selectionStart + prefix.length + replacement.length
237
111
  );
238
- this.updateValue(textarea.value);
112
+ setValue(textarea.value);
239
113
  };
240
114
 
241
- strToList(str: string, prefix: string) {
242
- return str
243
- .split("\n")
244
- .map((l) => prefix + " " + l)
245
- .join("\n");
246
- }
247
-
248
- getValue() {
249
- if (this.props.onChange) {
250
- return this.props.value;
251
- } else {
252
- return this.state.value;
253
- }
254
- }
115
+ const clickHandler = (fn: ActionFn) => (evt: React.MouseEvent) => {
116
+ evt.preventDefault();
117
+ applyAction(fn);
118
+ };
255
119
 
256
- updateValue(str: string) {
257
- if (this.props.onChange) {
258
- this.props.onChange(str);
259
- } else {
260
- this.setState({ value: str });
261
- }
262
- }
120
+ return (
121
+ <div className="rich-text-area">
122
+ <div className="rich-text toolbar">
123
+ {actions.map((action) => (
124
+ <ToolbarButton
125
+ key={action.name}
126
+ name={action.name}
127
+ className={action.className}
128
+ onClick={clickHandler(action.fn)}
129
+ />
130
+ ))}
131
+ </div>
132
+ <textarea
133
+ className={className || "rich"}
134
+ ref={inputRef}
135
+ id={id}
136
+ name={name}
137
+ value={value || ""}
138
+ rows={rows || 5}
139
+ onChange={handleChange}
140
+ onKeyDown={handleKeyPress}
141
+ {...localeOptions()}
142
+ />
143
+ </div>
144
+ );
263
145
  }
@@ -1,8 +1,8 @@
1
- import React, { ChangeEvent, MouseEvent, KeyboardEvent, useState } from "react";
1
+ import { ChangeEvent, MouseEvent, KeyboardEvent, useState } from "react";
2
2
 
3
3
  import * as Tags from "../../types/Tags";
4
4
 
5
- interface Props {
5
+ type Props = {
6
6
  dispatch: (action: Tags.Action) => void;
7
7
  }
8
8
 
@@ -1,16 +1,14 @@
1
- import React from "react";
2
-
3
1
  import { allTags, isEnabled } from "./useTags";
4
2
 
5
3
  import AddTagForm from "./AddTagForm";
6
4
  import Tag from "./Tag";
7
5
  import * as Tags from "../../types/Tags";
8
6
 
9
- interface Props {
7
+ type Props = {
10
8
  name: string;
11
9
  state: Tags.State;
12
- dispatch: (action: Tags.Action) => void;
13
- }
10
+ dispatch: React.Dispatch<Tags.Action>;
11
+ };
14
12
 
15
13
  export default function Editor(props: Props) {
16
14
  const { name, state, dispatch } = props;
@@ -1,12 +1,10 @@
1
- import React from "react";
2
-
3
1
  import * as Tags from "../../types/Tags";
4
2
 
5
- interface Props {
3
+ type Props = {
6
4
  enabled: boolean;
7
5
  tag: string;
8
- dispatch: (action: Tags.Action) => void;
9
- }
6
+ dispatch: React.Dispatch<Tags.Action>;
7
+ };
10
8
 
11
9
  export default function Tag(props: Props) {
12
10
  const handleChange = () => {
@@ -32,15 +32,18 @@ function toggle(tag: string, state: Tags.State) {
32
32
 
33
33
  function reducer(state: Tags.State, action: Tags.Action) {
34
34
  const { type, payload } = action;
35
- const normalized = normalize(payload, state);
36
35
  switch (type) {
37
- case "addTag":
36
+ case "addTag": {
37
+ const normalized = normalize(payload, state);
38
38
  return {
39
39
  tags: [...state.tags, normalized].filter(onlyUnique),
40
40
  enabled: [...state.enabled, normalized].filter(onlyUnique)
41
41
  };
42
+ }
42
43
  case "toggleTag":
43
- return toggle(normalized, state);
44
+ return toggle(normalize(payload, state), state);
45
+ case "update":
46
+ return payload;
44
47
  default:
45
48
  return state;
46
49
  }
@@ -49,7 +52,7 @@ function reducer(state: Tags.State, action: Tags.Action) {
49
52
  export default function useTags(
50
53
  initTags: string[],
51
54
  initEnabled: string[]
52
- ): [Tags.State, (action: Tags.Action) => void] {
55
+ ): [Tags.State, React.Dispatch<Tags.Action>] {
53
56
  const [state, dispatch] = useReducer(reducer, {
54
57
  tags: initTags,
55
58
  enabled: initEnabled
@@ -1,12 +1,10 @@
1
- import React from "react";
2
-
3
1
  import useTags from "./TagEditor/useTags";
4
2
  import Editor from "./TagEditor/Editor";
5
3
  import * as Tags from "../types/Tags";
6
4
 
7
- interface Props extends Tags.State {
5
+ type Props = Tags.State & {
8
6
  name: string;
9
- }
7
+ };
10
8
 
11
9
  export default function TagEditor(props: Props) {
12
10
  const [state, dispatch] = useTags(props.tags, props.enabled);
@@ -1,11 +1,11 @@
1
- import React, { useEffect, useRef, useState } from "react";
1
+ import { useEffect, useRef, useState } from "react";
2
2
 
3
3
  import useToastStore from "../stores/useToastStore";
4
4
 
5
- interface Props {
5
+ type Props = {
6
6
  error: string;
7
7
  notice: string;
8
- }
8
+ };
9
9
 
10
10
  export default function Toast(props: Props) {
11
11
  const [fadeout, setFadeout] = useState(false);
@@ -21,7 +21,7 @@ export default function Toast(props: Props) {
21
21
  if (props.notice) {
22
22
  notice(props.notice);
23
23
  }
24
- }, [props.error, props.notice]);
24
+ }, [props.error, props.notice, error, notice]);
25
25
 
26
26
  useEffect(() => {
27
27
  setFadeout(false);
@@ -38,7 +38,7 @@ export default function Toast(props: Props) {
38
38
  return () => {
39
39
  clearTimeout(timerRef.current);
40
40
  };
41
- }, [toast]);
41
+ }, [toast, next]);
42
42
 
43
43
  const classNames = ["toast"];
44
44
 
@@ -2,7 +2,7 @@ import * as Drag from "../../types/Drag";
2
2
 
3
3
  function hovering<T>(
4
4
  dragState: Drag.State<T>,
5
- target: Drag.Item<T> | React.MutableRefObject<HTMLDivElement>
5
+ target: Drag.DraggableOrFiles<T> | React.MutableRefObject<HTMLDivElement>
6
6
  ) {
7
7
  const { x, y } = dragState;
8
8
  let rect: DOMRect;
@@ -22,7 +22,7 @@ function hovering<T>(
22
22
  export function collectionOrder<T>(
23
23
  collection: Drag.Collection<T>,
24
24
  dragState: Drag.State<T>
25
- ): Array<Drag.Item<T>> {
25
+ ): Array<Drag.DraggableOrFiles<T>> {
26
26
  const { draggables, ref } = collection;
27
27
  const { dragging } = dragState;
28
28
 
@@ -30,11 +30,11 @@ export function collectionOrder<T>(
30
30
  return draggables;
31
31
  }
32
32
 
33
- let ordered: Drag.Item<T>[] = draggables;
34
- if (typeof dragging !== "string") {
33
+ let ordered = draggables;
34
+ if (dragging !== "Files") {
35
35
  ordered = draggables
36
- .filter((d) => typeof d !== "string")
37
- .filter((d: Drag.Draggable<T>) => d.handle !== dragging.handle);
36
+ .filter((d) => d !== "Files")
37
+ .filter((d) => d.handle !== dragging.handle);
38
38
  }
39
39
  if (hovering(dragState, ref)) {
40
40
  const hovered = ordered.filter((d) => hovering(dragState, d))[0];
@@ -11,20 +11,20 @@ function getPosition<T>(draggable: Drag.Draggable<T>) {
11
11
  }
12
12
  }
13
13
 
14
- function hideDraggable<T>(
15
- draggable: Drag.Item<T> | null,
16
- callback: () => Drag.Item<T>[]
14
+ function hideDragging<T>(
15
+ dragging: Drag.DraggableOrFiles<T> | null,
16
+ callback: () => Drag.DraggableOrFiles<T>[]
17
17
  ) {
18
18
  if (
19
- draggable &&
20
- draggable !== "Files" &&
21
- "ref" in draggable &&
22
- draggable.ref.current
19
+ dragging &&
20
+ dragging !== "Files" &&
21
+ "ref" in dragging &&
22
+ dragging.ref.current
23
23
  ) {
24
- const prevDisplay = draggable.ref.current.style.display;
25
- draggable.ref.current.style.display = "none";
24
+ const prevDisplay = dragging.ref.current.style.display;
25
+ dragging.ref.current.style.display = "none";
26
26
  const result = callback();
27
- draggable.ref.current.style.display = prevDisplay;
27
+ dragging.ref.current.style.display = prevDisplay;
28
28
  return result;
29
29
  } else {
30
30
  return callback();
@@ -32,9 +32,9 @@ function hideDraggable<T>(
32
32
  }
33
33
 
34
34
  function insertFiles<T>(
35
- state: Drag.Item<T>[],
36
- files: Drag.Item<T>[]
37
- ): Drag.Item<T>[] {
35
+ state: Drag.DraggableOrFiles<T>[],
36
+ files: Drag.DraggableOrFiles<T>[]
37
+ ): Drag.DraggableOrFiles<T>[] {
38
38
  const index = state.indexOf("Files");
39
39
  if (index === -1 || !files) {
40
40
  return state;
@@ -43,10 +43,10 @@ function insertFiles<T>(
43
43
  }
44
44
  }
45
45
 
46
- function dragCollectionReducer<T = Drag.DraggableRecord>(
47
- state: Drag.Item<T>[],
46
+ function reducer<T>(
47
+ state: Drag.DraggableOrFiles<T>[],
48
48
  action: Drag.CollectionAction<T>
49
- ): Drag.Item<T>[] {
49
+ ): Drag.DraggableOrFiles<T>[] {
50
50
  switch (action.type) {
51
51
  case "append":
52
52
  return [...state, ...action.payload];
@@ -56,12 +56,10 @@ function dragCollectionReducer<T = Drag.DraggableRecord>(
56
56
  return insertFiles(state, action.payload);
57
57
  case "update":
58
58
  return state.map((d: Drag.Draggable<T>) => {
59
- return d.handle === (action.payload as Drag.Draggable<T>).handle
60
- ? action.payload
61
- : d;
59
+ return d.handle === action.payload.handle ? action.payload : d;
62
60
  });
63
61
  case "updatePositions":
64
- return hideDraggable(action.payload, () => {
62
+ return hideDragging(action.payload, () => {
65
63
  return state.map((d: Drag.Draggable<T>) => {
66
64
  return { ...d, rect: getPosition(d) };
67
65
  });
@@ -81,9 +79,7 @@ function dragCollectionReducer<T = Drag.DraggableRecord>(
81
79
  }
82
80
  }
83
81
 
84
- export function createDraggable<T = Drag.DraggableRecord>(
85
- record: T
86
- ): Drag.Draggable<T> {
82
+ export function createDraggable<T>(record: T): Drag.Draggable<T> {
87
83
  return {
88
84
  record: record,
89
85
  rect: null,
@@ -92,11 +88,11 @@ export function createDraggable<T = Drag.DraggableRecord>(
92
88
  };
93
89
  }
94
90
 
95
- export default function useDragCollection<T = Drag.DraggableRecord>(
91
+ export default function useDragCollection<T>(
96
92
  records: Array<T>
97
93
  ): Drag.Collection<T> {
98
94
  const containerRef = useRef<HTMLDivElement>(null);
99
- const [draggables, dispatch] = useReducer(dragCollectionReducer<T>, [], () =>
95
+ const [draggables, dispatch] = useReducer(reducer<T>, [], () =>
100
96
  records.map((r) => createDraggable(r))
101
97
  );
102
98
 
@@ -8,6 +8,16 @@ type AnyTouchEvent =
8
8
  | React.MouseEvent
9
9
  | React.TouchEvent;
10
10
 
11
+ type StartDrag<T> = (
12
+ evt: AnyTouchEvent,
13
+ draggable: Drag.DraggableOrFiles<T>
14
+ ) => void;
15
+
16
+ type Listeners = {
17
+ onDragOver: (evt: AnyTouchEvent) => void;
18
+ onDrop: (evt: AnyTouchEvent) => void;
19
+ };
20
+
11
21
  function containsFiles(evt: AnyTouchEvent) {
12
22
  if ("dataTransfer" in evt) {
13
23
  const dataTransfer = evt.dataTransfer as DataTransfer;
@@ -52,29 +62,23 @@ function mousePosition(evt: AnyTouchEvent): Drag.Position {
52
62
  export default function useDragUploader<T>(
53
63
  collections: Drag.Collection<T>[],
54
64
  onDragEnd: (dragState: Drag.State<T>, files: File[]) => void
55
- ): [
56
- Drag.State<T>,
57
- (evt: AnyTouchEvent, draggable: Drag.Item<T>) => void,
58
- {
59
- onDragOver: (evt: AnyTouchEvent) => void;
60
- onDrop: (evt: AnyTouchEvent) => void;
61
- }
62
- ] {
63
- const initialState: Drag.State<T> = {
65
+ ): [Drag.State<T>, StartDrag<T>, Listeners] {
66
+ const [dragState, setDragState] = useState<Drag.State<T>>({
64
67
  dragging: false,
65
68
  x: null,
66
69
  y: null
67
- };
68
-
69
- const [dragState, setDragState] = useState(initialState);
70
+ });
70
71
 
71
- const updatePositions = (dragging?: Drag.Draggable<T> | string) => {
72
+ const updatePositions = (dragging?: Drag.DraggableOrFiles<T>) => {
72
73
  collections.forEach((c) => {
73
74
  c.dispatch({ type: "updatePositions", payload: dragging });
74
75
  });
75
76
  };
76
77
 
77
- const startDrag = (evt: AnyTouchEvent, draggable: Drag.Item<T>) => {
78
+ const startDrag = (
79
+ evt: AnyTouchEvent,
80
+ draggable: Drag.DraggableOrFiles<T>
81
+ ) => {
78
82
  updatePositions(draggable);
79
83
  setDragState({ dragging: draggable, ...mousePosition(evt) });
80
84
  };
@@ -84,10 +88,8 @@ export default function useDragUploader<T>(
84
88
  evt.stopPropagation();
85
89
  evt.preventDefault();
86
90
  setDragState({ ...dragState, ...mousePosition(evt) });
87
- } else {
88
- if (containsFiles(evt)) {
89
- startDrag(evt, "Files");
90
- }
91
+ } else if (containsFiles(evt)) {
92
+ startDrag(evt, "Files");
91
93
  }
92
94
  };
93
95