invenio-app-rdm 14.0.0b0.dev4__py2.py3-none-any.whl → 14.0.0b1.dev2__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. invenio_app_rdm/__init__.py +1 -1
  2. invenio_app_rdm/administration/moderation/__init__.py +11 -0
  3. invenio_app_rdm/administration/moderation/requests.py +175 -0
  4. invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/requests.html +14 -0
  5. invenio_app_rdm/administration/templates/semantic-ui/invenio_app_rdm/administration/requests_details.html +89 -0
  6. invenio_app_rdm/administration/user_moderation/user_moderation.py +4 -4
  7. invenio_app_rdm/config.py +47 -0
  8. invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/deposit.html +3 -0
  9. invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/side_bar/manage_menu.html +3 -3
  10. invenio_app_rdm/records_ui/utils.py +22 -1
  11. invenio_app_rdm/records_ui/views/deposits.py +59 -7
  12. invenio_app_rdm/records_ui/views/records.py +43 -1
  13. invenio_app_rdm/requests_ui/templates/semantic-ui/invenio_requests/guest-access-request/index.html +5 -5
  14. invenio_app_rdm/requests_ui/templates/semantic-ui/invenio_requests/record-deletion/index.html +93 -0
  15. invenio_app_rdm/requests_ui/views/requests.py +17 -2
  16. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/requests/RequestsSearchResultItem.js +81 -0
  17. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/requests/RequestsSearchbarLayout.js +48 -0
  18. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/requests/index.js +22 -0
  19. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordDeletion/DeletionModal.js +356 -0
  20. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordDeletion/DeletionRadioGroup.js +44 -0
  21. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordDeletion.js +81 -0
  22. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/RDMDepositForm.js +22 -2
  23. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/index.js +2 -1
  24. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/ManageButton.js +72 -20
  25. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordCommunitySubmission/RecordCommunitySubmissionModal.js +1 -1
  26. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/RecordManagement.js +10 -3
  27. invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/landing_page/index.js +1 -0
  28. invenio_app_rdm/theme/webpack.py +2 -1
  29. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/METADATA +20 -5
  30. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/RECORD +34 -23
  31. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/entry_points.txt +2 -0
  32. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/WHEEL +0 -0
  33. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/licenses/LICENSE +0 -0
  34. {invenio_app_rdm-14.0.0b0.dev4.dist-info → invenio_app_rdm-14.0.0b1.dev2.dist-info}/top_level.txt +0 -0
@@ -30,11 +30,11 @@
30
30
  {# only display the header for the receiver #}
31
31
  {%- set back_button_url = url_for("invenio_app_rdm_users.requests") %}
32
32
  {{ inclusion_request_header(
33
- back_button_url=back_button_url,
34
- back_button_text=_("Back to requests"),
35
- request=invenio_request,
36
- accepted=request_is_accepted
37
- ) }}
33
+ back_button_url=back_button_url,
34
+ back_button_text=_("Back to requests"),
35
+ request=invenio_request,
36
+ accepted=request_is_accepted
37
+ ) }}
38
38
 
39
39
  {%- else %}
40
40
  <div class="ui grid rel-mt-2">
@@ -0,0 +1,93 @@
1
+ {# -*- coding: utf-8 -*-
2
+
3
+ This file is part of Invenio.
4
+ Copyright (C) 2016-2025 CERN.
5
+
6
+ Invenio is free software; you can redistribute it and/or modify it
7
+ under the terms of the MIT License; see LICENSE file for more details.
8
+ #}
9
+
10
+ {% extends "invenio_requests/details/index.html" %}
11
+
12
+ {% set active_dashboard_menu_item = 'requests' %}
13
+ {% set active_community_header_menu_item = 'requests' %}
14
+
15
+ {%- block request_header %}
16
+ {% set back_button_url = url_for("invenio_app_rdm_users.requests") %}
17
+ {% from "invenio_requests/macros/request_header.html" import inclusion_request_header %}
18
+ {{ inclusion_request_header(
19
+ request=invenio_request,
20
+ record=record,
21
+ accepted=request_is_accepted,
22
+ back_button_url=back_button_url,
23
+ back_button_text=_("Back to requests")
24
+ ) }}
25
+
26
+ <div class="twelve wide column mt-10">
27
+ <p><strong>{{ _("Reason:") }}</strong> {{ invenio_request["payload"]["reason_label"] }}</p>
28
+ <p><strong>{{ _("Justification:") }}</strong> {{ invenio_request["payload"]["comment"] }}</p>
29
+ </div>
30
+ {%- endblock request_header %}
31
+
32
+ {% block request_timeline %}
33
+ <div
34
+ class="ui container rdm-tab-container fluid rel-pt-2 ml-0-mobile mr-0-mobile"
35
+ id="request-request-deletion-tab-container"
36
+ >
37
+ <div
38
+ class="ui secondary pointing menu rdm-tab-menu"
39
+ id="request-deletion-request-tab"
40
+ >
41
+ <a
42
+ class="active item"
43
+ data-tab="conversation"
44
+ role="tab"
45
+ aria-selected="true"
46
+ aria-controls="conversation-tab-panel"
47
+ id="conversation-tab"
48
+ >
49
+ {{ _("Conversation") }}
50
+ </a>
51
+
52
+ {% if record_ui %}
53
+ <a
54
+ role="tab"
55
+ class="item"
56
+ data-tab="record"
57
+ aria-selected="false"
58
+ aria-controls="record-tab-panel"
59
+ id="record-tab"
60
+ >
61
+ {{ _("Record") }}
62
+ </a>
63
+ {% endif %}
64
+ </div>
65
+
66
+ <div
67
+ class="ui bottom attached tab segment active borderless p-0"
68
+ data-tab="conversation"
69
+ role="tabpanel"
70
+ aria-labelledby="conversation-tab"
71
+ id="conversation-tab-panel"
72
+ >
73
+ {{ super() }}
74
+ </div>
75
+
76
+ {# The record tab content needs to be last since the HTML structure is complex and breaks following tab contents #}
77
+ {% if record_ui %}
78
+ <div
79
+ class="ui bottom attached tab segment borderless"
80
+ data-tab="record"
81
+ role="tabpanel"
82
+ aria-labelledby="record-tab"
83
+ id="record-tab-panel"
84
+ hidden="hidden"
85
+ >
86
+ {% set use_theme_basic_template = false %}
87
+ {% set preview_submission_request = true %}
88
+ {% include config.APP_RDM_RECORD_LANDING_PAGE_TEMPLATE %}
89
+ </div>
90
+ {% endif %}
91
+
92
+ </div>
93
+ {% endblock request_timeline %}
@@ -22,16 +22,20 @@ from invenio_communities.subcommunities.services.request import (
22
22
  from invenio_communities.utils import identity_cache_key
23
23
  from invenio_communities.views.communities import render_community_theme_template
24
24
  from invenio_communities.views.decorators import pass_community
25
+ from invenio_i18n.ext import current_i18n
25
26
  from invenio_pidstore.errors import PIDDoesNotExistError
26
27
  from invenio_rdm_records.proxies import current_rdm_records_service
27
28
  from invenio_rdm_records.requests import CommunityInclusion, CommunitySubmission
28
29
  from invenio_rdm_records.resources.serializers import UIJSONSerializer
30
+ from invenio_rdm_records.services.errors import RecordDeletedException
29
31
  from invenio_rdm_records.services.generators import CommunityInclusionNeed
30
32
  from invenio_records_resources.services.errors import PermissionDeniedError
31
33
  from invenio_requests.customizations import AcceptAction
32
34
  from invenio_requests.resolvers.registry import ResolverRegistry
33
35
  from invenio_requests.views.decorators import pass_request
34
36
  from invenio_users_resources.proxies import current_user_resources
37
+ from invenio_vocabularies.proxies import current_service as vocabulary_service
38
+ from marshmallow_utils.fields.babel import gettext_from_dict
35
39
  from sqlalchemy.orm.exc import NoResultFound
36
40
 
37
41
  from ...records_ui.utils import get_external_resources
@@ -89,7 +93,7 @@ def _resolve_topic_record(request):
89
93
  record = current_rdm_records_service.read_draft(
90
94
  g.identity, pid, expand=True
91
95
  )
92
- except (NoResultFound, PIDDoesNotExistError):
96
+ except (NoResultFound, PIDDoesNotExistError, RecordDeletedException):
93
97
  # We catch PIDDoesNotExistError because a published record with
94
98
  # a soft-deleted draft will raise this error. The lines below
95
99
  # will catch the case that a id does not exists and raise a
@@ -98,7 +102,7 @@ def _resolve_topic_record(request):
98
102
  try:
99
103
  # read published record
100
104
  record = current_rdm_records_service.read(g.identity, pid, expand=True)
101
- except NoResultFound:
105
+ except (NoResultFound, RecordDeletedException):
102
106
  # record tab not displayed when the record is not found
103
107
  # the request is probably not open anymore
104
108
  pass
@@ -202,6 +206,17 @@ def user_dashboard_request_view(request, **kwargs):
202
206
  else:
203
207
  checks = ChecksAPI.get_runs(record._record)
204
208
 
209
+ if request_type == "record-deletion":
210
+ reason_title = vocabulary_service.read(
211
+ g.identity,
212
+ ("removalreasons", request["payload"]["reason"]),
213
+ ).to_dict()
214
+ request["payload"]["reason_label"] = gettext_from_dict(
215
+ reason_title["title"],
216
+ current_i18n.locale,
217
+ current_app.config.get("BABEL_DEFAULT_LOCALE", "en"),
218
+ )
219
+
205
220
  return render_template(
206
221
  f"invenio_requests/{request_type}/index.html",
207
222
  base_template="invenio_app_rdm/users/base.html",
@@ -0,0 +1,81 @@
1
+ /*
2
+ * This file is part of Invenio.
3
+ * Copyright (C) 2025 CERN.
4
+ *
5
+ * InvenioAppRdm is free software; you can redistribute it and/or modify it
6
+ * under the terms of the MIT License; see LICENSE file for more details.
7
+ */
8
+
9
+ import PropTypes from "prop-types";
10
+ import React, { Component } from "react";
11
+ import { Table } from "semantic-ui-react";
12
+ import { withState } from "react-searchkit";
13
+ import { AdminUIRoutes } from "@js/invenio_administration/src/routes.js";
14
+ import { UserListItemCompact, toRelativeTime } from "react-invenio-forms";
15
+ import { i18next } from "@translations/invenio_app_rdm/i18next";
16
+
17
+ class SearchResultItemComponent extends Component {
18
+ refreshAfterAction = () => {
19
+ const { updateQueryState, currentQueryState } = this.props;
20
+ updateQueryState(currentQueryState);
21
+ };
22
+
23
+ render() {
24
+ const { result, columns, idKeyPath, listUIEndpoint } = this.props;
25
+ return (
26
+ <Table.Row>
27
+ <Table.Cell
28
+ key={`${columns[0][1]["text"]}-${columns[0][1]["order"]}`}
29
+ data-label={columns[0][1]["text"]}
30
+ className="word-break-all"
31
+ >
32
+ <a href={AdminUIRoutes.detailsView(listUIEndpoint, result, idKeyPath)}>
33
+ {/* TODO we need a better way to get the (translatable) label of a request type
34
+ https://github.com/inveniosoftware/invenio-requests/issues/414 */}
35
+ {result.type === "record-deletion" && "Record deletion"}
36
+ </a>
37
+ </Table.Cell>
38
+ <Table.Cell
39
+ key={`${columns[1][1]["text"]}-${columns[1][1]["order"]}`}
40
+ data-label={columns[1][1]["text"]}
41
+ className="word-break-all"
42
+ >
43
+ <UserListItemCompact
44
+ user={result.expanded.created_by}
45
+ id={result.created_by.user}
46
+ // TODO linkToDetailView= filter by user?
47
+ />
48
+ {toRelativeTime(result.created)}
49
+ </Table.Cell>
50
+ <Table.Cell
51
+ key={`${columns[2][1]["text"]}-${columns[2][1]["order"]}`}
52
+ data-label={columns[2][1]["text"]}
53
+ className="word-break-all"
54
+ >
55
+ {result.expanded.last_reply ? (
56
+ <>
57
+ <UserListItemCompact
58
+ user={result.expanded.last_reply.created_by}
59
+ id={result.last_reply.created_by.user}
60
+ />
61
+ {toRelativeTime(result.last_reply.created)}
62
+ </>
63
+ ) : (
64
+ i18next.t("No replies")
65
+ )}
66
+ </Table.Cell>
67
+ </Table.Row>
68
+ );
69
+ }
70
+ }
71
+
72
+ SearchResultItemComponent.propTypes = {
73
+ result: PropTypes.object.isRequired,
74
+ columns: PropTypes.array.isRequired,
75
+ updateQueryState: PropTypes.func.isRequired,
76
+ currentQueryState: PropTypes.object.isRequired,
77
+ idKeyPath: PropTypes.string.isRequired,
78
+ listUIEndpoint: PropTypes.string.isRequired,
79
+ };
80
+
81
+ export const SearchResultItem = withState(SearchResultItemComponent);
@@ -0,0 +1,48 @@
1
+ /*
2
+ * // This file is part of Invenio-Requests
3
+ * // Copyright (C) 2025 CERN.
4
+ * //
5
+ * // Invenio-Requests is free software; you can redistribute it and/or modify it
6
+ * // under the terms of the MIT License; see LICENSE file for more details.
7
+ */
8
+
9
+ import PropTypes from "prop-types";
10
+ import React, { Component } from "react";
11
+ import { RequestStatusFilter } from "@js/invenio_requests/search";
12
+ import { SearchBar, Sort } from "react-searchkit";
13
+
14
+ export class RequestsSearchbarLayout extends Component {
15
+ render() {
16
+ const { config } = this.props;
17
+
18
+ return (
19
+ <>
20
+ {/* auto column grid used instead of SUI grid for better searchbar width adjustment */}
21
+ <div className="auto-column-grid">
22
+ <div className="flex column-mobile">
23
+ <div className="mobile only rel-mb-1 flex align-items-center justify-space-between">
24
+ <RequestStatusFilter keepFiltersOnUpdate />
25
+ </div>
26
+
27
+ <div className="tablet only computer only rel-mr-2">
28
+ <RequestStatusFilter keepFiltersOnUpdate />
29
+ </div>
30
+ <div className="full-width">
31
+ <SearchBar fluid />
32
+ </div>
33
+ </div>
34
+ <div className="flex align-items-center column-mobile">
35
+ <div className="full-width flex align-items-center justify-end column-mobile">
36
+ <Sort values={config.sortOptions} />
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <div className="rel-mb-1" />
41
+ </>
42
+ );
43
+ }
44
+ }
45
+
46
+ RequestsSearchbarLayout.propTypes = {
47
+ config: PropTypes.object.isRequired,
48
+ };
@@ -0,0 +1,22 @@
1
+ // This file is part of InvenioCommunities
2
+ // Copyright (C) 2025 CERN.
3
+ //
4
+ // Invenio RDM is free software; you can redistribute it and/or modify it
5
+ // under the terms of the MIT License; see LICENSE file for more details.
6
+
7
+ import { initDefaultSearchComponents } from "@js/invenio_administration";
8
+ import { createSearchAppInit } from "@js/invenio_search_ui";
9
+ import { RequestsSearchbarLayout } from "./RequestsSearchbarLayout";
10
+ import { SearchResultItem } from "./RequestsSearchResultItem";
11
+
12
+ const domContainer = document.getElementById("invenio-search-config");
13
+
14
+ const defaultComponents = initDefaultSearchComponents(domContainer);
15
+
16
+ const overridenComponents = {
17
+ ...defaultComponents,
18
+ "SearchApp.searchbarContainer": RequestsSearchbarLayout,
19
+ "InvenioAdministration.SearchResultItem.layout": SearchResultItem,
20
+ };
21
+
22
+ createSearchAppInit(overridenComponents, true, "invenio-search-config", false);
@@ -0,0 +1,356 @@
1
+ // This file is part of InvenioRDM
2
+ // Copyright (C) 2025 CERN
3
+ //
4
+ // Invenio-app-rdm is free software; you can redistribute it and/or modify it
5
+ // under the terms of the MIT License; see LICENSE file for more details.
6
+
7
+ import { i18next } from "@translations/invenio_app_rdm/i18next";
8
+ import { Formik } from "formik";
9
+ import PropTypes from "prop-types";
10
+ import React, { Component } from "react";
11
+
12
+ import {
13
+ ErrorMessage,
14
+ http,
15
+ SelectField,
16
+ TextAreaField,
17
+ withCancel,
18
+ } from "react-invenio-forms";
19
+ import {
20
+ Button,
21
+ Checkbox,
22
+ Icon,
23
+ Form,
24
+ Message,
25
+ Modal,
26
+ ModalActions,
27
+ ModalContent,
28
+ ModalHeader,
29
+ Table,
30
+ TableBody,
31
+ TableHeader,
32
+ TableHeaderCell,
33
+ TableRow,
34
+ } from "semantic-ui-react";
35
+ import * as Yup from "yup";
36
+ import DeletionRadioGroup from "./DeletionRadioGroup";
37
+
38
+ export class DeletionModal extends Component {
39
+ constructor(props) {
40
+ super(props);
41
+ const { recordDeletion } = this.props;
42
+ const checklistLength = recordDeletion["checklist"]?.length || 0;
43
+ this.initState = {
44
+ loading: false,
45
+ error: undefined,
46
+ checklistState: Array(checklistLength).fill(undefined),
47
+ checkboxState: Array(2).fill(false), // TODO dynamic count of number of checkboxes
48
+ messages: [],
49
+ };
50
+ this.state = { ...this.initState };
51
+ }
52
+
53
+ handleClose = () => {
54
+ this.setState({
55
+ ...this.initState,
56
+ });
57
+ const { handleClose } = this.props;
58
+ handleClose();
59
+ };
60
+
61
+ handleRadioUpdate = (index, value) => {
62
+ const { recordDeletion } = this.props;
63
+ const { checklistState } = this.state;
64
+ const nextChecklistState = checklistState.map((c, i) => {
65
+ if (i === index) {
66
+ return value;
67
+ } else {
68
+ return c;
69
+ }
70
+ });
71
+ const filteredChecklist = recordDeletion["checklist"].filter((_, index) => {
72
+ return nextChecklistState[index];
73
+ });
74
+ const newMessages = filteredChecklist.map((x) => x["message"]);
75
+ this.setState({ checklistState: nextChecklistState, messages: newMessages });
76
+ };
77
+
78
+ handleCheckboxUpdate = (index) => {
79
+ const { checkboxState } = this.state;
80
+ const nextCheckboxState = checkboxState.map((c, i) => {
81
+ if (i === index) {
82
+ return !checkboxState[i];
83
+ } else {
84
+ return c;
85
+ }
86
+ });
87
+ this.setState({ checkboxState: nextCheckboxState });
88
+ };
89
+
90
+ deletionRequestSchema = (immediateDeletionAllowed) => {
91
+ if (immediateDeletionAllowed) {
92
+ return Yup.object({
93
+ reason: Yup.string().required("Required"),
94
+ });
95
+ } else {
96
+ return Yup.object({
97
+ reason: Yup.string().required("Required"),
98
+ comment: Yup.string()
99
+ .min(25, "Please write at least 25 characters")
100
+ .required("Required"),
101
+ });
102
+ }
103
+ };
104
+
105
+ handleSubmit = async (values) => {
106
+ this.setState({ loading: true });
107
+ const { record } = this.props;
108
+ const payload = {
109
+ reason: values.reason,
110
+ comment: values.comment,
111
+ };
112
+ if ("request_deletion" in record.links) {
113
+ this.cancellableAction = withCancel(
114
+ http.post(record.links.request_deletion, payload)
115
+ );
116
+ try {
117
+ const response = await this.cancellableAction.promise;
118
+ const data = response.data;
119
+
120
+ if (response.status === 200) {
121
+ window.location.reload();
122
+ } else if (response.status === 201) {
123
+ window.location.href = data.links.self_html;
124
+ }
125
+ } catch (error) {
126
+ this.setState({ error: error });
127
+ console.error(error);
128
+ } finally {
129
+ this.setState({ loading: false });
130
+ }
131
+ } else {
132
+ this.setState({ error: "Could not submit deletion request", loading: false });
133
+ }
134
+ };
135
+
136
+ render() {
137
+ const { open, recordDeletion, options } = this.props;
138
+ const { loading, error, checklistState, checkboxState, messages } = this.state;
139
+
140
+ const { checklist } = recordDeletion;
141
+
142
+ if (!("recordDeletion" in recordDeletion)) {
143
+ return null;
144
+ }
145
+
146
+ const immediateDeletionAllowed =
147
+ recordDeletion["recordDeletion"]["immediate_deletion"]["allowed"];
148
+
149
+ const modalHeaderText = immediateDeletionAllowed
150
+ ? i18next.t("Delete record")
151
+ : i18next.t("Request deletion");
152
+ const deletionButtonText = immediateDeletionAllowed
153
+ ? i18next.t("Delete immediately")
154
+ : i18next.t("Request deletion");
155
+ const deletionButtonClass = immediateDeletionAllowed ? "negative" : "primary";
156
+
157
+ const files = recordDeletion["context"]["files"];
158
+ const internalDoi = recordDeletion["context"]["internalDoi"];
159
+
160
+ const formDisabled =
161
+ checkboxState.some((v) => v === false) ||
162
+ checklistState.some((x) => x === true || x === undefined);
163
+
164
+ return (
165
+ <Modal
166
+ open={open}
167
+ closeIcon
168
+ onClose={this.handleClose}
169
+ role="dialog"
170
+ aria-modal="true"
171
+ tab-index="-1"
172
+ size="tiny"
173
+ closeOnDimmerClick={false}
174
+ onClick={(e) => e.stopPropagation()} // prevent interaction with dropdown
175
+ onKeyDown={(e) => e.stopPropagation()} // prevent interaction with dropdown
176
+ >
177
+ <ModalHeader>{modalHeaderText}</ModalHeader>
178
+ <Formik
179
+ onSubmit={this.handleSubmit}
180
+ validationSchema={this.deletionRequestSchema(immediateDeletionAllowed)}
181
+ initialValues={{ reason: "", comment: "" }}
182
+ validateOnChange={false}
183
+ validateOnBlur={false}
184
+ >
185
+ {({ handleSubmit }) => (
186
+ <Form>
187
+ <ModalContent>
188
+ <>
189
+ {immediateDeletionAllowed ? (
190
+ <p>
191
+ {
192
+ recordDeletion["recordDeletion"]["immediate_deletion"][
193
+ "policy"
194
+ ]["description"]
195
+ }
196
+ </p>
197
+ ) : (
198
+ <p>
199
+ {
200
+ recordDeletion["recordDeletion"]["request_deletion"]["policy"][
201
+ "description"
202
+ ]
203
+ }
204
+ </p>
205
+ )}
206
+ <Message negative>
207
+ <Icon name="warning sign" />
208
+ {i18next.t("By deleting this record you acknowledge that:")}
209
+ <br />
210
+ <Checkbox
211
+ label={
212
+ /* eslint-disable-next-line jsx-a11y/label-has-associated-control */
213
+ <label>
214
+ <strong>
215
+ {files} file{files !== 1 ? "s" : ""}
216
+ </strong>{" "}
217
+ {i18next.t("will be deleted.")}
218
+ </label>
219
+ }
220
+ className="mt-5 mb-5"
221
+ onChange={() => this.handleCheckboxUpdate(0)}
222
+ />
223
+ <br />
224
+ {internalDoi ? (
225
+ <Checkbox
226
+ label={
227
+ /* eslint-disable-next-line jsx-a11y/label-has-associated-control */
228
+ <label>
229
+ <strong>{i18next.t("The DOI cannot be reused")}</strong>{" "}
230
+ {i18next.t(
231
+ "and the DOI will resolve to a tombstone page with the record's citation (including title, author/s, publication year and publisher)"
232
+ )}
233
+ </label>
234
+ }
235
+ className="mb-5"
236
+ onChange={() => this.handleCheckboxUpdate(1)}
237
+ />
238
+ ) : (
239
+ <Checkbox
240
+ label={
241
+ /* eslint-disable-next-line jsx-a11y/label-has-associated-control */
242
+ <label>
243
+ {i18next.t(
244
+ "A tombstone page with the citation will replace the record page"
245
+ )}
246
+ </label>
247
+ }
248
+ className="mb-5"
249
+ onChange={() => this.handleCheckboxUpdate(1)}
250
+ />
251
+ )}
252
+ </Message>
253
+ </>
254
+ {checklist.length > 0 && (
255
+ <>
256
+ <strong>{i18next.t("Record deletion checklist:")}</strong>
257
+ <Table basic="very" unstackable className="mt-0">
258
+ <TableHeader>
259
+ <TableRow>
260
+ <TableHeaderCell />
261
+ <TableHeaderCell>{i18next.t("Yes")}</TableHeaderCell>
262
+ <TableHeaderCell>{i18next.t("No")}</TableHeaderCell>
263
+ </TableRow>
264
+ </TableHeader>
265
+ <TableBody>
266
+ {checklist.map((row, index) => (
267
+ <DeletionRadioGroup
268
+ index={index}
269
+ row={row}
270
+ key={row.name}
271
+ state={checklistState}
272
+ onStateChange={this.handleRadioUpdate}
273
+ />
274
+ ))}
275
+ </TableBody>
276
+ </Table>
277
+ {messages.length > 0 && (
278
+ <Message info>
279
+ {messages.length === 1 ? (
280
+ <p dangerouslySetInnerHTML={{ __html: messages[0] }} />
281
+ ) : (
282
+ <Message.List>
283
+ {messages.map((message) => (
284
+ <Message.Item key={message}>
285
+ <p dangerouslySetInnerHTML={{ __html: message }} />
286
+ </Message.Item>
287
+ ))}
288
+ </Message.List>
289
+ )}
290
+ </Message>
291
+ )}
292
+ </>
293
+ )}
294
+ <SelectField
295
+ fluid
296
+ fieldPath="reason"
297
+ name="reason"
298
+ label={i18next.t("I want to delete this record because:")}
299
+ options={options}
300
+ required
301
+ disabled={formDisabled}
302
+ />
303
+
304
+ {!immediateDeletionAllowed && (
305
+ <TextAreaField
306
+ fieldPath="comment"
307
+ name="comment"
308
+ label={i18next.t("Detailed justification:")}
309
+ required
310
+ placeholder={i18next.t(
311
+ "Your justification will not be shared publicly"
312
+ )}
313
+ disabled={formDisabled}
314
+ />
315
+ )}
316
+ {error && (
317
+ <ErrorMessage
318
+ header={i18next.t("Unable to request deletion.")}
319
+ content={i18next.t(error)}
320
+ icon="exclamation"
321
+ className="text-align-left"
322
+ negative
323
+ />
324
+ )}
325
+ </ModalContent>
326
+ <ModalActions>
327
+ <Button
328
+ onClick={this.handleClose}
329
+ content={i18next.t("Close")}
330
+ floated="left"
331
+ />
332
+ <Button
333
+ content={deletionButtonText}
334
+ className={deletionButtonClass}
335
+ icon="trash alternate outline"
336
+ type="submit"
337
+ onClick={(event) => handleSubmit(event)}
338
+ loading={loading}
339
+ disabled={formDisabled || loading}
340
+ />
341
+ </ModalActions>
342
+ </Form>
343
+ )}
344
+ </Formik>
345
+ </Modal>
346
+ );
347
+ }
348
+ }
349
+
350
+ DeletionModal.propTypes = {
351
+ record: PropTypes.object.isRequired,
352
+ open: PropTypes.bool.isRequired,
353
+ handleClose: PropTypes.func.isRequired,
354
+ recordDeletion: PropTypes.object.isRequired,
355
+ options: PropTypes.array.isRequired,
356
+ };