@plone/volto 16.32.1 → 16.34.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.
- package/.changelog.draft +6 -2
- package/.readthedocs.yaml +4 -5
- package/.yarn/install-state.gz +0 -0
- package/CHANGELOG.md +31 -0
- package/locales/ca/LC_MESSAGES/volto.po +55 -13
- package/locales/ca.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +56 -14
- package/locales/de.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +55 -13
- package/locales/en.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +55 -13
- package/locales/es.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +55 -13
- package/locales/eu.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +55 -13
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +55 -13
- package/locales/fr.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +55 -13
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +55 -13
- package/locales/ja.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +55 -13
- package/locales/nl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +55 -13
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +55 -13
- package/locales/pt_BR.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +55 -13
- package/locales/ro.json +1 -1
- package/locales/volto.pot +56 -14
- package/locales/zh_CN/LC_MESSAGES/volto.po +55 -13
- package/locales/zh_CN.json +1 -1
- package/package.json +1 -1
- package/packages/volto-slate/package.json +1 -1
- package/src/actions/aliases/aliases.js +27 -7
- package/src/actions/aliases/aliases.test.js +1 -1
- package/src/components/manage/Controlpanels/Aliases.jsx +542 -583
- package/src/components/manage/Controlpanels/Aliases.test.jsx +7 -0
- package/src/components/manage/Form/ModalForm.jsx +3 -1
- package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx +23 -0
- package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx +135 -0
- package/src/components/theme/View/View.jsx +2 -0
- package/src/constants/ActionTypes.js +1 -0
- package/src/express-middleware/devproxy.js +7 -2
- package/src/helpers/Api/Api.js +12 -1
- package/src/middleware/api.js +3 -0
- package/.python-version +0 -1
- package/packages/README.md +0 -7
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { compose } from 'redux';
|
|
5
|
-
import { Link } from 'react-router-dom';
|
|
6
|
-
import { getBaseUrl, getParentUrl, Helmet } from '@plone/volto/helpers';
|
|
7
|
-
import { removeAliases, addAliases, getAliases } from '@plone/volto/actions';
|
|
1
|
+
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
3
|
+
import { Link, useHistory, useLocation } from 'react-router-dom';
|
|
8
4
|
import { Portal } from 'react-portal';
|
|
5
|
+
import { getBaseUrl, getParentUrl, Helmet } from '@plone/volto/helpers';
|
|
6
|
+
import {
|
|
7
|
+
removeAliases,
|
|
8
|
+
addAliases,
|
|
9
|
+
getAliases,
|
|
10
|
+
uploadAliases,
|
|
11
|
+
} from '@plone/volto/actions/aliases/aliases';
|
|
9
12
|
import {
|
|
10
|
-
Container,
|
|
11
13
|
Button,
|
|
12
|
-
Segment,
|
|
13
|
-
Form,
|
|
14
14
|
Checkbox,
|
|
15
|
+
Container,
|
|
16
|
+
Form,
|
|
15
17
|
Header,
|
|
16
18
|
Input,
|
|
19
|
+
Loader,
|
|
20
|
+
Menu,
|
|
21
|
+
Pagination,
|
|
17
22
|
Radio,
|
|
18
|
-
|
|
23
|
+
Segment,
|
|
19
24
|
Table,
|
|
20
|
-
Pagination,
|
|
21
|
-
Menu,
|
|
22
25
|
} from 'semantic-ui-react';
|
|
23
|
-
import { FormattedMessage, defineMessages,
|
|
26
|
+
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
|
24
27
|
import DatetimeWidget from '@plone/volto/components/manage/Widgets/DatetimeWidget';
|
|
28
|
+
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
|
|
29
|
+
import ModalForm from '@plone/volto/components/manage/Form/ModalForm';
|
|
25
30
|
import { Icon, Toolbar } from '@plone/volto/components';
|
|
31
|
+
import FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate';
|
|
26
32
|
|
|
27
33
|
import backSVG from '@plone/volto/icons/back.svg';
|
|
34
|
+
import editingSVG from '@plone/volto/icons/editing.svg';
|
|
28
35
|
import { map } from 'lodash';
|
|
29
36
|
import { toast } from 'react-toastify';
|
|
30
37
|
import { Toast } from '@plone/volto/components';
|
|
@@ -38,6 +45,14 @@ const messages = defineMessages({
|
|
|
38
45
|
id: 'URL Management',
|
|
39
46
|
defaultMessage: 'URL Management',
|
|
40
47
|
},
|
|
48
|
+
AddUrl: {
|
|
49
|
+
id: 'Add Alternative URL',
|
|
50
|
+
defaultMessage: 'Add Alternative URL',
|
|
51
|
+
},
|
|
52
|
+
EditUrl: {
|
|
53
|
+
id: 'Edit Alternative URL',
|
|
54
|
+
defaultMessage: 'Edit Alternative URL',
|
|
55
|
+
},
|
|
41
56
|
success: {
|
|
42
57
|
id: 'Success',
|
|
43
58
|
defaultMessage: 'Success',
|
|
@@ -46,6 +61,50 @@ const messages = defineMessages({
|
|
|
46
61
|
id: 'Alias has been added',
|
|
47
62
|
defaultMessage: 'Alias has been added',
|
|
48
63
|
},
|
|
64
|
+
successUpload: {
|
|
65
|
+
id: 'Aliases have been uploaded.',
|
|
66
|
+
defaultMessage: 'Aliases have been uploaded.',
|
|
67
|
+
},
|
|
68
|
+
successRemove: {
|
|
69
|
+
id: 'Aliases have been removed.',
|
|
70
|
+
defaultMessage: 'Aliases have been removed.',
|
|
71
|
+
},
|
|
72
|
+
filterByPrefix: {
|
|
73
|
+
id: 'Filter by prefix',
|
|
74
|
+
defaultMessage: 'Filter by path',
|
|
75
|
+
},
|
|
76
|
+
manualOrAuto: {
|
|
77
|
+
id: 'Manually or automatically added?',
|
|
78
|
+
defaultMessage: 'Manually or automatically added?',
|
|
79
|
+
},
|
|
80
|
+
createdAfter: {
|
|
81
|
+
id: 'Created after',
|
|
82
|
+
defaultMessage: 'Created after',
|
|
83
|
+
},
|
|
84
|
+
createdBefore: {
|
|
85
|
+
id: 'Created before',
|
|
86
|
+
defaultMessage: 'Created before',
|
|
87
|
+
},
|
|
88
|
+
altUrlPathTitle: {
|
|
89
|
+
id: 'Alternative url path (Required)',
|
|
90
|
+
defaultMessage: 'Alternative URL path (Required)',
|
|
91
|
+
},
|
|
92
|
+
altUrlError: {
|
|
93
|
+
id: 'Alternative url path must start with a slash.',
|
|
94
|
+
defaultMessage: 'Alternative URL path must start with a slash.',
|
|
95
|
+
},
|
|
96
|
+
targetUrlPathTitle: {
|
|
97
|
+
id: 'Target Path (Required)',
|
|
98
|
+
defaultMessage: 'Target Path (Required)',
|
|
99
|
+
},
|
|
100
|
+
BulkUploadAltUrls: {
|
|
101
|
+
id: 'BulkUploadAltUrls',
|
|
102
|
+
defaultMessage: 'Bulk upload CSV',
|
|
103
|
+
},
|
|
104
|
+
CSVFile: {
|
|
105
|
+
id: 'CSVFile',
|
|
106
|
+
defaultMessage: 'CSV file',
|
|
107
|
+
},
|
|
49
108
|
});
|
|
50
109
|
|
|
51
110
|
const filterChoices = [
|
|
@@ -56,616 +115,516 @@ const filterChoices = [
|
|
|
56
115
|
|
|
57
116
|
const itemsPerPageChoices = [10, 25, 50, 'All'];
|
|
58
117
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Property types.
|
|
67
|
-
* @property {Object} propTypes Property types.
|
|
68
|
-
* @static
|
|
69
|
-
*/
|
|
70
|
-
static propTypes = {
|
|
71
|
-
addAliases: PropTypes.func.isRequired,
|
|
72
|
-
getAliases: PropTypes.func.isRequired,
|
|
73
|
-
removeAliases: PropTypes.func.isRequired,
|
|
74
|
-
};
|
|
118
|
+
const Aliases = (props) => {
|
|
119
|
+
const title = props;
|
|
120
|
+
const intl = useIntl();
|
|
121
|
+
const dispatch = useDispatch();
|
|
122
|
+
const { pathname } = useLocation();
|
|
123
|
+
const history = useHistory();
|
|
75
124
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
isClient: false,
|
|
86
|
-
filterType: filterChoices[0],
|
|
87
|
-
createdBefore: null,
|
|
88
|
-
altUrlPath: '',
|
|
89
|
-
isAltUrlCorrect: false,
|
|
90
|
-
targetUrlPath: '',
|
|
91
|
-
aliasesToRemove: [],
|
|
92
|
-
errorMessageAdd: '',
|
|
93
|
-
filterQuery: '',
|
|
94
|
-
aliases: [],
|
|
95
|
-
activePage: 1,
|
|
96
|
-
pages: '',
|
|
97
|
-
itemsPerPage: 10,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
125
|
+
// workaround for Volto 16 backport, which doesn't have getSite action
|
|
126
|
+
const [site, setSite] = useState({});
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
dispatch({ type: 'GET_SITE', request: { op: 'get', path: '/@site' } })
|
|
129
|
+
.then((resp) => setSite(resp))
|
|
130
|
+
.catch((err) => {});
|
|
131
|
+
}, [dispatch]);
|
|
132
|
+
const hasAdvancedFiltering = site.features?.filter_aliases_by_date;
|
|
133
|
+
const hasBulkUpload = hasAdvancedFiltering !== undefined;
|
|
100
134
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
135
|
+
const aliases = useSelector((state) => state.aliases);
|
|
136
|
+
const [filterType, setFilterType] = useState(filterChoices[0]);
|
|
137
|
+
const [createdBefore, setCreatedBefore] = useState(null);
|
|
138
|
+
const [createdAfter, setCreatedAfter] = useState(null);
|
|
139
|
+
const [aliasesToRemove, setAliasesToRemove] = useState([]);
|
|
140
|
+
const [filterQuery, setFilterQuery] = useState('');
|
|
141
|
+
const [activePage, setActivePage] = useState(1);
|
|
142
|
+
const [itemsPerPage, setItemsPerPage] = useState(10);
|
|
143
|
+
const [addModalOpen, setAddModalOpen] = useState(false);
|
|
144
|
+
const [addError, setAddError] = useState(null);
|
|
145
|
+
const [editingData, setEditingData] = useState(null);
|
|
146
|
+
const [uploadModalOpen, setUploadModalOpen] = useState(false);
|
|
147
|
+
const [uploadError, setUploadError] = useState(null);
|
|
148
|
+
|
|
149
|
+
const updateResults = useCallback(() => {
|
|
150
|
+
const options = {
|
|
110
151
|
query: filterQuery,
|
|
111
152
|
manual: filterType.value,
|
|
112
|
-
|
|
113
|
-
batchSize: itemsPerPage,
|
|
114
|
-
}
|
|
115
|
-
|
|
153
|
+
batchStart: (activePage - 1) * itemsPerPage,
|
|
154
|
+
batchSize: itemsPerPage === 'All' ? 999999999999 : itemsPerPage,
|
|
155
|
+
};
|
|
156
|
+
if (hasAdvancedFiltering) {
|
|
157
|
+
options.start = createdAfter || '';
|
|
158
|
+
options.end = createdBefore || '';
|
|
159
|
+
} else {
|
|
160
|
+
options.datetime = createdBefore || '';
|
|
161
|
+
}
|
|
162
|
+
dispatch(getAliases(getBaseUrl(pathname), options));
|
|
163
|
+
}, [
|
|
164
|
+
activePage,
|
|
165
|
+
createdAfter,
|
|
166
|
+
createdBefore,
|
|
167
|
+
dispatch,
|
|
168
|
+
filterQuery,
|
|
169
|
+
filterType.value,
|
|
170
|
+
hasAdvancedFiltering,
|
|
171
|
+
itemsPerPage,
|
|
172
|
+
pathname,
|
|
173
|
+
]);
|
|
116
174
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
*/
|
|
122
|
-
componentDidUpdate(prevProps, prevState) {
|
|
123
|
-
const { filterQuery, filterType, createdBefore, itemsPerPage } = this.state;
|
|
124
|
-
if (
|
|
125
|
-
prevProps.aliases.items_total !== this.props.aliases.items_total ||
|
|
126
|
-
prevState.itemsPerPage !== this.state.itemsPerPage
|
|
127
|
-
) {
|
|
128
|
-
const pages = Math.ceil(
|
|
129
|
-
this.props.aliases.items_total / this.state.itemsPerPage,
|
|
130
|
-
);
|
|
175
|
+
// Update results after changing the page.
|
|
176
|
+
// (We intentionally leave updateResults out of the deps.)
|
|
177
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
178
|
+
useEffect(() => updateResults(), [activePage, itemsPerPage]);
|
|
131
179
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
180
|
+
// Calculate page count from results
|
|
181
|
+
const pages = useMemo(() => {
|
|
182
|
+
let pages = Math.ceil(aliases.items_total / itemsPerPage);
|
|
183
|
+
if (pages === 0 || isNaN(pages)) {
|
|
184
|
+
pages = '';
|
|
137
185
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
prevState.itemsPerPage !== this.state.itemsPerPage
|
|
141
|
-
) {
|
|
142
|
-
this.props.getAliases(getBaseUrl(this.props.pathname), {
|
|
143
|
-
query: filterQuery,
|
|
144
|
-
manual: filterType.value,
|
|
145
|
-
datetime: createdBefore,
|
|
146
|
-
batchSize: itemsPerPage === 'All' ? 999999999999 : itemsPerPage,
|
|
147
|
-
batchStart: (this.state.activePage - 1) * this.state.itemsPerPage,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
if (prevState.altUrlPath !== this.state.altUrlPath) {
|
|
151
|
-
if (this.state.altUrlPath.charAt(0) === '/') {
|
|
152
|
-
this.setState({ isAltUrlCorrect: true });
|
|
153
|
-
} else {
|
|
154
|
-
this.setState({ isAltUrlCorrect: false });
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
186
|
+
return pages;
|
|
187
|
+
}, [aliases.items_total, itemsPerPage]);
|
|
158
188
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
189
|
+
// Add new alias
|
|
190
|
+
const handleAdd = (formData) => {
|
|
191
|
+
const { altUrlPath, targetUrlPath } = formData;
|
|
192
|
+
// Validate altUrlPath starts with a slash
|
|
193
|
+
if (!altUrlPath || altUrlPath.charAt(0) !== '/') {
|
|
194
|
+
setAddError(intl.formatMessage(messages.altUrlError));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Remove existing alias first if we're editing it.
|
|
198
|
+
const precondition = editingData
|
|
199
|
+
? dispatch(
|
|
200
|
+
removeAliases('', { items: [{ path: editingData.altUrlPath }] }),
|
|
201
|
+
)
|
|
202
|
+
: Promise.resolve();
|
|
203
|
+
precondition.then(() => {
|
|
204
|
+
dispatch(
|
|
205
|
+
addAliases('', {
|
|
206
|
+
items: [{ path: altUrlPath, 'redirect-to': targetUrlPath }],
|
|
207
|
+
}),
|
|
208
|
+
)
|
|
209
|
+
.then(() => {
|
|
210
|
+
updateResults();
|
|
211
|
+
setAddModalOpen(false);
|
|
212
|
+
setEditingData(null);
|
|
213
|
+
toast.success(
|
|
214
|
+
<Toast
|
|
215
|
+
success
|
|
216
|
+
title={intl.formatMessage(messages.success)}
|
|
217
|
+
content={intl.formatMessage(messages.successAdd)}
|
|
218
|
+
/>,
|
|
219
|
+
);
|
|
220
|
+
})
|
|
221
|
+
.catch((error) => {
|
|
222
|
+
setAddError(error.response?.body?.message);
|
|
170
223
|
});
|
|
171
|
-
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Check/uncheck an alias
|
|
228
|
+
const handleCheckAlias = (alias) => {
|
|
229
|
+
if (aliasesToRemove.includes(alias)) {
|
|
230
|
+
setAliasesToRemove(aliasesToRemove.filter((x) => x !== alias));
|
|
231
|
+
} else {
|
|
232
|
+
setAliasesToRemove([...aliasesToRemove, alias]);
|
|
172
233
|
}
|
|
173
|
-
|
|
174
|
-
const {
|
|
175
|
-
filterQuery,
|
|
176
|
-
filterType,
|
|
177
|
-
createdBefore,
|
|
178
|
-
itemsPerPage,
|
|
179
|
-
} = this.state;
|
|
234
|
+
};
|
|
180
235
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
})
|
|
236
|
+
// Remove selected aliases
|
|
237
|
+
const handleRemoveAliases = () => {
|
|
238
|
+
dispatch(
|
|
239
|
+
removeAliases('', {
|
|
240
|
+
items: aliasesToRemove.map((a) => ({ path: a })),
|
|
241
|
+
}),
|
|
242
|
+
).then(() => {
|
|
243
|
+
updateResults();
|
|
187
244
|
toast.success(
|
|
188
245
|
<Toast
|
|
189
246
|
success
|
|
190
|
-
title={
|
|
191
|
-
content={
|
|
247
|
+
title={intl.formatMessage(messages.success)}
|
|
248
|
+
content={intl.formatMessage(messages.successRemove)}
|
|
192
249
|
/>,
|
|
193
250
|
);
|
|
194
|
-
if (!nextProps.aliases.add.error) {
|
|
195
|
-
this.setState({
|
|
196
|
-
errorMessageAdd: '',
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (this.props.aliases.remove.loading && nextProps.aliases.remove.loaded) {
|
|
201
|
-
const {
|
|
202
|
-
filterQuery,
|
|
203
|
-
filterType,
|
|
204
|
-
createdBefore,
|
|
205
|
-
itemsPerPage,
|
|
206
|
-
} = this.state;
|
|
207
|
-
|
|
208
|
-
this.props.getAliases(getBaseUrl(this.props.pathname), {
|
|
209
|
-
query: filterQuery,
|
|
210
|
-
manual: filterType.value,
|
|
211
|
-
datetime: createdBefore,
|
|
212
|
-
batchSize: itemsPerPage,
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Back/Cancel handler
|
|
219
|
-
* @method onCancel
|
|
220
|
-
* @returns {undefined}
|
|
221
|
-
*/
|
|
222
|
-
onCancel() {
|
|
223
|
-
this.props.history.push(getParentUrl(this.props.pathname));
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Select filter type handler
|
|
228
|
-
* @method handleSelectFilterType
|
|
229
|
-
* @returns {undefined}
|
|
230
|
-
*/
|
|
231
|
-
handleSelectFilterType(type) {
|
|
232
|
-
this.setState({ filterType: type });
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Select filter type handler
|
|
237
|
-
* @method handleFilterQueryChange
|
|
238
|
-
* @returns {undefined}
|
|
239
|
-
*/
|
|
240
|
-
handleFilterQueryChange(query) {
|
|
241
|
-
this.setState({ filterQuery: query });
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Select Creation date handler
|
|
246
|
-
* @method handleCreateDate
|
|
247
|
-
* @returns {undefined}
|
|
248
|
-
*/
|
|
249
|
-
handleCreateDate(date) {
|
|
250
|
-
this.setState({ createdBefore: date });
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Select Creation date handler
|
|
255
|
-
* @method handleSubmitFilter
|
|
256
|
-
* @returns {undefined}
|
|
257
|
-
*/
|
|
258
|
-
handleSubmitFilter() {
|
|
259
|
-
const { filterQuery, filterType, createdBefore, itemsPerPage } = this.state;
|
|
260
|
-
this.props.getAliases(getBaseUrl(this.props.pathname), {
|
|
261
|
-
query: filterQuery,
|
|
262
|
-
manual: filterType.value,
|
|
263
|
-
datetime: createdBefore,
|
|
264
|
-
batchSize: itemsPerPage,
|
|
265
251
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Alternative url handler
|
|
270
|
-
* @method handleAltUrlChange
|
|
271
|
-
* @returns {undefined}
|
|
272
|
-
*/
|
|
273
|
-
handleAltUrlChange(url) {
|
|
274
|
-
this.setState({ altUrlPath: url });
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Target url handler
|
|
279
|
-
* @method handleTargetUrlChange
|
|
280
|
-
* @returns {undefined}
|
|
281
|
-
*/
|
|
282
|
-
handleTargetUrlChange(url) {
|
|
283
|
-
this.setState({ targetUrlPath: url });
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* New alias submit handler
|
|
288
|
-
* @method handleSubmitAlias
|
|
289
|
-
* @returns {undefined}
|
|
290
|
-
*/
|
|
291
|
-
handleSubmitAlias() {
|
|
292
|
-
if (this.state.isAltUrlCorrect) {
|
|
293
|
-
this.props.addAliases('', {
|
|
294
|
-
items: [
|
|
295
|
-
{
|
|
296
|
-
path: this.state.altUrlPath,
|
|
297
|
-
'redirect-to': this.state.targetUrlPath,
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
});
|
|
301
|
-
this.setState({ altUrlPath: '', targetUrlPath: '' });
|
|
302
|
-
}
|
|
303
|
-
}
|
|
252
|
+
setAliasesToRemove([]);
|
|
253
|
+
};
|
|
304
254
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
255
|
+
// Upload CSV
|
|
256
|
+
const handleBulkUpload = (formData) => {
|
|
257
|
+
fetch(`data:${formData.file['content-type']};base64,${formData.file.data}`)
|
|
258
|
+
.then((res) => res.blob())
|
|
259
|
+
.then((blob) => {
|
|
260
|
+
dispatch(uploadAliases(blob))
|
|
261
|
+
.then(() => {
|
|
262
|
+
updateResults();
|
|
263
|
+
setUploadError(null);
|
|
264
|
+
setUploadModalOpen(false);
|
|
265
|
+
toast.success(
|
|
266
|
+
<Toast
|
|
267
|
+
success
|
|
268
|
+
title={intl.formatMessage(messages.success)}
|
|
269
|
+
content={intl.formatMessage(messages.successUpload)}
|
|
270
|
+
/>,
|
|
271
|
+
);
|
|
272
|
+
})
|
|
273
|
+
.catch((error) => {
|
|
274
|
+
setUploadError(error.response?.body?.message);
|
|
275
|
+
});
|
|
322
276
|
});
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Remove aliases handler
|
|
328
|
-
* @method handleRemoveAliases
|
|
329
|
-
* @returns {undefined}
|
|
330
|
-
*/
|
|
331
|
-
handleRemoveAliases = () => {
|
|
332
|
-
const items = this.state.aliasesToRemove.map((a) => {
|
|
333
|
-
return {
|
|
334
|
-
path: a,
|
|
335
|
-
};
|
|
336
|
-
});
|
|
337
|
-
this.props.removeAliases('', {
|
|
338
|
-
items,
|
|
339
|
-
});
|
|
340
|
-
this.setState({ aliasesToRemove: [] });
|
|
341
277
|
};
|
|
342
278
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
/>
|
|
387
|
-
</Header>
|
|
388
|
-
<p className="help">
|
|
389
|
-
<FormattedMessage
|
|
390
|
-
id="Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring."
|
|
391
|
-
defaultMessage="Enter the absolute path where the alternative url should exist. The path must start with '/'. Only urls that result in a 404 not found page will result in a redirect occurring."
|
|
392
|
-
/>
|
|
393
|
-
</p>
|
|
394
|
-
<Form.Field>
|
|
395
|
-
<Input
|
|
396
|
-
id="alternative-url-input"
|
|
397
|
-
name="alternative-url-path"
|
|
398
|
-
placeholder="/example"
|
|
399
|
-
value={this.state.altUrlPath}
|
|
400
|
-
onChange={(e) => this.handleAltUrlChange(e.target.value)}
|
|
401
|
-
/>
|
|
402
|
-
{!this.state.isAltUrlCorrect &&
|
|
403
|
-
this.state.altUrlPath !== '' && (
|
|
404
|
-
<p style={{ color: 'red' }}>
|
|
279
|
+
return (
|
|
280
|
+
<div id="page-aliases">
|
|
281
|
+
<Helmet title={intl.formatMessage(messages.aliases)} />
|
|
282
|
+
<Container>
|
|
283
|
+
<article id="content">
|
|
284
|
+
<Segment.Group raised>
|
|
285
|
+
<Segment className="primary">
|
|
286
|
+
<FormattedMessage
|
|
287
|
+
id="URL Management"
|
|
288
|
+
defaultMessage="URL Management"
|
|
289
|
+
values={{ title: <q>{title}</q> }}
|
|
290
|
+
/>
|
|
291
|
+
</Segment>
|
|
292
|
+
<Segment>
|
|
293
|
+
<Button
|
|
294
|
+
primary
|
|
295
|
+
id="add-alt-url"
|
|
296
|
+
onClick={() => setAddModalOpen(true)}
|
|
297
|
+
>
|
|
298
|
+
{intl.formatMessage(messages.AddUrl)}…
|
|
299
|
+
</Button>
|
|
300
|
+
{addModalOpen && (
|
|
301
|
+
<ModalForm
|
|
302
|
+
open={true}
|
|
303
|
+
onSubmit={handleAdd}
|
|
304
|
+
onCancel={() => setAddModalOpen(false)}
|
|
305
|
+
title={
|
|
306
|
+
editingData
|
|
307
|
+
? intl.formatMessage(messages.EditUrl)
|
|
308
|
+
: intl.formatMessage(messages.AddUrl)
|
|
309
|
+
}
|
|
310
|
+
submitError={addError}
|
|
311
|
+
schema={{
|
|
312
|
+
fieldsets: [
|
|
313
|
+
{
|
|
314
|
+
id: 'default',
|
|
315
|
+
fields: ['altUrlPath', 'targetUrlPath'],
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
properties: {
|
|
319
|
+
altUrlPath: {
|
|
320
|
+
title: intl.formatMessage(messages.altUrlPathTitle),
|
|
321
|
+
description: (
|
|
405
322
|
<FormattedMessage
|
|
406
|
-
id="
|
|
407
|
-
defaultMessage="
|
|
323
|
+
id="Enter the absolute path where the alternative url should exist. The path must start with '/'. Only URLs that result in a 404 not found page will result in a redirect occurring."
|
|
324
|
+
defaultMessage="Enter the absolute path where the alternative URL should exist. The path must start with '/'. Only URLs that result in a 404 not found page will result in a redirect occurring."
|
|
408
325
|
/>
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
326
|
+
),
|
|
327
|
+
placeholder: '/example',
|
|
328
|
+
},
|
|
329
|
+
targetUrlPath: {
|
|
330
|
+
title: intl.formatMessage(messages.targetUrlPathTitle),
|
|
331
|
+
description: (
|
|
332
|
+
<FormattedMessage
|
|
333
|
+
id="Enter the absolute path of the target. Target must exist or be an existing alternative url path to the target."
|
|
334
|
+
defaultMessage="Enter the absolute path of the target. Target must exist or be an existing alternative URL path to the target."
|
|
335
|
+
/>
|
|
336
|
+
),
|
|
337
|
+
placeholder: '/example',
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
required: ['altUrlPath', 'targetUrlPath'],
|
|
341
|
+
}}
|
|
342
|
+
formData={editingData || {}}
|
|
343
|
+
/>
|
|
344
|
+
)}
|
|
345
|
+
{hasBulkUpload && (
|
|
346
|
+
<>
|
|
347
|
+
<Button onClick={() => setUploadModalOpen(true)}>
|
|
348
|
+
{intl.formatMessage(messages.BulkUploadAltUrls)}…
|
|
349
|
+
</Button>
|
|
350
|
+
{uploadModalOpen && (
|
|
351
|
+
<ModalForm
|
|
352
|
+
open={true}
|
|
353
|
+
onSubmit={handleBulkUpload}
|
|
354
|
+
onCancel={() => setUploadModalOpen(false)}
|
|
355
|
+
title={intl.formatMessage(messages.BulkUploadAltUrls)}
|
|
356
|
+
submitError={uploadError}
|
|
357
|
+
description={
|
|
358
|
+
<>
|
|
359
|
+
<p>
|
|
360
|
+
<FormattedMessage
|
|
361
|
+
id="bulkUploadUrlsHelp"
|
|
362
|
+
defaultMessage="Add many alternative URLs at once by uploading a CSV file. The first column should be the path to redirect from; the second, the path to redirect to. Both paths must be Plone-site-relative, starting with a slash (/). An optional third column can contain a date and time. An optional fourth column can contain a boolean to mark as a manual redirect (default true)."
|
|
363
|
+
/>
|
|
364
|
+
</p>
|
|
365
|
+
<p>
|
|
366
|
+
Example:
|
|
367
|
+
<br />
|
|
368
|
+
<code>
|
|
369
|
+
/old-home-page.asp,/front-page,2019/01/27 10:42:59
|
|
370
|
+
GMT+1,true
|
|
371
|
+
<br />
|
|
372
|
+
/people/JoeT,/Users/joe-thurston,2018-12-31,false
|
|
373
|
+
</code>
|
|
374
|
+
</p>
|
|
375
|
+
</>
|
|
432
376
|
}
|
|
377
|
+
schema={{
|
|
378
|
+
fieldsets: [
|
|
379
|
+
{
|
|
380
|
+
id: 'default',
|
|
381
|
+
fields: ['file'],
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
properties: {
|
|
385
|
+
file: {
|
|
386
|
+
title: intl.formatMessage(messages.CSVFile),
|
|
387
|
+
type: 'object',
|
|
388
|
+
factory: 'File Upload',
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
required: ['file'],
|
|
392
|
+
}}
|
|
433
393
|
/>
|
|
434
|
-
</Form.Field>
|
|
435
|
-
<Button
|
|
436
|
-
id="submit-alias"
|
|
437
|
-
primary
|
|
438
|
-
onClick={() => this.handleSubmitAlias()}
|
|
439
|
-
disabled={
|
|
440
|
-
!this.state.isAltUrlCorrect ||
|
|
441
|
-
this.state.altUrlPath === '' ||
|
|
442
|
-
this.state.targetUrlPath === ''
|
|
443
|
-
}
|
|
444
|
-
>
|
|
445
|
-
<FormattedMessage id="Add" defaultMessage="Add" />
|
|
446
|
-
</Button>
|
|
447
|
-
{this.state.errorMessageAdd && (
|
|
448
|
-
<Message color="red">
|
|
449
|
-
<Message.Header>
|
|
450
|
-
<FormattedMessage
|
|
451
|
-
id="ErrorHeader"
|
|
452
|
-
defaultMessage="Error"
|
|
453
|
-
/>
|
|
454
|
-
</Message.Header>
|
|
455
|
-
<p>{this.state.errorMessageAdd}</p>
|
|
456
|
-
</Message>
|
|
457
394
|
)}
|
|
458
|
-
|
|
459
|
-
|
|
395
|
+
</>
|
|
396
|
+
)}
|
|
397
|
+
</Segment>
|
|
398
|
+
<Segment>
|
|
460
399
|
<Form>
|
|
461
|
-
<
|
|
462
|
-
<
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
<Header size="small">
|
|
469
|
-
<FormattedMessage
|
|
470
|
-
id="Filter by prefix"
|
|
471
|
-
defaultMessage="Filter by prefix"
|
|
472
|
-
/>
|
|
473
|
-
</Header>
|
|
400
|
+
<Header size="medium">
|
|
401
|
+
<FormattedMessage
|
|
402
|
+
id="All existing alternative urls for this site"
|
|
403
|
+
defaultMessage="Existing alternative URLs for this site"
|
|
404
|
+
/>
|
|
405
|
+
</Header>
|
|
406
|
+
<Segment>
|
|
474
407
|
<Form.Field>
|
|
475
|
-
<
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
<Header size="small">
|
|
485
|
-
<FormattedMessage
|
|
486
|
-
id="Manually or automatically added?"
|
|
487
|
-
defaultMessage="Manually or automatically added?"
|
|
488
|
-
/>
|
|
489
|
-
</Header>
|
|
490
|
-
{filterChoices.map((o, i) => (
|
|
491
|
-
<Form.Field key={i}>
|
|
492
|
-
<Radio
|
|
493
|
-
label={o.label}
|
|
494
|
-
name="radioGroup"
|
|
495
|
-
value={o.value}
|
|
496
|
-
checked={this.state.filterType === o}
|
|
497
|
-
onChange={() => this.handleSelectFilterType(o)}
|
|
408
|
+
<FormFieldWrapper
|
|
409
|
+
id="filterQuery"
|
|
410
|
+
title={intl.formatMessage(messages.filterByPrefix)}
|
|
411
|
+
>
|
|
412
|
+
<Input
|
|
413
|
+
name="filter"
|
|
414
|
+
placeholder="/example"
|
|
415
|
+
value={filterQuery}
|
|
416
|
+
onChange={(e) => setFilterQuery(e.target.value)}
|
|
498
417
|
/>
|
|
499
|
-
</
|
|
500
|
-
|
|
418
|
+
</FormFieldWrapper>
|
|
419
|
+
</Form.Field>
|
|
420
|
+
<Form.Field>
|
|
421
|
+
<FormFieldWrapper
|
|
422
|
+
id="filterType"
|
|
423
|
+
title={intl.formatMessage(messages.manualOrAuto)}
|
|
424
|
+
>
|
|
425
|
+
<Form.Group inline>
|
|
426
|
+
{filterChoices.map((o, i) => (
|
|
427
|
+
<Form.Field key={i}>
|
|
428
|
+
<Radio
|
|
429
|
+
label={o.label}
|
|
430
|
+
name="radioGroup"
|
|
431
|
+
value={o.value}
|
|
432
|
+
checked={filterType === o}
|
|
433
|
+
onChange={() => setFilterType(o)}
|
|
434
|
+
/>
|
|
435
|
+
</Form.Field>
|
|
436
|
+
))}
|
|
437
|
+
</Form.Group>
|
|
438
|
+
</FormFieldWrapper>
|
|
439
|
+
</Form.Field>
|
|
501
440
|
<Form.Field>
|
|
502
441
|
<DatetimeWidget
|
|
503
442
|
id="created-before-date"
|
|
504
|
-
title={
|
|
443
|
+
title={intl.formatMessage(messages.createdBefore)}
|
|
505
444
|
dateOnly={true}
|
|
506
|
-
value={
|
|
445
|
+
value={createdBefore}
|
|
507
446
|
onChange={(id, value) => {
|
|
508
|
-
|
|
447
|
+
setCreatedBefore(value);
|
|
509
448
|
}}
|
|
510
449
|
/>
|
|
511
450
|
</Form.Field>
|
|
512
|
-
|
|
451
|
+
{hasAdvancedFiltering && (
|
|
452
|
+
<Form.Field>
|
|
453
|
+
<DatetimeWidget
|
|
454
|
+
id="created-after-date"
|
|
455
|
+
title={intl.formatMessage(messages.createdAfter)}
|
|
456
|
+
dateOnly={true}
|
|
457
|
+
value={createdAfter}
|
|
458
|
+
onChange={(id, value) => {
|
|
459
|
+
setCreatedAfter(value);
|
|
460
|
+
}}
|
|
461
|
+
/>
|
|
462
|
+
</Form.Field>
|
|
463
|
+
)}
|
|
464
|
+
<Button onClick={() => updateResults()} primary>
|
|
513
465
|
Filter
|
|
514
466
|
</Button>
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
467
|
+
</Segment>
|
|
468
|
+
</Form>
|
|
469
|
+
</Segment>
|
|
470
|
+
<Segment>
|
|
471
|
+
<Header size="small">
|
|
472
|
+
<FormattedMessage
|
|
473
|
+
id="Alternative url path → target url path (date and time of creation, manually created yes/no)"
|
|
474
|
+
defaultMessage="Alternative URL path → target URL path (date and time of creation, manually created yes/no)"
|
|
475
|
+
/>
|
|
476
|
+
</Header>
|
|
521
477
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
<
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
478
|
+
<Table celled compact>
|
|
479
|
+
<Table.Header>
|
|
480
|
+
<Table.Row>
|
|
481
|
+
<Table.HeaderCell width="1">
|
|
482
|
+
<FormattedMessage id="Select" defaultMessage="Select" />
|
|
483
|
+
</Table.HeaderCell>
|
|
484
|
+
<Table.HeaderCell width="10">
|
|
485
|
+
<FormattedMessage id="Alias" defaultMessage="Alias" />
|
|
486
|
+
</Table.HeaderCell>
|
|
487
|
+
<Table.HeaderCell width="1">
|
|
488
|
+
<FormattedMessage id="Date" defaultMessage="Date" />
|
|
489
|
+
</Table.HeaderCell>
|
|
490
|
+
<Table.HeaderCell width="1">
|
|
491
|
+
<FormattedMessage id="Manual" defaultMessage="Manual" />
|
|
492
|
+
</Table.HeaderCell>
|
|
493
|
+
</Table.Row>
|
|
494
|
+
</Table.Header>
|
|
495
|
+
<Table.Body>
|
|
496
|
+
{aliases.get.loading && (
|
|
497
|
+
<Table.Row>
|
|
498
|
+
<Table.Cell colSpan="4">
|
|
499
|
+
<Loader active inline="centered" />
|
|
500
|
+
</Table.Cell>
|
|
501
|
+
</Table.Row>
|
|
502
|
+
)}
|
|
503
|
+
{aliases.items.length > 0 &&
|
|
504
|
+
aliases.items.map((alias, i) => (
|
|
505
|
+
<Table.Row key={i} verticalAlign="top">
|
|
506
|
+
<Table.Cell>
|
|
507
|
+
<Checkbox
|
|
508
|
+
onChange={(e, { value }) => handleCheckAlias(value)}
|
|
509
|
+
checked={aliasesToRemove.includes(alias.path)}
|
|
510
|
+
value={alias.path}
|
|
547
511
|
/>
|
|
548
|
-
</Table.
|
|
512
|
+
</Table.Cell>
|
|
513
|
+
<Table.Cell>
|
|
514
|
+
{alias.path}
|
|
515
|
+
<br />
|
|
516
|
+
→ {alias['redirect-to']}{' '}
|
|
517
|
+
<Button
|
|
518
|
+
basic
|
|
519
|
+
style={{ verticalAlign: 'middle' }}
|
|
520
|
+
aria-label={intl.formatMessage(messages.EditUrl)}
|
|
521
|
+
onClick={() => {
|
|
522
|
+
setEditingData({
|
|
523
|
+
altUrlPath: alias.path,
|
|
524
|
+
targetUrlPath: alias['redirect-to'],
|
|
525
|
+
});
|
|
526
|
+
setAddModalOpen(true);
|
|
527
|
+
}}
|
|
528
|
+
>
|
|
529
|
+
<Icon name={editingSVG} size="18px" />
|
|
530
|
+
</Button>
|
|
531
|
+
</Table.Cell>
|
|
532
|
+
<Table.Cell>
|
|
533
|
+
<FormattedDate date={alias.datetime} />
|
|
534
|
+
</Table.Cell>
|
|
535
|
+
<Table.Cell>{`${alias.manual}`}</Table.Cell>
|
|
549
536
|
</Table.Row>
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
<Table.Cell>
|
|
574
|
-
<p>{`${alias.manual}`}</p>
|
|
575
|
-
</Table.Cell>
|
|
576
|
-
</Table.Row>
|
|
577
|
-
))}
|
|
578
|
-
</Table.Body>
|
|
579
|
-
</Table>
|
|
580
|
-
<div
|
|
581
|
-
style={{
|
|
582
|
-
display: 'flex',
|
|
583
|
-
flexWrap: 'wrap',
|
|
584
|
-
alignItems: 'center',
|
|
585
|
-
marginBottom: '20px',
|
|
586
|
-
}}
|
|
587
|
-
>
|
|
588
|
-
{this.state.pages && (
|
|
589
|
-
<Pagination
|
|
590
|
-
boundaryRange={0}
|
|
591
|
-
activePage={this.state.activePage}
|
|
592
|
-
ellipsisItem={null}
|
|
593
|
-
firstItem={null}
|
|
594
|
-
lastItem={null}
|
|
595
|
-
siblingRange={1}
|
|
596
|
-
totalPages={this.state.pages}
|
|
597
|
-
onPageChange={(e, o) => this.handlePageChange(e, o)}
|
|
598
|
-
/>
|
|
599
|
-
)}
|
|
600
|
-
<Menu.Menu
|
|
601
|
-
position="right"
|
|
602
|
-
style={{ display: 'flex', marginLeft: 'auto' }}
|
|
603
|
-
>
|
|
604
|
-
<Menu.Item style={{ color: 'grey' }}>
|
|
605
|
-
<FormattedMessage id="Show" defaultMessage="Show" />:
|
|
606
|
-
</Menu.Item>
|
|
607
|
-
{map(itemsPerPageChoices, (size) => (
|
|
608
|
-
<Menu.Item
|
|
609
|
-
style={{
|
|
610
|
-
padding: '0 0.4em',
|
|
611
|
-
margin: '0em 0.357em',
|
|
612
|
-
cursor: 'pointer',
|
|
613
|
-
}}
|
|
614
|
-
key={size}
|
|
615
|
-
value={size}
|
|
616
|
-
active={size === this.state.itemsPerPage}
|
|
617
|
-
onClick={(e, o) => this.handleItemsPerPage(e, o)}
|
|
618
|
-
>
|
|
619
|
-
{size}
|
|
620
|
-
</Menu.Item>
|
|
621
|
-
))}
|
|
622
|
-
</Menu.Menu>
|
|
623
|
-
</div>
|
|
624
|
-
<Button
|
|
625
|
-
disabled={this.state.aliasesToRemove.length === 0}
|
|
626
|
-
onClick={this.handleRemoveAliases}
|
|
627
|
-
primary
|
|
628
|
-
>
|
|
629
|
-
<FormattedMessage
|
|
630
|
-
id="Remove selected"
|
|
631
|
-
defaultMessage="Remove selected"
|
|
632
|
-
/>
|
|
633
|
-
</Button>
|
|
634
|
-
</Segment>
|
|
635
|
-
</Form>
|
|
636
|
-
</Segment.Group>
|
|
637
|
-
</article>
|
|
638
|
-
</Container>
|
|
639
|
-
{this.state.isClient && (
|
|
640
|
-
<Portal node={document.getElementById('toolbar')}>
|
|
641
|
-
<Toolbar
|
|
642
|
-
pathname={this.props.pathname}
|
|
643
|
-
hideDefaultViewButtons
|
|
644
|
-
inner={
|
|
645
|
-
<Link className="item" to="#" onClick={() => this.onCancel()}>
|
|
646
|
-
<Icon
|
|
647
|
-
name={backSVG}
|
|
648
|
-
className="contents circled"
|
|
649
|
-
size="30px"
|
|
650
|
-
title={this.props.intl.formatMessage(messages.back)}
|
|
537
|
+
))}
|
|
538
|
+
</Table.Body>
|
|
539
|
+
</Table>
|
|
540
|
+
<div
|
|
541
|
+
style={{
|
|
542
|
+
display: 'flex',
|
|
543
|
+
flexWrap: 'wrap',
|
|
544
|
+
alignItems: 'center',
|
|
545
|
+
marginBottom: '20px',
|
|
546
|
+
}}
|
|
547
|
+
>
|
|
548
|
+
{pages && (
|
|
549
|
+
<Pagination
|
|
550
|
+
boundaryRange={0}
|
|
551
|
+
activePage={activePage}
|
|
552
|
+
ellipsisItem={null}
|
|
553
|
+
firstItem={null}
|
|
554
|
+
lastItem={null}
|
|
555
|
+
siblingRange={1}
|
|
556
|
+
totalPages={pages}
|
|
557
|
+
onPageChange={(e, { activePage }) =>
|
|
558
|
+
setActivePage(activePage)
|
|
559
|
+
}
|
|
651
560
|
/>
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
561
|
+
)}
|
|
562
|
+
<Menu.Menu
|
|
563
|
+
position="right"
|
|
564
|
+
style={{ display: 'flex', marginLeft: 'auto' }}
|
|
565
|
+
>
|
|
566
|
+
<Menu.Item style={{ color: 'grey' }}>
|
|
567
|
+
<FormattedMessage id="Show" defaultMessage="Show" />:
|
|
568
|
+
</Menu.Item>
|
|
569
|
+
{map(itemsPerPageChoices, (size) => (
|
|
570
|
+
<Menu.Item
|
|
571
|
+
style={{
|
|
572
|
+
padding: '0 0.4em',
|
|
573
|
+
margin: '0em 0.357em',
|
|
574
|
+
cursor: 'pointer',
|
|
575
|
+
}}
|
|
576
|
+
key={size}
|
|
577
|
+
value={size}
|
|
578
|
+
active={size === itemsPerPage}
|
|
579
|
+
onClick={(e, { value }) => {
|
|
580
|
+
setItemsPerPage(value);
|
|
581
|
+
setActivePage(1);
|
|
582
|
+
}}
|
|
583
|
+
>
|
|
584
|
+
{size}
|
|
585
|
+
</Menu.Item>
|
|
586
|
+
))}
|
|
587
|
+
</Menu.Menu>
|
|
588
|
+
</div>
|
|
589
|
+
<Button
|
|
590
|
+
id="remove-alt-urls"
|
|
591
|
+
disabled={aliasesToRemove.length === 0}
|
|
592
|
+
onClick={handleRemoveAliases}
|
|
593
|
+
primary
|
|
594
|
+
>
|
|
595
|
+
<FormattedMessage
|
|
596
|
+
id="Remove selected"
|
|
597
|
+
defaultMessage="Remove selected"
|
|
598
|
+
/>
|
|
599
|
+
</Button>
|
|
600
|
+
</Segment>
|
|
601
|
+
</Segment.Group>
|
|
602
|
+
</article>
|
|
603
|
+
</Container>
|
|
604
|
+
{__CLIENT__ && (
|
|
605
|
+
<Portal node={document.getElementById('toolbar')}>
|
|
606
|
+
<Toolbar
|
|
607
|
+
pathname={pathname}
|
|
608
|
+
hideDefaultViewButtons
|
|
609
|
+
inner={
|
|
610
|
+
<Link
|
|
611
|
+
className="item"
|
|
612
|
+
to="#"
|
|
613
|
+
onClick={() => history.push(getParentUrl(pathname))}
|
|
614
|
+
>
|
|
615
|
+
<Icon
|
|
616
|
+
name={backSVG}
|
|
617
|
+
className="contents circled"
|
|
618
|
+
size="30px"
|
|
619
|
+
title={intl.formatMessage(messages.back)}
|
|
620
|
+
/>
|
|
621
|
+
</Link>
|
|
622
|
+
}
|
|
623
|
+
/>
|
|
624
|
+
</Portal>
|
|
625
|
+
)}
|
|
626
|
+
</div>
|
|
627
|
+
);
|
|
628
|
+
};
|
|
661
629
|
|
|
662
|
-
export default
|
|
663
|
-
injectIntl,
|
|
664
|
-
connect(
|
|
665
|
-
(state, props) => ({
|
|
666
|
-
aliases: state.aliases,
|
|
667
|
-
pathname: props.location.pathname,
|
|
668
|
-
}),
|
|
669
|
-
{ addAliases, getAliases, removeAliases },
|
|
670
|
-
),
|
|
671
|
-
)(Aliases);
|
|
630
|
+
export default Aliases;
|