invenio-previewer 6.0.2__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_previewer/__init__.py +374 -0
- invenio_previewer/api.py +64 -0
- invenio_previewer/assets/bootstrap3/css/invenio_previewer/prismjs/simple.css +14 -0
- invenio_previewer/assets/bootstrap3/css/invenio_previewer/simple_image/simple_image.css +8 -0
- invenio_previewer/assets/bootstrap3/js/invenio_previewer/csv_previewer/init.js +83 -0
- invenio_previewer/assets/bootstrap3/js/invenio_previewer/geojson.js +40 -0
- invenio_previewer/assets/bootstrap3/js/invenio_previewer/previewer_theme.js +9 -0
- invenio_previewer/assets/bootstrap3/js/invenio_previewer/prismjs.js +7 -0
- invenio_previewer/assets/bootstrap3/scss/invenio_previewer/geojson.scss +20 -0
- invenio_previewer/assets/bootstrap3/scss/invenio_previewer/prismjs.scss +14 -0
- invenio_previewer/assets/bootstrap3/scss/invenio_previewer/simple_image.scss +8 -0
- invenio_previewer/assets/bootstrap3/scss/invenio_previewer/style.scss +10 -0
- invenio_previewer/assets/bootstrap3/scss/invenio_previewer/web_archive.scss +10 -0
- invenio_previewer/assets/semantic-ui/css/invenio_previewer/prismjs/simple.css +14 -0
- invenio_previewer/assets/semantic-ui/css/invenio_previewer/simple_image/simple_image.css +8 -0
- invenio_previewer/assets/semantic-ui/js/invenio_previewer/bottom.js +14 -0
- invenio_previewer/assets/semantic-ui/js/invenio_previewer/csv_previewer/init.js +83 -0
- invenio_previewer/assets/semantic-ui/js/invenio_previewer/geojson.js +40 -0
- invenio_previewer/assets/semantic-ui/js/invenio_previewer/previewer_theme.js +14 -0
- invenio_previewer/assets/semantic-ui/js/invenio_previewer/prismjs.js +7 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/audio_videojs.scss +15 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/bottom.scss +21 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/geojson.scss +20 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/prismjs.scss +14 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/simple_image.scss +10 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/style.scss +10 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/txt.scss +8 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/video_videojs.scss +9 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/web_archive.scss +10 -0
- invenio_previewer/assets/semantic-ui/scss/invenio_previewer/zip.scss +34 -0
- invenio_previewer/config.py +80 -0
- invenio_previewer/ext.py +168 -0
- invenio_previewer/extensions/__init__.py +4 -0
- invenio_previewer/extensions/audio_videojs.py +43 -0
- invenio_previewer/extensions/csv_papaparsejs.py +35 -0
- invenio_previewer/extensions/default.py +25 -0
- invenio_previewer/extensions/ipynb.py +69 -0
- invenio_previewer/extensions/json_prismjs.py +96 -0
- invenio_previewer/extensions/mistune.py +40 -0
- invenio_previewer/extensions/pdfjs.py +26 -0
- invenio_previewer/extensions/simple_image.py +34 -0
- invenio_previewer/extensions/txt.py +38 -0
- invenio_previewer/extensions/video_videojs.py +39 -0
- invenio_previewer/extensions/web_archive.py +29 -0
- invenio_previewer/extensions/xml_prismjs.py +63 -0
- invenio_previewer/extensions/zip.py +91 -0
- invenio_previewer/proxies.py +10 -0
- invenio_previewer/static/css/pdfjs.css +663 -0
- invenio_previewer/static/images/pdfjs/download.svg +5 -0
- invenio_previewer/static/images/pdfjs/fullscreen.svg +31 -0
- invenio_previewer/static/images/pdfjs/minus.svg +43 -0
- invenio_previewer/static/images/pdfjs/next.svg +3 -0
- invenio_previewer/static/images/pdfjs/plus.svg +43 -0
- invenio_previewer/static/images/pdfjs/previous.svg +3 -0
- invenio_previewer/static/js/fullscreen.js +56 -0
- invenio_previewer/static/js/open_pdf.js +214 -0
- invenio_previewer/templates/invenio_previewer/abstract_previewer.html +16 -0
- invenio_previewer/templates/invenio_previewer/base.html +26 -0
- invenio_previewer/templates/invenio_previewer/bottom.html +74 -0
- invenio_previewer/templates/invenio_previewer/csv_bar.html +18 -0
- invenio_previewer/templates/invenio_previewer/default.html +17 -0
- invenio_previewer/templates/invenio_previewer/geojson.html +10 -0
- invenio_previewer/templates/invenio_previewer/ipynb.html +17 -0
- invenio_previewer/templates/invenio_previewer/json_prismjs.html +10 -0
- invenio_previewer/templates/invenio_previewer/macros.html +55 -0
- invenio_previewer/templates/invenio_previewer/mistune.html +16 -0
- invenio_previewer/templates/invenio_previewer/pdfjs.html +118 -0
- invenio_previewer/templates/invenio_previewer/simple_image.html +12 -0
- invenio_previewer/templates/invenio_previewer/top.html +30 -0
- invenio_previewer/templates/invenio_previewer/txt.html +15 -0
- invenio_previewer/templates/invenio_previewer/web_archive.html +17 -0
- invenio_previewer/templates/invenio_previewer/xml_prismjs.html +10 -0
- invenio_previewer/templates/invenio_previewer/zip.html +85 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/abstract_previewer.html +16 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/audio_videojs.html +12 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/base.html +26 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/bottom.html +45 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/csv_bar.html +18 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/default.html +20 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/geojson.html +10 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/ipynb.html +17 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/json_prismjs.html +10 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/macros.html +55 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/mistune.html +16 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/simple_image.html +14 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/top.html +32 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/txt.html +15 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/videojs.html +12 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/web_archive.html +17 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/xml_prismjs.html +10 -0
- invenio_previewer/templates/semantic-ui/invenio_previewer/zip.html +58 -0
- invenio_previewer/translations/ar/LC_MESSAGES/messages.po +405 -0
- invenio_previewer/translations/bg/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/ca/LC_MESSAGES/messages.po +402 -0
- invenio_previewer/translations/cs/LC_MESSAGES/messages.po +408 -0
- invenio_previewer/translations/da/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/de/LC_MESSAGES/messages.po +411 -0
- invenio_previewer/translations/el/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/es/LC_MESSAGES/messages.po +407 -0
- invenio_previewer/translations/et/LC_MESSAGES/messages.po +404 -0
- invenio_previewer/translations/fa/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/fi/LC_MESSAGES/messages.po +403 -0
- invenio_previewer/translations/fr/LC_MESSAGES/messages.po +408 -0
- invenio_previewer/translations/hr/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/hu/LC_MESSAGES/messages.po +407 -0
- invenio_previewer/translations/it/LC_MESSAGES/messages.po +404 -0
- invenio_previewer/translations/ja/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/ka/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/ko/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/lt/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/messages.pot +397 -0
- invenio_previewer/translations/no/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/pl/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/pt/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/ro/LC_MESSAGES/messages.po +404 -0
- invenio_previewer/translations/ru/LC_MESSAGES/messages.po +404 -0
- invenio_previewer/translations/sk/LC_MESSAGES/messages.po +402 -0
- invenio_previewer/translations/sv/LC_MESSAGES/messages.po +406 -0
- invenio_previewer/translations/tr/LC_MESSAGES/messages.po +406 -0
- invenio_previewer/translations/uk/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/translations/zh_CN/LC_MESSAGES/messages.po +402 -0
- invenio_previewer/translations/zh_TW/LC_MESSAGES/messages.po +401 -0
- invenio_previewer/utils.py +55 -0
- invenio_previewer/views.py +80 -0
- invenio_previewer/webpack.py +121 -0
- invenio_previewer-6.0.2.dist-info/METADATA +79 -0
- invenio_previewer-6.0.2.dist-info/RECORD +131 -0
- invenio_previewer-6.0.2.dist-info/WHEEL +4 -0
- invenio_previewer-6.0.2.dist-info/entry_points.txt +23 -0
- invenio_previewer-6.0.2.dist-info/licenses/AUTHORS.rst +21 -0
- invenio_previewer-6.0.2.dist-info/licenses/LICENSE +20 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2016-2025 CERN.
|
|
2
|
+
# SPDX-FileCopyrightText: 2024-2026 Graz University of Technology.
|
|
3
|
+
# SPDX-FileCopyrightText: 2025 KTH Royal Institute of Technology.
|
|
4
|
+
# SPDX-FileCopyrightText: 2025 Brian Kelly.
|
|
5
|
+
# SPDX-FileCopyrightText: 2026 TU Wien.
|
|
6
|
+
# SPDX-License-Identifier: MIT
|
|
7
|
+
|
|
8
|
+
r"""Invenio module for previewing files.
|
|
9
|
+
|
|
10
|
+
Invenio-Previewer provides extensible file previewers for Invenio. It can be
|
|
11
|
+
easily integrated with
|
|
12
|
+
`Invenio-Records-UI <https://invenio-records-ui.rtfd.io>`_ to serve the file
|
|
13
|
+
preview under a simple URL path like ``/records/<pid_value>/preview``.
|
|
14
|
+
|
|
15
|
+
It includes previewers for the following file types:
|
|
16
|
+
|
|
17
|
+
- PDF (using PDF.js)
|
|
18
|
+
- ZIP
|
|
19
|
+
- CSV (using d3.js)
|
|
20
|
+
- Markdown (using Mistune library)
|
|
21
|
+
- XML and JSON (using Prism.js)
|
|
22
|
+
- Simple images (PNG, JPG, GIF, WEBP)
|
|
23
|
+
- Jupyter Notebooks
|
|
24
|
+
- Web Archives (using ReplayWeb.page)
|
|
25
|
+
|
|
26
|
+
Invenio-Previewer only provides the front-end layer for displaying previews
|
|
27
|
+
of files. Specifically, Invenio-Previewer does not take care of generating
|
|
28
|
+
derived formats such thumbnails etc. This could be done by
|
|
29
|
+
`Invenio-IIIF <https://invenio-iiif.readthedocs.io>`_.
|
|
30
|
+
|
|
31
|
+
Initialization
|
|
32
|
+
--------------
|
|
33
|
+
|
|
34
|
+
First create a Flask application (Flask-CLI is not needed for Flask
|
|
35
|
+
version 1.0+):
|
|
36
|
+
|
|
37
|
+
>>> from flask import Flask
|
|
38
|
+
>>> app = Flask('myapp')
|
|
39
|
+
>>> app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
|
|
40
|
+
|
|
41
|
+
Configuration
|
|
42
|
+
~~~~~~~~~~~~~
|
|
43
|
+
|
|
44
|
+
Invenio-Previewer is enabled by adding a
|
|
45
|
+
:py:data:`invenio_records_ui.config.RECORDS_UI_ENDPOINTS`
|
|
46
|
+
endpoint with a custom view function set to
|
|
47
|
+
:py:data:`invenio_previewer.views.preview`
|
|
48
|
+
|
|
49
|
+
>>> app.config.update(
|
|
50
|
+
... SQLALCHEMY_TRACK_MODIFICATIONS=False,
|
|
51
|
+
... RECORDS_UI_DEFAULT_PERMISSION_FACTORY=None,
|
|
52
|
+
... RECORDS_UI_ENDPOINTS=dict(
|
|
53
|
+
... recid=dict(
|
|
54
|
+
... pid_type='recid',
|
|
55
|
+
... route='/records/<pid_value>',
|
|
56
|
+
... template='invenio_records_ui/detail.html',
|
|
57
|
+
... ),
|
|
58
|
+
... recid_previewer=dict(
|
|
59
|
+
... pid_type='recid',
|
|
60
|
+
... route='/records/<pid_value>/preview/<filename>',
|
|
61
|
+
... view_imp='invenio_previewer.views:preview',
|
|
62
|
+
... record_class='invenio_records_files.api:Record',
|
|
63
|
+
... ),
|
|
64
|
+
... )
|
|
65
|
+
... )
|
|
66
|
+
|
|
67
|
+
Here, we configure the URL route to ``/records/<pid_value>/preview``, but you
|
|
68
|
+
can set it to whatever you like. Records-UI takes care resolving
|
|
69
|
+
the URL to a record and checks access control for the record's file.
|
|
70
|
+
|
|
71
|
+
Extensions
|
|
72
|
+
~~~~~~~~~~
|
|
73
|
+
|
|
74
|
+
Now that we have configured the Flask application, let's initialize all
|
|
75
|
+
dependent Invenio extensions:
|
|
76
|
+
|
|
77
|
+
>>> from invenio_i18n import Babel
|
|
78
|
+
>>> from invenio_assets import InvenioAssets
|
|
79
|
+
>>> from invenio_db import InvenioDB, db
|
|
80
|
+
>>> from invenio_records import InvenioRecords
|
|
81
|
+
>>> from invenio_records_ui import InvenioRecordsUI
|
|
82
|
+
>>> from invenio_files_rest import InvenioFilesREST
|
|
83
|
+
>>> from invenio_records_ui.views import create_blueprint_from_app
|
|
84
|
+
>>> ext_babel = Babel(app)
|
|
85
|
+
>>> ext_assets = InvenioAssets(app)
|
|
86
|
+
>>> ext_db = InvenioDB(app)
|
|
87
|
+
>>> ext_records = InvenioRecords(app)
|
|
88
|
+
>>> ext_records_ui = InvenioRecordsUI(app)
|
|
89
|
+
>>> ext_files_rest = InvenioFilesREST(app)
|
|
90
|
+
>>> ext_blueprints = app.register_blueprint(create_blueprint_from_app(app))
|
|
91
|
+
|
|
92
|
+
The above modules provide the following features to Invenio-Previewer:
|
|
93
|
+
|
|
94
|
+
- `Invenio-Assets <https://invenio-assets.rtfd.io/>`_: JavaScript/CSS bundling
|
|
95
|
+
for interactive previewers.
|
|
96
|
+
- `Invenio-Records-UI <https://invenio-records-ui.rtfd.io>`_: Retrieval of
|
|
97
|
+
record and access control.
|
|
98
|
+
- `Invenio-Files-REST <https://invenio-files-rest.rtfd.io/>`_: Access to local
|
|
99
|
+
files (required only if the previewer plugin needs access to the content of a
|
|
100
|
+
file).
|
|
101
|
+
|
|
102
|
+
Lastly, initialize Invenio-Previewer:
|
|
103
|
+
|
|
104
|
+
>>> from invenio_previewer import InvenioPreviewer
|
|
105
|
+
>>> ext_previewer = InvenioPreviewer(app)
|
|
106
|
+
|
|
107
|
+
In order for the following examples to work, you need to work within a Flask
|
|
108
|
+
application context. Let's push one:
|
|
109
|
+
|
|
110
|
+
>>> app.app_context().push()
|
|
111
|
+
|
|
112
|
+
Also, for the examples to work we need to create the database and tables
|
|
113
|
+
(note, in this example we use an in-memory SQLite database):
|
|
114
|
+
|
|
115
|
+
>>> from invenio_db import db
|
|
116
|
+
>>> db.create_all()
|
|
117
|
+
|
|
118
|
+
Previewing files
|
|
119
|
+
----------------
|
|
120
|
+
|
|
121
|
+
Invenio-Previewer looks into record's metadata to find which files can be
|
|
122
|
+
previewed. First, we need to create a record and attach a file to it.
|
|
123
|
+
|
|
124
|
+
Creating a record
|
|
125
|
+
~~~~~~~~~~~~~~~~~
|
|
126
|
+
|
|
127
|
+
When creating a record, by default a bucket is created and associated to the
|
|
128
|
+
record. Therefore, before creating the record, we need to initialize a
|
|
129
|
+
default location:
|
|
130
|
+
|
|
131
|
+
>>> import tempfile
|
|
132
|
+
>>> from six import BytesIO
|
|
133
|
+
>>> from invenio_files_rest.models import Bucket, Location, \
|
|
134
|
+
... ObjectVersion
|
|
135
|
+
>>> from invenio_records_files.api import RecordsBuckets
|
|
136
|
+
>>> tmpdir = tempfile.mkdtemp()
|
|
137
|
+
>>> loc = Location(name='local', uri=tmpdir, default=True)
|
|
138
|
+
>>> db.session.add(loc)
|
|
139
|
+
>>> db.session.commit()
|
|
140
|
+
|
|
141
|
+
Now we can create the record and its persistent identifier:
|
|
142
|
+
|
|
143
|
+
>>> from uuid import uuid4
|
|
144
|
+
>>> from invenio_pidstore.providers.recordid import RecordIdProvider
|
|
145
|
+
>>> rec_uuid = uuid4()
|
|
146
|
+
>>> provider = RecordIdProvider.create(
|
|
147
|
+
... object_type='rec', object_uuid=rec_uuid)
|
|
148
|
+
>>> from invenio_records_files.api import Record
|
|
149
|
+
>>> data = {
|
|
150
|
+
... 'pid_value': provider.pid.pid_value,
|
|
151
|
+
... }
|
|
152
|
+
>>> record = Record.create(data, id_=rec_uuid)
|
|
153
|
+
>>> db.session.commit()
|
|
154
|
+
|
|
155
|
+
Adding files
|
|
156
|
+
~~~~~~~~~~~~
|
|
157
|
+
|
|
158
|
+
We can then add a few demo files into the record:
|
|
159
|
+
|
|
160
|
+
>>> import os
|
|
161
|
+
>>> demo_files_path = 'examples/demo_files'
|
|
162
|
+
>>> demo_files = (
|
|
163
|
+
... 'markdown.md',
|
|
164
|
+
... 'csvfile.csv',
|
|
165
|
+
... 'zipfile.zip',
|
|
166
|
+
... 'jsonfile.json',
|
|
167
|
+
... 'xmlfile.xml',
|
|
168
|
+
... 'notebook.ipynb',
|
|
169
|
+
... 'jpgfile.jpg',
|
|
170
|
+
... 'pngfile.png',
|
|
171
|
+
... 'webpfile.webp',
|
|
172
|
+
... )
|
|
173
|
+
|
|
174
|
+
>>> for f in demo_files:
|
|
175
|
+
... with open(os.path.join(demo_files_path, f), 'rb') as fp:
|
|
176
|
+
... record.files[f] = fp
|
|
177
|
+
|
|
178
|
+
>>> record.files.flush()
|
|
179
|
+
>>> _ = record.commit()
|
|
180
|
+
>>> db.session.commit()
|
|
181
|
+
|
|
182
|
+
Previewing a file
|
|
183
|
+
~~~~~~~~~~~~~~~~~
|
|
184
|
+
|
|
185
|
+
Let's try to preview the MarkDown file. What we expect is that the previewer
|
|
186
|
+
will be able to convert the MarkDown to HTML on the fly and we will see a nice
|
|
187
|
+
MarkDown preview:
|
|
188
|
+
|
|
189
|
+
>>> with app.test_client() as client:
|
|
190
|
+
... res = client.get('/records/1/preview/markdown.md')
|
|
191
|
+
|
|
192
|
+
Here a more detailed explanation:
|
|
193
|
+
|
|
194
|
+
1. When calling the url ``/records/1/preview/markdown.md``, Invenio-Records-UI
|
|
195
|
+
resolves the record from the PID 1 and it passes it to Invenio-Previewer.
|
|
196
|
+
2. Invenio-Previewer retrieves the file object by the filename ``markdown.md``
|
|
197
|
+
using the ``invenio_previewer.ext.record_file_factory`` property
|
|
198
|
+
(implemented by
|
|
199
|
+
`Invenio-Records-Files <https://invenio-records-files.rtfd.io>`_ if
|
|
200
|
+
installed).
|
|
201
|
+
3. With the file object, Invenio-Previewer iterates the list of configured
|
|
202
|
+
previewers until it finds the first that is able to preview the file.
|
|
203
|
+
4. The preview plugin finally takes care of rendering the file.
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
Bundled previewers
|
|
207
|
+
------------------
|
|
208
|
+
|
|
209
|
+
This module contains several previewers out-of-the-box:
|
|
210
|
+
|
|
211
|
+
- ``Markdown``: Previews a markdown file. It is based on python
|
|
212
|
+
`mistune <http://mistune.readthedocs.io>`_ library.
|
|
213
|
+
|
|
214
|
+
- ``JSON/XML``: Previews JSON and XML files. It pretty-prints the contents
|
|
215
|
+
and applies syntax highlighting using the `Prism.js <https://prismjs.com>`_
|
|
216
|
+
library. You can also configure the maximum file size in order to avoid
|
|
217
|
+
client and server freezes. By default it is set to 1MB.
|
|
218
|
+
|
|
219
|
+
- ``GeoJSON``: Previews GeoJSON files. It displays a
|
|
220
|
+
`Leaflet <https://leafletjs.com>`_ map using
|
|
221
|
+
`OpenStreetMap <https://www.openstreetmap.org/>`_ tiles.
|
|
222
|
+
|
|
223
|
+
- ``CSV`` - Previews `CSV` files but it can actually work
|
|
224
|
+
with any other tabular data format in plain text based on the idea of
|
|
225
|
+
separated values due to it is detecting the delimiter between the characters
|
|
226
|
+
automatically. On the client side, the file is previewed using
|
|
227
|
+
`d3.js <https://d3js.org>`_ library.
|
|
228
|
+
|
|
229
|
+
- ``PDF`` - Previews a PDF file in your browser using
|
|
230
|
+
`PDF.js <https://mozilla.github.io/pdf.js/>`_ library.
|
|
231
|
+
|
|
232
|
+
- ``Simple Images`` - Previews simple images. Supported formats are JPG, PNG
|
|
233
|
+
and GIF. There is also a configurable file size limit, which by default is
|
|
234
|
+
set to 512KB.
|
|
235
|
+
|
|
236
|
+
- ``ZIP`` - Previews file tree inside the archive. You can specify a files
|
|
237
|
+
limit to avoid a temporary lock in both of client and server side when you
|
|
238
|
+
are dealing with large ZIP files. By default, this limit is set 1000 files.
|
|
239
|
+
|
|
240
|
+
- ``Jupyter Notebook`` - Previews a Jupyter Notebook in your browser using
|
|
241
|
+
`Jupyter notebook <https://jupyter.org>`_ python converter.
|
|
242
|
+
|
|
243
|
+
- ``Web Archive`` - Previews a Web Archive in your browser using
|
|
244
|
+
`ReplayWeb <https://replayweb.page/>`_ embedded player.
|
|
245
|
+
|
|
246
|
+
- ``Default`` - This previewer is intended to be a fallback previewer to those
|
|
247
|
+
cases when there is no previewer to deal with some file type. It is showing a
|
|
248
|
+
simple message that the file cannot be previewed.
|
|
249
|
+
|
|
250
|
+
Local vs. remote files
|
|
251
|
+
~~~~~~~~~~~~~~~~~~~~~~
|
|
252
|
+
|
|
253
|
+
Some of the bundled previewers are only working with locally managed files
|
|
254
|
+
(i.e. files stored in Invenio-Files-REST, which supports many different storage
|
|
255
|
+
backends). This is the case for JSON, XML, CSV, Markdown and ZIP previewers.
|
|
256
|
+
The PDF and Image previewer doesn't need to have the files stored locally.
|
|
257
|
+
|
|
258
|
+
OpenStreetmap & Content Securty Policy
|
|
259
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
260
|
+
|
|
261
|
+
In order for map tiles to load from OpenStreetmap your content security policy
|
|
262
|
+
must allow images to load from OSM servers. For example:
|
|
263
|
+
|
|
264
|
+
.. code-block:: python
|
|
265
|
+
|
|
266
|
+
APP_DEFAULT_SECURE_HEADERS = {
|
|
267
|
+
'content_security_policy': {
|
|
268
|
+
'img-src': [
|
|
269
|
+
"'self'",
|
|
270
|
+
'data:',
|
|
271
|
+
"https://tile.openstreetmap.org",
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
Override default previewer
|
|
275
|
+
--------------------------
|
|
276
|
+
|
|
277
|
+
The default previewer for a file can be overridden in two ways:
|
|
278
|
+
|
|
279
|
+
1. By providing a ``previewer`` key in the file object, with the name of the
|
|
280
|
+
previewer to use. This works on a per-file basis.
|
|
281
|
+
|
|
282
|
+
.. code-block:: python
|
|
283
|
+
|
|
284
|
+
fileobj['previewer'] = 'zip'
|
|
285
|
+
|
|
286
|
+
2. By listing the previewer plugin search order in ``PREVIEWER_PREFERENCES``.
|
|
287
|
+
The first item in the list is the most prioritized previewer in case of
|
|
288
|
+
collision.
|
|
289
|
+
|
|
290
|
+
Enabling Web Archives
|
|
291
|
+
---------------------
|
|
292
|
+
|
|
293
|
+
Note: Enabling this previewer could potentially execute malicious content if
|
|
294
|
+
an unknown vulnerability exists, even though it comes with Content Security
|
|
295
|
+
Policy (CSP) rules to mitigate many common risks. When accpeting uploads from
|
|
296
|
+
untrusted users, the safest approach is to expose and render the previewer
|
|
297
|
+
under a different domain or sub-domain.
|
|
298
|
+
|
|
299
|
+
To enable the ReplayWeb.page Web Archive viewer, add ``web_archive``
|
|
300
|
+
to ``PREVIEWER_PREFERENCE`` in your configuration:
|
|
301
|
+
|
|
302
|
+
.. code-block:: python
|
|
303
|
+
|
|
304
|
+
PREVIEWER_PREFERENCE = [
|
|
305
|
+
...
|
|
306
|
+
"web_archive",
|
|
307
|
+
...
|
|
308
|
+
]
|
|
309
|
+
|
|
310
|
+
Additionally, ReplayWeb.page works best with HTTP Range support. To enable
|
|
311
|
+
it in InvenioRDM 13+, add the following entry to your configuration:
|
|
312
|
+
|
|
313
|
+
.. code-block:: python
|
|
314
|
+
|
|
315
|
+
FILES_REST_ALLOW_RANGE_REQUESTS = True
|
|
316
|
+
|
|
317
|
+
Custom previewer
|
|
318
|
+
----------------
|
|
319
|
+
|
|
320
|
+
The implementation of a custom previewer is an easy process. You need to
|
|
321
|
+
implement the preview function, declare the previewer in the related entry
|
|
322
|
+
points and define its priority.
|
|
323
|
+
|
|
324
|
+
Let's try to create a ``txt`` file previewer. We need to implement:
|
|
325
|
+
|
|
326
|
+
1. ``can_preview()`` method: called in order to check if a given file can be
|
|
327
|
+
previewed and should return a boolean.
|
|
328
|
+
2. ``preview()`` method: called to render the preview.
|
|
329
|
+
3. ``previewable_extensions`` property: string list of previewer's supported
|
|
330
|
+
file extensions.
|
|
331
|
+
|
|
332
|
+
Here an example code:
|
|
333
|
+
|
|
334
|
+
>>> class TxtPreviewer(object):
|
|
335
|
+
... previewable_extensions = ['txt']
|
|
336
|
+
... def can_preview(file):
|
|
337
|
+
... return file.file['uri'].endswith('.txt')
|
|
338
|
+
... def preview(file):
|
|
339
|
+
... with file.open() as fp:
|
|
340
|
+
... content = fp.read().decode('utf-8')
|
|
341
|
+
... return content
|
|
342
|
+
>>> txt_previewer = TxtPreviewer()
|
|
343
|
+
|
|
344
|
+
Once we have our code, we have to register the previewer in the previewer entry
|
|
345
|
+
points:
|
|
346
|
+
|
|
347
|
+
>>> entry_points = {
|
|
348
|
+
... 'invenio_previewer.previewers': [
|
|
349
|
+
... 'tex_previewer = \
|
|
350
|
+
... myproject.modules.previewer.extensions.txt_previewer',
|
|
351
|
+
... ]}
|
|
352
|
+
|
|
353
|
+
Now define the priority for all previewers by adding the newly created
|
|
354
|
+
``txt_previewer`` to the 1st position, so it has the highest priority:
|
|
355
|
+
|
|
356
|
+
>>> PREVIEWER_PREVIEWERS_ORDER = [
|
|
357
|
+
... 'invenio_previewer.extensions.txt_previewer',
|
|
358
|
+
... 'invenio_previewer.extensions.csv_papaparsejs',
|
|
359
|
+
... 'invenio_previewer.extensions.json_prismjs',
|
|
360
|
+
... 'invenio_previewer.extensions.xml_prismjs',
|
|
361
|
+
... 'invenio_previewer.extensions.simple_image',
|
|
362
|
+
... 'invenio_previewer.extensions.mistune',
|
|
363
|
+
... 'invenio_previewer.extensions.pdfjs',
|
|
364
|
+
... 'invenio_previewer.extensions.zip',
|
|
365
|
+
... 'invenio_previewer.extensions.default',
|
|
366
|
+
... ]
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
from .ext import InvenioPreviewer
|
|
370
|
+
from .proxies import current_previewer
|
|
371
|
+
|
|
372
|
+
__version__ = "6.0.2"
|
|
373
|
+
|
|
374
|
+
__all__ = ("__version__", "current_previewer", "InvenioPreviewer")
|
invenio_previewer/api.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2016-2019 CERN.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""File reader utility."""
|
|
5
|
+
|
|
6
|
+
from os.path import basename, splitext
|
|
7
|
+
|
|
8
|
+
from flask import url_for
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PreviewFile(object):
|
|
12
|
+
"""Preview file default implementation."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, pid, record, fileobj):
|
|
15
|
+
"""Initialize object.
|
|
16
|
+
|
|
17
|
+
:param file: ObjectVersion instance from Invenio-Files-REST.
|
|
18
|
+
"""
|
|
19
|
+
self.file = fileobj
|
|
20
|
+
self.pid = pid
|
|
21
|
+
self.record = record
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def size(self):
|
|
25
|
+
"""Get file size."""
|
|
26
|
+
return self.file["size"]
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def filename(self):
|
|
30
|
+
"""Get filename."""
|
|
31
|
+
return basename(self.file.key)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def bucket(self):
|
|
35
|
+
"""Get bucket."""
|
|
36
|
+
return self.file.bucket_id
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def uri(self):
|
|
40
|
+
"""Get file download link.
|
|
41
|
+
|
|
42
|
+
.. note::
|
|
43
|
+
|
|
44
|
+
The URI generation assumes that you can download the file using the
|
|
45
|
+
view ``invenio_records_ui.<pid_type>_files``.
|
|
46
|
+
"""
|
|
47
|
+
return url_for(
|
|
48
|
+
".{0}_files".format(self.pid.pid_type),
|
|
49
|
+
pid_value=self.pid.pid_value,
|
|
50
|
+
filename=self.file.key,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def is_local(self):
|
|
54
|
+
"""Check if file is local."""
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def has_extensions(self, *exts):
|
|
58
|
+
"""Check if file has one of the extensions."""
|
|
59
|
+
file_ext = splitext(self.filename)[1].lower()
|
|
60
|
+
return file_ext in exts
|
|
61
|
+
|
|
62
|
+
def open(self):
|
|
63
|
+
"""Open the file."""
|
|
64
|
+
return self.file.file.storage().open()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2016-2019 CERN.
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
code[class*="language-"],
|
|
7
|
+
pre[class*="language-"] {
|
|
8
|
+
line-height: 1.0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
:not(pre) > code[class*="language-"],
|
|
12
|
+
pre[class*="language-"] {
|
|
13
|
+
background: white;
|
|
14
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2015-2023 CERN.
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import $ from "jquery";
|
|
7
|
+
import Papa from "papaparse";
|
|
8
|
+
|
|
9
|
+
(function ($, Papa) {
|
|
10
|
+
function createDataElement(htmlTag, innerText) {
|
|
11
|
+
const node = document.createElement(htmlTag);
|
|
12
|
+
const textNode = document.createTextNode(innerText);
|
|
13
|
+
node.appendChild(textNode);
|
|
14
|
+
return node;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const URL = $("#app").attr("data-csv-source");
|
|
18
|
+
const fileSize = $("#app").attr("data-csv-size");
|
|
19
|
+
const maxRowsPerChunk = 50;
|
|
20
|
+
const $tableHeader = $("#table-header");
|
|
21
|
+
const $tableBody = $("#table-body");
|
|
22
|
+
const $showMore = $("#show-more");
|
|
23
|
+
|
|
24
|
+
let isFirst = true;
|
|
25
|
+
let currentStep = 0;
|
|
26
|
+
let papaParser = null;
|
|
27
|
+
|
|
28
|
+
$showMore.hide(); // hide it when init
|
|
29
|
+
$showMore.on("click", function () {
|
|
30
|
+
currentStep = 0;
|
|
31
|
+
papaParser.resume();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// If the RemoteChunkSize is bigger than the size of the file it fails with a 416
|
|
35
|
+
if (fileSize < Papa.RemoteChunkSize) {
|
|
36
|
+
Papa.RemoteChunkSize = fileSize
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Papa.parse(URL, {
|
|
40
|
+
download: true,
|
|
41
|
+
skipEmptyLines: true,
|
|
42
|
+
step: function (results, parser) {
|
|
43
|
+
papaParser = papaParser || parser;
|
|
44
|
+
currentStep++;
|
|
45
|
+
console.debug("CSV previewer: rendering step", results);
|
|
46
|
+
const row = results.data || [];
|
|
47
|
+
|
|
48
|
+
const tableRowEl = document.createElement("tr");
|
|
49
|
+
let tableColEl;
|
|
50
|
+
if (isFirst) {
|
|
51
|
+
$tableHeader.append(tableRowEl);
|
|
52
|
+
tableColEl = "th";
|
|
53
|
+
isFirst = false;
|
|
54
|
+
} else {
|
|
55
|
+
$tableBody.append(tableRowEl);
|
|
56
|
+
tableColEl = "td";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
row.forEach((col) => {
|
|
60
|
+
const node = createDataElement(tableColEl, col);
|
|
61
|
+
tableRowEl.appendChild(node);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (currentStep >= maxRowsPerChunk) {
|
|
65
|
+
parser.pause();
|
|
66
|
+
$showMore.show();
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
error: function (err, file, inputElem, reason) {
|
|
70
|
+
console.error(
|
|
71
|
+
"CSV previewer: error rendering CSV file: ",
|
|
72
|
+
err,
|
|
73
|
+
file,
|
|
74
|
+
inputElem,
|
|
75
|
+
reason
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
complete: function (results, file) {
|
|
79
|
+
console.debug("CSV previewer: rendering completed", results, file);
|
|
80
|
+
$showMore.hide();
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
})($, Papa);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2025 Brian Kelly.
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import L from "leaflet"
|
|
7
|
+
import "leaflet/dist/leaflet.css";
|
|
8
|
+
|
|
9
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
10
|
+
const mapElement = document.getElementById('map')
|
|
11
|
+
const fileUri = mapElement.getAttribute('data-file-uri')
|
|
12
|
+
const fileUrl = new URL(fileUri, window.location.href);
|
|
13
|
+
|
|
14
|
+
const map = L.map('map').setView([0, 0], 13);
|
|
15
|
+
|
|
16
|
+
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
17
|
+
maxZoom: 19,
|
|
18
|
+
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
|
19
|
+
}).addTo(map);
|
|
20
|
+
|
|
21
|
+
fetch(fileUrl.href).then(res => res.json()).then(data => {
|
|
22
|
+
const geoJsonLayer = L.geoJson(data, {
|
|
23
|
+
style: {
|
|
24
|
+
color: "#3399ff",
|
|
25
|
+
weight: 2,
|
|
26
|
+
opacity: 0.5
|
|
27
|
+
},
|
|
28
|
+
pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
|
|
29
|
+
radius: 4,
|
|
30
|
+
fillColor: "#3399ff",
|
|
31
|
+
color: "#3399ff",
|
|
32
|
+
weight: 2,
|
|
33
|
+
opacity: 1,
|
|
34
|
+
fillOpacity: 0.8
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
geoJsonLayer.addTo(map);
|
|
38
|
+
map.fitBounds(geoJsonLayer.getBounds());
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2025 Brian Kelly.
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
@import "leaflet/dist/leaflet.css";
|
|
7
|
+
|
|
8
|
+
* {
|
|
9
|
+
padding: 0;
|
|
10
|
+
margin: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
html {
|
|
14
|
+
height: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#map {
|
|
18
|
+
width: 100%;
|
|
19
|
+
height: 400px;
|
|
20
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2015-2018 CERN.
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
@import "~prismjs/themes/prism";
|
|
7
|
+
|
|
8
|
+
code[class*="language-"], pre[class*="language-"] {
|
|
9
|
+
line-height: 1.0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:not(pre) > code[class*="language-"], pre[class*="language-"] {
|
|
13
|
+
background: white;
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2015-2018 CERN.
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
$icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
|
|
7
|
+
$fa-font-path: "~font-awesome/fonts";
|
|
8
|
+
|
|
9
|
+
@import "~bootstrap-sass/assets/stylesheets/_bootstrap";
|
|
10
|
+
@import "~font-awesome/scss/font-awesome";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2016-2020 CERN.
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
code[class*="language-"],
|
|
7
|
+
pre[class*="language-"] {
|
|
8
|
+
line-height: 1.0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
:not(pre) > code[class*="language-"],
|
|
12
|
+
pre[class*="language-"] {
|
|
13
|
+
background: white;
|
|
14
|
+
}
|