invenio-previewer 5.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 +373 -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 +94 -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-5.0.2.dist-info/METADATA +79 -0
  127. invenio_previewer-5.0.2.dist-info/RECORD +131 -0
  128. invenio_previewer-5.0.2.dist-info/WHEEL +4 -0
  129. invenio_previewer-5.0.2.dist-info/entry_points.txt +23 -0
  130. invenio_previewer-5.0.2.dist-info/licenses/AUTHORS.rst +21 -0
  131. invenio_previewer-5.0.2.dist-info/licenses/LICENSE +20 -0
@@ -0,0 +1,373 @@
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-License-Identifier: MIT
6
+
7
+ r"""Invenio module for previewing files.
8
+
9
+ Invenio-Previewer provides extensible file previewers for Invenio. It can be
10
+ easily integrated with
11
+ `Invenio-Records-UI <https://invenio-records-ui.rtfd.io>`_ to serve the file
12
+ preview under a simple URL path like ``/records/<pid_value>/preview``.
13
+
14
+ It includes previewers for the following file types:
15
+
16
+ - PDF (using PDF.js)
17
+ - ZIP
18
+ - CSV (using d3.js)
19
+ - Markdown (using Mistune library)
20
+ - XML and JSON (using Prism.js)
21
+ - Simple images (PNG, JPG, GIF, WEBP)
22
+ - Jupyter Notebooks
23
+ - Web Archives (using ReplayWeb.page)
24
+
25
+ Invenio-Previewer only provides the front-end layer for displaying previews
26
+ of files. Specifically, Invenio-Previewer does not take care of generating
27
+ derived formats such thumbnails etc. This could be done by
28
+ `Invenio-IIIF <https://invenio-iiif.readthedocs.io>`_.
29
+
30
+ Initialization
31
+ --------------
32
+
33
+ First create a Flask application (Flask-CLI is not needed for Flask
34
+ version 1.0+):
35
+
36
+ >>> from flask import Flask
37
+ >>> app = Flask('myapp')
38
+ >>> app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
39
+
40
+ Configuration
41
+ ~~~~~~~~~~~~~
42
+
43
+ Invenio-Previewer is enabled by adding a
44
+ :py:data:`invenio_records_ui.config.RECORDS_UI_ENDPOINTS`
45
+ endpoint with a custom view function set to
46
+ :py:data:`invenio_previewer.views.preview`
47
+
48
+ >>> app.config.update(
49
+ ... SQLALCHEMY_TRACK_MODIFICATIONS=False,
50
+ ... RECORDS_UI_DEFAULT_PERMISSION_FACTORY=None,
51
+ ... RECORDS_UI_ENDPOINTS=dict(
52
+ ... recid=dict(
53
+ ... pid_type='recid',
54
+ ... route='/records/<pid_value>',
55
+ ... template='invenio_records_ui/detail.html',
56
+ ... ),
57
+ ... recid_previewer=dict(
58
+ ... pid_type='recid',
59
+ ... route='/records/<pid_value>/preview/<filename>',
60
+ ... view_imp='invenio_previewer.views:preview',
61
+ ... record_class='invenio_records_files.api:Record',
62
+ ... ),
63
+ ... )
64
+ ... )
65
+
66
+ Here, we configure the URL route to ``/records/<pid_value>/preview``, but you
67
+ can set it to whatever you like. Records-UI takes care resolving
68
+ the URL to a record and checks access control for the record's file.
69
+
70
+ Extensions
71
+ ~~~~~~~~~~
72
+
73
+ Now that we have configured the Flask application, let's initialize all
74
+ dependent Invenio extensions:
75
+
76
+ >>> from invenio_i18n import Babel
77
+ >>> from invenio_assets import InvenioAssets
78
+ >>> from invenio_db import InvenioDB, db
79
+ >>> from invenio_records import InvenioRecords
80
+ >>> from invenio_records_ui import InvenioRecordsUI
81
+ >>> from invenio_files_rest import InvenioFilesREST
82
+ >>> from invenio_records_ui.views import create_blueprint_from_app
83
+ >>> ext_babel = Babel(app)
84
+ >>> ext_assets = InvenioAssets(app)
85
+ >>> ext_db = InvenioDB(app)
86
+ >>> ext_records = InvenioRecords(app)
87
+ >>> ext_records_ui = InvenioRecordsUI(app)
88
+ >>> ext_files_rest = InvenioFilesREST(app)
89
+ >>> ext_blueprints = app.register_blueprint(create_blueprint_from_app(app))
90
+
91
+ The above modules provide the following features to Invenio-Previewer:
92
+
93
+ - `Invenio-Assets <https://invenio-assets.rtfd.io/>`_: JavaScript/CSS bundling
94
+ for interactive previewers.
95
+ - `Invenio-Records-UI <https://invenio-records-ui.rtfd.io>`_: Retrieval of
96
+ record and access control.
97
+ - `Invenio-Files-REST <https://invenio-files-rest.rtfd.io/>`_: Access to local
98
+ files (required only if the previewer plugin needs access to the content of a
99
+ file).
100
+
101
+ Lastly, initialize Invenio-Previewer:
102
+
103
+ >>> from invenio_previewer import InvenioPreviewer
104
+ >>> ext_previewer = InvenioPreviewer(app)
105
+
106
+ In order for the following examples to work, you need to work within a Flask
107
+ application context. Let's push one:
108
+
109
+ >>> app.app_context().push()
110
+
111
+ Also, for the examples to work we need to create the database and tables
112
+ (note, in this example we use an in-memory SQLite database):
113
+
114
+ >>> from invenio_db import db
115
+ >>> db.create_all()
116
+
117
+ Previewing files
118
+ ----------------
119
+
120
+ Invenio-Previewer looks into record's metadata to find which files can be
121
+ previewed. First, we need to create a record and attach a file to it.
122
+
123
+ Creating a record
124
+ ~~~~~~~~~~~~~~~~~
125
+
126
+ When creating a record, by default a bucket is created and associated to the
127
+ record. Therefore, before creating the record, we need to initialize a
128
+ default location:
129
+
130
+ >>> import tempfile
131
+ >>> from six import BytesIO
132
+ >>> from invenio_files_rest.models import Bucket, Location, \
133
+ ... ObjectVersion
134
+ >>> from invenio_records_files.api import RecordsBuckets
135
+ >>> tmpdir = tempfile.mkdtemp()
136
+ >>> loc = Location(name='local', uri=tmpdir, default=True)
137
+ >>> db.session.add(loc)
138
+ >>> db.session.commit()
139
+
140
+ Now we can create the record and its persistent identifier:
141
+
142
+ >>> from uuid import uuid4
143
+ >>> from invenio_pidstore.providers.recordid import RecordIdProvider
144
+ >>> rec_uuid = uuid4()
145
+ >>> provider = RecordIdProvider.create(
146
+ ... object_type='rec', object_uuid=rec_uuid)
147
+ >>> from invenio_records_files.api import Record
148
+ >>> data = {
149
+ ... 'pid_value': provider.pid.pid_value,
150
+ ... }
151
+ >>> record = Record.create(data, id_=rec_uuid)
152
+ >>> db.session.commit()
153
+
154
+ Adding files
155
+ ~~~~~~~~~~~~
156
+
157
+ We can then add a few demo files into the record:
158
+
159
+ >>> import os
160
+ >>> demo_files_path = 'examples/demo_files'
161
+ >>> demo_files = (
162
+ ... 'markdown.md',
163
+ ... 'csvfile.csv',
164
+ ... 'zipfile.zip',
165
+ ... 'jsonfile.json',
166
+ ... 'xmlfile.xml',
167
+ ... 'notebook.ipynb',
168
+ ... 'jpgfile.jpg',
169
+ ... 'pngfile.png',
170
+ ... 'webpfile.webp',
171
+ ... )
172
+
173
+ >>> for f in demo_files:
174
+ ... with open(os.path.join(demo_files_path, f), 'rb') as fp:
175
+ ... record.files[f] = fp
176
+
177
+ >>> record.files.flush()
178
+ >>> _ = record.commit()
179
+ >>> db.session.commit()
180
+
181
+ Previewing a file
182
+ ~~~~~~~~~~~~~~~~~
183
+
184
+ Let's try to preview the MarkDown file. What we expect is that the previewer
185
+ will be able to convert the MarkDown to HTML on the fly and we will see a nice
186
+ MarkDown preview:
187
+
188
+ >>> with app.test_client() as client:
189
+ ... res = client.get('/records/1/preview/markdown.md')
190
+
191
+ Here a more detailed explanation:
192
+
193
+ 1. When calling the url ``/records/1/preview/markdown.md``, Invenio-Records-UI
194
+ resolves the record from the PID 1 and it passes it to Invenio-Previewer.
195
+ 2. Invenio-Previewer retrieves the file object by the filename ``markdown.md``
196
+ using the ``invenio_previewer.ext.record_file_factory`` property
197
+ (implemented by
198
+ `Invenio-Records-Files <https://invenio-records-files.rtfd.io>`_ if
199
+ installed).
200
+ 3. With the file object, Invenio-Previewer iterates the list of configured
201
+ previewers until it finds the first that is able to preview the file.
202
+ 4. The preview plugin finally takes care of rendering the file.
203
+
204
+
205
+ Bundled previewers
206
+ ------------------
207
+
208
+ This module contains several previewers out-of-the-box:
209
+
210
+ - ``Markdown``: Previews a markdown file. It is based on python
211
+ `mistune <http://mistune.readthedocs.io>`_ library.
212
+
213
+ - ``JSON/XML``: Previews JSON and XML files. It pretty-prints the contents
214
+ and applies syntax highlighting using the `Prism.js <https://prismjs.com>`_
215
+ library. You can also configure the maximum file size in order to avoid
216
+ client and server freezes. By default it is set to 1MB.
217
+
218
+ - ``GeoJSON``: Previews GeoJSON files. It displays a
219
+ `Leaflet <https://leafletjs.com>`_ map using
220
+ `OpenStreetMap <https://www.openstreetmap.org/>`_ tiles.
221
+
222
+ - ``CSV`` - Previews `CSV` files but it can actually work
223
+ with any other tabular data format in plain text based on the idea of
224
+ separated values due to it is detecting the delimiter between the characters
225
+ automatically. On the client side, the file is previewed using
226
+ `d3.js <https://d3js.org>`_ library.
227
+
228
+ - ``PDF`` - Previews a PDF file in your browser using
229
+ `PDF.js <https://mozilla.github.io/pdf.js/>`_ library.
230
+
231
+ - ``Simple Images`` - Previews simple images. Supported formats are JPG, PNG
232
+ and GIF. There is also a configurable file size limit, which by default is
233
+ set to 512KB.
234
+
235
+ - ``ZIP`` - Previews file tree inside the archive. You can specify a files
236
+ limit to avoid a temporary lock in both of client and server side when you
237
+ are dealing with large ZIP files. By default, this limit is set 1000 files.
238
+
239
+ - ``Jupyter Notebook`` - Previews a Jupyter Notebook in your browser using
240
+ `Jupyter notebook <https://jupyter.org>`_ python converter.
241
+
242
+ - ``Web Archive`` - Previews a Web Archive in your browser using
243
+ `ReplayWeb <https://replayweb.page/>`_ embedded player.
244
+
245
+ - ``Default`` - This previewer is intended to be a fallback previewer to those
246
+ cases when there is no previewer to deal with some file type. It is showing a
247
+ simple message that the file cannot be previewed.
248
+
249
+ Local vs. remote files
250
+ ~~~~~~~~~~~~~~~~~~~~~~
251
+
252
+ Some of the bundled previewers are only working with locally managed files
253
+ (i.e. files stored in Invenio-Files-REST, which supports many different storage
254
+ backends). This is the case for JSON, XML, CSV, Markdown and ZIP previewers.
255
+ The PDF and Image previewer doesn't need to have the files stored locally.
256
+
257
+ OpenStreetmap & Content Securty Policy
258
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
259
+
260
+ In order for map tiles to load from OpenStreetmap your content security policy
261
+ must allow images to load from OSM servers. For example:
262
+
263
+ .. code-block:: python
264
+
265
+ APP_DEFAULT_SECURE_HEADERS = {
266
+ 'content_security_policy': {
267
+ 'img-src': [
268
+ "'self'",
269
+ 'data:',
270
+ "https://tile.openstreetmap.org",
271
+ ]
272
+
273
+ Override default previewer
274
+ --------------------------
275
+
276
+ The default previewer for a file can be overridden in two ways:
277
+
278
+ 1. By providing a ``previewer`` key in the file object, with the name of the
279
+ previewer to use. This works on a per-file basis.
280
+
281
+ .. code-block:: python
282
+
283
+ fileobj['previewer'] = 'zip'
284
+
285
+ 2. By listing the previewer plugin search order in ``PREVIEWER_PREFERENCES``.
286
+ The first item in the list is the most prioritized previewer in case of
287
+ collision.
288
+
289
+ Enabling Web Archives
290
+ ---------------------
291
+
292
+ Note: Enabling this previewer could potentially execute malicious content if
293
+ an unknown vulnerability exists, even though it comes with Content Security
294
+ Policy (CSP) rules to mitigate many common risks. When accpeting uploads from
295
+ untrusted users, the safest approach is to expose and render the previewer
296
+ under a different domain or sub-domain.
297
+
298
+ To enable the ReplayWeb.page Web Archive viewer, add ``web_archive``
299
+ to ``PREVIEWER_PREFERENCE`` in your configuration:
300
+
301
+ .. code-block:: python
302
+
303
+ PREVIEWER_PREFERENCE = [
304
+ ...
305
+ "web_archive",
306
+ ...
307
+ ]
308
+
309
+ Additionally, ReplayWeb.page works best with HTTP Range support. To enable
310
+ it in InvenioRDM 13+, add the following entry to your configuration:
311
+
312
+ .. code-block:: python
313
+
314
+ FILES_REST_ALLOW_RANGE_REQUESTS = True
315
+
316
+ Custom previewer
317
+ ----------------
318
+
319
+ The implementation of a custom previewer is an easy process. You need to
320
+ implement the preview function, declare the previewer in the related entry
321
+ points and define its priority.
322
+
323
+ Let's try to create a ``txt`` file previewer. We need to implement:
324
+
325
+ 1. ``can_preview()`` method: called in order to check if a given file can be
326
+ previewed and should return a boolean.
327
+ 2. ``preview()`` method: called to render the preview.
328
+ 3. ``previewable_extensions`` property: string list of previewer's supported
329
+ file extensions.
330
+
331
+ Here an example code:
332
+
333
+ >>> class TxtPreviewer(object):
334
+ ... previewable_extensions = ['txt']
335
+ ... def can_preview(file):
336
+ ... return file.file['uri'].endswith('.txt')
337
+ ... def preview(file):
338
+ ... with file.open() as fp:
339
+ ... content = fp.read().decode('utf-8')
340
+ ... return content
341
+ >>> txt_previewer = TxtPreviewer()
342
+
343
+ Once we have our code, we have to register the previewer in the previewer entry
344
+ points:
345
+
346
+ >>> entry_points = {
347
+ ... 'invenio_previewer.previewers': [
348
+ ... 'tex_previewer = \
349
+ ... myproject.modules.previewer.extensions.txt_previewer',
350
+ ... ]}
351
+
352
+ Now define the priority for all previewers by adding the newly created
353
+ ``txt_previewer`` to the 1st position, so it has the highest priority:
354
+
355
+ >>> PREVIEWER_PREVIEWERS_ORDER = [
356
+ ... 'invenio_previewer.extensions.txt_previewer',
357
+ ... 'invenio_previewer.extensions.csv_papaparsejs',
358
+ ... 'invenio_previewer.extensions.json_prismjs',
359
+ ... 'invenio_previewer.extensions.xml_prismjs',
360
+ ... 'invenio_previewer.extensions.simple_image',
361
+ ... 'invenio_previewer.extensions.mistune',
362
+ ... 'invenio_previewer.extensions.pdfjs',
363
+ ... 'invenio_previewer.extensions.zip',
364
+ ... 'invenio_previewer.extensions.default',
365
+ ... ]
366
+ """
367
+
368
+ from .ext import InvenioPreviewer
369
+ from .proxies import current_previewer
370
+
371
+ __version__ = "5.0.2"
372
+
373
+ __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
+ }