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.
Files changed (131) hide show
  1. invenio_previewer/__init__.py +374 -0
  2. invenio_previewer/api.py +64 -0
  3. invenio_previewer/assets/bootstrap3/css/invenio_previewer/prismjs/simple.css +14 -0
  4. invenio_previewer/assets/bootstrap3/css/invenio_previewer/simple_image/simple_image.css +8 -0
  5. invenio_previewer/assets/bootstrap3/js/invenio_previewer/csv_previewer/init.js +83 -0
  6. invenio_previewer/assets/bootstrap3/js/invenio_previewer/geojson.js +40 -0
  7. invenio_previewer/assets/bootstrap3/js/invenio_previewer/previewer_theme.js +9 -0
  8. invenio_previewer/assets/bootstrap3/js/invenio_previewer/prismjs.js +7 -0
  9. invenio_previewer/assets/bootstrap3/scss/invenio_previewer/geojson.scss +20 -0
  10. invenio_previewer/assets/bootstrap3/scss/invenio_previewer/prismjs.scss +14 -0
  11. invenio_previewer/assets/bootstrap3/scss/invenio_previewer/simple_image.scss +8 -0
  12. invenio_previewer/assets/bootstrap3/scss/invenio_previewer/style.scss +10 -0
  13. invenio_previewer/assets/bootstrap3/scss/invenio_previewer/web_archive.scss +10 -0
  14. invenio_previewer/assets/semantic-ui/css/invenio_previewer/prismjs/simple.css +14 -0
  15. invenio_previewer/assets/semantic-ui/css/invenio_previewer/simple_image/simple_image.css +8 -0
  16. invenio_previewer/assets/semantic-ui/js/invenio_previewer/bottom.js +14 -0
  17. invenio_previewer/assets/semantic-ui/js/invenio_previewer/csv_previewer/init.js +83 -0
  18. invenio_previewer/assets/semantic-ui/js/invenio_previewer/geojson.js +40 -0
  19. invenio_previewer/assets/semantic-ui/js/invenio_previewer/previewer_theme.js +14 -0
  20. invenio_previewer/assets/semantic-ui/js/invenio_previewer/prismjs.js +7 -0
  21. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/audio_videojs.scss +15 -0
  22. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/bottom.scss +21 -0
  23. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/geojson.scss +20 -0
  24. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/prismjs.scss +14 -0
  25. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/simple_image.scss +10 -0
  26. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/style.scss +10 -0
  27. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/txt.scss +8 -0
  28. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/video_videojs.scss +9 -0
  29. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/web_archive.scss +10 -0
  30. invenio_previewer/assets/semantic-ui/scss/invenio_previewer/zip.scss +34 -0
  31. invenio_previewer/config.py +80 -0
  32. invenio_previewer/ext.py +168 -0
  33. invenio_previewer/extensions/__init__.py +4 -0
  34. invenio_previewer/extensions/audio_videojs.py +43 -0
  35. invenio_previewer/extensions/csv_papaparsejs.py +35 -0
  36. invenio_previewer/extensions/default.py +25 -0
  37. invenio_previewer/extensions/ipynb.py +69 -0
  38. invenio_previewer/extensions/json_prismjs.py +96 -0
  39. invenio_previewer/extensions/mistune.py +40 -0
  40. invenio_previewer/extensions/pdfjs.py +26 -0
  41. invenio_previewer/extensions/simple_image.py +34 -0
  42. invenio_previewer/extensions/txt.py +38 -0
  43. invenio_previewer/extensions/video_videojs.py +39 -0
  44. invenio_previewer/extensions/web_archive.py +29 -0
  45. invenio_previewer/extensions/xml_prismjs.py +63 -0
  46. invenio_previewer/extensions/zip.py +91 -0
  47. invenio_previewer/proxies.py +10 -0
  48. invenio_previewer/static/css/pdfjs.css +663 -0
  49. invenio_previewer/static/images/pdfjs/download.svg +5 -0
  50. invenio_previewer/static/images/pdfjs/fullscreen.svg +31 -0
  51. invenio_previewer/static/images/pdfjs/minus.svg +43 -0
  52. invenio_previewer/static/images/pdfjs/next.svg +3 -0
  53. invenio_previewer/static/images/pdfjs/plus.svg +43 -0
  54. invenio_previewer/static/images/pdfjs/previous.svg +3 -0
  55. invenio_previewer/static/js/fullscreen.js +56 -0
  56. invenio_previewer/static/js/open_pdf.js +214 -0
  57. invenio_previewer/templates/invenio_previewer/abstract_previewer.html +16 -0
  58. invenio_previewer/templates/invenio_previewer/base.html +26 -0
  59. invenio_previewer/templates/invenio_previewer/bottom.html +74 -0
  60. invenio_previewer/templates/invenio_previewer/csv_bar.html +18 -0
  61. invenio_previewer/templates/invenio_previewer/default.html +17 -0
  62. invenio_previewer/templates/invenio_previewer/geojson.html +10 -0
  63. invenio_previewer/templates/invenio_previewer/ipynb.html +17 -0
  64. invenio_previewer/templates/invenio_previewer/json_prismjs.html +10 -0
  65. invenio_previewer/templates/invenio_previewer/macros.html +55 -0
  66. invenio_previewer/templates/invenio_previewer/mistune.html +16 -0
  67. invenio_previewer/templates/invenio_previewer/pdfjs.html +118 -0
  68. invenio_previewer/templates/invenio_previewer/simple_image.html +12 -0
  69. invenio_previewer/templates/invenio_previewer/top.html +30 -0
  70. invenio_previewer/templates/invenio_previewer/txt.html +15 -0
  71. invenio_previewer/templates/invenio_previewer/web_archive.html +17 -0
  72. invenio_previewer/templates/invenio_previewer/xml_prismjs.html +10 -0
  73. invenio_previewer/templates/invenio_previewer/zip.html +85 -0
  74. invenio_previewer/templates/semantic-ui/invenio_previewer/abstract_previewer.html +16 -0
  75. invenio_previewer/templates/semantic-ui/invenio_previewer/audio_videojs.html +12 -0
  76. invenio_previewer/templates/semantic-ui/invenio_previewer/base.html +26 -0
  77. invenio_previewer/templates/semantic-ui/invenio_previewer/bottom.html +45 -0
  78. invenio_previewer/templates/semantic-ui/invenio_previewer/csv_bar.html +18 -0
  79. invenio_previewer/templates/semantic-ui/invenio_previewer/default.html +20 -0
  80. invenio_previewer/templates/semantic-ui/invenio_previewer/geojson.html +10 -0
  81. invenio_previewer/templates/semantic-ui/invenio_previewer/ipynb.html +17 -0
  82. invenio_previewer/templates/semantic-ui/invenio_previewer/json_prismjs.html +10 -0
  83. invenio_previewer/templates/semantic-ui/invenio_previewer/macros.html +55 -0
  84. invenio_previewer/templates/semantic-ui/invenio_previewer/mistune.html +16 -0
  85. invenio_previewer/templates/semantic-ui/invenio_previewer/simple_image.html +14 -0
  86. invenio_previewer/templates/semantic-ui/invenio_previewer/top.html +32 -0
  87. invenio_previewer/templates/semantic-ui/invenio_previewer/txt.html +15 -0
  88. invenio_previewer/templates/semantic-ui/invenio_previewer/videojs.html +12 -0
  89. invenio_previewer/templates/semantic-ui/invenio_previewer/web_archive.html +17 -0
  90. invenio_previewer/templates/semantic-ui/invenio_previewer/xml_prismjs.html +10 -0
  91. invenio_previewer/templates/semantic-ui/invenio_previewer/zip.html +58 -0
  92. invenio_previewer/translations/ar/LC_MESSAGES/messages.po +405 -0
  93. invenio_previewer/translations/bg/LC_MESSAGES/messages.po +401 -0
  94. invenio_previewer/translations/ca/LC_MESSAGES/messages.po +402 -0
  95. invenio_previewer/translations/cs/LC_MESSAGES/messages.po +408 -0
  96. invenio_previewer/translations/da/LC_MESSAGES/messages.po +401 -0
  97. invenio_previewer/translations/de/LC_MESSAGES/messages.po +411 -0
  98. invenio_previewer/translations/el/LC_MESSAGES/messages.po +401 -0
  99. invenio_previewer/translations/es/LC_MESSAGES/messages.po +407 -0
  100. invenio_previewer/translations/et/LC_MESSAGES/messages.po +404 -0
  101. invenio_previewer/translations/fa/LC_MESSAGES/messages.po +401 -0
  102. invenio_previewer/translations/fi/LC_MESSAGES/messages.po +403 -0
  103. invenio_previewer/translations/fr/LC_MESSAGES/messages.po +408 -0
  104. invenio_previewer/translations/hr/LC_MESSAGES/messages.po +401 -0
  105. invenio_previewer/translations/hu/LC_MESSAGES/messages.po +407 -0
  106. invenio_previewer/translations/it/LC_MESSAGES/messages.po +404 -0
  107. invenio_previewer/translations/ja/LC_MESSAGES/messages.po +401 -0
  108. invenio_previewer/translations/ka/LC_MESSAGES/messages.po +401 -0
  109. invenio_previewer/translations/ko/LC_MESSAGES/messages.po +401 -0
  110. invenio_previewer/translations/lt/LC_MESSAGES/messages.po +401 -0
  111. invenio_previewer/translations/messages.pot +397 -0
  112. invenio_previewer/translations/no/LC_MESSAGES/messages.po +401 -0
  113. invenio_previewer/translations/pl/LC_MESSAGES/messages.po +401 -0
  114. invenio_previewer/translations/pt/LC_MESSAGES/messages.po +401 -0
  115. invenio_previewer/translations/ro/LC_MESSAGES/messages.po +404 -0
  116. invenio_previewer/translations/ru/LC_MESSAGES/messages.po +404 -0
  117. invenio_previewer/translations/sk/LC_MESSAGES/messages.po +402 -0
  118. invenio_previewer/translations/sv/LC_MESSAGES/messages.po +406 -0
  119. invenio_previewer/translations/tr/LC_MESSAGES/messages.po +406 -0
  120. invenio_previewer/translations/uk/LC_MESSAGES/messages.po +401 -0
  121. invenio_previewer/translations/zh_CN/LC_MESSAGES/messages.po +402 -0
  122. invenio_previewer/translations/zh_TW/LC_MESSAGES/messages.po +401 -0
  123. invenio_previewer/utils.py +55 -0
  124. invenio_previewer/views.py +80 -0
  125. invenio_previewer/webpack.py +121 -0
  126. invenio_previewer-6.0.2.dist-info/METADATA +79 -0
  127. invenio_previewer-6.0.2.dist-info/RECORD +131 -0
  128. invenio_previewer-6.0.2.dist-info/WHEEL +4 -0
  129. invenio_previewer-6.0.2.dist-info/entry_points.txt +23 -0
  130. invenio_previewer-6.0.2.dist-info/licenses/AUTHORS.rst +21 -0
  131. 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")
@@ -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,8 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2016-2019 CERN.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ img.previewer-simple-image{
7
+ max-width: 100%;
8
+ }
@@ -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: '&copy; <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,9 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2020 CERN.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import "jquery/dist/jquery.js";
7
+ import "bootstrap/dist/js/bootstrap.js";
8
+
9
+ import "@scss/invenio_previewer/style.scss";
@@ -0,0 +1,7 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2015-2018 CERN.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import Prism from 'prismjs';
7
+ Prism.highlightAll();
@@ -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,8 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2015-2019 CERN.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ img.previewer-simple-image{
7
+ max-width: 100%;
8
+ }
@@ -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,10 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2025 Brian Kelly.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ html, body {
7
+ width: 100%;
8
+ height: 100%;
9
+ overflow: hidden;
10
+ }
@@ -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
+ }