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
|
@@ -5,10 +5,12 @@ import Placeholder from "./Attachments/Placeholder";
|
|
|
5
5
|
import FileUploadButton from "./FileUploadButton";
|
|
6
6
|
import { post } from "../lib/request";
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import {
|
|
9
|
+
createDraggable,
|
|
10
|
+
draggedOrder,
|
|
11
|
+
useDragCollection,
|
|
12
|
+
useDragUploader
|
|
13
|
+
} from "./drag";
|
|
12
14
|
|
|
13
15
|
function filenameToName(str) {
|
|
14
16
|
return str.replace(/\.[\w\d]+$/, "").replace(/_/g, " ");
|
|
@@ -16,18 +18,20 @@ function filenameToName(str) {
|
|
|
16
18
|
|
|
17
19
|
export default function Attachments(props) {
|
|
18
20
|
const collection = useDragCollection(props.records);
|
|
19
|
-
const locales =
|
|
20
|
-
|
|
21
|
+
const locales =
|
|
22
|
+
props.locales && props.locales.length > 0
|
|
23
|
+
? Object.keys(props.locales)
|
|
24
|
+
: [props.locale];
|
|
21
25
|
const [deleted, setDeleted] = useState([]);
|
|
22
26
|
|
|
23
27
|
const uploadAttachment = (file) => {
|
|
24
28
|
let name = {};
|
|
25
|
-
locales.forEach((l) => name[l] = file.name);
|
|
29
|
+
locales.forEach((l) => (name[l] = file.name));
|
|
26
30
|
|
|
27
|
-
const draggable = createDraggable(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
);
|
|
31
|
+
const draggable = createDraggable({
|
|
32
|
+
attachment: { filename: file.name, name: name },
|
|
33
|
+
uploading: true
|
|
34
|
+
});
|
|
31
35
|
|
|
32
36
|
let data = new FormData();
|
|
33
37
|
|
|
@@ -36,14 +40,15 @@ export default function Attachments(props) {
|
|
|
36
40
|
data.append(`attachment[name][${l}]`, filenameToName(file.name));
|
|
37
41
|
});
|
|
38
42
|
|
|
39
|
-
post("/admin/attachments.json", data)
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
43
|
+
post("/admin/attachments.json", data).then((json) => {
|
|
44
|
+
collection.dispatch({
|
|
45
|
+
type: "update",
|
|
46
|
+
payload: {
|
|
47
|
+
...draggable,
|
|
48
|
+
record: { attachment: json, uploading: false }
|
|
49
|
+
}
|
|
46
50
|
});
|
|
51
|
+
});
|
|
47
52
|
|
|
48
53
|
return draggable;
|
|
49
54
|
};
|
|
@@ -51,7 +56,7 @@ export default function Attachments(props) {
|
|
|
51
56
|
const receiveFiles = (files) => {
|
|
52
57
|
collection.dispatch({
|
|
53
58
|
type: "append",
|
|
54
|
-
payload: files.map(f => uploadAttachment(f))
|
|
59
|
+
payload: files.map((f) => uploadAttachment(f))
|
|
55
60
|
});
|
|
56
61
|
};
|
|
57
62
|
|
|
@@ -62,17 +67,21 @@ export default function Attachments(props) {
|
|
|
62
67
|
});
|
|
63
68
|
collection.dispatch({
|
|
64
69
|
type: "insertFiles",
|
|
65
|
-
payload: files.map(f => uploadAttachment(f))
|
|
70
|
+
payload: files.map((f) => uploadAttachment(f))
|
|
66
71
|
});
|
|
67
72
|
};
|
|
68
73
|
|
|
69
|
-
const [dragState,
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
const [dragState, dragStart, listeners] = useDragUploader(
|
|
75
|
+
[collection],
|
|
76
|
+
dragEnd
|
|
77
|
+
);
|
|
72
78
|
|
|
73
79
|
const position = (record) => {
|
|
74
|
-
return
|
|
75
|
-
|
|
80
|
+
return (
|
|
81
|
+
[...collection.draggables.map((d) => d.record), ...deleted].indexOf(
|
|
82
|
+
record
|
|
83
|
+
) + 1
|
|
84
|
+
);
|
|
76
85
|
};
|
|
77
86
|
|
|
78
87
|
const attrName = (record) => {
|
|
@@ -102,21 +111,23 @@ export default function Attachments(props) {
|
|
|
102
111
|
const { dragging } = dragState;
|
|
103
112
|
|
|
104
113
|
if (draggable === "Files") {
|
|
105
|
-
return
|
|
114
|
+
return <Placeholder key="placeholder" />;
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
return (
|
|
109
|
-
<Attachment
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
<Attachment
|
|
119
|
+
key={draggable.handle}
|
|
120
|
+
draggable={draggable}
|
|
121
|
+
locale={props.locale}
|
|
122
|
+
locales={props.locales}
|
|
123
|
+
showEmbed={props.showEmbed}
|
|
124
|
+
startDrag={dragStart}
|
|
125
|
+
position={position(draggable.record)}
|
|
126
|
+
onUpdate={update(draggable)}
|
|
127
|
+
deleteRecord={remove(draggable)}
|
|
128
|
+
attributeName={attrName(draggable.record)}
|
|
129
|
+
placeholder={dragging && dragging == draggable}
|
|
130
|
+
/>
|
|
120
131
|
);
|
|
121
132
|
};
|
|
122
133
|
|
|
@@ -128,30 +139,31 @@ export default function Attachments(props) {
|
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
return (
|
|
131
|
-
<div className={classes.join(" ")}
|
|
132
|
-
|
|
133
|
-
{...listeners}>
|
|
134
|
-
<div className="files">
|
|
135
|
-
{dragOrder.map(d => attachment(d))}
|
|
136
|
-
</div>
|
|
142
|
+
<div className={classes.join(" ")} ref={collection.ref} {...listeners}>
|
|
143
|
+
<div className="files">{dragOrder.map((d) => attachment(d))}</div>
|
|
137
144
|
<div className="deleted">
|
|
138
|
-
{deleted.map(r =>
|
|
145
|
+
{deleted.map((r) => (
|
|
139
146
|
<span className="deleted-attachment" key={r.id}>
|
|
140
|
-
<input name={`${attrName(r)}[id]`}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
<input
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
<input name={`${attrName(r)}[id]`} type="hidden" value={r.id} />
|
|
148
|
+
<input
|
|
149
|
+
name={`${attrName(r)}[attachment_id]`}
|
|
150
|
+
type="hidden"
|
|
151
|
+
value={(r.attachment && r.attachment.id) || ""}
|
|
152
|
+
/>
|
|
153
|
+
<input
|
|
154
|
+
name={`${attrName(r)}[_destroy]`}
|
|
155
|
+
type="hidden"
|
|
156
|
+
value={true}
|
|
157
|
+
/>
|
|
158
|
+
</span>
|
|
159
|
+
))}
|
|
150
160
|
</div>
|
|
151
161
|
<div className="drop-target">
|
|
152
|
-
<FileUploadButton
|
|
153
|
-
|
|
154
|
-
|
|
162
|
+
<FileUploadButton
|
|
163
|
+
multiple={true}
|
|
164
|
+
multiline={true}
|
|
165
|
+
callback={receiveFiles}
|
|
166
|
+
/>
|
|
155
167
|
</div>
|
|
156
168
|
</div>
|
|
157
169
|
);
|
|
@@ -6,9 +6,9 @@ export default class DateRangeSelect extends React.Component {
|
|
|
6
6
|
super(props);
|
|
7
7
|
this.state = {
|
|
8
8
|
startsAt: this.parseDate(props.startsAt) || this.defaultDate(),
|
|
9
|
-
endsAt:
|
|
9
|
+
endsAt: this.parseDate(props.endsAt) || this.defaultDate(60),
|
|
10
10
|
startTime: "",
|
|
11
|
-
endTime:
|
|
11
|
+
endTime: ""
|
|
12
12
|
};
|
|
13
13
|
this.state.startTime = this.timeToString(this.state.startsAt);
|
|
14
14
|
this.state.endTime = this.timeToString(this.state.endsAt);
|
|
@@ -33,8 +33,9 @@ export default class DateRangeSelect extends React.Component {
|
|
|
33
33
|
defaultDate(offset = 0) {
|
|
34
34
|
let coeff = 1000 * 60 * 60;
|
|
35
35
|
return new Date(
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
Math.round(new Date().getTime() / coeff) * coeff +
|
|
37
|
+
coeff +
|
|
38
|
+
1000 * 60 * offset
|
|
38
39
|
);
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -49,8 +50,10 @@ export default class DateRangeSelect extends React.Component {
|
|
|
49
50
|
if (Object.prototype.hasOwnProperty.call(options, "date")) {
|
|
50
51
|
newDate.setDate(options.date);
|
|
51
52
|
}
|
|
52
|
-
if (
|
|
53
|
-
|
|
53
|
+
if (
|
|
54
|
+
Object.prototype.hasOwnProperty.call(options, "time") &&
|
|
55
|
+
options.time.match(/^[\d]{1,2}(:[\d]{1,2})?$/)
|
|
56
|
+
) {
|
|
54
57
|
newDate.setHours(options.time.split(":")[0]);
|
|
55
58
|
newDate.setMinutes(options.time.split(":")[1] || 0);
|
|
56
59
|
}
|
|
@@ -58,7 +61,9 @@ export default class DateRangeSelect extends React.Component {
|
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
parseDate(str) {
|
|
61
|
-
if (!str) {
|
|
64
|
+
if (!str) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
62
67
|
return new Date(str);
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -75,40 +80,50 @@ export default class DateRangeSelect extends React.Component {
|
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
startsAtToString() {
|
|
78
|
-
if (this.props.disabled) {
|
|
83
|
+
if (this.props.disabled) {
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
79
86
|
return this.state.startsAt.toJSON();
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
endsAtToString() {
|
|
83
|
-
if (this.props.disabled) {
|
|
90
|
+
if (this.props.disabled) {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
84
93
|
return this.state.endsAt.toJSON();
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
renderDateSelect(key, date, handleChange) {
|
|
88
97
|
return (
|
|
89
98
|
<div className="date-select">
|
|
90
|
-
<select
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
<select
|
|
100
|
+
value={date.getMonth()}
|
|
101
|
+
onChange={(e) => handleChange({ month: e.target.value })}
|
|
102
|
+
disabled={this.props.disabled}>
|
|
93
103
|
{this.monthOptions().map((m, i) => (
|
|
94
|
-
<option key={key + "-month-" + i}
|
|
95
|
-
|
|
104
|
+
<option key={key + "-month-" + i} value={i}>
|
|
105
|
+
{m}
|
|
106
|
+
</option>
|
|
96
107
|
))}
|
|
97
108
|
</select>
|
|
98
|
-
<select
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{this.
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
<select
|
|
110
|
+
value={date.getDate()}
|
|
111
|
+
onChange={(e) => handleChange({ date: e.target.value })}
|
|
112
|
+
disabled={this.props.disabled}>
|
|
113
|
+
{this.dayOptions().map((d) => (
|
|
114
|
+
<option key={key + "-date-" + d} value={d}>
|
|
115
|
+
{d}
|
|
116
|
+
</option>
|
|
104
117
|
))}
|
|
105
118
|
</select>
|
|
106
|
-
<select
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{this.
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
<select
|
|
120
|
+
value={date.getFullYear()}
|
|
121
|
+
onChange={(e) => handleChange({ year: e.target.value })}
|
|
122
|
+
disabled={this.props.disabled}>
|
|
123
|
+
{this.yearOptions().map((y) => (
|
|
124
|
+
<option key={key + "-year-" + y} value={y}>
|
|
125
|
+
{y}
|
|
126
|
+
</option>
|
|
112
127
|
))}
|
|
113
128
|
</select>
|
|
114
129
|
</div>
|
|
@@ -118,37 +133,49 @@ export default class DateRangeSelect extends React.Component {
|
|
|
118
133
|
render() {
|
|
119
134
|
return (
|
|
120
135
|
<div className="date-range-select">
|
|
121
|
-
<input
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
<input
|
|
137
|
+
type="hidden"
|
|
138
|
+
name={this.props.objectName + "[starts_at]"}
|
|
139
|
+
value={this.startsAtToString()}
|
|
140
|
+
/>
|
|
141
|
+
<input
|
|
142
|
+
type="hidden"
|
|
143
|
+
name={this.props.objectName + "[ends_at]"}
|
|
144
|
+
value={this.endsAtToString()}
|
|
145
|
+
/>
|
|
127
146
|
<div className="date">
|
|
128
|
-
{this.renderDateSelect(
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
{this.renderDateSelect(
|
|
148
|
+
"starts-at",
|
|
149
|
+
this.state.startsAt,
|
|
150
|
+
this.changeStartsAt
|
|
151
|
+
)}
|
|
131
152
|
{!this.props.disableTime && (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
<input
|
|
154
|
+
type="text"
|
|
155
|
+
size="5"
|
|
156
|
+
value={this.state.startTime}
|
|
157
|
+
disabled={this.props.disabled}
|
|
158
|
+
onChange={(e) => this.setState({ startTime: e.target.value })}
|
|
159
|
+
onBlur={(e) => this.changeStartsAt({ time: e.target.value })}
|
|
160
|
+
/>
|
|
138
161
|
)}
|
|
139
162
|
</div>
|
|
140
163
|
<span className="to">to</span>
|
|
141
164
|
<div className="date">
|
|
142
|
-
{this.renderDateSelect(
|
|
143
|
-
|
|
144
|
-
|
|
165
|
+
{this.renderDateSelect(
|
|
166
|
+
"ends-at",
|
|
167
|
+
this.state.endsAt,
|
|
168
|
+
this.changeEndsAt
|
|
169
|
+
)}
|
|
145
170
|
{!this.props.disableTime && (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
171
|
+
<input
|
|
172
|
+
type="text"
|
|
173
|
+
size="5"
|
|
174
|
+
value={this.state.endTime}
|
|
175
|
+
disabled={this.props.disabled}
|
|
176
|
+
onChange={(e) => this.setState({ endTime: e.target.value })}
|
|
177
|
+
onBlur={(e) => this.changeEndsAt({ time: e.target.value })}
|
|
178
|
+
/>
|
|
152
179
|
)}
|
|
153
180
|
</div>
|
|
154
181
|
</div>
|
|
@@ -162,13 +189,26 @@ export default class DateRangeSelect extends React.Component {
|
|
|
162
189
|
// Returns an array with years from 2000 to 10 years from now.
|
|
163
190
|
yearOptions() {
|
|
164
191
|
let start = 2000;
|
|
165
|
-
return Array.apply(null, Array(
|
|
166
|
-
|
|
192
|
+
return Array.apply(null, Array(new Date().getFullYear() - start + 11)).map(
|
|
193
|
+
(_, i) => i + start
|
|
194
|
+
);
|
|
167
195
|
}
|
|
168
196
|
|
|
169
197
|
monthOptions() {
|
|
170
|
-
return
|
|
171
|
-
|
|
198
|
+
return [
|
|
199
|
+
"January",
|
|
200
|
+
"February",
|
|
201
|
+
"March",
|
|
202
|
+
"April",
|
|
203
|
+
"May",
|
|
204
|
+
"June",
|
|
205
|
+
"July",
|
|
206
|
+
"August",
|
|
207
|
+
"September",
|
|
208
|
+
"October",
|
|
209
|
+
"November",
|
|
210
|
+
"December"
|
|
211
|
+
];
|
|
172
212
|
}
|
|
173
213
|
|
|
174
214
|
dayOptions() {
|
|
@@ -5,13 +5,13 @@ import useModalStore from "../stores/useModalStore";
|
|
|
5
5
|
import { Locale, ImageResource } from "../types";
|
|
6
6
|
|
|
7
7
|
interface EditableImageProps {
|
|
8
|
-
image: ImageResource
|
|
9
|
-
src: string
|
|
10
|
-
caption: boolean
|
|
11
|
-
locale: string
|
|
12
|
-
locales: Record<string, Locale
|
|
13
|
-
width: number
|
|
14
|
-
onUpdate?: (newImage: ImageResource, src: string) => void
|
|
8
|
+
image: ImageResource;
|
|
9
|
+
src: string;
|
|
10
|
+
caption: boolean;
|
|
11
|
+
locale: string;
|
|
12
|
+
locales: Record<string, Locale>;
|
|
13
|
+
width: number;
|
|
14
|
+
onUpdate?: (newImage: ImageResource, src: string) => void;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export default function EditableImage(props: EditableImageProps) {
|
|
@@ -43,7 +43,8 @@ export default function EditableImage(props: EditableImageProps) {
|
|
|
43
43
|
caption={props.caption}
|
|
44
44
|
locale={props.locale}
|
|
45
45
|
locales={props.locales}
|
|
46
|
-
onUpdate={updateImage}
|
|
46
|
+
onUpdate={updateImage}
|
|
47
|
+
/>
|
|
47
48
|
);
|
|
48
49
|
};
|
|
49
50
|
|
|
@@ -51,14 +52,17 @@ export default function EditableImage(props: EditableImageProps) {
|
|
|
51
52
|
|
|
52
53
|
return (
|
|
53
54
|
<div className="editable-image">
|
|
54
|
-
{altWarning &&
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
{altWarning && (
|
|
56
|
+
<span className="alt-warning" title="Alternative text is missing">
|
|
57
|
+
<i className="fa-solid fa-triangle-exclamation icon" />
|
|
58
|
+
</span>
|
|
59
|
+
)}
|
|
60
|
+
<img
|
|
61
|
+
src={src}
|
|
62
|
+
width={props.width}
|
|
63
|
+
height={height()}
|
|
64
|
+
onClick={handleClick}
|
|
65
|
+
/>
|
|
62
66
|
</div>
|
|
63
67
|
);
|
|
64
68
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { ChangeEvent, useRef } from "react";
|
|
2
2
|
|
|
3
3
|
interface FileUploadButtonProps {
|
|
4
|
-
callback: (files: File[]) => void
|
|
5
|
-
type: string
|
|
6
|
-
multiple: boolean
|
|
7
|
-
multiline: boolean
|
|
4
|
+
callback: (files: File[]) => void;
|
|
5
|
+
type: string;
|
|
6
|
+
multiple: boolean;
|
|
7
|
+
multiline: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export default function FileUploadButton(props: FileUploadButtonProps) {
|
|
@@ -32,15 +32,15 @@ export default function FileUploadButton(props: FileUploadButtonProps) {
|
|
|
32
32
|
Drag and drop {props.type || "file"}
|
|
33
33
|
{props.multiple && "s"} here, or
|
|
34
34
|
{props.multiline && <br />}
|
|
35
|
-
<button onClick={triggerDialog}>
|
|
36
|
-
choose a file
|
|
37
|
-
</button>
|
|
35
|
+
<button onClick={triggerDialog}>choose a file</button>
|
|
38
36
|
</span>
|
|
39
|
-
<input
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
<input
|
|
38
|
+
type="file"
|
|
39
|
+
onChange={handleChange}
|
|
40
|
+
ref={inputRef}
|
|
41
|
+
style={{ display: "none" }}
|
|
42
|
+
multiple={props.multiple || false}
|
|
43
|
+
/>
|
|
44
44
|
</div>
|
|
45
45
|
);
|
|
46
46
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import React, { useRef, useState } from "react";
|
|
2
2
|
|
|
3
3
|
interface Position {
|
|
4
|
-
x: number
|
|
5
|
-
y: number
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
interface FocalPointProps {
|
|
9
|
-
x: number
|
|
10
|
-
y: number
|
|
11
|
-
onChange: (pos: Position) => void
|
|
12
|
-
width: number
|
|
13
|
-
height: number
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
onChange: (pos: Position) => void;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function clamp(val: number, min: number, max: number): number {
|
|
@@ -27,7 +27,10 @@ export default function FocalPoint(props: FocalPointProps) {
|
|
|
27
27
|
const { width, height, onChange } = props;
|
|
28
28
|
|
|
29
29
|
const [dragging, setDragging] = useState(false);
|
|
30
|
-
const [position, setPosition] = useState<Position>({
|
|
30
|
+
const [position, setPosition] = useState<Position>({
|
|
31
|
+
x: props.x,
|
|
32
|
+
y: props.y
|
|
33
|
+
});
|
|
31
34
|
|
|
32
35
|
const containerRef = useRef<HTMLDivElement>();
|
|
33
36
|
const pointRef = useRef<HTMLDivElement>();
|
|
@@ -49,7 +52,7 @@ export default function FocalPoint(props: FocalPointProps) {
|
|
|
49
52
|
|
|
50
53
|
const drag = (evt: TouchEvent | MouseEvent) => {
|
|
51
54
|
if (dragging) {
|
|
52
|
-
let x: number
|
|
55
|
+
let x: number, y: number;
|
|
53
56
|
const containerSize = containerRef.current.getBoundingClientRect();
|
|
54
57
|
evt.preventDefault();
|
|
55
58
|
|
|
@@ -78,17 +81,16 @@ export default function FocalPoint(props: FocalPointProps) {
|
|
|
78
81
|
};
|
|
79
82
|
|
|
80
83
|
return (
|
|
81
|
-
<div
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
ref={pointRef} />
|
|
84
|
+
<div
|
|
85
|
+
className="focal-editor"
|
|
86
|
+
ref={containerRef}
|
|
87
|
+
onTouchStart={dragStart}
|
|
88
|
+
onTouchEnd={dragEnd}
|
|
89
|
+
onTouchMove={drag}
|
|
90
|
+
onMouseDown={dragStart}
|
|
91
|
+
onMouseUp={dragEnd}
|
|
92
|
+
onMouseMove={drag}>
|
|
93
|
+
<div className="focal-point" style={pointStyle} ref={pointRef} />
|
|
92
94
|
</div>
|
|
93
95
|
);
|
|
94
96
|
}
|
|
@@ -5,12 +5,12 @@ import { cropSize, CropSize, CropState, Position, Size } from "./useCrop";
|
|
|
5
5
|
import FocalPoint from "./FocalPoint";
|
|
6
6
|
|
|
7
7
|
interface ImageProps {
|
|
8
|
-
containerSize: Size
|
|
9
|
-
croppedImage: string
|
|
10
|
-
cropState: CropState
|
|
11
|
-
focalPoint: Position
|
|
12
|
-
setCrop: (crop: CropSize) => void
|
|
13
|
-
setFocal: (focal: Position) => void
|
|
8
|
+
containerSize: Size;
|
|
9
|
+
croppedImage: string;
|
|
10
|
+
cropState: CropState;
|
|
11
|
+
focalPoint: Position;
|
|
12
|
+
setCrop: (crop: CropSize) => void;
|
|
13
|
+
setFocal: (focal: Position) => void;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export default function Image(props: ImageProps) {
|
|
@@ -40,25 +40,29 @@ export default function Image(props: ImageProps) {
|
|
|
40
40
|
if (props.cropState.cropping) {
|
|
41
41
|
return (
|
|
42
42
|
<div className="image-wrapper" style={style}>
|
|
43
|
-
<ReactCrop
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
<ReactCrop
|
|
44
|
+
src={props.cropState.image.uncropped_url}
|
|
45
|
+
crop={cropSize(props.cropState)}
|
|
46
|
+
minWidth={10}
|
|
47
|
+
minHeight={10}
|
|
48
|
+
onChange={props.setCrop}
|
|
49
|
+
/>
|
|
48
50
|
</div>
|
|
49
51
|
);
|
|
50
52
|
} else {
|
|
51
53
|
return (
|
|
52
54
|
<div className="image-wrapper" style={style}>
|
|
53
55
|
{props.focalPoint && (
|
|
54
|
-
<FocalPoint
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
<FocalPoint
|
|
57
|
+
width={width}
|
|
58
|
+
height={height}
|
|
59
|
+
x={props.focalPoint.x}
|
|
60
|
+
y={props.focalPoint.y}
|
|
61
|
+
onChange={props.setFocal}
|
|
62
|
+
/>
|
|
58
63
|
)}
|
|
59
64
|
<img src={props.croppedImage} />
|
|
60
65
|
</div>
|
|
61
66
|
);
|
|
62
67
|
}
|
|
63
|
-
|
|
64
68
|
}
|