pages_core 3.12.4 → 3.12.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.
- 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
|
}
|