pages_core 3.12.4 → 3.12.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/app/assets/builds/pages_core/admin-dist.js +8 -43
- data/app/assets/builds/pages_core/admin-dist.js.map +4 -4
- data/app/assets/builds/pages_core/admin.css +264 -133
- data/app/assets/stylesheets/pages_core/admin/components/attachments.css +3 -4
- data/app/assets/stylesheets/pages_core/admin/components/forms.css +17 -16
- data/app/assets/stylesheets/pages_core/admin/components/image_editor.css +8 -4
- data/app/assets/stylesheets/pages_core/admin/components/image_grid.css +1 -1
- data/app/assets/stylesheets/pages_core/admin/components/list_table.css +11 -3
- data/app/assets/stylesheets/pages_core/admin/components/modal.css +9 -5
- data/app/assets/stylesheets/pages_core/admin/components/page_tree.css +5 -1
- data/app/assets/stylesheets/pages_core/admin/components/toast.css +2 -2
- data/app/assets/stylesheets/pages_core/admin/controllers/pages.css +4 -2
- data/app/assets/stylesheets/pages_core/admin/vars.css +2 -1
- data/app/controllers/admin/calendars_controller.rb +2 -2
- data/app/controllers/admin/categories_controller.rb +3 -3
- data/app/controllers/admin/news_controller.rb +6 -6
- data/app/controllers/admin/pages_controller.rb +12 -11
- data/app/controllers/admin/users_controller.rb +1 -1
- data/app/controllers/concerns/pages_core/preview_pages_controller.rb +15 -17
- data/app/controllers/pages_core/admin_controller.rb +2 -2
- data/app/controllers/pages_core/base_controller.rb +1 -8
- data/app/controllers/pages_core/frontend/pages_controller.rb +13 -5
- data/app/controllers/pages_core/frontend_controller.rb +12 -7
- data/app/helpers/admin/menu_helper.rb +2 -0
- data/app/helpers/admin/pages_helper.rb +1 -4
- data/app/helpers/pages_core/admin/admin_helper.rb +0 -1
- data/app/helpers/pages_core/admin/content_tabs_helper.rb +9 -2
- data/app/helpers/pages_core/application_helper.rb +2 -3
- data/app/helpers/pages_core/frontend_helper.rb +1 -1
- data/app/helpers/pages_core/head_tags_helper.rb +15 -46
- data/app/helpers/pages_core/images_helper.rb +76 -21
- data/app/helpers/pages_core/locales_helper.rb +9 -0
- data/app/helpers/pages_core/open_graph_tags_helper.rb +3 -5
- data/app/helpers/pages_core/page_path_helper.rb +1 -1
- data/app/javascript/components/Attachments/Attachment.tsx +55 -52
- data/app/javascript/components/Attachments/AttachmentEditor.tsx +45 -50
- data/app/javascript/components/Attachments/Placeholder.tsx +1 -2
- data/app/javascript/components/Attachments.jsx +69 -57
- data/app/javascript/components/DateRangeSelect.jsx +94 -54
- data/app/javascript/components/EditableImage.tsx +20 -16
- data/app/javascript/components/FileUploadButton.tsx +12 -12
- data/app/javascript/components/ImageCropper/FocalPoint.tsx +22 -20
- data/app/javascript/components/ImageCropper/Image.tsx +20 -16
- data/app/javascript/components/ImageCropper/Toolbar.tsx +35 -27
- data/app/javascript/components/ImageCropper/useCrop.ts +105 -91
- data/app/javascript/components/ImageCropper.tsx +34 -25
- data/app/javascript/components/ImageEditor/Form.tsx +32 -43
- data/app/javascript/components/ImageEditor.tsx +29 -21
- data/app/javascript/components/ImageGrid/DragElement.tsx +6 -4
- data/app/javascript/components/ImageGrid/GridImage.tsx +56 -52
- data/app/javascript/components/ImageGrid/Placeholder.tsx +1 -1
- data/app/javascript/components/ImageGrid.jsx +132 -101
- data/app/javascript/components/ImageUploader.tsx +59 -55
- data/app/javascript/components/Modal.tsx +2 -4
- data/app/javascript/components/PageDates.jsx +25 -20
- data/app/javascript/components/PageFiles.jsx +7 -5
- data/app/javascript/components/PageImages.tsx +9 -7
- data/app/javascript/components/PageTree/Draggable.tsx +46 -40
- data/app/javascript/components/PageTree/Node.tsx +111 -95
- data/app/javascript/components/PageTree/types.ts +9 -9
- data/app/javascript/components/PageTree.tsx +44 -29
- data/app/javascript/components/RichTextArea.jsx +51 -37
- data/app/javascript/components/RichTextToolbarButton.tsx +8 -5
- data/app/javascript/components/TagEditor/AddTagForm.tsx +11 -10
- data/app/javascript/components/TagEditor/Tag.tsx +10 -8
- data/app/javascript/components/TagEditor.tsx +15 -10
- data/app/javascript/components/Toast.tsx +3 -7
- data/app/javascript/components/drag/draggedOrder.ts +16 -15
- data/app/javascript/components/drag/types.ts +12 -12
- data/app/javascript/components/drag/useDragCollection.ts +36 -42
- data/app/javascript/components/drag/useDragUploader.ts +3 -2
- data/app/javascript/components/drag.ts +5 -4
- data/app/javascript/controllers/LoginController.ts +0 -1
- data/app/javascript/controllers/MainController.ts +6 -2
- data/app/javascript/controllers/PageOptionsController.js +7 -2
- data/app/javascript/features/RichText.tsx +9 -7
- data/app/javascript/index.ts +5 -3
- data/app/javascript/lib/Tree.ts +27 -24
- data/app/javascript/lib/copyToClipboard.ts +5 -4
- data/app/javascript/lib/readyHandler.ts +4 -4
- data/app/javascript/lib/request.ts +7 -3
- data/app/javascript/stores/useModalStore.ts +3 -3
- data/app/javascript/stores/useToastStore.ts +14 -12
- data/app/javascript/types.ts +22 -22
- data/app/models/concerns/pages_core/page_model/templateable.rb +1 -1
- data/app/views/admin/calendars/show.html.erb +1 -1
- data/app/views/admin/news/index.html.erb +1 -1
- data/app/views/admin/pages/_edit_files.html.erb +1 -1
- data/app/views/admin/pages/_edit_images.html.erb +1 -1
- data/app/views/admin/pages/_list_item.html.erb +1 -1
- data/app/views/admin/pages/_search_bar.html.erb +1 -1
- data/app/views/admin/pages/deleted.html.erb +2 -2
- data/app/views/admin/pages/edit.html.erb +3 -3
- data/app/views/admin/pages/index.html.erb +4 -4
- data/app/views/admin/pages/new.html.erb +1 -1
- data/app/views/admin/pages/search.html.erb +3 -3
- data/app/views/feeds/pages.rss.builder +2 -2
- data/app/views/layouts/admin/_page_header.html.erb +4 -4
- data/app/views/layouts/admin.html.erb +1 -2
- data/config/locales/en.yml +1 -0
- data/lib/pages_core/pages_plugin.rb +5 -3
- data/lib/rails/generators/pages_core/frontend/templates/application.html.erb +15 -13
- data/lib/tasks/pages/reports.rake +26 -0
- metadata +32 -4
- data/app/helpers/pages_core/admin/deprecated_admin_helper.rb +0 -40
- data/app/views/pages_core/_google_analytics.html.erb +0 -8
@@ -4,9 +4,9 @@ import { ImageResource } from "../../types";
|
|
4
4
|
import { DragState } from "../drag";
|
5
5
|
|
6
6
|
interface DragElementProps {
|
7
|
-
container: RefObject<HTMLDivElement
|
8
|
-
draggable: string | { record: { image: ImageResource
|
9
|
-
dragState: DragState
|
7
|
+
container: RefObject<HTMLDivElement>;
|
8
|
+
draggable: string | { record: { image: ImageResource; src?: string } };
|
9
|
+
dragState: DragState;
|
10
10
|
}
|
11
11
|
|
12
12
|
export default function DragElement(props: DragElementProps) {
|
@@ -24,7 +24,9 @@ export default function DragElement(props: DragElementProps) {
|
|
24
24
|
return (
|
25
25
|
<div className="drag-image" style={translateStyle}>
|
26
26
|
{"record" in draggable && draggable.record.image && (
|
27
|
-
<img
|
27
|
+
<img
|
28
|
+
src={draggable.record.src || draggable.record.image.thumbnail_url}
|
29
|
+
/>
|
28
30
|
)}
|
29
31
|
</div>
|
30
32
|
);
|
@@ -8,25 +8,25 @@ import Placeholder from "./Placeholder";
|
|
8
8
|
import { useDraggable } from "../drag";
|
9
9
|
|
10
10
|
interface Record {
|
11
|
-
id: number | null
|
12
|
-
image: ImageResource
|
13
|
-
src: string | null
|
14
|
-
file: File | null
|
11
|
+
id: number | null;
|
12
|
+
image: ImageResource;
|
13
|
+
src: string | null;
|
14
|
+
file: File | null;
|
15
15
|
}
|
16
16
|
|
17
17
|
interface GridImageProps {
|
18
|
-
draggable: { handle: string
|
19
|
-
attributeName: string
|
20
|
-
locale: string
|
21
|
-
locales: { [index: string]: Locale }
|
22
|
-
placeholder: boolean
|
23
|
-
enablePrimary: boolean
|
24
|
-
showEmbed: boolean
|
25
|
-
primary: boolean
|
26
|
-
position: number
|
27
|
-
deleteImage: () => void
|
28
|
-
startDrag: (evt: Event, draggable: Draggable) => void
|
29
|
-
onUpdate: (newImage: ImageResource, src: string) => void
|
18
|
+
draggable: { handle: string; record: Record };
|
19
|
+
attributeName: string;
|
20
|
+
locale: string;
|
21
|
+
locales: { [index: string]: Locale };
|
22
|
+
placeholder: boolean;
|
23
|
+
enablePrimary: boolean;
|
24
|
+
showEmbed: boolean;
|
25
|
+
primary: boolean;
|
26
|
+
position: number;
|
27
|
+
deleteImage: () => void;
|
28
|
+
startDrag: (evt: Event, draggable: Draggable) => void;
|
29
|
+
onUpdate: (newImage: ImageResource, src: string) => void;
|
30
30
|
}
|
31
31
|
|
32
32
|
export default function GridImage(props: GridImageProps) {
|
@@ -70,44 +70,48 @@ export default function GridImage(props: GridImageProps) {
|
|
70
70
|
}
|
71
71
|
|
72
72
|
return (
|
73
|
-
<div className={classes.join(" ")}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
<input
|
80
|
-
|
73
|
+
<div className={classes.join(" ")} {...dragAttrs}>
|
74
|
+
<input
|
75
|
+
name={`${attributeName}[id]`}
|
76
|
+
type="hidden"
|
77
|
+
value={record.id || ""}
|
78
|
+
/>
|
79
|
+
<input
|
80
|
+
name={`${attributeName}[image_id]`}
|
81
|
+
type="hidden"
|
82
|
+
value={(image && image.id) || ""}
|
83
|
+
/>
|
84
|
+
<input
|
85
|
+
name={`${attributeName}[position]`}
|
86
|
+
type="hidden"
|
87
|
+
value={props.position}
|
88
|
+
/>
|
81
89
|
{props.enablePrimary && (
|
82
|
-
<input
|
83
|
-
|
90
|
+
<input
|
91
|
+
name={`${attributeName}[primary]`}
|
92
|
+
type="hidden"
|
93
|
+
value={props.primary}
|
94
|
+
/>
|
95
|
+
)}
|
96
|
+
{!image && <Placeholder src={src} />}
|
97
|
+
{image && (
|
98
|
+
<>
|
99
|
+
<EditableImage
|
100
|
+
image={image}
|
101
|
+
key={props.placeholder ? "placeholder" : draggable.handle}
|
102
|
+
src={src || image.thumbnail_url}
|
103
|
+
width={250}
|
104
|
+
caption={true}
|
105
|
+
locale={props.locale}
|
106
|
+
locales={props.locales}
|
107
|
+
onUpdate={props.onUpdate}
|
108
|
+
/>
|
109
|
+
<div className="actions">
|
110
|
+
{props.showEmbed && <button onClick={copyEmbed}>Embed</button>}
|
111
|
+
{props.deleteImage && <button onClick={deleteImage}>Remove</button>}
|
112
|
+
</div>
|
113
|
+
</>
|
84
114
|
)}
|
85
|
-
{!image &&
|
86
|
-
<Placeholder src={src} />}
|
87
|
-
{image &&
|
88
|
-
<>
|
89
|
-
<EditableImage
|
90
|
-
image={image}
|
91
|
-
key={props.placeholder ? "placeholder" : draggable.handle}
|
92
|
-
src={src || image.thumbnail_url}
|
93
|
-
width={250}
|
94
|
-
caption={true}
|
95
|
-
locale={props.locale}
|
96
|
-
locales={props.locales}
|
97
|
-
onUpdate={props.onUpdate} />
|
98
|
-
<div className="actions">
|
99
|
-
{props.showEmbed && (
|
100
|
-
<button onClick={copyEmbed}>
|
101
|
-
Embed
|
102
|
-
</button>
|
103
|
-
)}
|
104
|
-
{props.deleteImage && (
|
105
|
-
<button onClick={deleteImage}>
|
106
|
-
Remove
|
107
|
-
</button>
|
108
|
-
)}
|
109
|
-
</div>
|
110
|
-
</>}
|
111
115
|
</div>
|
112
116
|
);
|
113
117
|
}
|
@@ -7,26 +7,35 @@ import GridImage from "./ImageGrid/GridImage";
|
|
7
7
|
import useToastStore from "../stores/useToastStore";
|
8
8
|
import { post } from "../lib/request";
|
9
9
|
|
10
|
-
import {
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
import {
|
11
|
+
createDraggable,
|
12
|
+
collectionOrder,
|
13
|
+
useDragCollection,
|
14
|
+
useDragUploader
|
15
|
+
} from "./drag";
|
14
16
|
|
15
17
|
function filterFiles(files) {
|
16
|
-
const validMimeTypes = [
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
const validMimeTypes = [
|
19
|
+
"image/gif",
|
20
|
+
"image/jpeg",
|
21
|
+
"image/pjpeg",
|
22
|
+
"image/png",
|
23
|
+
"image/tiff"
|
24
|
+
];
|
25
|
+
return files.filter((f) => validMimeTypes.indexOf(f.type) !== -1);
|
22
26
|
}
|
23
27
|
|
24
28
|
function draggedImageOrder(primaryCollection, imagesCollection, dragState) {
|
25
29
|
const [primary, ...rest] = collectionOrder(primaryCollection, dragState);
|
26
30
|
let images = [...rest, ...collectionOrder(imagesCollection, dragState)];
|
27
31
|
|
28
|
-
if (
|
29
|
-
|
32
|
+
if (
|
33
|
+
dragState.dragging &&
|
34
|
+
[primary, ...images].indexOf(dragState.dragging) === -1
|
35
|
+
) {
|
36
|
+
if (
|
37
|
+
dragState.y < imagesCollection.ref.current.getBoundingClientRect().top
|
38
|
+
) {
|
30
39
|
images = [dragState.dragging, ...images];
|
31
40
|
} else {
|
32
41
|
images.push(dragState.dragging);
|
@@ -37,11 +46,11 @@ function draggedImageOrder(primaryCollection, imagesCollection, dragState) {
|
|
37
46
|
}
|
38
47
|
|
39
48
|
function initRecords(props) {
|
40
|
-
const primary = props.enablePrimary
|
41
|
-
|
42
|
-
|
49
|
+
const primary = props.enablePrimary
|
50
|
+
? props.records.filter((r) => r.primary).slice(0, 1)
|
51
|
+
: [];
|
43
52
|
|
44
|
-
return [primary, props.records.filter(r => primary.indexOf(r) === -1)];
|
53
|
+
return [primary, props.records.filter((r) => primary.indexOf(r) === -1)];
|
45
54
|
}
|
46
55
|
|
47
56
|
export default function ImageGrid(props) {
|
@@ -59,8 +68,11 @@ export default function ImageGrid(props) {
|
|
59
68
|
};
|
60
69
|
|
61
70
|
const dragEnd = (dragState, files) => {
|
62
|
-
const [draggedPrimary,
|
63
|
-
|
71
|
+
const [draggedPrimary, draggedImages] = draggedImageOrder(
|
72
|
+
primary,
|
73
|
+
images,
|
74
|
+
dragState
|
75
|
+
);
|
64
76
|
|
65
77
|
primary.dispatch({
|
66
78
|
type: "reorder",
|
@@ -69,19 +81,24 @@ export default function ImageGrid(props) {
|
|
69
81
|
images.dispatch({ type: "reorder", payload: draggedImages });
|
70
82
|
|
71
83
|
if (files) {
|
72
|
-
const uploads = filterFiles(files).map(f => uploadImage(f));
|
84
|
+
const uploads = filterFiles(files).map((f) => uploadImage(f));
|
73
85
|
dispatchAll({ type: "insertFiles", payload: uploads });
|
74
86
|
}
|
75
87
|
};
|
76
88
|
|
77
|
-
const [dragState,
|
78
|
-
|
79
|
-
|
89
|
+
const [dragState, dragStart, listeners] = useDragUploader(
|
90
|
+
[primary, images],
|
91
|
+
dragEnd
|
92
|
+
);
|
80
93
|
|
81
94
|
const position = (record) => {
|
82
|
-
return
|
83
|
-
|
84
|
-
|
95
|
+
return (
|
96
|
+
[
|
97
|
+
...primary.draggables.map((d) => d.record),
|
98
|
+
...images.draggables.map((d) => d.record),
|
99
|
+
...deleted
|
100
|
+
].indexOf(record) + 1
|
101
|
+
);
|
85
102
|
};
|
86
103
|
|
87
104
|
const attrName = (record) => {
|
@@ -89,26 +106,23 @@ export default function ImageGrid(props) {
|
|
89
106
|
};
|
90
107
|
|
91
108
|
const uploadImage = (file) => {
|
92
|
-
const draggable = createDraggable(
|
93
|
-
{ image: null, file: file }
|
94
|
-
);
|
109
|
+
const draggable = createDraggable({ image: null, file: file });
|
95
110
|
|
96
111
|
let data = new FormData();
|
97
112
|
|
98
113
|
data.append("image[file]", file);
|
99
114
|
|
100
|
-
post("/admin/images.json", data)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
});
|
115
|
+
post("/admin/images.json", data).then((json) => {
|
116
|
+
if (json.status === "error") {
|
117
|
+
error("Error uploading image: " + json.error);
|
118
|
+
dispatchAll({ type: "remove", payload: draggable });
|
119
|
+
} else {
|
120
|
+
dispatchAll({
|
121
|
+
type: "update",
|
122
|
+
payload: { ...draggable, record: { image: json } }
|
123
|
+
});
|
124
|
+
}
|
125
|
+
});
|
112
126
|
|
113
127
|
return draggable;
|
114
128
|
};
|
@@ -136,28 +150,30 @@ export default function ImageGrid(props) {
|
|
136
150
|
const { dragging } = dragState;
|
137
151
|
|
138
152
|
if (draggable === "Files") {
|
139
|
-
return
|
153
|
+
return <FilePlaceholder key="placeholder" />;
|
140
154
|
}
|
141
155
|
|
142
156
|
return (
|
143
|
-
<GridImage
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
157
|
+
<GridImage
|
158
|
+
key={draggable.handle}
|
159
|
+
draggable={draggable}
|
160
|
+
locale={props.locale}
|
161
|
+
locales={props.locales}
|
162
|
+
showEmbed={props.showEmbed}
|
163
|
+
startDrag={dragStart}
|
164
|
+
position={position(draggable.record)}
|
165
|
+
primary={isPrimary}
|
166
|
+
onUpdate={update(draggable)}
|
167
|
+
enablePrimary={props.enablePrimary}
|
168
|
+
deleteImage={remove(draggable)}
|
169
|
+
attributeName={attrName(draggable.record)}
|
170
|
+
placeholder={dragging && dragging == draggable}
|
171
|
+
/>
|
156
172
|
);
|
157
173
|
};
|
158
174
|
|
159
175
|
const uploadPrimary = (files) => {
|
160
|
-
const [first, ...rest] = filterFiles(files).map(f => uploadImage(f));
|
176
|
+
const [first, ...rest] = filterFiles(files).map((f) => uploadImage(f));
|
161
177
|
if (first) {
|
162
178
|
images.dispatch({
|
163
179
|
type: "prepend",
|
@@ -170,7 +186,7 @@ export default function ImageGrid(props) {
|
|
170
186
|
const uploadAdditional = (files) => {
|
171
187
|
images.dispatch({
|
172
188
|
type: "append",
|
173
|
-
payload: filterFiles(files).map(f => uploadImage(f))
|
189
|
+
payload: filterFiles(files).map((f) => uploadImage(f))
|
174
190
|
});
|
175
191
|
};
|
176
192
|
|
@@ -179,67 +195,82 @@ export default function ImageGrid(props) {
|
|
179
195
|
classNames.push("with-primary-image");
|
180
196
|
}
|
181
197
|
|
182
|
-
const [draggedPrimary,
|
183
|
-
|
198
|
+
const [draggedPrimary, draggedImages] = draggedImageOrder(
|
199
|
+
primary,
|
200
|
+
images,
|
201
|
+
dragState
|
202
|
+
);
|
184
203
|
|
185
204
|
return (
|
186
|
-
<div className={classNames.join(" ")}
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
205
|
+
<div className={classNames.join(" ")} ref={containerRef} {...listeners}>
|
206
|
+
{dragState.dragging && (
|
207
|
+
<DragElement
|
208
|
+
draggable={dragState.dragging}
|
209
|
+
dragState={dragState}
|
210
|
+
container={containerRef}
|
211
|
+
/>
|
212
|
+
)}
|
193
213
|
{props.enablePrimary && (
|
194
214
|
<div className="primary-image" ref={primary.ref}>
|
195
|
-
<h3>
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
215
|
+
<h3>Main image</h3>
|
216
|
+
{draggedPrimary && (
|
217
|
+
<>
|
218
|
+
{renderImage(draggedPrimary, true)}
|
219
|
+
{props.primaryAttribute && (
|
220
|
+
<input
|
221
|
+
type="hidden"
|
222
|
+
name={props.primaryAttribute}
|
223
|
+
value={
|
224
|
+
(draggedPrimary.record &&
|
225
|
+
draggedPrimary.record.image &&
|
226
|
+
draggedPrimary.record.image.id) ||
|
227
|
+
""
|
228
|
+
}
|
229
|
+
/>
|
230
|
+
)}
|
231
|
+
</>
|
232
|
+
)}
|
208
233
|
{!draggedPrimary && (
|
209
234
|
<div className="drop-target">
|
210
|
-
<FileUploadButton
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
235
|
+
<FileUploadButton
|
236
|
+
multiple={true}
|
237
|
+
type="image"
|
238
|
+
multiline={true}
|
239
|
+
callback={uploadPrimary}
|
240
|
+
/>
|
241
|
+
</div>
|
242
|
+
)}
|
215
243
|
</div>
|
216
244
|
)}
|
217
245
|
<div className="grid" ref={images.ref}>
|
218
|
-
<h3>
|
219
|
-
{props.enablePrimary ? "More images" : "Images"}
|
220
|
-
</h3>
|
246
|
+
<h3>{props.enablePrimary ? "More images" : "Images"}</h3>
|
221
247
|
<div className="drop-target">
|
222
|
-
<FileUploadButton
|
223
|
-
|
224
|
-
|
248
|
+
<FileUploadButton
|
249
|
+
multiple={true}
|
250
|
+
type="image"
|
251
|
+
callback={uploadAdditional}
|
252
|
+
/>
|
225
253
|
</div>
|
226
254
|
<div className="images">
|
227
|
-
{draggedImages.map(r => renderImage(r, false))}
|
255
|
+
{draggedImages.map((r) => renderImage(r, false))}
|
228
256
|
</div>
|
229
257
|
</div>
|
230
258
|
<div className="deleted">
|
231
|
-
{deleted.map(r =>
|
259
|
+
{deleted.map((r) => (
|
232
260
|
<span className="deleted-image" key={r.id}>
|
233
|
-
<input name={`${attrName(r)}[id]`}
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
<input
|
240
|
-
|
241
|
-
|
242
|
-
|
261
|
+
<input name={`${attrName(r)}[id]`} type="hidden" value={r.id} />
|
262
|
+
<input
|
263
|
+
name={`${attrName(r)}[attachment_id]`}
|
264
|
+
type="hidden"
|
265
|
+
value={(r.image && r.image.id) || ""}
|
266
|
+
/>
|
267
|
+
<input
|
268
|
+
name={`${attrName(r)}[_destroy]`}
|
269
|
+
type="hidden"
|
270
|
+
value={true}
|
271
|
+
/>
|
272
|
+
</span>
|
273
|
+
))}
|
243
274
|
</div>
|
244
275
|
</div>
|
245
276
|
);
|
@@ -5,17 +5,17 @@ import useToastStore from "../stores/useToastStore";
|
|
5
5
|
import { ImageResource, Locale } from "../types";
|
6
6
|
import { post } from "../lib/request";
|
7
7
|
|
8
|
-
type ImageResponse = ImageResource | { status: "error"
|
8
|
+
type ImageResponse = ImageResource | { status: "error"; error: string };
|
9
9
|
|
10
10
|
interface ImageUploaderProps {
|
11
|
-
locale: string
|
12
|
-
locales: { [index: string]: Locale }
|
13
|
-
image: ImageResource
|
14
|
-
src: string
|
15
|
-
width: number
|
16
|
-
caption: boolean
|
17
|
-
attr: string
|
18
|
-
alternative: string
|
11
|
+
locale: string;
|
12
|
+
locales: { [index: string]: Locale };
|
13
|
+
image: ImageResource;
|
14
|
+
src: string;
|
15
|
+
width: number;
|
16
|
+
caption: boolean;
|
17
|
+
attr: string;
|
18
|
+
alternative: string;
|
19
19
|
}
|
20
20
|
|
21
21
|
function getFiles(dt: DataTransfer): File[] {
|
@@ -87,15 +87,19 @@ export default function ImageUploader(props: ImageUploaderProps) {
|
|
87
87
|
};
|
88
88
|
|
89
89
|
const uploadImage = (file: File) => {
|
90
|
-
const validTypes = [
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
90
|
+
const validTypes = [
|
91
|
+
"image/gif",
|
92
|
+
"image/jpeg",
|
93
|
+
"image/pjpeg",
|
94
|
+
"image/png",
|
95
|
+
"image/tiff"
|
96
|
+
];
|
95
97
|
|
96
98
|
if (validTypes.indexOf(file.type) == -1) {
|
97
|
-
alert(
|
98
|
-
|
99
|
+
alert(
|
100
|
+
"Invalid file type, only images in JPEG, PNG or GIF " +
|
101
|
+
"formats are supported"
|
102
|
+
);
|
99
103
|
return;
|
100
104
|
}
|
101
105
|
|
@@ -111,19 +115,18 @@ export default function ImageUploader(props: ImageUploaderProps) {
|
|
111
115
|
|
112
116
|
data.append("image[file]", file);
|
113
117
|
locales.forEach((l) => {
|
114
|
-
data.append(`image[alternative][${l}]`,
|
118
|
+
data.append(`image[alternative][${l}]`, props.alternative || "");
|
115
119
|
});
|
116
120
|
|
117
|
-
void post("/admin/images.json", data)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
});
|
121
|
+
void post("/admin/images.json", data).then((response: ImageResponse) => {
|
122
|
+
setUploading(false);
|
123
|
+
if ("status" in response && response.status === "error") {
|
124
|
+
error(`Error uploading image: ${response.error}`);
|
125
|
+
} else if ("thumbnail_url" in response) {
|
126
|
+
setSrc(response.thumbnail_url);
|
127
|
+
setImage(response);
|
128
|
+
}
|
129
|
+
});
|
127
130
|
};
|
128
131
|
|
129
132
|
const classes = ["image-uploader"];
|
@@ -133,38 +136,39 @@ export default function ImageUploader(props: ImageUploaderProps) {
|
|
133
136
|
classes.push("dragover");
|
134
137
|
}
|
135
138
|
return (
|
136
|
-
<div
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
139
|
+
<div
|
140
|
+
className={classes.join(" ")}
|
141
|
+
onDragOver={handleDragOver}
|
142
|
+
onDragLeave={handleDragLeave}
|
143
|
+
onDragEnd={handleDragEnd}
|
144
|
+
onDrop={handleDrop}>
|
145
|
+
<input type="hidden" name={props.attr} value={image ? image.id : ""} />
|
146
|
+
{image && (
|
147
|
+
<div className="image">
|
148
|
+
<EditableImage
|
149
|
+
image={image}
|
150
|
+
src={src}
|
151
|
+
width={props.width}
|
152
|
+
caption={props.caption}
|
153
|
+
locale={props.locale}
|
154
|
+
locales={props.locales}
|
155
|
+
/>
|
156
|
+
</div>
|
157
|
+
)}
|
153
158
|
<div className="ui-wrapper">
|
154
|
-
{uploading &&
|
155
|
-
<div className="ui">
|
156
|
-
Uploading image...
|
157
|
-
</div>
|
158
|
-
)}
|
159
|
+
{uploading && <div className="ui">Uploading image...</div>}
|
159
160
|
{!uploading && (
|
160
161
|
<div className="ui">
|
161
|
-
<FileUploadButton
|
162
|
-
|
163
|
-
|
162
|
+
<FileUploadButton
|
163
|
+
type="image"
|
164
|
+
multiline={true}
|
165
|
+
callback={receiveFiles}
|
166
|
+
/>
|
164
167
|
{image && (
|
165
|
-
<a
|
166
|
-
|
167
|
-
|
168
|
+
<a
|
169
|
+
className="delete remove-image"
|
170
|
+
href="#"
|
171
|
+
onClick={handleRemove}>
|
168
172
|
Remove image
|
169
173
|
</a>
|
170
174
|
)}
|
@@ -37,12 +37,10 @@ export default function Modal() {
|
|
37
37
|
return (
|
38
38
|
<div className="modal-wrapper open">
|
39
39
|
<div className="background" onClick={handleClose} />
|
40
|
-
<div className="modal">
|
41
|
-
{component}
|
42
|
-
</div>
|
40
|
+
<div className="modal">{component}</div>
|
43
41
|
</div>
|
44
42
|
);
|
45
43
|
} else {
|
46
|
-
return
|
44
|
+
return <div className="modal-wrapper"></div>;
|
47
45
|
}
|
48
46
|
}
|