invenio-app-rdm 13.0.0b2.dev0__py2.py3-none-any.whl → 13.0.0b2.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.
- invenio_app_rdm/__init__.py +2 -2
- invenio_app_rdm/administration/records/records.py +5 -0
- invenio_app_rdm/config.py +4 -1
- invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/description.html +2 -2
- invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/macros/detail.html +26 -29
- invenio_app_rdm/records_ui/views/decorators.py +21 -0
- invenio_app_rdm/records_ui/views/deposits.py +38 -2
- invenio_app_rdm/records_ui/views/records.py +21 -5
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/CompareRevisionsDropdown.js +83 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/ImpersonateUserForm.js +0 -1
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/components/RevisionsDiffViewer.js +77 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/CompareRevisions.js +128 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/RecordResourceActions.js +43 -4
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/api/api.js +5 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/administration/records/api/routes.js +3 -0
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/RecordsResultsListItem.js +10 -3
- invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/deposit/RDMDepositForm.js +1 -0
- invenio_app_rdm/theme/assets/semantic-ui/less/invenio_app_rdm/theme/elements/label.overrides +3 -3
- invenio_app_rdm/theme/assets/semantic-ui/less/invenio_app_rdm/theme/globals/site.overrides +2 -0
- invenio_app_rdm/theme/assets/semantic-ui/less/invenio_app_rdm/theme/views/item.overrides +4 -0
- invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/administration_page.html +15 -0
- invenio_app_rdm/theme/templates/semantic-ui/invenio_app_rdm/header.html +2 -1
- invenio_app_rdm/theme/webpack.py +5 -0
- invenio_app_rdm/translations/af/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/ar/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/bg/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/ca/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/cs/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/da/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/de/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/de_AT/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/de_DE/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/el/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/en_AT/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/en_HU/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/es/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/es_CU/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/es_MX/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/et/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/et_EE/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/fa/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/fa_IR/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/fr/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/fr_CI/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/fr_FR/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/gl/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/hi_IN/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/hr/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/hu/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/hu_HU/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/it/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/ja/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/ka/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/lt/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/ne/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/no/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/pl/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/pt/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/ro/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/ru/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/rw/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/sk/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/sv/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/sv_SE/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/tr/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/uk/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/uk_UA/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/zh_CN/LC_MESSAGES/messages.mo +0 -0
- invenio_app_rdm/translations/zh_TW/LC_MESSAGES/messages.mo +0 -0
- {invenio_app_rdm-13.0.0b2.dev0.dist-info → invenio_app_rdm-13.0.0b2.dev2.dist-info}/METADATA +38 -5
- {invenio_app_rdm-13.0.0b2.dev0.dist-info → invenio_app_rdm-13.0.0b2.dev2.dist-info}/RECORD +75 -71
- {invenio_app_rdm-13.0.0b2.dev0.dist-info → invenio_app_rdm-13.0.0b2.dev2.dist-info}/WHEEL +1 -1
- {invenio_app_rdm-13.0.0b2.dev0.dist-info → invenio_app_rdm-13.0.0b2.dev2.dist-info}/LICENSE +0 -0
- {invenio_app_rdm-13.0.0b2.dev0.dist-info → invenio_app_rdm-13.0.0b2.dev2.dist-info}/entry_points.txt +0 -0
- {invenio_app_rdm-13.0.0b2.dev0.dist-info → invenio_app_rdm-13.0.0b2.dev2.dist-info}/top_level.txt +0 -0
invenio_app_rdm/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C) 2019-
|
|
3
|
+
# Copyright (C) 2019-2025 CERN.
|
|
4
4
|
# Copyright (C) 2019-2022 Northwestern University.
|
|
5
5
|
# Copyright (C) 2024 Graz University of Technology.
|
|
6
6
|
#
|
|
@@ -17,6 +17,6 @@
|
|
|
17
17
|
#
|
|
18
18
|
# See PEP 0440 for details - https://www.python.org/dev/peps/pep-0440
|
|
19
19
|
|
|
20
|
-
__version__ = "13.0.0b2.
|
|
20
|
+
__version__ = "13.0.0b2.dev2"
|
|
21
21
|
|
|
22
22
|
__all__ = ("__version__",)
|
invenio_app_rdm/config.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright (C) 2019-
|
|
3
|
+
# Copyright (C) 2019-2025 CERN.
|
|
4
4
|
# Copyright (C) 2019-2020 Northwestern University.
|
|
5
5
|
# Copyright (C) 2021-2024 Graz University of Technology.
|
|
6
6
|
# Copyright (C) 2022-2024 KTH Royal Institute of Technology.
|
|
@@ -1461,6 +1461,9 @@ from invenio_app_rdm import __version__
|
|
|
1461
1461
|
ADMINISTRATION_DISPLAY_VERSIONS = [("invenio-app-rdm", f"v{__version__}")]
|
|
1462
1462
|
"""Show the InvenioRDM version in the administration panel."""
|
|
1463
1463
|
|
|
1464
|
+
ADMINISTRATION_THEME_BASE_TEMPLATE = "invenio_app_rdm/administration_page.html"
|
|
1465
|
+
"""Administration base template."""
|
|
1466
|
+
|
|
1464
1467
|
|
|
1465
1468
|
APP_RDM_SUBCOMMUNITIES_LABEL = "Subcommunities"
|
|
1466
1469
|
"""Label for the subcommunities in the community browse page."""
|
invenio_app_rdm/records_ui/templates/semantic-ui/invenio_app_rdm/records/details/description.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{#
|
|
2
|
-
Copyright (C) 2020 CERN.
|
|
2
|
+
Copyright (C) 2020-2025 CERN.
|
|
3
3
|
Copyright (C) 2020 Northwestern University.
|
|
4
4
|
Copyright (C) 2021 New York University.
|
|
5
5
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<h2 id="description-heading" class="sr-only">{{ _('Description') }}</h2>
|
|
16
16
|
{# description data is being sanitized by marshmallow in the backend #}
|
|
17
17
|
<div style="word-wrap: break-word;">
|
|
18
|
-
|
|
18
|
+
{{ description | safe }}
|
|
19
19
|
</div>
|
|
20
20
|
</section>
|
|
21
21
|
{% endif %}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{#
|
|
2
|
-
Copyright (C) 2020-
|
|
2
|
+
Copyright (C) 2020-2025 CERN.
|
|
3
3
|
Copyright (C) 2024 Northwestern University.
|
|
4
4
|
|
|
5
5
|
Invenio RDM Records is free software; you can redistribute it and/or modify
|
|
@@ -96,40 +96,37 @@
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
{% macro _show_funding_item(item, index) %}
|
|
99
|
+
<dt class="ui tiny header">{{ item.funder.name if item.funder }}</dt>
|
|
99
100
|
{%- if item.award -%}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
101
|
+
<dt class="ui tiny header">
|
|
102
|
+
<span class="mr-0 text-muted">
|
|
103
|
+
{% if item.award.acronym %}
|
|
104
|
+
{{ item.award.acronym }} –
|
|
105
|
+
{% endif %}
|
|
106
|
+
|
|
107
|
+
{%- if item.award.title_l10n -%}
|
|
108
|
+
{{ item.award.title_l10n }}
|
|
109
|
+
{%- endif -%}
|
|
110
|
+
</span>
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
{%- if item.award.number -%}
|
|
113
|
+
<span class="ui mini basic label {% if not item.award.title_l10n %}ml-0{% endif %}" id="number-label-{{ index }}">
|
|
114
|
+
{{ item.award.number }}
|
|
115
|
+
</span>
|
|
116
|
+
{%- endif -%}
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
{%- endif -%}
|
|
124
|
-
</dt>
|
|
118
|
+
{%- if item.award.identifiers -%}
|
|
119
|
+
{% for identifier in item.award.identifiers if 'url' == identifier.scheme %}
|
|
120
|
+
<a href="{{ identifier.identifier }}" target="_blank"
|
|
121
|
+
rel="noopener noreferrer" aria-label="{{ _('Open external link') }}">
|
|
122
|
+
<i class="external alternate icon"></i>
|
|
123
|
+
</a>
|
|
124
|
+
{%- endfor -%}
|
|
125
125
|
{%- endif -%}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<dt class="ui tiny header">{{ item.funder.name if item.funder }}</dt>
|
|
129
|
-
{%- endif -%}
|
|
126
|
+
</dt>
|
|
127
|
+
{%- endif -%}
|
|
130
128
|
{% endmacro %}
|
|
131
129
|
|
|
132
|
-
|
|
133
130
|
{% macro show_references(references) %}
|
|
134
131
|
<ul class="ui bulleted list details-list">
|
|
135
132
|
{% for reference in references %}
|
|
@@ -403,3 +403,24 @@ def secret_link_or_login_required():
|
|
|
403
403
|
return view
|
|
404
404
|
|
|
405
405
|
return decorator
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def no_cache_response(f):
|
|
409
|
+
"""Add appropriate response headers to force no caching.
|
|
410
|
+
|
|
411
|
+
This decorator is used to prevent caching of the response in the browser. This is needed
|
|
412
|
+
in the deposit form as we initialize the form with the record metadata included in the html page
|
|
413
|
+
and we don't want the browser to cache this page so that the user always gets the latest version of the record.
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
@wraps(f)
|
|
417
|
+
def view(*args, **kwargs):
|
|
418
|
+
response = make_response(f(*args, **kwargs))
|
|
419
|
+
|
|
420
|
+
response.cache_control.no_cache = True
|
|
421
|
+
response.cache_control.no_store = True
|
|
422
|
+
response.cache_control.must_revalidate = True
|
|
423
|
+
|
|
424
|
+
return response
|
|
425
|
+
|
|
426
|
+
return view
|
|
@@ -23,6 +23,7 @@ from invenio_i18n.ext import current_i18n
|
|
|
23
23
|
from invenio_rdm_records.proxies import current_rdm_records
|
|
24
24
|
from invenio_rdm_records.records.api import get_files_quota
|
|
25
25
|
from invenio_rdm_records.resources.serializers import UIJSONSerializer
|
|
26
|
+
from invenio_rdm_records.services.components.pids import _get_optional_doi_transitions
|
|
26
27
|
from invenio_rdm_records.services.schemas import RDMRecordSchema
|
|
27
28
|
from invenio_rdm_records.services.schemas.utils import dump_empty
|
|
28
29
|
from invenio_records_resources.services.errors import PermissionDeniedError
|
|
@@ -34,6 +35,7 @@ from sqlalchemy.orm import load_only
|
|
|
34
35
|
|
|
35
36
|
from ..utils import set_default_value
|
|
36
37
|
from .decorators import (
|
|
38
|
+
no_cache_response,
|
|
37
39
|
pass_draft,
|
|
38
40
|
pass_draft_community,
|
|
39
41
|
pass_draft_files,
|
|
@@ -45,7 +47,7 @@ from .filters import get_scheme_label
|
|
|
45
47
|
#
|
|
46
48
|
# Helpers
|
|
47
49
|
#
|
|
48
|
-
def get_form_pids_config():
|
|
50
|
+
def get_form_pids_config(record=None):
|
|
49
51
|
"""Prepare configuration for the pids field.
|
|
50
52
|
|
|
51
53
|
Currently supporting only doi.
|
|
@@ -55,17 +57,45 @@ def get_form_pids_config():
|
|
|
55
57
|
# FIXME: User provider.is_managed() requires tiny fix in config
|
|
56
58
|
can_be_managed = True
|
|
57
59
|
can_be_unmanaged = True
|
|
60
|
+
# We initialize the optional doi to empty to indicate that there is no restriction on the transitions
|
|
61
|
+
# This is valid for new uploads and when the DOI is required in an instance
|
|
62
|
+
optional_doi_transitions = []
|
|
58
63
|
for scheme in service.config.pids_providers.keys():
|
|
59
64
|
if not scheme == "doi":
|
|
60
65
|
continue
|
|
66
|
+
|
|
61
67
|
record_pid_config = current_app.config["RDM_PERSISTENT_IDENTIFIERS"]
|
|
62
68
|
scheme_label = record_pid_config.get(scheme, {}).get("label", scheme)
|
|
63
69
|
is_doi_required = record_pid_config.get(scheme, {}).get("required")
|
|
64
70
|
default_selected = (
|
|
65
71
|
record_pid_config.get(scheme, {}).get("ui", {}).get("default_selected")
|
|
66
72
|
)
|
|
73
|
+
if record is not None and not is_doi_required:
|
|
74
|
+
sitename = current_app.config.get("THEME_SITENAME", "this repository")
|
|
75
|
+
previous_published_record = (
|
|
76
|
+
service.record_cls.get_latest_published_by_parent(record.parent)
|
|
77
|
+
)
|
|
78
|
+
optional_doi_transitions = _get_optional_doi_transitions(
|
|
79
|
+
previous_published_record
|
|
80
|
+
)
|
|
81
|
+
if optional_doi_transitions:
|
|
82
|
+
optional_doi_transitions["message"] = optional_doi_transitions.get(
|
|
83
|
+
"message"
|
|
84
|
+
).format(sitename=sitename)
|
|
85
|
+
if set(optional_doi_transitions.get("allowed_providers", [])) - set(
|
|
86
|
+
["external", "not_needed"]
|
|
87
|
+
):
|
|
88
|
+
# In case we have locally managed provider as an allowed one, we need to
|
|
89
|
+
# select it by default. That is relevant for the case when the
|
|
90
|
+
# user creates a new version of the record and the previous version
|
|
91
|
+
# had a datacite DOI.
|
|
92
|
+
default_selected = "no"
|
|
93
|
+
|
|
94
|
+
# if the DOI is required but the default selected is not_needed then we set it to yes
|
|
95
|
+
# to force the user to mint a DOI
|
|
67
96
|
if is_doi_required and default_selected == "not_needed":
|
|
68
97
|
default_selected = "yes"
|
|
98
|
+
|
|
69
99
|
pids_provider = {
|
|
70
100
|
"scheme": scheme,
|
|
71
101
|
"field_label": "Digital Object Identifier",
|
|
@@ -89,6 +119,7 @@ def get_form_pids_config():
|
|
|
89
119
|
"unambiguously cited. Example: 10.1234/foo.bar"
|
|
90
120
|
).format(scheme_label=scheme_label),
|
|
91
121
|
"default_selected": default_selected,
|
|
122
|
+
"optional_doi_transitions": optional_doi_transitions,
|
|
92
123
|
}
|
|
93
124
|
pids_providers.append(pids_provider)
|
|
94
125
|
|
|
@@ -332,6 +363,8 @@ def get_form_config(**kwargs):
|
|
|
332
363
|
if record_quota:
|
|
333
364
|
quota["maxStorage"] = record_quota["quota_size"]
|
|
334
365
|
|
|
366
|
+
record = kwargs.pop("record", None)
|
|
367
|
+
|
|
335
368
|
return dict(
|
|
336
369
|
vocabularies=VocabulariesOptions().dump(),
|
|
337
370
|
autocomplete_names=conf.get(
|
|
@@ -339,7 +372,7 @@ def get_form_config(**kwargs):
|
|
|
339
372
|
),
|
|
340
373
|
current_locale=str(current_i18n.locale),
|
|
341
374
|
default_locale=conf.get("BABEL_DEFAULT_LOCALE", "en"),
|
|
342
|
-
pids=get_form_pids_config(),
|
|
375
|
+
pids=get_form_pids_config(record=record),
|
|
343
376
|
quota=quota,
|
|
344
377
|
decimal_size_display=conf.get("APP_RDM_DISPLAY_DECIMAL_FILE_SIZES", True),
|
|
345
378
|
links=dict(
|
|
@@ -390,6 +423,7 @@ def new_record():
|
|
|
390
423
|
# Views
|
|
391
424
|
#
|
|
392
425
|
@login_required
|
|
426
|
+
@no_cache_response
|
|
393
427
|
@pass_draft_community
|
|
394
428
|
def deposit_create(community=None):
|
|
395
429
|
"""Create a new deposit."""
|
|
@@ -441,6 +475,7 @@ def deposit_create(community=None):
|
|
|
441
475
|
@secret_link_or_login_required()
|
|
442
476
|
@pass_draft(expand=True)
|
|
443
477
|
@pass_draft_files
|
|
478
|
+
@no_cache_response
|
|
444
479
|
def deposit_edit(pid_value, draft=None, draft_files=None, files_locked=True):
|
|
445
480
|
"""Edit an existing deposit."""
|
|
446
481
|
# don't show draft's deposit form if the user can't edit it
|
|
@@ -490,6 +525,7 @@ def deposit_edit(pid_value, draft=None, draft_files=None, files_locked=True):
|
|
|
490
525
|
# hide react community component
|
|
491
526
|
hide_community_selection=community_use_jinja_header,
|
|
492
527
|
is_doi_required=is_doi_required,
|
|
528
|
+
record=draft._record,
|
|
493
529
|
)
|
|
494
530
|
|
|
495
531
|
if is_doi_required and not record.get("pids", {}).get("doi"):
|
|
@@ -178,7 +178,7 @@ def record_detail(
|
|
|
178
178
|
)
|
|
179
179
|
except ValidationError:
|
|
180
180
|
abort(404)
|
|
181
|
-
# inject parent doi format for new drafts so we can show in preview
|
|
181
|
+
# inject parent doi format for new drafts so we can show in preview if parent doi is required
|
|
182
182
|
if current_app.config["DATACITE_ENABLED"]:
|
|
183
183
|
service = current_rdm_records.records_service
|
|
184
184
|
datacite_provider = [
|
|
@@ -187,11 +187,27 @@ def record_detail(
|
|
|
187
187
|
if p == "doi" and "datacite" in v
|
|
188
188
|
]
|
|
189
189
|
if datacite_provider:
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
should_mint_parent_doi = True
|
|
191
|
+
is_doi_required = (
|
|
192
|
+
current_app.config.get("RDM_PARENT_PERSISTENT_IDENTIFIERS", {})
|
|
193
|
+
.get("doi", {})
|
|
194
|
+
.get("required")
|
|
193
195
|
)
|
|
194
|
-
|
|
196
|
+
if not is_doi_required:
|
|
197
|
+
# check if the draft has a reserved doi and mint parent doi only in that case
|
|
198
|
+
record_doi = record._record.pids.get("doi", {})
|
|
199
|
+
is_doi_reserved = record_doi.get(
|
|
200
|
+
"provider", ""
|
|
201
|
+
) == "datacite" and record_doi.get("identifier")
|
|
202
|
+
if not is_doi_reserved:
|
|
203
|
+
should_mint_parent_doi = False
|
|
204
|
+
|
|
205
|
+
if should_mint_parent_doi:
|
|
206
|
+
datacite_provider = datacite_provider[0]
|
|
207
|
+
parent_doi = datacite_provider.client.generate_doi(
|
|
208
|
+
record._record.parent
|
|
209
|
+
)
|
|
210
|
+
record_ui["ui"]["new_draft_parent_doi"] = parent_doi
|
|
195
211
|
|
|
196
212
|
# emit a record view stats event
|
|
197
213
|
emitter = current_stats.get_event_emitter("record-view")
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* // This file is part of Invenio-App-Rdm
|
|
3
|
+
* // Copyright (C) 2025 CERN.
|
|
4
|
+
* //
|
|
5
|
+
* // Invenio-App-Rdm 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 React, { useState } from "react";
|
|
10
|
+
import PropTypes from "prop-types";
|
|
11
|
+
import { Grid, Dropdown, Button } from "semantic-ui-react";
|
|
12
|
+
|
|
13
|
+
export const CompareRevisionsDropdown = ({
|
|
14
|
+
loading,
|
|
15
|
+
options,
|
|
16
|
+
onCompare,
|
|
17
|
+
srcRevision: srcOption,
|
|
18
|
+
targetRevision: targetOption,
|
|
19
|
+
}) => {
|
|
20
|
+
// Local state for selected revisions
|
|
21
|
+
const [srcRevision, setSrcRevision] = useState(srcOption);
|
|
22
|
+
const [targetRevision, setTargetRevision] = useState(targetOption);
|
|
23
|
+
|
|
24
|
+
const handleCompare = () => {
|
|
25
|
+
onCompare(srcRevision, targetRevision);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Grid>
|
|
30
|
+
<Grid.Column mobile={16} computer={6} tablet={16} largeScreen={6} widescreen={6}>
|
|
31
|
+
<label htmlFor="source-revision">From</label>
|
|
32
|
+
<Dropdown
|
|
33
|
+
id="source-revision"
|
|
34
|
+
loading={loading}
|
|
35
|
+
placeholder="Source revision..."
|
|
36
|
+
fluid
|
|
37
|
+
selection
|
|
38
|
+
value={srcRevision}
|
|
39
|
+
onChange={(e, { value }) => setSrcRevision(value)}
|
|
40
|
+
options={options}
|
|
41
|
+
scrolling
|
|
42
|
+
/>
|
|
43
|
+
</Grid.Column>
|
|
44
|
+
<Grid.Column mobile={16} computer={6} tablet={16} largeScreen={6} widescreen={6}>
|
|
45
|
+
<label htmlFor="target-revision">To</label>
|
|
46
|
+
<Dropdown
|
|
47
|
+
id="target-revision"
|
|
48
|
+
loading={loading}
|
|
49
|
+
placeholder="Target revision..."
|
|
50
|
+
fluid
|
|
51
|
+
selection
|
|
52
|
+
value={targetRevision}
|
|
53
|
+
onChange={(e, { value }) => setTargetRevision(value)}
|
|
54
|
+
options={options}
|
|
55
|
+
scrolling
|
|
56
|
+
/>
|
|
57
|
+
</Grid.Column>
|
|
58
|
+
<Grid.Column
|
|
59
|
+
verticalAlign="bottom"
|
|
60
|
+
mobile={16}
|
|
61
|
+
computer={2}
|
|
62
|
+
tablet={16}
|
|
63
|
+
largeScreen={2}
|
|
64
|
+
widescreen={2}
|
|
65
|
+
>
|
|
66
|
+
<Button
|
|
67
|
+
onClick={handleCompare}
|
|
68
|
+
disabled={loading || !srcRevision || !targetRevision}
|
|
69
|
+
>
|
|
70
|
+
Compare
|
|
71
|
+
</Button>
|
|
72
|
+
</Grid.Column>
|
|
73
|
+
</Grid>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
CompareRevisionsDropdown.propTypes = {
|
|
78
|
+
loading: PropTypes.bool.isRequired,
|
|
79
|
+
options: PropTypes.array.isRequired,
|
|
80
|
+
onCompare: PropTypes.func.isRequired,
|
|
81
|
+
srcRevision: PropTypes.object,
|
|
82
|
+
targetRevision: PropTypes.object,
|
|
83
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* // This file is part of Invenio-App-Rdm
|
|
3
|
+
* // Copyright (C) 2025 CERN.
|
|
4
|
+
* //
|
|
5
|
+
* // Invenio-App-Rdm 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 React, { Component } from "react";
|
|
10
|
+
import PropTypes from "prop-types";
|
|
11
|
+
import { Grid, Container } from "semantic-ui-react";
|
|
12
|
+
import { Differ, Viewer } from "json-diff-kit";
|
|
13
|
+
|
|
14
|
+
export class RevisionsDiffViewer extends Component {
|
|
15
|
+
constructor(props) {
|
|
16
|
+
super(props);
|
|
17
|
+
this.differ = new Differ({
|
|
18
|
+
detectCircular: true,
|
|
19
|
+
maxDepth: null,
|
|
20
|
+
showModifications: true,
|
|
21
|
+
arrayDiffMethod: "lcs",
|
|
22
|
+
ignoreCase: false,
|
|
23
|
+
ignoreCaseForKey: false,
|
|
24
|
+
recursiveEqual: true,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
this.viewerProps = {
|
|
28
|
+
indent: 4,
|
|
29
|
+
lineNumbers: true,
|
|
30
|
+
highlightInlineDiff: true,
|
|
31
|
+
inlineDiffOptions: {
|
|
32
|
+
mode: "word",
|
|
33
|
+
wordSeparator: " ",
|
|
34
|
+
},
|
|
35
|
+
hideUnchangedLines: true,
|
|
36
|
+
syntaxHighlight: false,
|
|
37
|
+
virtual: true,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
this.state = {
|
|
41
|
+
currentDiff: undefined,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
componentDidUpdate(prevProps) {
|
|
46
|
+
const { diff } = this.props;
|
|
47
|
+
|
|
48
|
+
if (diff !== prevProps.diff) {
|
|
49
|
+
this.computeDiff();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
computeDiff = () => {
|
|
54
|
+
const { diff } = this.props;
|
|
55
|
+
const _diff = this.differ.diff(diff?.srcRevision, diff?.targetRevision);
|
|
56
|
+
this.setState({ currentDiff: _diff });
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
render() {
|
|
60
|
+
const { currentDiff } = this.state;
|
|
61
|
+
|
|
62
|
+
return currentDiff ? (
|
|
63
|
+
<Grid>
|
|
64
|
+
<Grid.Column width={16}>
|
|
65
|
+
<Container fluid>
|
|
66
|
+
<Viewer diff={currentDiff} {...this.viewerProps} />
|
|
67
|
+
</Container>
|
|
68
|
+
</Grid.Column>
|
|
69
|
+
</Grid>
|
|
70
|
+
) : null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
RevisionsDiffViewer.propTypes = {
|
|
75
|
+
diff: PropTypes.object,
|
|
76
|
+
viewerProps: PropTypes.object.isRequired,
|
|
77
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* // This file is part of Invenio-App-Rdm
|
|
3
|
+
* // Copyright (C) 2025 CERN.
|
|
4
|
+
* //
|
|
5
|
+
* // Invenio-App-Rdm 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
|
+
import React, { Component } from "react";
|
|
9
|
+
import PropTypes from "prop-types";
|
|
10
|
+
import { RecordModerationApi } from "./api";
|
|
11
|
+
import { withCancel, ErrorMessage } from "react-invenio-forms";
|
|
12
|
+
import { Modal, Button, Grid } from "semantic-ui-react";
|
|
13
|
+
import { i18next } from "@translations/invenio_app_rdm/i18next";
|
|
14
|
+
import { CompareRevisionsDropdown } from "../components/CompareRevisionsDropdown";
|
|
15
|
+
import { RevisionsDiffViewer } from "../components/RevisionsDiffViewer";
|
|
16
|
+
|
|
17
|
+
export class CompareRevisions extends Component {
|
|
18
|
+
constructor(props) {
|
|
19
|
+
super(props);
|
|
20
|
+
|
|
21
|
+
this.state = {
|
|
22
|
+
loading: true,
|
|
23
|
+
error: undefined,
|
|
24
|
+
allRevisions: {},
|
|
25
|
+
srcRevision: undefined,
|
|
26
|
+
targetRevision: undefined,
|
|
27
|
+
diff: undefined,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async fetchRevisions() {
|
|
32
|
+
const { resource } = this.props;
|
|
33
|
+
this.setState({ loading: true });
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
this.cancellableAction = withCancel(RecordModerationApi.getRevisions(resource));
|
|
37
|
+
const response = await this.cancellableAction.promise;
|
|
38
|
+
const revisions = await response.data;
|
|
39
|
+
|
|
40
|
+
this.setState({
|
|
41
|
+
allRevisions: revisions,
|
|
42
|
+
targetRevision: revisions[0],
|
|
43
|
+
srcRevision: revisions.length > 1 ? revisions[1] : revisions[0],
|
|
44
|
+
loading: false,
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error === "UNMOUNTED") return;
|
|
48
|
+
this.setState({ error: error, loading: false });
|
|
49
|
+
console.error(error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
componentDidMount() {
|
|
54
|
+
this.fetchRevisions();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
componentWillUnmount() {
|
|
58
|
+
this.cancellableAction && this.cancellableAction.cancel();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
handleModalClose = () => {
|
|
62
|
+
const { actionCancelCallback } = this.props;
|
|
63
|
+
actionCancelCallback();
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
handleCompare = (srcRevision, targetRevision) => {
|
|
67
|
+
this.setState({ diff: { srcRevision, targetRevision } });
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
render() {
|
|
71
|
+
const { error, loading, allRevisions, srcRevision, targetRevision, diff } =
|
|
72
|
+
this.state;
|
|
73
|
+
const options = Object.values(allRevisions).map((rev) => ({
|
|
74
|
+
key: rev.updated,
|
|
75
|
+
text: `${rev.updated} (${rev.revision_id})`,
|
|
76
|
+
value: rev,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
return loading ? (
|
|
80
|
+
<p>Loading...</p>
|
|
81
|
+
) : (
|
|
82
|
+
<>
|
|
83
|
+
<Modal.Content>
|
|
84
|
+
<CompareRevisionsDropdown
|
|
85
|
+
loading={loading}
|
|
86
|
+
options={options}
|
|
87
|
+
srcRevision={srcRevision}
|
|
88
|
+
targetRevision={targetRevision}
|
|
89
|
+
onCompare={this.handleCompare}
|
|
90
|
+
/>
|
|
91
|
+
{error && (
|
|
92
|
+
<Modal.Content>
|
|
93
|
+
<ErrorMessage
|
|
94
|
+
header={i18next.t("Unable to fetch revisions.")}
|
|
95
|
+
content={error}
|
|
96
|
+
icon="exclamation"
|
|
97
|
+
className="text-align-left"
|
|
98
|
+
negative
|
|
99
|
+
/>
|
|
100
|
+
</Modal.Content>
|
|
101
|
+
)}
|
|
102
|
+
<Modal.Content scrolling>
|
|
103
|
+
<RevisionsDiffViewer diff={this.state.diff} />
|
|
104
|
+
</Modal.Content>
|
|
105
|
+
</Modal.Content>
|
|
106
|
+
<Modal.Actions>
|
|
107
|
+
<Grid>
|
|
108
|
+
<Grid.Column floated="left" width={8} textAlign="left">
|
|
109
|
+
<Button
|
|
110
|
+
onClick={this.handleModalClose}
|
|
111
|
+
disabled={loading}
|
|
112
|
+
loading={loading}
|
|
113
|
+
aria-label={i18next.t("Cancel revision comparison")}
|
|
114
|
+
>
|
|
115
|
+
Close
|
|
116
|
+
</Button>
|
|
117
|
+
</Grid.Column>
|
|
118
|
+
</Grid>
|
|
119
|
+
</Modal.Actions>
|
|
120
|
+
</>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
CompareRevisions.propTypes = {
|
|
126
|
+
resource: PropTypes.object.isRequired,
|
|
127
|
+
actionCancelCallback: PropTypes.func.isRequired,
|
|
128
|
+
};
|