pages_core 3.7.0 → 3.8.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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/app/assets/builds/pages_core/admin-dist.js +55 -0
  4. data/app/assets/stylesheets/pages/admin/components/image_editor.scss +1 -0
  5. data/app/assets/stylesheets/pages/admin/components/image_grid.scss +33 -5
  6. data/app/assets/stylesheets/pages/admin/components/layout.scss +2 -1
  7. data/app/assets/stylesheets/pages/admin/components/login.scss +6 -0
  8. data/app/assets/stylesheets/pages/admin/components/tabs.scss +5 -0
  9. data/app/assets/stylesheets/pages/admin/components/tag_editor.scss +13 -7
  10. data/app/assets/stylesheets/pages/admin/controllers/pages.scss +13 -5
  11. data/app/assets/stylesheets/pages/admin.scss +0 -1
  12. data/app/controller_dummies/admin/admin_controller.rb +1 -1
  13. data/app/controller_dummies/application_controller.rb +1 -1
  14. data/app/controller_dummies/attachments_controller.rb +1 -1
  15. data/app/controller_dummies/frontend_controller.rb +1 -1
  16. data/app/controller_dummies/images_controller.rb +1 -1
  17. data/app/controller_dummies/page_files_controller.rb +1 -1
  18. data/app/controller_dummies/pages_controller.rb +1 -1
  19. data/app/controller_dummies/sitemaps_controller.rb +1 -1
  20. data/app/controllers/admin/attachments_controller.rb +1 -1
  21. data/app/controllers/admin/images_controller.rb +10 -7
  22. data/app/controllers/concerns/pages_core/authentication.rb +9 -4
  23. data/app/controllers/concerns/pages_core/preview_pages_controller.rb +5 -0
  24. data/app/controllers/pages_core/frontend/pages_controller.rb +5 -2
  25. data/app/controllers/sessions_controller.rb +1 -1
  26. data/app/formatters/pages_core/link_renderer.rb +2 -2
  27. data/app/helpers/application_helper.rb +1 -1
  28. data/app/helpers/frontend_helper.rb +1 -1
  29. data/app/helpers/pages_core/admin/content_tabs_helper.rb +5 -2
  30. data/app/helpers/pages_core/admin/image_uploads_helper.rb +2 -3
  31. data/app/helpers/pages_core/admin/tag_editor_helper.rb +9 -39
  32. data/app/helpers/pages_core/head_tags_helper.rb +11 -20
  33. data/app/helpers/pages_core/open_graph_tags_helper.rb +1 -1
  34. data/app/javascript/admin-dist.js +2 -0
  35. data/app/javascript/components/Attachments/Attachment.jsx +121 -0
  36. data/app/javascript/components/Attachments/AttachmentEditor.jsx +116 -0
  37. data/app/javascript/components/Attachments/Placeholder.jsx +10 -0
  38. data/app/javascript/components/Attachments.jsx +165 -0
  39. data/app/{assets/javascripts/pages/admin/components/date_range_select.jsx → javascript/components/DateRangeSelect.jsx} +16 -5
  40. data/app/javascript/components/EditableImage.jsx +61 -0
  41. data/app/{assets/javascripts/pages/admin/components/file_upload_button.jsx → javascript/components/FileUploadButton.jsx} +11 -1
  42. data/app/{assets/javascripts/pages/admin/components/focal_point.jsx → javascript/components/ImageCropper/FocalPoint.jsx} +12 -1
  43. data/app/javascript/components/ImageCropper/Image.jsx +65 -0
  44. data/app/javascript/components/ImageCropper/Toolbar.jsx +73 -0
  45. data/app/javascript/components/ImageCropper/useCrop.js +199 -0
  46. data/app/javascript/components/ImageCropper.jsx +90 -0
  47. data/app/javascript/components/ImageEditor/Form.jsx +98 -0
  48. data/app/javascript/components/ImageEditor.jsx +62 -0
  49. data/app/javascript/components/ImageGrid/DragElement.jsx +30 -0
  50. data/app/javascript/components/ImageGrid/FilePlaceholder.jsx +9 -0
  51. data/app/javascript/components/ImageGrid/GridImage.jsx +103 -0
  52. data/app/javascript/components/ImageGrid/Placeholder.jsx +23 -0
  53. data/app/javascript/components/ImageGrid.jsx +257 -0
  54. data/app/javascript/components/ImageUploader.jsx +171 -0
  55. data/app/{assets/javascripts/pages/admin/components/modal.jsx → javascript/components/Modal.jsx} +13 -2
  56. data/app/javascript/components/ModalStore.jsx +12 -0
  57. data/app/{assets/javascripts/pages/admin/components/page_dates.jsx → javascript/components/PageDates.jsx} +11 -1
  58. data/app/{assets/javascripts/pages/admin/components/page_files.jsx → javascript/components/PageFiles.jsx} +11 -2
  59. data/app/{assets/javascripts/pages/admin/components/page_images.jsx → javascript/components/PageImages.jsx} +11 -2
  60. data/app/{assets/javascripts/pages/admin/components/page_tree_store.jsx → javascript/components/PageTree.jsx} +127 -137
  61. data/app/{assets/javascripts/pages/admin/components/page_tree.jsx → javascript/components/PageTreeDraggable.jsx} +35 -29
  62. data/app/{assets/javascripts/pages/admin/components/page_tree_node.jsx → javascript/components/PageTreeNode.jsx} +35 -20
  63. data/app/javascript/components/RichTextArea.jsx +213 -0
  64. data/app/javascript/components/RichTextToolbarButton.jsx +20 -0
  65. data/app/javascript/components/TagEditor/AddTagForm.jsx +42 -0
  66. data/app/javascript/components/TagEditor/Tag.jsx +32 -0
  67. data/app/javascript/components/TagEditor.jsx +61 -0
  68. data/app/javascript/components/Toast.jsx +72 -0
  69. data/app/javascript/components/ToastStore.jsx +14 -0
  70. data/app/javascript/components/drag/draggedOrder.js +51 -0
  71. data/app/javascript/components/drag/useDragCollection.js +84 -0
  72. data/app/javascript/components/drag/useDragUploader.js +112 -0
  73. data/app/javascript/components/drag/useDraggable.js +17 -0
  74. data/app/javascript/components/drag.js +6 -0
  75. data/app/javascript/components.js +14 -0
  76. data/app/javascript/controllers/EditPageController.js +20 -0
  77. data/app/javascript/controllers/LoginController.js +29 -0
  78. data/app/javascript/controllers/MainController.js +65 -0
  79. data/app/javascript/controllers/PageOptionsController.js +62 -0
  80. data/app/javascript/features/RichText.jsx +34 -0
  81. data/app/javascript/hooks.js +2 -0
  82. data/app/javascript/index.js +33 -0
  83. data/app/{assets/javascripts/pages/admin/lib/tree.jsx → javascript/lib/Tree.js} +55 -54
  84. data/app/javascript/lib/copyToClipboard.js +13 -0
  85. data/app/javascript/lib/readyHandler.js +22 -0
  86. data/app/javascript/lib/request.js +36 -0
  87. data/app/models/concerns/pages_core/page_model/images.rb +3 -1
  88. data/app/models/concerns/pages_core/page_model/searchable.rb +19 -0
  89. data/app/models/concerns/pages_core/searchable_document.rb +71 -0
  90. data/app/models/concerns/pages_core/taggable.rb +27 -12
  91. data/app/models/page.rb +2 -0
  92. data/app/models/page_exporter.rb +2 -2
  93. data/app/models/page_image.rb +0 -2
  94. data/app/models/role.rb +1 -1
  95. data/app/models/search_document.rb +72 -0
  96. data/app/models/tag.rb +1 -0
  97. data/app/models/user.rb +1 -1
  98. data/app/{serializers/admin/attachment_serializer.rb → resources/admin/attachment_resource.rb} +6 -5
  99. data/app/{serializers/admin/image_serializer.rb → resources/admin/image_resource.rb} +9 -9
  100. data/app/resources/admin/page_file_resource.rb +10 -0
  101. data/app/{serializers/admin/page_image_serializer.rb → resources/admin/page_image_resource.rb} +4 -2
  102. data/app/resources/export/attachment_resource.rb +10 -0
  103. data/app/resources/export/page_image_resource.rb +45 -0
  104. data/app/resources/export/page_resource.rb +42 -0
  105. data/app/{serializers/page_image_serializer.rb → resources/page_image_resource.rb} +8 -16
  106. data/app/resources/page_resource.rb +33 -0
  107. data/app/services/pages_core/destroy_invite_service.rb +2 -2
  108. data/app/services/pages_core/invite_service.rb +2 -2
  109. data/app/views/admin/pages/_edit_content.html.erb +1 -1
  110. data/app/views/admin/pages/_edit_files.html.erb +1 -5
  111. data/app/views/admin/pages/_edit_images.html.erb +1 -5
  112. data/app/views/admin/pages/_edit_options.html.erb +74 -55
  113. data/app/views/admin/pages/_form.html.erb +19 -0
  114. data/app/views/admin/pages/edit.html.erb +35 -61
  115. data/app/views/admin/pages/index.html.erb +0 -1
  116. data/app/views/admin/pages/new.html.erb +32 -32
  117. data/app/views/admin/users/_access_control.html.erb +5 -1
  118. data/app/views/admin/users/login.html.erb +12 -4
  119. data/app/views/feeds/pages.rss.builder +1 -2
  120. data/app/views/layouts/admin/_header.html.erb +1 -1
  121. data/app/views/layouts/admin/_page_header.html.erb +33 -0
  122. data/app/views/layouts/admin.html.erb +23 -42
  123. data/app/views/pages_core/_google_analytics.html.erb +8 -0
  124. data/db/migrate/20180625154059_enable_search_extensions.rb +10 -0
  125. data/db/migrate/20210209151400_create_search_configurations.rb +35 -0
  126. data/db/migrate/20210210235200_create_search_documents.rb +74 -0
  127. data/lib/pages_core/engine.rb +1 -5
  128. data/lib/pages_core/templates/block_configuration.rb +1 -1
  129. data/lib/pages_core/templates/configuration_handler.rb +1 -1
  130. data/lib/pages_core/version.rb +1 -1
  131. data/lib/pages_core.rb +3 -5
  132. data/lib/rails/generators/pages_core/frontend/frontend_generator.rb +0 -7
  133. data/lib/rails/generators/pages_core/install/templates/page_templates_initializer.rb +2 -2
  134. metadata +101 -115
  135. data/app/assets/javascripts/pages/admin/components/attachment.jsx +0 -130
  136. data/app/assets/javascripts/pages/admin/components/attachment_editor.jsx +0 -131
  137. data/app/assets/javascripts/pages/admin/components/attachments.jsx +0 -211
  138. data/app/assets/javascripts/pages/admin/components/drag_uploader.jsx +0 -174
  139. data/app/assets/javascripts/pages/admin/components/editable_image.jsx +0 -57
  140. data/app/assets/javascripts/pages/admin/components/grid_image.jsx +0 -124
  141. data/app/assets/javascripts/pages/admin/components/image_editor.jsx +0 -496
  142. data/app/assets/javascripts/pages/admin/components/image_grid.jsx +0 -306
  143. data/app/assets/javascripts/pages/admin/components/image_uploader.jsx +0 -176
  144. data/app/assets/javascripts/pages/admin/components/modal_store.jsx +0 -20
  145. data/app/assets/javascripts/pages/admin/components/rich_text_area.jsx +0 -64
  146. data/app/assets/javascripts/pages/admin/components/rich_text_toolbar.jsx +0 -91
  147. data/app/assets/javascripts/pages/admin/components/toast.jsx +0 -34
  148. data/app/assets/javascripts/pages/admin/components/toast_store.jsx +0 -52
  149. data/app/assets/javascripts/pages/admin/components.jsx +0 -2
  150. data/app/assets/javascripts/pages/admin/features/content_tabs.jsx +0 -72
  151. data/app/assets/javascripts/pages/admin/features/edit_page.jsx +0 -97
  152. data/app/assets/javascripts/pages/admin/features/rich_text.jsx +0 -14
  153. data/app/assets/javascripts/pages/admin/features/tag_editor.jsx +0 -160
  154. data/app/assets/javascripts/pages/admin.jsx +0 -17
  155. data/app/assets/javascripts/pages/login_form.jsx +0 -21
  156. data/app/serializers/admin/page_file_serializer.rb +0 -8
  157. data/app/serializers/page_export_serializer.rb +0 -32
  158. data/app/serializers/page_file_export_serializer.rb +0 -6
  159. data/app/serializers/page_image_export_serializer.rb +0 -42
  160. data/app/serializers/page_serializer.rb +0 -23
  161. data/app/views/layouts/admin/_analytics.html.erb +0 -16
  162. data/lib/rails/generators/pages_core/frontend/templates/application.js.erb +0 -15
  163. data/vendor/assets/javascripts/ReactCrop.min.js +0 -1
  164. data/vendor/assets/javascripts/reflux.min.js +0 -1
@@ -0,0 +1,257 @@
1
+ import React, { useRef, useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import FileUploadButton from "./FileUploadButton";
4
+ import DragElement from "./ImageGrid/DragElement";
5
+ import FilePlaceholder from "./ImageGrid/FilePlaceholder";
6
+ import GridImage from "./ImageGrid/GridImage";
7
+ import ToastStore from "./ToastStore";
8
+ import { post } from "../lib/request";
9
+
10
+ import { createDraggable,
11
+ collectionOrder,
12
+ useDragCollection,
13
+ useDragUploader } from "./drag";
14
+
15
+ function filterFiles(files) {
16
+ const validMimeTypes = ["image/gif",
17
+ "image/jpeg",
18
+ "image/pjpeg",
19
+ "image/png",
20
+ "image/tiff"];
21
+ return files.filter(f => (validMimeTypes.indexOf(f.type) !== -1));
22
+ }
23
+
24
+ function draggedImageOrder(primaryCollection, imagesCollection, dragState) {
25
+ const [primary, ...rest] = collectionOrder(primaryCollection, dragState);
26
+ let images = [...rest, ...collectionOrder(imagesCollection, dragState)];
27
+
28
+ if (dragState.dragging && [primary, ...images].indexOf(dragState.dragging) === -1) {
29
+ if (dragState.y < imagesCollection.ref.current.getBoundingClientRect().top) {
30
+ images = [dragState.dragging, ...images];
31
+ } else {
32
+ images.push(dragState.dragging);
33
+ }
34
+ }
35
+
36
+ return [primary, images];
37
+ }
38
+
39
+ function initRecords(props) {
40
+ const primary = props.enablePrimary ?
41
+ props.records.filter(r => r.primary).slice(0, 1) :
42
+ [];
43
+
44
+ return [primary, props.records.filter(r => primary.indexOf(r) === -1)];
45
+ }
46
+
47
+ export default function ImageGrid(props) {
48
+ const [initPrimary, initImages] = initRecords(props);
49
+ const primary = useDragCollection(initPrimary);
50
+ const images = useDragCollection(initImages);
51
+ const [deleted, setDeleted] = useState([]);
52
+
53
+ const containerRef = useRef();
54
+
55
+ const dispatchAll = (action) => {
56
+ primary.dispatch(action);
57
+ images.dispatch(action);
58
+ };
59
+
60
+ const dragEnd = (dragState, files) => {
61
+ const [draggedPrimary,
62
+ draggedImages] = draggedImageOrder(primary, images, dragState);
63
+
64
+ primary.dispatch({
65
+ type: "reorder",
66
+ payload: draggedPrimary ? [draggedPrimary] : []
67
+ });
68
+ images.dispatch({ type: "reorder", payload: draggedImages });
69
+
70
+ if (files) {
71
+ const uploads = filterFiles(files).map(f => uploadImage(f));
72
+ dispatchAll({ type: "insertFiles", payload: uploads });
73
+ }
74
+ };
75
+
76
+ const [dragState,
77
+ dragStart,
78
+ listeners] = useDragUploader([primary, images], dragEnd);
79
+
80
+ const position = (record) => {
81
+ return [...primary.draggables.map(d => d.record),
82
+ ...images.draggables.map(d => d.record),
83
+ ...deleted].indexOf(record) + 1;
84
+ };
85
+
86
+ const attrName = (record) => {
87
+ return `${props.attribute}[${position(record)}]`;
88
+ };
89
+
90
+ const uploadImage = (file) => {
91
+ const draggable = createDraggable(
92
+ { image: null, file: file }
93
+ );
94
+
95
+ let data = new FormData();
96
+
97
+ data.append("image[file]", file);
98
+
99
+ post("/admin/images.json", data)
100
+ .then(json => {
101
+ if (json.status === "error") {
102
+ ToastStore.dispatch({
103
+ type: "ERROR", message: "Error uploading image: " + json.error
104
+ });
105
+ dispatchAll({ type: "remove", payload: draggable });
106
+ } else {
107
+ dispatchAll({
108
+ type: "update",
109
+ payload: { ...draggable, record: { image: json } }
110
+ });
111
+ }
112
+ });
113
+
114
+ return draggable;
115
+ };
116
+
117
+ const update = (draggable) => (image) => {
118
+ const { record } = draggable;
119
+ const updated = {
120
+ ...draggable,
121
+ record: {
122
+ ...record,
123
+ image: { ...record.image, ...image }
124
+ }
125
+ };
126
+ dispatchAll({ type: "update", payload: updated });
127
+ };
128
+
129
+ const remove = (draggable) => () => {
130
+ dispatchAll({ type: "remove", payload: draggable });
131
+ if (draggable.record.id) {
132
+ setDeleted([...deleted, draggable.record]);
133
+ }
134
+ };
135
+
136
+ const renderImage = (draggable, isPrimary) => {
137
+ const { dragging } = dragState;
138
+
139
+ if (draggable === "Files") {
140
+ return (<FilePlaceholder key="placeholder" />);
141
+ }
142
+
143
+ return (
144
+ <GridImage key={draggable.handle}
145
+ draggable={draggable}
146
+ locale={props.locale}
147
+ locales={props.locales}
148
+ showEmbed={props.showEmbed}
149
+ startDrag={dragStart}
150
+ position={position(draggable.record)}
151
+ primary={isPrimary}
152
+ onUpdate={update(draggable)}
153
+ enablePrimary={props.enablePrimary}
154
+ deleteImage={remove(draggable)}
155
+ attributeName={attrName(draggable.record)}
156
+ placeholder={dragging && dragging == draggable} />
157
+ );
158
+ };
159
+
160
+ const uploadPrimary = (files) => {
161
+ const [first, ...rest] = filterFiles(files).map(f => uploadImage(f));
162
+ if (first) {
163
+ images.dispatch({
164
+ type: "prepend",
165
+ payload: [...primary.draggables, ...rest]
166
+ });
167
+ primary.dispatch({ type: "replace", payload: [first] });
168
+ }
169
+ };
170
+
171
+ const uploadAdditional = (files) => {
172
+ images.dispatch({
173
+ type: "append",
174
+ payload: filterFiles(files).map(f => uploadImage(f))
175
+ });
176
+ };
177
+
178
+ let classNames = ["image-grid"];
179
+ if (props.enablePrimary) {
180
+ classNames.push("with-primary-image");
181
+ }
182
+
183
+ const [draggedPrimary,
184
+ draggedImages] = draggedImageOrder(primary, images, dragState);
185
+
186
+ return (
187
+ <div className={classNames.join(" ")}
188
+ ref={containerRef}
189
+ {...listeners}>
190
+ {dragState.dragging &&
191
+ <DragElement draggable={dragState.dragging}
192
+ dragState={dragState}
193
+ container={containerRef} />}
194
+ {props.enablePrimary && (
195
+ <div className="primary-image" ref={primary.ref}>
196
+ <h3>
197
+ Main image
198
+ </h3>
199
+ {draggedPrimary &&
200
+ <>
201
+ {renderImage(draggedPrimary, true)}
202
+ {props.primaryAttribute && (
203
+ <input type="hidden" name={props.primaryAttribute}
204
+ value={(draggedPrimary.record &&
205
+ draggedPrimary.record.image &&
206
+ draggedPrimary.record.image.id) || ""} />
207
+ )}
208
+ </>}
209
+ {!draggedPrimary && (
210
+ <div className="drop-target">
211
+ <FileUploadButton multiple={true}
212
+ type="image"
213
+ multiline={true}
214
+ callback={uploadPrimary} />
215
+ </div>)}
216
+ </div>
217
+ )}
218
+ <div className="grid" ref={images.ref}>
219
+ <h3>
220
+ {props.enablePrimary ? "More images" : "Images"}
221
+ </h3>
222
+ <div className="drop-target">
223
+ <FileUploadButton multiple={true}
224
+ type="image"
225
+ callback={uploadAdditional} />
226
+ </div>
227
+ <div className="images">
228
+ {draggedImages.map(r => renderImage(r, false))}
229
+ </div>
230
+ </div>
231
+ <div className="deleted">
232
+ {deleted.map(r =>
233
+ <span className="deleted-image" key={r.id}>
234
+ <input name={`${attrName(r)}[id]`}
235
+ type="hidden"
236
+ value={r.id} />
237
+ <input name={`${attrName(r)}[attachment_id]`}
238
+ type="hidden"
239
+ value={(r.image && r.image.id) || ""} />
240
+ <input name={`${attrName(r)}[_destroy]`}
241
+ type="hidden"
242
+ value={true} />
243
+ </span>)}
244
+ </div>
245
+ </div>
246
+ );
247
+ }
248
+
249
+ ImageGrid.propTypes = {
250
+ attribute: PropTypes.string,
251
+ locale: PropTypes.string,
252
+ locales: PropTypes.array,
253
+ records: PropTypes.array,
254
+ enablePrimary: PropTypes.bool,
255
+ primaryAttribute: PropTypes.string,
256
+ showEmbed: PropTypes.bool
257
+ };
@@ -0,0 +1,171 @@
1
+ import React, { useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import EditableImage from "./EditableImage";
4
+ import FileUploadButton from "./FileUploadButton";
5
+ import ToastStore from "./ToastStore";
6
+ import { post } from "../lib/request";
7
+
8
+ function getFiles(dt) {
9
+ var files = [];
10
+ if (dt.items) {
11
+ for (let i = 0; i < dt.items.length; i++) {
12
+ if (dt.items[i].kind == "file") {
13
+ files.push(dt.items[i].getAsFile());
14
+ }
15
+ }
16
+ } else {
17
+ for (let i = 0; i < dt.files.length; i++) {
18
+ files.push(dt.files[i]);
19
+ }
20
+ }
21
+ return files;
22
+ }
23
+
24
+ export default function ImageUploader(props) {
25
+ const [uploading, setUploading] = useState(false);
26
+ const [dragover, setDragover] = useState(false);
27
+ const [image, setImage] = useState(props.image);
28
+ const [src, setSrc] = useState(props.src);
29
+
30
+ const handleDragOver = (evt) => {
31
+ evt.preventDefault();
32
+ setDragover(true);
33
+ };
34
+
35
+ const handleDragLeave = () => {
36
+ setDragover(false);
37
+ };
38
+
39
+ const handleDragEnd = (evt) => {
40
+ if (evt.dataTransfer.items) {
41
+ for (var i = 0; i < evt.dataTransfer.items.length; i++) {
42
+ evt.dataTransfer.items.remove(i);
43
+ }
44
+ } else {
45
+ evt.dataTransfer.clearData();
46
+ }
47
+ setDragover(false);
48
+ };
49
+
50
+ const handleDrop = (evt) => {
51
+ let files = getFiles(evt.dataTransfer);
52
+ evt.preventDefault();
53
+ if (files.length > 0) {
54
+ uploadImage(files[0]);
55
+ }
56
+ };
57
+
58
+ const handleRemove = (evt) => {
59
+ evt.preventDefault();
60
+ setImage(null);
61
+ setSrc(null);
62
+ };
63
+
64
+ const receiveFiles = (files) => {
65
+ if (files.length > 0) {
66
+ uploadImage(files[0]);
67
+ }
68
+ };
69
+
70
+ const uploadImage = (file) => {
71
+ let validTypes = ["image/gif",
72
+ "image/jpeg",
73
+ "image/pjpeg",
74
+ "image/png",
75
+ "image/tiff"];
76
+
77
+ if (validTypes.indexOf(file.type) == -1) {
78
+ alert("Invalid file type, only images in JPEG, PNG or GIF " +
79
+ "formats are supported");
80
+ return;
81
+ }
82
+
83
+ let locale = props.locale;
84
+ let locales = props.locales ? Object.keys(props.locales) : [locale];
85
+
86
+ let data = new FormData();
87
+
88
+ setImage(null);
89
+ setSrc(null);
90
+ setDragover(false);
91
+ setUploading(true);
92
+
93
+ data.append("image[file]", file);
94
+ locales.forEach((l) => {
95
+ data.append(`image[alternative][${l}]`, (props.alternative || ""));
96
+ });
97
+
98
+ post("/admin/images.json", data)
99
+ .then(response => {
100
+ setUploading(false);
101
+ if (response.status === "error") {
102
+ ToastStore.dispatch({
103
+ type: "ERROR",
104
+ message: "Error uploading image: " + response.error
105
+ });
106
+ } else {
107
+ setSrc(response.thumbnail_url);
108
+ setImage(response);
109
+ }
110
+ });
111
+ };
112
+
113
+ let classes = ["image-uploader"];
114
+ if (uploading) {
115
+ classes.push("uploading");
116
+ } else if (dragover) {
117
+ classes.push("dragover");
118
+ }
119
+ return (
120
+ <div className={classes.join(" ")}
121
+ onDragOver={handleDragOver}
122
+ onDragLeave={handleDragLeave}
123
+ onDragEnd={handleDragEnd}
124
+ onDrop={handleDrop}>
125
+ <input type="hidden"
126
+ name={props.attr}
127
+ value={image ? image.id : ""} />
128
+ {image &&
129
+ <div className="image">
130
+ <EditableImage image={image}
131
+ src={src}
132
+ width={props.width}
133
+ caption={props.caption}
134
+ locale={props.locale}
135
+ locales={props.locales} />
136
+ </div>}
137
+ <div className="ui-wrapper">
138
+ {uploading && (
139
+ <div className="ui">
140
+ Uploading image...
141
+ </div>
142
+ )}
143
+ {!uploading && (
144
+ <div className="ui">
145
+ <FileUploadButton type="image"
146
+ multiline={true}
147
+ callback={receiveFiles} />
148
+ {image && (
149
+ <a className="delete remove-image"
150
+ href="#"
151
+ onClick={handleRemove}>
152
+ Remove image
153
+ </a>
154
+ )}
155
+ </div>
156
+ )}
157
+ </div>
158
+ </div>
159
+ );
160
+ }
161
+
162
+ ImageUploader.propTypes = {
163
+ locale: PropTypes.string,
164
+ locales: PropTypes.object,
165
+ image: PropTypes.object,
166
+ src: PropTypes.string,
167
+ width: PropTypes.number,
168
+ caption: PropTypes.bool,
169
+ attr: PropTypes.string,
170
+ alternative: PropTypes.string
171
+ };
@@ -1,23 +1,34 @@
1
- class Modal extends Reflux.Component {
1
+ import React from "react";
2
+ import ModalStore from "./ModalStore";
3
+
4
+ export default class Modal extends React.Component {
2
5
  constructor(props) {
3
6
  super(props);
7
+ this.state = { component: null };
4
8
  this.store = ModalStore;
5
9
  this.closeModal = this.closeModal.bind(this);
10
+ this.handleChange = this.handleChange.bind(this);
6
11
  this.handleKeypress = this.handleKeypress.bind(this);
7
12
  }
8
13
 
9
14
  componentDidMount() {
15
+ this.unsubscribe = this.store.subscribe(this.handleChange);
10
16
  window.addEventListener("keypress", this.handleKeypress);
11
17
  }
12
18
 
13
19
  componentWillUnmount() {
20
+ this.unsubscribe();
14
21
  window.removeEventListener("keypress", this.handleKeypress);
15
22
  }
16
23
 
17
24
  closeModal(evt) {
18
25
  evt.stopPropagation();
19
26
  evt.preventDefault();
20
- ModalActions.close();
27
+ ModalStore.dispatch({ type: "CLOSE" });
28
+ }
29
+
30
+ handleChange() {
31
+ this.setState({ ...this.store.getState() });
21
32
  }
22
33
 
23
34
  handleKeypress(evt) {
@@ -0,0 +1,12 @@
1
+ import { createStore } from "redux";
2
+
3
+ export default createStore((state = {}, action) => {
4
+ switch(action.type) {
5
+ case "OPEN":
6
+ return {...state, component: action.payload };
7
+ case "CLOSE":
8
+ return {...state, component: null };
9
+ default:
10
+ return state;
11
+ }
12
+ });
@@ -1,4 +1,8 @@
1
- class PageDates extends React.Component {
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import DateRangeSelect from "./DateRangeSelect";
4
+
5
+ export default class PageDates extends React.Component {
2
6
  constructor(props) {
3
7
  super(props);
4
8
  this.state = {
@@ -56,3 +60,9 @@ class PageDates extends React.Component {
56
60
  );
57
61
  }
58
62
  }
63
+
64
+ PageDates.propTypes = {
65
+ starts_at: PropTypes.string,
66
+ ends_at: PropTypes.string,
67
+ all_day: PropTypes.bool
68
+ };
@@ -1,4 +1,8 @@
1
- class PageFiles extends React.Component {
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import Attachments from "./Attachments";
4
+
5
+ export default class PageFiles extends React.Component {
2
6
  render() {
3
7
  return (
4
8
  <div className="page-files">
@@ -6,9 +10,14 @@ class PageFiles extends React.Component {
6
10
  showEmbed={true}
7
11
  locale={this.props.locale}
8
12
  locales={this.props.locales}
9
- csrf_token={this.props.csrf_token}
10
13
  records={this.props.records} />
11
14
  </div>
12
15
  );
13
16
  }
14
17
  }
18
+
19
+ PageFiles.propTypes = {
20
+ locale: PropTypes.string,
21
+ locales: PropTypes.object,
22
+ records: PropTypes.array
23
+ };
@@ -1,4 +1,8 @@
1
- class PageImages extends React.Component {
1
+ import React from "react";
2
+ import PropTypes from "prop-types";
3
+ import ImageGrid from "./ImageGrid";
4
+
5
+ export default class PageImages extends React.Component {
2
6
  render() {
3
7
  return (
4
8
  <div className="page-images">
@@ -8,9 +12,14 @@ class PageImages extends React.Component {
8
12
  showEmbed={true}
9
13
  locale={this.props.locale}
10
14
  locales={this.props.locales}
11
- csrf_token={this.props.csrf_token}
12
15
  records={this.props.records} />
13
16
  </div>
14
17
  );
15
18
  }
16
19
  }
20
+
21
+ PageImages.propTypes = {
22
+ locale: PropTypes.string,
23
+ locales: PropTypes.object,
24
+ records: PropTypes.array
25
+ };