Merge b7404e2678
into 996711d2f9
112
attachment_preview/README.rst
Normal file
@ -0,0 +1,112 @@
|
||||
===================
|
||||
Preview attachments
|
||||
===================
|
||||
|
||||
..
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:573f74622167c92c4eb0881999cde8d69bb3de4a4b7489a1703158d3a66a8269
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
:alt: Beta
|
||||
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fknowledge-lightgray.png?logo=github
|
||||
:target: https://github.com/OCA/knowledge/tree/16.0/attachment_preview
|
||||
:alt: OCA/knowledge
|
||||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
||||
:target: https://translation.odoo-community.org/projects/knowledge-16-0/knowledge-16-0-attachment_preview
|
||||
:alt: Translate me on Weblate
|
||||
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
|
||||
:target: https://runboat.odoo-community.org/builds?repo=OCA/knowledge&target_branch=16.0
|
||||
:alt: Try me on Runboat
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|
||||
This addon allows to preview attachments supported by http://viewerjs.org.
|
||||
|
||||
Currently, that's most Libreoffice files and PDFs.
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/attachment_preview/static/description/screenshot-split.png
|
||||
:alt: Screenshot of split form view
|
||||
:width: 100%
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
For filetype recognition, you'll get the best results by installing
|
||||
``python-magic``:
|
||||
|
||||
sudo apt-get install python-magic
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
The module adds a little print preview icon right of download links for
|
||||
attachments or binary fields. When a preview is opened from the attachments
|
||||
menu it's shown next to the form view. From this screen you can navigate
|
||||
through the attachments using the arrow buttons. Using the pop-out button
|
||||
next to the navigational buttons you can open the preview in a separate window.
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/attachment_preview/static/description/screenshot-paginator.png
|
||||
:alt: Screenshot navigator
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/knowledge/issues>`_.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||
`feedback <https://github.com/OCA/knowledge/issues/new?body=module:%20attachment_preview%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
|
||||
Do not contact contributors directly about support or help with technical issues.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Authors
|
||||
~~~~~~~
|
||||
|
||||
* Therp BV
|
||||
* Onestein
|
||||
|
||||
Contributors
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Holger Brunn <mail@hunki-enterprises.com>
|
||||
* Dennis Sluijk <d.sluijk@onestein.nl>
|
||||
|
||||
Other credits
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Addon icon
|
||||
----------
|
||||
|
||||
* courtesy of http://commons.wikimedia.org/wiki/Crystal_Clear
|
||||
|
||||
Maintainers
|
||||
~~~~~~~~~~~
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
This module is part of the `OCA/knowledge <https://github.com/OCA/knowledge/tree/16.0/attachment_preview>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
4
attachment_preview/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# Copyright 2014 Therp BV (<http://therp.nl>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import models
|
29
attachment_preview/__manifest__.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright 2014 Therp BV (<http://therp.nl>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
{
|
||||
"name": "Preview attachments",
|
||||
"version": "16.0.1.0.0",
|
||||
"author": "Therp BV," "Onestein," "Odoo Community Association (OCA)",
|
||||
"website": "https://github.com/OCA/knowledge",
|
||||
"license": "AGPL-3",
|
||||
"summary": "Preview attachments supported by Viewer.js",
|
||||
"category": "Knowledge Management",
|
||||
"depends": ["web", "mail"],
|
||||
"data": [],
|
||||
"qweb": [],
|
||||
"assets": {
|
||||
"web._assets_primary_variables": [],
|
||||
"web.assets_backend": [
|
||||
"attachment_preview/static/src/js/models/attachment_card/attachment_card.esm.js",
|
||||
"attachment_preview/static/src/js/attachmentPreviewWidget.esm.js",
|
||||
"attachment_preview/static/src/js/components/chatter/chatter.esm.js",
|
||||
"attachment_preview/static/src/scss/attachment_preview.scss",
|
||||
"attachment_preview/static/src/xml/attachment_preview.xml",
|
||||
],
|
||||
"web.assets_frontend": [],
|
||||
"web.assets_tests": [],
|
||||
"web.qunit_suite_tests": [],
|
||||
},
|
||||
"installable": True,
|
||||
}
|
55
attachment_preview/i18n/attachment_preview.pot
Normal file
@ -0,0 +1,55 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
57
attachment_preview/i18n/de.po
Normal file
@ -0,0 +1,57 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-08-23 14:27+0000\n"
|
||||
"PO-Revision-Date: 2022-08-23 14:27+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr "Dateianhang"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr "In neuer Seite öffnen"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr "Vorschau"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "Vorschau %s"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
61
attachment_preview/i18n/es.po
Normal file
@ -0,0 +1,61 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
# Translators:
|
||||
# Antonio Trueba, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: knowledge (8.0)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-12-19 10:18+0000\n"
|
||||
"PO-Revision-Date: 2023-06-05 17:08+0000\n"
|
||||
"Last-Translator: luis-ron <luis.ron@sygel.es>\n"
|
||||
"Language-Team: Spanish (http://www.transifex.com/oca/OCA-knowledge-8-0/"
|
||||
"language/es/)\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.17\n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr "Archivo adjunto"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr "Abrir en página nueva"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr "Vista previa"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "Previsualizar %s"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
61
attachment_preview/i18n/it.po
Normal file
@ -0,0 +1,61 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
# Translators:
|
||||
# Paolo Valier, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: knowledge (8.0)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-03-05 11:50+0000\n"
|
||||
"PO-Revision-Date: 2023-06-22 15:08+0000\n"
|
||||
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
|
||||
"Language-Team: Italian (http://www.transifex.com/oca/OCA-knowledge-8-0/"
|
||||
"language/it/)\n"
|
||||
"Language: it\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.17\n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr "Allegato"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr "Apri in una pagina nuova"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr "Anteprima"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "Anteprima %s"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
59
attachment_preview/i18n/nl.po
Normal file
@ -0,0 +1,59 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
# Translators:
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: knowledge (8.0)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-12-08 08:32+0000\n"
|
||||
"PO-Revision-Date: 2015-11-07 12:44+0000\n"
|
||||
"Last-Translator: OCA Transbot <transbot@odoo-community.org>\n"
|
||||
"Language-Team: Dutch (http://www.transifex.com/oca/OCA-knowledge-8-0/"
|
||||
"language/nl/)\n"
|
||||
"Language: nl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "Voorbeeld %s"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
55
attachment_preview/i18n/pt_BR.po
Normal file
@ -0,0 +1,55 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 12.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: pt_BR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
60
attachment_preview/i18n/pt_PT.po
Normal file
@ -0,0 +1,60 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
# Translators:
|
||||
# Pedro Castro Silva <pedrocs@sossia.pt>, 2016
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: knowledge (8.0)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-05-07 16:07+0000\n"
|
||||
"PO-Revision-Date: 2016-05-22 22:19+0000\n"
|
||||
"Last-Translator: Pedro Castro Silva <pedrocs@sossia.pt>\n"
|
||||
"Language-Team: Portuguese (Portugal) (http://www.transifex.com/oca/OCA-"
|
||||
"knowledge-8-0/language/pt_PT/)\n"
|
||||
"Language: pt_PT\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "Antever %s"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
65
attachment_preview/i18n/sl.po
Normal file
@ -0,0 +1,65 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
# Translators:
|
||||
# Matjaž Mozetič <m.mozetic@matmoz.si>, 2015
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: knowledge (8.0)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-12-08 08:32+0000\n"
|
||||
"PO-Revision-Date: 2020-08-11 14:59+0000\n"
|
||||
"Last-Translator: Matjaz Mozetic <matjaz@matmoz.si>\n"
|
||||
"Language-Team: Slovenian (http://www.transifex.com/oca/OCA-knowledge-8-0/"
|
||||
"language/sl/)\n"
|
||||
"Language: sl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n"
|
||||
"%100==4 ? 2 : 3;\n"
|
||||
"X-Generator: Weblate 3.10\n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "Predogled %s"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "ir.attachment"
|
||||
#~ msgstr "ir.attachment"
|
57
attachment_preview/i18n/tr.po
Normal file
@ -0,0 +1,57 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 12.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2022-01-06 16:40+0000\n"
|
||||
"Last-Translator: Yavuz Avci <yavuzavci@gmail.com>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: tr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.3.2\n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr "Ek"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "%s Önizle"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
57
attachment_preview/i18n/vi_VN.po
Normal file
@ -0,0 +1,57 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-08-23 14:29+0000\n"
|
||||
"PO-Revision-Date: 2022-08-23 14:29+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr "Tập tin đính kèm"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr "Mở trong trang mới"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr "Xem trước"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "Xem trước %s"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
60
attachment_preview/i18n/zh_CN.po
Normal file
@ -0,0 +1,60 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
# Translators:
|
||||
# ITGeeker <alanljj@qq.com>, 2017
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: knowledge (8.0)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2016-07-31 02:38+0000\n"
|
||||
"PO-Revision-Date: 2017-04-19 01:16+0000\n"
|
||||
"Last-Translator: ITGeeker <alanljj@qq.com>\n"
|
||||
"Language-Team: Chinese (China) (http://www.transifex.com/oca/OCA-"
|
||||
"knowledge-8-0/language/zh_CN/)\n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#: model:ir.model,name:attachment_preview.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open in new page"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview"
|
||||
msgstr ""
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/js/components/chatter/chatter.esm.js:0
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "预览 %s"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:addons/attachment_preview/static/src/xml/attachment_preview.xml:0
|
||||
#, python-format
|
||||
msgid "Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
msgstr ""
|
4
attachment_preview/models/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# Copyright 2014 Therp BV (<http://therp.nl>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import ir_attachment
|
72
attachment_preview/models/ir_attachment.py
Normal file
@ -0,0 +1,72 @@
|
||||
# Copyright 2014 Therp BV (<http://therp.nl>)
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import collections
|
||||
import logging
|
||||
import mimetypes
|
||||
import os.path
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IrAttachment(models.Model):
|
||||
_inherit = "ir.attachment"
|
||||
|
||||
@api.model
|
||||
def get_binary_extension(self, model, ids, binary_field, filename_field=None):
|
||||
result = {}
|
||||
ids_to_browse = ids if isinstance(ids, collections.abc.Iterable) else [ids]
|
||||
|
||||
# First pass: load fields in bin_size mode to avoid loading big files
|
||||
# unnecessarily.
|
||||
if filename_field:
|
||||
for this in (
|
||||
self.env[model].with_context(bin_size=True).browse(ids_to_browse)
|
||||
):
|
||||
result[this.id] = False
|
||||
extension = ""
|
||||
if this[filename_field]:
|
||||
filename, extension = os.path.splitext(this[filename_field])
|
||||
if this[binary_field] and extension:
|
||||
result[this.id] = extension
|
||||
_logger.debug(
|
||||
"Got extension %s from filename %s",
|
||||
extension,
|
||||
this[filename_field],
|
||||
)
|
||||
# Second pass for all attachments which have to be loaded fully
|
||||
# to get the extension from the content
|
||||
ids_to_browse = [_id for _id in ids_to_browse if _id not in result]
|
||||
for this in self.env[model].with_context(bin_size=True).browse(ids_to_browse):
|
||||
result[this.id] = False
|
||||
try:
|
||||
import magic
|
||||
|
||||
if model == self._name and binary_field == "datas" and this.store_fname:
|
||||
mimetype = magic.from_file(
|
||||
this._full_path(this.store_fname), mime=True
|
||||
)
|
||||
# _logger.debug(
|
||||
# "Magic determined mimetype %s from file %s",
|
||||
# mimetype,
|
||||
# this.store_fname
|
||||
# )
|
||||
else:
|
||||
mimetype = magic.from_buffer(this[binary_field], mime=True)
|
||||
_logger.debug("Magic determined mimetype %s from buffer", mimetype)
|
||||
except ImportError:
|
||||
(mimetype, encoding) = mimetypes.guess_type(
|
||||
"data:;base64," + this[binary_field].decode("utf-8"), strict=False
|
||||
)
|
||||
# _logger.debug("Mimetypes guessed type %s from buffer", mimetype)
|
||||
extension = mimetypes.guess_extension(mimetype.split(";")[0], strict=False)
|
||||
result[this.id] = extension
|
||||
for _id in result:
|
||||
result[_id] = (result[_id] or "").lstrip(".").lower()
|
||||
return result if isinstance(ids, collections.abc.Iterable) else result[ids]
|
||||
|
||||
@api.model
|
||||
def get_attachment_extension(self, ids):
|
||||
return self.get_binary_extension(self._name, ids, "datas", "name")
|
2
attachment_preview/readme/CONTRIBUTORS.rst
Normal file
@ -0,0 +1,2 @@
|
||||
* Holger Brunn <mail@hunki-enterprises.com>
|
||||
* Dennis Sluijk <d.sluijk@onestein.nl>
|
4
attachment_preview/readme/CREDITS.rst
Normal file
@ -0,0 +1,4 @@
|
||||
Addon icon
|
||||
----------
|
||||
|
||||
* courtesy of http://commons.wikimedia.org/wiki/Crystal_Clear
|
7
attachment_preview/readme/DESCRIPTION.rst
Normal file
@ -0,0 +1,7 @@
|
||||
This addon allows to preview attachments supported by http://viewerjs.org.
|
||||
|
||||
Currently, that's most Libreoffice files and PDFs.
|
||||
|
||||
.. image:: /attachment_preview/static/description/screenshot-split.png
|
||||
:alt: Screenshot of split form view
|
||||
:width: 100%
|
4
attachment_preview/readme/INSTALL.rst
Normal file
@ -0,0 +1,4 @@
|
||||
For filetype recognition, you'll get the best results by installing
|
||||
``python-magic``:
|
||||
|
||||
sudo apt-get install python-magic
|
8
attachment_preview/readme/USAGE.rst
Normal file
@ -0,0 +1,8 @@
|
||||
The module adds a little print preview icon right of download links for
|
||||
attachments or binary fields. When a preview is opened from the attachments
|
||||
menu it's shown next to the form view. From this screen you can navigate
|
||||
through the attachments using the arrow buttons. Using the pop-out button
|
||||
next to the navigational buttons you can open the preview in a separate window.
|
||||
|
||||
.. image:: /attachment_preview/static/description/screenshot-paginator.png
|
||||
:alt: Screenshot navigator
|
BIN
attachment_preview/static/description/icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
457
attachment_preview/static/description/index.html
Normal file
@ -0,0 +1,457 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
|
||||
<title>Preview attachments</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
Despite the name, some widely supported CSS2 features are used.
|
||||
|
||||
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless, table.borderless td, table.borderless th {
|
||||
border: 0 }
|
||||
|
||||
table.borderless td, table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important }
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important }
|
||||
|
||||
.last, .with-subtitle {
|
||||
margin-bottom: 0 ! important }
|
||||
|
||||
.hidden {
|
||||
display: none }
|
||||
|
||||
.subscript {
|
||||
vertical-align: sub;
|
||||
font-size: smaller }
|
||||
|
||||
.superscript {
|
||||
vertical-align: super;
|
||||
font-size: smaller }
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none ;
|
||||
color: black }
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em ; }
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em }
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold ;
|
||||
text-align: center }
|
||||
|
||||
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||
div.hint, div.important, div.note, div.tip, div.warning {
|
||||
margin: 2em ;
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||
div.important p.admonition-title, div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||
div.danger p.admonition-title, div.error p.admonition-title,
|
||||
div.warning p.admonition-title, .code .error {
|
||||
color: red ;
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em ;
|
||||
text-align: center ;
|
||||
font-style: italic }
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold ;
|
||||
font-style: normal }
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
div.footer, div.header {
|
||||
clear: both;
|
||||
font-size: smaller }
|
||||
|
||||
div.line-block {
|
||||
display: block ;
|
||||
margin-top: 1em ;
|
||||
margin-bottom: 1em }
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0 ;
|
||||
margin-bottom: 0 ;
|
||||
margin-left: 1.5em }
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em ;
|
||||
border: medium outset ;
|
||||
padding: 1em ;
|
||||
background-color: #ffffee ;
|
||||
width: 40% ;
|
||||
float: right ;
|
||||
clear: right }
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif ;
|
||||
font-size: medium }
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em }
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red }
|
||||
|
||||
div.system-message {
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red ;
|
||||
font-weight: bold }
|
||||
|
||||
div.topic {
|
||||
margin: 2em }
|
||||
|
||||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||
margin-top: 0.4em }
|
||||
|
||||
h1.title {
|
||||
text-align: center }
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center }
|
||||
|
||||
hr.docutils {
|
||||
width: 75% }
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left, table.align-left {
|
||||
clear: left ;
|
||||
float: left ;
|
||||
margin-right: 1em }
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right, table.align-right {
|
||||
clear: right ;
|
||||
float: right ;
|
||||
margin-left: 1em }
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left }
|
||||
|
||||
.align-center {
|
||||
clear: both ;
|
||||
text-align: center }
|
||||
|
||||
.align-right {
|
||||
text-align: right }
|
||||
|
||||
/* reset inner alignment in figures */
|
||||
div.align-right {
|
||||
text-align: inherit }
|
||||
|
||||
/* div.align-center * { */
|
||||
/* text-align: left } */
|
||||
|
||||
.align-top {
|
||||
vertical-align: top }
|
||||
|
||||
.align-middle {
|
||||
vertical-align: middle }
|
||||
|
||||
.align-bottom {
|
||||
vertical-align: bottom }
|
||||
|
||||
ol.simple, ul.simple {
|
||||
margin-bottom: 1em }
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal }
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha }
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha }
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman }
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman }
|
||||
|
||||
p.attribution {
|
||||
text-align: right ;
|
||||
margin-left: 50% }
|
||||
|
||||
p.caption {
|
||||
font-style: italic }
|
||||
|
||||
p.credits {
|
||||
font-style: italic ;
|
||||
font-size: smaller }
|
||||
|
||||
p.label {
|
||||
white-space: nowrap }
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold ;
|
||||
font-size: larger ;
|
||||
color: maroon ;
|
||||
text-align: center }
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold ;
|
||||
font-size: larger }
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold }
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0 ;
|
||||
margin-top: 0 ;
|
||||
font: inherit }
|
||||
|
||||
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
pre.code .ln { color: gray; } /* line numbers */
|
||||
pre.code, code { background-color: #eeeeee }
|
||||
pre.code .comment, code .comment { color: #5C6576 }
|
||||
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
|
||||
pre.code .literal.string, code .literal.string { color: #0C5404 }
|
||||
pre.code .name.builtin, code .name.builtin { color: #352B84 }
|
||||
pre.code .deleted, code .deleted { background-color: #DEB0A1}
|
||||
pre.code .inserted, code .inserted { background-color: #A3D289}
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif ;
|
||||
font-style: oblique }
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif }
|
||||
|
||||
span.option {
|
||||
white-space: nowrap }
|
||||
|
||||
span.pre {
|
||||
white-space: pre }
|
||||
|
||||
span.problematic, pre.problematic {
|
||||
color: red }
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80% }
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em }
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em ;
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docutils td, table.docutils th,
|
||||
table.docinfo td, table.docinfo th {
|
||||
padding-left: 0.5em ;
|
||||
padding-right: 0.5em ;
|
||||
vertical-align: top }
|
||||
|
||||
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||
font-weight: bold ;
|
||||
text-align: left ;
|
||||
white-space: nowrap ;
|
||||
padding-left: 0 }
|
||||
|
||||
/* "booktabs" style (no vertical lines) */
|
||||
table.docutils.booktabs {
|
||||
border: 0px;
|
||||
border-top: 2px solid;
|
||||
border-bottom: 2px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.docutils.booktabs * {
|
||||
border: 0px;
|
||||
}
|
||||
table.docutils.booktabs th {
|
||||
border-bottom: thin solid;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||
font-size: 100% }
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="document" id="preview-attachments">
|
||||
<h1 class="title">Preview attachments</h1>
|
||||
|
||||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! source digest: sha256:573f74622167c92c4eb0881999cde8d69bb3de4a4b7489a1703158d3a66a8269
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/knowledge/tree/16.0/attachment_preview"><img alt="OCA/knowledge" src="https://img.shields.io/badge/github-OCA%2Fknowledge-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/knowledge-16-0/knowledge-16-0-attachment_preview"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/knowledge&target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
|
||||
<p>This addon allows to preview attachments supported by <a class="reference external" href="http://viewerjs.org">http://viewerjs.org</a>.</p>
|
||||
<p>Currently, that’s most Libreoffice files and PDFs.</p>
|
||||
<img alt="Screenshot of split form view" src="https://raw.githubusercontent.com/attachment_preview/static/description/screenshot-split.png" style="width: 100%;" />
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
<ul class="simple">
|
||||
<li><a class="reference internal" href="#installation" id="toc-entry-1">Installation</a></li>
|
||||
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#other-credits" id="toc-entry-7">Other credits</a><ul>
|
||||
<li><a class="reference internal" href="#addon-icon" id="toc-entry-8">Addon icon</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#maintainers" id="toc-entry-9">Maintainers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="installation">
|
||||
<h1><a class="toc-backref" href="#toc-entry-1">Installation</a></h1>
|
||||
<p>For filetype recognition, you’ll get the best results by installing
|
||||
<tt class="docutils literal"><span class="pre">python-magic</span></tt>:</p>
|
||||
<p>sudo apt-get install python-magic</p>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1><a class="toc-backref" href="#toc-entry-2">Usage</a></h1>
|
||||
<p>The module adds a little print preview icon right of download links for
|
||||
attachments or binary fields. When a preview is opened from the attachments
|
||||
menu it’s shown next to the form view. From this screen you can navigate
|
||||
through the attachments using the arrow buttons. Using the pop-out button
|
||||
next to the navigational buttons you can open the preview in a separate window.</p>
|
||||
<img alt="Screenshot navigator" src="https://raw.githubusercontent.com/attachment_preview/static/description/screenshot-paginator.png" />
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/knowledge/issues">GitHub Issues</a>.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us to smash it by providing a detailed and welcomed
|
||||
<a class="reference external" href="https://github.com/OCA/knowledge/issues/new?body=module:%20attachment_preview%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
<h1><a class="toc-backref" href="#toc-entry-4">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#toc-entry-5">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Therp BV</li>
|
||||
<li>Onestein</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#toc-entry-6">Contributors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Holger Brunn <<a class="reference external" href="mailto:mail@hunki-enterprises.com">mail@hunki-enterprises.com</a>></li>
|
||||
<li>Dennis Sluijk <<a class="reference external" href="mailto:d.sluijk@onestein.nl">d.sluijk@onestein.nl</a>></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="other-credits">
|
||||
<h2><a class="toc-backref" href="#toc-entry-7">Other credits</a></h2>
|
||||
<div class="section" id="addon-icon">
|
||||
<h3><a class="toc-backref" href="#toc-entry-8">Addon icon</a></h3>
|
||||
<ul class="simple">
|
||||
<li>courtesy of <a class="reference external" href="http://commons.wikimedia.org/wiki/Crystal_Clear">http://commons.wikimedia.org/wiki/Crystal_Clear</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#toc-entry-9">Maintainers</a></h2>
|
||||
<p>This module is maintained by the OCA.</p>
|
||||
<a class="reference external image-reference" href="https://odoo-community.org">
|
||||
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
|
||||
</a>
|
||||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.</p>
|
||||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/knowledge/tree/16.0/attachment_preview">OCA/knowledge</a> project on GitHub.</p>
|
||||
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
BIN
attachment_preview/static/description/screenshot-paginator.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
attachment_preview/static/description/screenshot-split.png
Normal file
After Width: | Height: | Size: 159 KiB |
577
attachment_preview/static/lib/ViewerJS/compatibility.js
Normal file
@ -0,0 +1,577 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* globals VBArray, PDFJS */
|
||||
|
||||
'use strict';
|
||||
|
||||
// Initializing PDFJS global object here, it case if we need to change/disable
|
||||
// some PDF.js features, e.g. range requests
|
||||
if (typeof PDFJS === 'undefined') {
|
||||
(typeof window !== 'undefined' ? window : this).PDFJS = {};
|
||||
}
|
||||
|
||||
// Checking if the typed arrays are supported
|
||||
// Support: iOS<6.0 (subarray), IE<10, Android<4.0
|
||||
(function checkTypedArrayCompatibility() {
|
||||
if (typeof Uint8Array !== 'undefined') {
|
||||
// Support: iOS<6.0
|
||||
if (typeof Uint8Array.prototype.subarray === 'undefined') {
|
||||
Uint8Array.prototype.subarray = function subarray(start, end) {
|
||||
return new Uint8Array(this.slice(start, end));
|
||||
};
|
||||
Float32Array.prototype.subarray = function subarray(start, end) {
|
||||
return new Float32Array(this.slice(start, end));
|
||||
};
|
||||
}
|
||||
|
||||
// Support: Android<4.1
|
||||
if (typeof Float64Array === 'undefined') {
|
||||
window.Float64Array = Float32Array;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function subarray(start, end) {
|
||||
return new TypedArray(this.slice(start, end));
|
||||
}
|
||||
|
||||
function setArrayOffset(array, offset) {
|
||||
if (arguments.length < 2) {
|
||||
offset = 0;
|
||||
}
|
||||
for (var i = 0, n = array.length; i < n; ++i, ++offset) {
|
||||
this[offset] = array[i] & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
function TypedArray(arg1) {
|
||||
var result, i, n;
|
||||
if (typeof arg1 === 'number') {
|
||||
result = [];
|
||||
for (i = 0; i < arg1; ++i) {
|
||||
result[i] = 0;
|
||||
}
|
||||
} else if ('slice' in arg1) {
|
||||
result = arg1.slice(0);
|
||||
} else {
|
||||
result = [];
|
||||
for (i = 0, n = arg1.length; i < n; ++i) {
|
||||
result[i] = arg1[i];
|
||||
}
|
||||
}
|
||||
|
||||
result.subarray = subarray;
|
||||
result.buffer = result;
|
||||
result.byteLength = result.length;
|
||||
result.set = setArrayOffset;
|
||||
|
||||
if (typeof arg1 === 'object' && arg1.buffer) {
|
||||
result.buffer = arg1.buffer;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
window.Uint8Array = TypedArray;
|
||||
window.Int8Array = TypedArray;
|
||||
|
||||
// we don't need support for set, byteLength for 32-bit array
|
||||
// so we can use the TypedArray as well
|
||||
window.Uint32Array = TypedArray;
|
||||
window.Int32Array = TypedArray;
|
||||
window.Uint16Array = TypedArray;
|
||||
window.Float32Array = TypedArray;
|
||||
window.Float64Array = TypedArray;
|
||||
})();
|
||||
|
||||
// URL = URL || webkitURL
|
||||
// Support: Safari<7, Android 4.2+
|
||||
(function normalizeURLObject() {
|
||||
if (!window.URL) {
|
||||
window.URL = window.webkitURL;
|
||||
}
|
||||
})();
|
||||
|
||||
// Object.defineProperty()?
|
||||
// Support: Android<4.0, Safari<5.1
|
||||
(function checkObjectDefinePropertyCompatibility() {
|
||||
if (typeof Object.defineProperty !== 'undefined') {
|
||||
var definePropertyPossible = true;
|
||||
try {
|
||||
// some browsers (e.g. safari) cannot use defineProperty() on DOM objects
|
||||
// and thus the native version is not sufficient
|
||||
Object.defineProperty(new Image(), 'id', { value: 'test' });
|
||||
// ... another test for android gb browser for non-DOM objects
|
||||
var Test = function Test() {};
|
||||
Test.prototype = { get id() { } };
|
||||
Object.defineProperty(new Test(), 'id',
|
||||
{ value: '', configurable: true, enumerable: true, writable: false });
|
||||
} catch (e) {
|
||||
definePropertyPossible = false;
|
||||
}
|
||||
if (definePropertyPossible) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty = function objectDefineProperty(obj, name, def) {
|
||||
delete obj[name];
|
||||
if ('get' in def) {
|
||||
obj.__defineGetter__(name, def['get']);
|
||||
}
|
||||
if ('set' in def) {
|
||||
obj.__defineSetter__(name, def['set']);
|
||||
}
|
||||
if ('value' in def) {
|
||||
obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
|
||||
this.__defineGetter__(name, function objectDefinePropertyGetter() {
|
||||
return value;
|
||||
});
|
||||
return value;
|
||||
});
|
||||
obj[name] = def.value;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
// No XMLHttpRequest#response?
|
||||
// Support: IE<11, Android <4.0
|
||||
(function checkXMLHttpRequestResponseCompatibility() {
|
||||
var xhrPrototype = XMLHttpRequest.prototype;
|
||||
var xhr = new XMLHttpRequest();
|
||||
if (!('overrideMimeType' in xhr)) {
|
||||
// IE10 might have response, but not overrideMimeType
|
||||
// Support: IE10
|
||||
Object.defineProperty(xhrPrototype, 'overrideMimeType', {
|
||||
value: function xmlHttpRequestOverrideMimeType(mimeType) {}
|
||||
});
|
||||
}
|
||||
if ('responseType' in xhr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The worker will be using XHR, so we can save time and disable worker.
|
||||
PDFJS.disableWorker = true;
|
||||
|
||||
Object.defineProperty(xhrPrototype, 'responseType', {
|
||||
get: function xmlHttpRequestGetResponseType() {
|
||||
return this._responseType || 'text';
|
||||
},
|
||||
set: function xmlHttpRequestSetResponseType(value) {
|
||||
if (value === 'text' || value === 'arraybuffer') {
|
||||
this._responseType = value;
|
||||
if (value === 'arraybuffer' &&
|
||||
typeof this.overrideMimeType === 'function') {
|
||||
this.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Support: IE9
|
||||
if (typeof VBArray !== 'undefined') {
|
||||
Object.defineProperty(xhrPrototype, 'response', {
|
||||
get: function xmlHttpRequestResponseGet() {
|
||||
if (this.responseType === 'arraybuffer') {
|
||||
return new Uint8Array(new VBArray(this.responseBody).toArray());
|
||||
} else {
|
||||
return this.responseText;
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(xhrPrototype, 'response', {
|
||||
get: function xmlHttpRequestResponseGet() {
|
||||
if (this.responseType !== 'arraybuffer') {
|
||||
return this.responseText;
|
||||
}
|
||||
var text = this.responseText;
|
||||
var i, n = text.length;
|
||||
var result = new Uint8Array(n);
|
||||
for (i = 0; i < n; ++i) {
|
||||
result[i] = text.charCodeAt(i) & 0xFF;
|
||||
}
|
||||
return result.buffer;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// window.btoa (base64 encode function) ?
|
||||
// Support: IE<10
|
||||
(function checkWindowBtoaCompatibility() {
|
||||
if ('btoa' in window) {
|
||||
return;
|
||||
}
|
||||
|
||||
var digits =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
window.btoa = function windowBtoa(chars) {
|
||||
var buffer = '';
|
||||
var i, n;
|
||||
for (i = 0, n = chars.length; i < n; i += 3) {
|
||||
var b1 = chars.charCodeAt(i) & 0xFF;
|
||||
var b2 = chars.charCodeAt(i + 1) & 0xFF;
|
||||
var b3 = chars.charCodeAt(i + 2) & 0xFF;
|
||||
var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
|
||||
var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
|
||||
var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
|
||||
buffer += (digits.charAt(d1) + digits.charAt(d2) +
|
||||
digits.charAt(d3) + digits.charAt(d4));
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
})();
|
||||
|
||||
// window.atob (base64 encode function)?
|
||||
// Support: IE<10
|
||||
(function checkWindowAtobCompatibility() {
|
||||
if ('atob' in window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://github.com/davidchambers/Base64.js
|
||||
var digits =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
window.atob = function (input) {
|
||||
input = input.replace(/=+$/, '');
|
||||
if (input.length % 4 === 1) {
|
||||
throw new Error('bad atob input');
|
||||
}
|
||||
for (
|
||||
// initialize result and counters
|
||||
var bc = 0, bs, buffer, idx = 0, output = '';
|
||||
// get next character
|
||||
buffer = input.charAt(idx++);
|
||||
// character found in table?
|
||||
// initialize bit storage and add its ascii value
|
||||
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
|
||||
// and if not first of each 4 characters,
|
||||
// convert the first 8 bits to one ascii character
|
||||
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
|
||||
) {
|
||||
// try to find character in table (0-63, not found => -1)
|
||||
buffer = digits.indexOf(buffer);
|
||||
}
|
||||
return output;
|
||||
};
|
||||
})();
|
||||
|
||||
// Function.prototype.bind?
|
||||
// Support: Android<4.0, iOS<6.0
|
||||
(function checkFunctionPrototypeBindCompatibility() {
|
||||
if (typeof Function.prototype.bind !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
Function.prototype.bind = function functionPrototypeBind(obj) {
|
||||
var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
|
||||
var bound = function functionPrototypeBindBound() {
|
||||
var args = headArgs.concat(Array.prototype.slice.call(arguments));
|
||||
return fn.apply(obj, args);
|
||||
};
|
||||
return bound;
|
||||
};
|
||||
})();
|
||||
|
||||
// HTMLElement dataset property
|
||||
// Support: IE<11, Safari<5.1, Android<4.0
|
||||
(function checkDatasetProperty() {
|
||||
var div = document.createElement('div');
|
||||
if ('dataset' in div) {
|
||||
return; // dataset property exists
|
||||
}
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'dataset', {
|
||||
get: function() {
|
||||
if (this._dataset) {
|
||||
return this._dataset;
|
||||
}
|
||||
|
||||
var dataset = {};
|
||||
for (var j = 0, jj = this.attributes.length; j < jj; j++) {
|
||||
var attribute = this.attributes[j];
|
||||
if (attribute.name.substring(0, 5) !== 'data-') {
|
||||
continue;
|
||||
}
|
||||
var key = attribute.name.substring(5).replace(/\-([a-z])/g,
|
||||
function(all, ch) {
|
||||
return ch.toUpperCase();
|
||||
});
|
||||
dataset[key] = attribute.value;
|
||||
}
|
||||
|
||||
Object.defineProperty(this, '_dataset', {
|
||||
value: dataset,
|
||||
writable: false,
|
||||
enumerable: false
|
||||
});
|
||||
return dataset;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
|
||||
// HTMLElement classList property
|
||||
// Support: IE<10, Android<4.0, iOS<5.0
|
||||
(function checkClassListProperty() {
|
||||
var div = document.createElement('div');
|
||||
if ('classList' in div) {
|
||||
return; // classList property exists
|
||||
}
|
||||
|
||||
function changeList(element, itemName, add, remove) {
|
||||
var s = element.className || '';
|
||||
var list = s.split(/\s+/g);
|
||||
if (list[0] === '') {
|
||||
list.shift();
|
||||
}
|
||||
var index = list.indexOf(itemName);
|
||||
if (index < 0 && add) {
|
||||
list.push(itemName);
|
||||
}
|
||||
if (index >= 0 && remove) {
|
||||
list.splice(index, 1);
|
||||
}
|
||||
element.className = list.join(' ');
|
||||
return (index >= 0);
|
||||
}
|
||||
|
||||
var classListPrototype = {
|
||||
add: function(name) {
|
||||
changeList(this.element, name, true, false);
|
||||
},
|
||||
contains: function(name) {
|
||||
return changeList(this.element, name, false, false);
|
||||
},
|
||||
remove: function(name) {
|
||||
changeList(this.element, name, false, true);
|
||||
},
|
||||
toggle: function(name) {
|
||||
changeList(this.element, name, true, true);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'classList', {
|
||||
get: function() {
|
||||
if (this._classList) {
|
||||
return this._classList;
|
||||
}
|
||||
|
||||
var classList = Object.create(classListPrototype, {
|
||||
element: {
|
||||
value: this,
|
||||
writable: false,
|
||||
enumerable: true
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, '_classList', {
|
||||
value: classList,
|
||||
writable: false,
|
||||
enumerable: false
|
||||
});
|
||||
return classList;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
|
||||
// Check console compatibility
|
||||
// In older IE versions the console object is not available
|
||||
// unless console is open.
|
||||
// Support: IE<10
|
||||
(function checkConsoleCompatibility() {
|
||||
if (!('console' in window)) {
|
||||
window.console = {
|
||||
log: function() {},
|
||||
error: function() {},
|
||||
warn: function() {}
|
||||
};
|
||||
} else if (!('bind' in console.log)) {
|
||||
// native functions in IE9 might not have bind
|
||||
console.log = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.log);
|
||||
console.error = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.error);
|
||||
console.warn = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.warn);
|
||||
}
|
||||
})();
|
||||
|
||||
// Check onclick compatibility in Opera
|
||||
// Support: Opera<15
|
||||
(function checkOnClickCompatibility() {
|
||||
// workaround for reported Opera bug DSK-354448:
|
||||
// onclick fires on disabled buttons with opaque content
|
||||
function ignoreIfTargetDisabled(event) {
|
||||
if (isDisabled(event.target)) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
function isDisabled(node) {
|
||||
return node.disabled || (node.parentNode && isDisabled(node.parentNode));
|
||||
}
|
||||
if (navigator.userAgent.indexOf('Opera') !== -1) {
|
||||
// use browser detection since we cannot feature-check this bug
|
||||
document.addEventListener('click', ignoreIfTargetDisabled, true);
|
||||
}
|
||||
})();
|
||||
|
||||
// Checks if possible to use URL.createObjectURL()
|
||||
// Support: IE
|
||||
(function checkOnBlobSupport() {
|
||||
// sometimes IE loosing the data created with createObjectURL(), see #3977
|
||||
if (navigator.userAgent.indexOf('Trident') >= 0) {
|
||||
PDFJS.disableCreateObjectURL = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Checks if navigator.language is supported
|
||||
(function checkNavigatorLanguage() {
|
||||
if ('language' in navigator) {
|
||||
return;
|
||||
}
|
||||
PDFJS.locale = navigator.userLanguage || 'en-US';
|
||||
})();
|
||||
|
||||
(function checkRangeRequests() {
|
||||
// Safari has issues with cached range requests see:
|
||||
// https://github.com/mozilla/pdf.js/issues/3260
|
||||
// Last tested with version 6.0.4.
|
||||
// Support: Safari 6.0+
|
||||
var isSafari = Object.prototype.toString.call(
|
||||
window.HTMLElement).indexOf('Constructor') > 0;
|
||||
|
||||
// Older versions of Android (pre 3.0) has issues with range requests, see:
|
||||
// https://github.com/mozilla/pdf.js/issues/3381.
|
||||
// Make sure that we only match webkit-based Android browsers,
|
||||
// since Firefox/Fennec works as expected.
|
||||
// Support: Android<3.0
|
||||
var regex = /Android\s[0-2][^\d]/;
|
||||
var isOldAndroid = regex.test(navigator.userAgent);
|
||||
|
||||
// Range requests are broken in Chrome 39 and 40, https://crbug.com/442318
|
||||
var isChromeWithRangeBug = /Chrome\/(39|40)\./.test(navigator.userAgent);
|
||||
|
||||
if (isSafari || isOldAndroid || isChromeWithRangeBug) {
|
||||
PDFJS.disableRange = true;
|
||||
PDFJS.disableStream = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Check if the browser supports manipulation of the history.
|
||||
// Support: IE<10, Android<4.2
|
||||
(function checkHistoryManipulation() {
|
||||
// Android 2.x has so buggy pushState support that it was removed in
|
||||
// Android 3.0 and restored as late as in Android 4.2.
|
||||
// Support: Android 2.x
|
||||
if (!history.pushState || navigator.userAgent.indexOf('Android 2.') >= 0) {
|
||||
PDFJS.disableHistory = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Support: IE<11, Chrome<21, Android<4.4, Safari<6
|
||||
(function checkSetPresenceInImageData() {
|
||||
// IE < 11 will use window.CanvasPixelArray which lacks set function.
|
||||
if (window.CanvasPixelArray) {
|
||||
if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
|
||||
window.CanvasPixelArray.prototype.set = function(arr) {
|
||||
for (var i = 0, ii = this.length; i < ii; i++) {
|
||||
this[i] = arr[i];
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Old Chrome and Android use an inaccessible CanvasPixelArray prototype.
|
||||
// Because we cannot feature detect it, we rely on user agent parsing.
|
||||
var polyfill = false, versionMatch;
|
||||
if (navigator.userAgent.indexOf('Chrom') >= 0) {
|
||||
versionMatch = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
||||
// Chrome < 21 lacks the set function.
|
||||
polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
|
||||
} else if (navigator.userAgent.indexOf('Android') >= 0) {
|
||||
// Android < 4.4 lacks the set function.
|
||||
// Android >= 4.4 will contain Chrome in the user agent,
|
||||
// thus pass the Chrome check above and not reach this block.
|
||||
polyfill = /Android\s[0-4][^\d]/g.test(navigator.userAgent);
|
||||
} else if (navigator.userAgent.indexOf('Safari') >= 0) {
|
||||
versionMatch = navigator.userAgent.
|
||||
match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
|
||||
// Safari < 6 lacks the set function.
|
||||
polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
|
||||
}
|
||||
|
||||
if (polyfill) {
|
||||
var contextPrototype = window.CanvasRenderingContext2D.prototype;
|
||||
contextPrototype._createImageData = contextPrototype.createImageData;
|
||||
contextPrototype.createImageData = function(w, h) {
|
||||
var imageData = this._createImageData(w, h);
|
||||
imageData.data.set = function(arr) {
|
||||
for (var i = 0, ii = this.length; i < ii; i++) {
|
||||
this[i] = arr[i];
|
||||
}
|
||||
};
|
||||
return imageData;
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Support: IE<10, Android<4.0, iOS
|
||||
(function checkRequestAnimationFrame() {
|
||||
function fakeRequestAnimationFrame(callback) {
|
||||
window.setTimeout(callback, 20);
|
||||
}
|
||||
|
||||
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
|
||||
if (isIOS) {
|
||||
// requestAnimationFrame on iOS is broken, replacing with fake one.
|
||||
window.requestAnimationFrame = fakeRequestAnimationFrame;
|
||||
return;
|
||||
}
|
||||
if ('requestAnimationFrame' in window) {
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame =
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
fakeRequestAnimationFrame;
|
||||
})();
|
||||
|
||||
(function checkCanvasSizeLimitation() {
|
||||
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
|
||||
var isAndroid = /Android/g.test(navigator.userAgent);
|
||||
if (isIOS || isAndroid) {
|
||||
// 5MP
|
||||
PDFJS.maxCanvasPixels = 5242880;
|
||||
}
|
||||
})();
|
||||
|
||||
// Disable fullscreen support for certain problematic configurations.
|
||||
// Support: IE11+ (when embedded).
|
||||
(function checkFullscreenSupport() {
|
||||
var isEmbeddedIE = (navigator.userAgent.indexOf('Trident') >= 0 &&
|
||||
window.parent !== window);
|
||||
if (isEmbeddedIE) {
|
||||
PDFJS.disableFullscreen = true;
|
||||
}
|
||||
})();
|
27
attachment_preview/static/lib/ViewerJS/example.local.css
Normal file
@ -0,0 +1,27 @@
|
||||
/* This is just a sample file with CSS rules. You should write your own @font-face declarations
|
||||
* to add support for your desired fonts.
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Novecentowide Book';
|
||||
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot");
|
||||
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot?#iefix") format("embedded-opentype"),
|
||||
url("/ViewerJS/fonts/Novecentowide-Bold-webfont.woff") format("woff"),
|
||||
url("/fonts/Novecentowide-Bold-webfont.ttf") format("truetype"),
|
||||
url("/fonts/Novecentowide-Bold-webfont.svg#NovecentowideBookBold") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'exotica';
|
||||
src: url('/ViewerJS/fonts/Exotica-webfont.eot');
|
||||
src: url('/ViewerJS/fonts/Exotica-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('/ViewerJS/fonts/Exotica-webfont.woff') format('woff'),
|
||||
url('/ViewerJS/fonts/Exotica-webfont.ttf') format('truetype'),
|
||||
url('/ViewerJS/fonts/Exotica-webfont.svg#exoticamedium') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
}
|
||||
|
BIN
attachment_preview/static/lib/ViewerJS/images/kogmbh.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
attachment_preview/static/lib/ViewerJS/images/nlnet.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
attachment_preview/static/lib/ViewerJS/images/texture.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 512 B |
After Width: | Height: | Size: 491 B |
After Width: | Height: | Size: 237 B |
After Width: | Height: | Size: 353 B |
After Width: | Height: | Size: 344 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 143 B |
147
attachment_preview/static/lib/ViewerJS/index.html
Normal file
8052
attachment_preview/static/lib/ViewerJS/pdf.js
Normal file
39353
attachment_preview/static/lib/ViewerJS/pdf.worker.js
vendored
Normal file
1
attachment_preview/static/lib/ViewerJS/pdfjsversion.js
Normal file
@ -0,0 +1 @@
|
||||
var /**@const{!string}*/pdfjs_version = "v1.1.114";
|
419
attachment_preview/static/lib/ViewerJS/text_layer_builder.js
Normal file
@ -0,0 +1,419 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* globals CustomStyle, PDFJS */
|
||||
|
||||
'use strict';
|
||||
|
||||
var MAX_TEXT_DIVS_TO_RENDER = 100000;
|
||||
|
||||
var NonWhitespaceRegexp = /\S/;
|
||||
|
||||
function isAllWhitespace(str) {
|
||||
return !NonWhitespaceRegexp.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} TextLayerBuilderOptions
|
||||
* @property {HTMLDivElement} textLayerDiv - The text layer container.
|
||||
* @property {number} pageIndex - The page index.
|
||||
* @property {PageViewport} viewport - The viewport of the text layer.
|
||||
* @property {PDFFindController} findController
|
||||
*/
|
||||
|
||||
/**
|
||||
* TextLayerBuilder provides text-selection functionality for the PDF.
|
||||
* It does this by creating overlay divs over the PDF text. These divs
|
||||
* contain text that matches the PDF text they are overlaying. This object
|
||||
* also provides a way to highlight text that is being searched for.
|
||||
* @class
|
||||
*/
|
||||
var TextLayerBuilder = (function TextLayerBuilderClosure() {
|
||||
function TextLayerBuilder(options) {
|
||||
this.textLayerDiv = options.textLayerDiv;
|
||||
this.renderingDone = false;
|
||||
this.divContentDone = false;
|
||||
this.pageIdx = options.pageIndex;
|
||||
this.pageNumber = this.pageIdx + 1;
|
||||
this.matches = [];
|
||||
this.viewport = options.viewport;
|
||||
this.textDivs = [];
|
||||
this.findController = options.findController || null;
|
||||
}
|
||||
|
||||
TextLayerBuilder.prototype = {
|
||||
_finishRendering: function TextLayerBuilder_finishRendering() {
|
||||
this.renderingDone = true;
|
||||
|
||||
var event = document.createEvent('CustomEvent');
|
||||
event.initCustomEvent('textlayerrendered', true, true, {
|
||||
pageNumber: this.pageNumber
|
||||
});
|
||||
this.textLayerDiv.dispatchEvent(event);
|
||||
},
|
||||
|
||||
renderLayer: function TextLayerBuilder_renderLayer() {
|
||||
var textLayerFrag = document.createDocumentFragment();
|
||||
var textDivs = this.textDivs;
|
||||
var textDivsLength = textDivs.length;
|
||||
var canvas = document.createElement('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// No point in rendering many divs as it would make the browser
|
||||
// unusable even after the divs are rendered.
|
||||
if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
|
||||
this._finishRendering();
|
||||
return;
|
||||
}
|
||||
|
||||
var lastFontSize;
|
||||
var lastFontFamily;
|
||||
for (var i = 0; i < textDivsLength; i++) {
|
||||
var textDiv = textDivs[i];
|
||||
if (textDiv.dataset.isWhitespace !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var fontSize = textDiv.style.fontSize;
|
||||
var fontFamily = textDiv.style.fontFamily;
|
||||
|
||||
// Only build font string and set to context if different from last.
|
||||
if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
|
||||
ctx.font = fontSize + ' ' + fontFamily;
|
||||
lastFontSize = fontSize;
|
||||
lastFontFamily = fontFamily;
|
||||
}
|
||||
|
||||
var width = ctx.measureText(textDiv.textContent).width;
|
||||
if (width > 0) {
|
||||
textLayerFrag.appendChild(textDiv);
|
||||
var transform;
|
||||
if (textDiv.dataset.canvasWidth !== undefined) {
|
||||
// Dataset values come of type string.
|
||||
var textScale = textDiv.dataset.canvasWidth / width;
|
||||
transform = 'scaleX(' + textScale + ')';
|
||||
} else {
|
||||
transform = '';
|
||||
}
|
||||
var rotation = textDiv.dataset.angle;
|
||||
if (rotation) {
|
||||
transform = 'rotate(' + rotation + 'deg) ' + transform;
|
||||
}
|
||||
if (transform) {
|
||||
CustomStyle.setProp('transform' , textDiv, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.textLayerDiv.appendChild(textLayerFrag);
|
||||
this._finishRendering();
|
||||
this.updateMatches();
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the text layer.
|
||||
* @param {number} timeout (optional) if specified, the rendering waits
|
||||
* for specified amount of ms.
|
||||
*/
|
||||
render: function TextLayerBuilder_render(timeout) {
|
||||
if (!this.divContentDone || this.renderingDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.renderTimer) {
|
||||
clearTimeout(this.renderTimer);
|
||||
this.renderTimer = null;
|
||||
}
|
||||
|
||||
if (!timeout) { // Render right away
|
||||
this.renderLayer();
|
||||
} else { // Schedule
|
||||
var self = this;
|
||||
this.renderTimer = setTimeout(function() {
|
||||
self.renderLayer();
|
||||
self.renderTimer = null;
|
||||
}, timeout);
|
||||
}
|
||||
},
|
||||
|
||||
appendText: function TextLayerBuilder_appendText(geom, styles) {
|
||||
var style = styles[geom.fontName];
|
||||
var textDiv = document.createElement('div');
|
||||
this.textDivs.push(textDiv);
|
||||
if (isAllWhitespace(geom.str)) {
|
||||
textDiv.dataset.isWhitespace = true;
|
||||
return;
|
||||
}
|
||||
var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform);
|
||||
var angle = Math.atan2(tx[1], tx[0]);
|
||||
if (style.vertical) {
|
||||
angle += Math.PI / 2;
|
||||
}
|
||||
var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
|
||||
var fontAscent = fontHeight;
|
||||
if (style.ascent) {
|
||||
fontAscent = style.ascent * fontAscent;
|
||||
} else if (style.descent) {
|
||||
fontAscent = (1 + style.descent) * fontAscent;
|
||||
}
|
||||
|
||||
var left;
|
||||
var top;
|
||||
if (angle === 0) {
|
||||
left = tx[4];
|
||||
top = tx[5] - fontAscent;
|
||||
} else {
|
||||
left = tx[4] + (fontAscent * Math.sin(angle));
|
||||
top = tx[5] - (fontAscent * Math.cos(angle));
|
||||
}
|
||||
textDiv.style.left = left + 'px';
|
||||
textDiv.style.top = top + 'px';
|
||||
textDiv.style.fontSize = fontHeight + 'px';
|
||||
textDiv.style.fontFamily = style.fontFamily;
|
||||
|
||||
textDiv.textContent = geom.str;
|
||||
// |fontName| is only used by the Font Inspector. This test will succeed
|
||||
// when e.g. the Font Inspector is off but the Stepper is on, but it's
|
||||
// not worth the effort to do a more accurate test.
|
||||
if (PDFJS.pdfBug) {
|
||||
textDiv.dataset.fontName = geom.fontName;
|
||||
}
|
||||
// Storing into dataset will convert number into string.
|
||||
if (angle !== 0) {
|
||||
textDiv.dataset.angle = angle * (180 / Math.PI);
|
||||
}
|
||||
// We don't bother scaling single-char text divs, because it has very
|
||||
// little effect on text highlighting. This makes scrolling on docs with
|
||||
// lots of such divs a lot faster.
|
||||
if (textDiv.textContent.length > 1) {
|
||||
if (style.vertical) {
|
||||
textDiv.dataset.canvasWidth = geom.height * this.viewport.scale;
|
||||
} else {
|
||||
textDiv.dataset.canvasWidth = geom.width * this.viewport.scale;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setTextContent: function TextLayerBuilder_setTextContent(textContent) {
|
||||
this.textContent = textContent;
|
||||
|
||||
var textItems = textContent.items;
|
||||
for (var i = 0, len = textItems.length; i < len; i++) {
|
||||
this.appendText(textItems[i], textContent.styles);
|
||||
}
|
||||
this.divContentDone = true;
|
||||
},
|
||||
|
||||
convertMatches: function TextLayerBuilder_convertMatches(matches) {
|
||||
var i = 0;
|
||||
var iIndex = 0;
|
||||
var bidiTexts = this.textContent.items;
|
||||
var end = bidiTexts.length - 1;
|
||||
var queryLen = (this.findController === null ?
|
||||
0 : this.findController.state.query.length);
|
||||
var ret = [];
|
||||
|
||||
for (var m = 0, len = matches.length; m < len; m++) {
|
||||
// Calculate the start position.
|
||||
var matchIdx = matches[m];
|
||||
|
||||
// Loop over the divIdxs.
|
||||
while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
|
||||
iIndex += bidiTexts[i].str.length;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i === bidiTexts.length) {
|
||||
console.error('Could not find a matching mapping');
|
||||
}
|
||||
|
||||
var match = {
|
||||
begin: {
|
||||
divIdx: i,
|
||||
offset: matchIdx - iIndex
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate the end position.
|
||||
matchIdx += queryLen;
|
||||
|
||||
// Somewhat the same array as above, but use > instead of >= to get
|
||||
// the end position right.
|
||||
while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
|
||||
iIndex += bidiTexts[i].str.length;
|
||||
i++;
|
||||
}
|
||||
|
||||
match.end = {
|
||||
divIdx: i,
|
||||
offset: matchIdx - iIndex
|
||||
};
|
||||
ret.push(match);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
renderMatches: function TextLayerBuilder_renderMatches(matches) {
|
||||
// Early exit if there is nothing to render.
|
||||
if (matches.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bidiTexts = this.textContent.items;
|
||||
var textDivs = this.textDivs;
|
||||
var prevEnd = null;
|
||||
var pageIdx = this.pageIdx;
|
||||
var isSelectedPage = (this.findController === null ?
|
||||
false : (pageIdx === this.findController.selected.pageIdx));
|
||||
var selectedMatchIdx = (this.findController === null ?
|
||||
-1 : this.findController.selected.matchIdx);
|
||||
var highlightAll = (this.findController === null ?
|
||||
false : this.findController.state.highlightAll);
|
||||
var infinity = {
|
||||
divIdx: -1,
|
||||
offset: undefined
|
||||
};
|
||||
|
||||
function beginText(begin, className) {
|
||||
var divIdx = begin.divIdx;
|
||||
textDivs[divIdx].textContent = '';
|
||||
appendTextToDiv(divIdx, 0, begin.offset, className);
|
||||
}
|
||||
|
||||
function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
|
||||
var div = textDivs[divIdx];
|
||||
var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
|
||||
var node = document.createTextNode(content);
|
||||
if (className) {
|
||||
var span = document.createElement('span');
|
||||
span.className = className;
|
||||
span.appendChild(node);
|
||||
div.appendChild(span);
|
||||
return;
|
||||
}
|
||||
div.appendChild(node);
|
||||
}
|
||||
|
||||
var i0 = selectedMatchIdx, i1 = i0 + 1;
|
||||
if (highlightAll) {
|
||||
i0 = 0;
|
||||
i1 = matches.length;
|
||||
} else if (!isSelectedPage) {
|
||||
// Not highlighting all and this isn't the selected page, so do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = i0; i < i1; i++) {
|
||||
var match = matches[i];
|
||||
var begin = match.begin;
|
||||
var end = match.end;
|
||||
var isSelected = (isSelectedPage && i === selectedMatchIdx);
|
||||
var highlightSuffix = (isSelected ? ' selected' : '');
|
||||
|
||||
if (this.findController) {
|
||||
this.findController.updateMatchPosition(pageIdx, i, textDivs,
|
||||
begin.divIdx, end.divIdx);
|
||||
}
|
||||
|
||||
// Match inside new div.
|
||||
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
|
||||
// If there was a previous div, then add the text at the end.
|
||||
if (prevEnd !== null) {
|
||||
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
|
||||
}
|
||||
// Clear the divs and set the content until the starting point.
|
||||
beginText(begin);
|
||||
} else {
|
||||
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
|
||||
}
|
||||
|
||||
if (begin.divIdx === end.divIdx) {
|
||||
appendTextToDiv(begin.divIdx, begin.offset, end.offset,
|
||||
'highlight' + highlightSuffix);
|
||||
} else {
|
||||
appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
|
||||
'highlight begin' + highlightSuffix);
|
||||
for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
|
||||
textDivs[n0].className = 'highlight middle' + highlightSuffix;
|
||||
}
|
||||
beginText(end, 'highlight end' + highlightSuffix);
|
||||
}
|
||||
prevEnd = end;
|
||||
}
|
||||
|
||||
if (prevEnd) {
|
||||
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
|
||||
}
|
||||
},
|
||||
|
||||
updateMatches: function TextLayerBuilder_updateMatches() {
|
||||
// Only show matches when all rendering is done.
|
||||
if (!this.renderingDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear all matches.
|
||||
var matches = this.matches;
|
||||
var textDivs = this.textDivs;
|
||||
var bidiTexts = this.textContent.items;
|
||||
var clearedUntilDivIdx = -1;
|
||||
|
||||
// Clear all current matches.
|
||||
for (var i = 0, len = matches.length; i < len; i++) {
|
||||
var match = matches[i];
|
||||
var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
|
||||
for (var n = begin, end = match.end.divIdx; n <= end; n++) {
|
||||
var div = textDivs[n];
|
||||
div.textContent = bidiTexts[n].str;
|
||||
div.className = '';
|
||||
}
|
||||
clearedUntilDivIdx = match.end.divIdx + 1;
|
||||
}
|
||||
|
||||
if (this.findController === null || !this.findController.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the matches on the page controller into the match format
|
||||
// used for the textLayer.
|
||||
this.matches = this.convertMatches(this.findController === null ?
|
||||
[] : (this.findController.pageMatches[this.pageIdx] || []));
|
||||
this.renderMatches(this.matches);
|
||||
}
|
||||
};
|
||||
return TextLayerBuilder;
|
||||
})();
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @implements IPDFTextLayerFactory
|
||||
*/
|
||||
function DefaultTextLayerFactory() {}
|
||||
DefaultTextLayerFactory.prototype = {
|
||||
/**
|
||||
* @param {HTMLDivElement} textLayerDiv
|
||||
* @param {number} pageIndex
|
||||
* @param {PageViewport} viewport
|
||||
* @returns {TextLayerBuilder}
|
||||
*/
|
||||
createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
|
||||
return new TextLayerBuilder({
|
||||
textLayerDiv: textLayerDiv,
|
||||
pageIndex: pageIndex,
|
||||
viewport: viewport
|
||||
});
|
||||
}
|
||||
};
|
394
attachment_preview/static/lib/ViewerJS/ui_utils.js
Normal file
@ -0,0 +1,394 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var CSS_UNITS = 96.0 / 72.0;
|
||||
var DEFAULT_SCALE = 'auto';
|
||||
var UNKNOWN_SCALE = 0;
|
||||
var MAX_AUTO_SCALE = 1.25;
|
||||
var SCROLLBAR_PADDING = 40;
|
||||
var VERTICAL_PADDING = 5;
|
||||
|
||||
// optimised CSS custom property getter/setter
|
||||
var CustomStyle = (function CustomStyleClosure() {
|
||||
|
||||
// As noted on: http://www.zachstronaut.com/posts/2009/02/17/
|
||||
// animate-css-transforms-firefox-webkit.html
|
||||
// in some versions of IE9 it is critical that ms appear in this list
|
||||
// before Moz
|
||||
var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
|
||||
var _cache = {};
|
||||
|
||||
function CustomStyle() {}
|
||||
|
||||
CustomStyle.getProp = function get(propName, element) {
|
||||
// check cache only when no element is given
|
||||
if (arguments.length === 1 && typeof _cache[propName] === 'string') {
|
||||
return _cache[propName];
|
||||
}
|
||||
|
||||
element = element || document.documentElement;
|
||||
var style = element.style, prefixed, uPropName;
|
||||
|
||||
// test standard property first
|
||||
if (typeof style[propName] === 'string') {
|
||||
return (_cache[propName] = propName);
|
||||
}
|
||||
|
||||
// capitalize
|
||||
uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
|
||||
|
||||
// test vendor specific properties
|
||||
for (var i = 0, l = prefixes.length; i < l; i++) {
|
||||
prefixed = prefixes[i] + uPropName;
|
||||
if (typeof style[prefixed] === 'string') {
|
||||
return (_cache[propName] = prefixed);
|
||||
}
|
||||
}
|
||||
|
||||
//if all fails then set to undefined
|
||||
return (_cache[propName] = 'undefined');
|
||||
};
|
||||
|
||||
CustomStyle.setProp = function set(propName, element, str) {
|
||||
var prop = this.getProp(propName);
|
||||
if (prop !== 'undefined') {
|
||||
element.style[prop] = str;
|
||||
}
|
||||
};
|
||||
|
||||
return CustomStyle;
|
||||
})();
|
||||
|
||||
function getFileName(url) {
|
||||
var anchor = url.indexOf('#');
|
||||
var query = url.indexOf('?');
|
||||
var end = Math.min(
|
||||
anchor > 0 ? anchor : url.length,
|
||||
query > 0 ? query : url.length);
|
||||
return url.substring(url.lastIndexOf('/', end) + 1, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns scale factor for the canvas. It makes sense for the HiDPI displays.
|
||||
* @return {Object} The object with horizontal (sx) and vertical (sy)
|
||||
scales. The scaled property is set to false if scaling is
|
||||
not required, true otherwise.
|
||||
*/
|
||||
function getOutputScale(ctx) {
|
||||
var devicePixelRatio = window.devicePixelRatio || 1;
|
||||
var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
|
||||
ctx.mozBackingStorePixelRatio ||
|
||||
ctx.msBackingStorePixelRatio ||
|
||||
ctx.oBackingStorePixelRatio ||
|
||||
ctx.backingStorePixelRatio || 1;
|
||||
var pixelRatio = devicePixelRatio / backingStoreRatio;
|
||||
return {
|
||||
sx: pixelRatio,
|
||||
sy: pixelRatio,
|
||||
scaled: pixelRatio !== 1
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls specified element into view of its parent.
|
||||
* element {Object} The element to be visible.
|
||||
* spot {Object} An object with optional top and left properties,
|
||||
* specifying the offset from the top left edge.
|
||||
*/
|
||||
function scrollIntoView(element, spot) {
|
||||
// Assuming offsetParent is available (it's not available when viewer is in
|
||||
// hidden iframe or object). We have to scroll: if the offsetParent is not set
|
||||
// producing the error. See also animationStartedClosure.
|
||||
var parent = element.offsetParent;
|
||||
var offsetY = element.offsetTop + element.clientTop;
|
||||
var offsetX = element.offsetLeft + element.clientLeft;
|
||||
if (!parent) {
|
||||
console.error('offsetParent is not set -- cannot scroll');
|
||||
return;
|
||||
}
|
||||
while (parent.clientHeight === parent.scrollHeight) {
|
||||
if (parent.dataset._scaleY) {
|
||||
offsetY /= parent.dataset._scaleY;
|
||||
offsetX /= parent.dataset._scaleX;
|
||||
}
|
||||
offsetY += parent.offsetTop;
|
||||
offsetX += parent.offsetLeft;
|
||||
parent = parent.offsetParent;
|
||||
if (!parent) {
|
||||
return; // no need to scroll
|
||||
}
|
||||
}
|
||||
if (spot) {
|
||||
if (spot.top !== undefined) {
|
||||
offsetY += spot.top;
|
||||
}
|
||||
if (spot.left !== undefined) {
|
||||
offsetX += spot.left;
|
||||
parent.scrollLeft = offsetX;
|
||||
}
|
||||
}
|
||||
parent.scrollTop = offsetY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to start monitoring the scroll event and converting them into
|
||||
* PDF.js friendly one: with scroll debounce and scroll direction.
|
||||
*/
|
||||
function watchScroll(viewAreaElement, callback) {
|
||||
var debounceScroll = function debounceScroll(evt) {
|
||||
if (rAF) {
|
||||
return;
|
||||
}
|
||||
// schedule an invocation of scroll for next animation frame.
|
||||
rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
|
||||
rAF = null;
|
||||
|
||||
var currentY = viewAreaElement.scrollTop;
|
||||
var lastY = state.lastY;
|
||||
if (currentY !== lastY) {
|
||||
state.down = currentY > lastY;
|
||||
}
|
||||
state.lastY = currentY;
|
||||
callback(state);
|
||||
});
|
||||
};
|
||||
|
||||
var state = {
|
||||
down: true,
|
||||
lastY: viewAreaElement.scrollTop,
|
||||
_eventHandler: debounceScroll
|
||||
};
|
||||
|
||||
var rAF = null;
|
||||
viewAreaElement.addEventListener('scroll', debounceScroll, true);
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use binary search to find the index of the first item in a given array which
|
||||
* passes a given condition. The items are expected to be sorted in the sense
|
||||
* that if the condition is true for one item in the array, then it is also true
|
||||
* for all following items.
|
||||
*
|
||||
* @returns {Number} Index of the first array element to pass the test,
|
||||
* or |items.length| if no such element exists.
|
||||
*/
|
||||
function binarySearchFirstItem(items, condition) {
|
||||
var minIndex = 0;
|
||||
var maxIndex = items.length - 1;
|
||||
|
||||
if (items.length === 0 || !condition(items[maxIndex])) {
|
||||
return items.length;
|
||||
}
|
||||
if (condition(items[minIndex])) {
|
||||
return minIndex;
|
||||
}
|
||||
|
||||
while (minIndex < maxIndex) {
|
||||
var currentIndex = (minIndex + maxIndex) >> 1;
|
||||
var currentItem = items[currentIndex];
|
||||
if (condition(currentItem)) {
|
||||
maxIndex = currentIndex;
|
||||
} else {
|
||||
minIndex = currentIndex + 1;
|
||||
}
|
||||
}
|
||||
return minIndex; /* === maxIndex */
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic helper to find out what elements are visible within a scroll pane.
|
||||
*/
|
||||
function getVisibleElements(scrollEl, views, sortByVisibility) {
|
||||
var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
|
||||
var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
|
||||
|
||||
function isElementBottomBelowViewTop(view) {
|
||||
var element = view.div;
|
||||
var elementBottom =
|
||||
element.offsetTop + element.clientTop + element.clientHeight;
|
||||
return elementBottom > top;
|
||||
}
|
||||
|
||||
var visible = [], view, element;
|
||||
var currentHeight, viewHeight, hiddenHeight, percentHeight;
|
||||
var currentWidth, viewWidth;
|
||||
var firstVisibleElementInd = (views.length === 0) ? 0 :
|
||||
binarySearchFirstItem(views, isElementBottomBelowViewTop);
|
||||
|
||||
for (var i = firstVisibleElementInd, ii = views.length; i < ii; i++) {
|
||||
view = views[i];
|
||||
element = view.div;
|
||||
currentHeight = element.offsetTop + element.clientTop;
|
||||
viewHeight = element.clientHeight;
|
||||
|
||||
if (currentHeight > bottom) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentWidth = element.offsetLeft + element.clientLeft;
|
||||
viewWidth = element.clientWidth;
|
||||
if (currentWidth + viewWidth < left || currentWidth > right) {
|
||||
continue;
|
||||
}
|
||||
hiddenHeight = Math.max(0, top - currentHeight) +
|
||||
Math.max(0, currentHeight + viewHeight - bottom);
|
||||
percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
|
||||
|
||||
visible.push({
|
||||
id: view.id,
|
||||
x: currentWidth,
|
||||
y: currentHeight,
|
||||
view: view,
|
||||
percent: percentHeight
|
||||
});
|
||||
}
|
||||
|
||||
var first = visible[0];
|
||||
var last = visible[visible.length - 1];
|
||||
|
||||
if (sortByVisibility) {
|
||||
visible.sort(function(a, b) {
|
||||
var pc = a.percent - b.percent;
|
||||
if (Math.abs(pc) > 0.001) {
|
||||
return -pc;
|
||||
}
|
||||
return a.id - b.id; // ensure stability
|
||||
});
|
||||
}
|
||||
return {first: first, last: last, views: visible};
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler to suppress context menu.
|
||||
*/
|
||||
function noContextMenuHandler(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename or guessed filename from the url (see issue 3455).
|
||||
* url {String} The original PDF location.
|
||||
* @return {String} Guessed PDF file name.
|
||||
*/
|
||||
function getPDFFileNameFromURL(url) {
|
||||
var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
|
||||
// SCHEME HOST 1.PATH 2.QUERY 3.REF
|
||||
// Pattern to get last matching NAME.pdf
|
||||
var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
|
||||
var splitURI = reURI.exec(url);
|
||||
var suggestedFilename = reFilename.exec(splitURI[1]) ||
|
||||
reFilename.exec(splitURI[2]) ||
|
||||
reFilename.exec(splitURI[3]);
|
||||
if (suggestedFilename) {
|
||||
suggestedFilename = suggestedFilename[0];
|
||||
if (suggestedFilename.indexOf('%') !== -1) {
|
||||
// URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
|
||||
try {
|
||||
suggestedFilename =
|
||||
reFilename.exec(decodeURIComponent(suggestedFilename))[0];
|
||||
} catch(e) { // Possible (extremely rare) errors:
|
||||
// URIError "Malformed URI", e.g. for "%AA.pdf"
|
||||
// TypeError "null has no properties", e.g. for "%2F.pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
return suggestedFilename || 'document.pdf';
|
||||
}
|
||||
|
||||
var ProgressBar = (function ProgressBarClosure() {
|
||||
|
||||
function clamp(v, min, max) {
|
||||
return Math.min(Math.max(v, min), max);
|
||||
}
|
||||
|
||||
function ProgressBar(id, opts) {
|
||||
this.visible = true;
|
||||
|
||||
// Fetch the sub-elements for later.
|
||||
this.div = document.querySelector(id + ' .progress');
|
||||
|
||||
// Get the loading bar element, so it can be resized to fit the viewer.
|
||||
this.bar = this.div.parentNode;
|
||||
|
||||
// Get options, with sensible defaults.
|
||||
this.height = opts.height || 100;
|
||||
this.width = opts.width || 100;
|
||||
this.units = opts.units || '%';
|
||||
|
||||
// Initialize heights.
|
||||
this.div.style.height = this.height + this.units;
|
||||
this.percent = 0;
|
||||
}
|
||||
|
||||
ProgressBar.prototype = {
|
||||
|
||||
updateBar: function ProgressBar_updateBar() {
|
||||
if (this._indeterminate) {
|
||||
this.div.classList.add('indeterminate');
|
||||
this.div.style.width = this.width + this.units;
|
||||
return;
|
||||
}
|
||||
|
||||
this.div.classList.remove('indeterminate');
|
||||
var progressSize = this.width * this._percent / 100;
|
||||
this.div.style.width = progressSize + this.units;
|
||||
},
|
||||
|
||||
get percent() {
|
||||
return this._percent;
|
||||
},
|
||||
|
||||
set percent(val) {
|
||||
this._indeterminate = isNaN(val);
|
||||
this._percent = clamp(val, 0, 100);
|
||||
this.updateBar();
|
||||
},
|
||||
|
||||
setWidth: function ProgressBar_setWidth(viewer) {
|
||||
if (viewer) {
|
||||
var container = viewer.parentNode;
|
||||
var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
|
||||
if (scrollbarWidth > 0) {
|
||||
this.bar.setAttribute('style', 'width: calc(100% - ' +
|
||||
scrollbarWidth + 'px);');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hide: function ProgressBar_hide() {
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
this.visible = false;
|
||||
this.bar.classList.add('hidden');
|
||||
document.body.classList.remove('loadingInProgress');
|
||||
},
|
||||
|
||||
show: function ProgressBar_show() {
|
||||
if (this.visible) {
|
||||
return;
|
||||
}
|
||||
this.visible = true;
|
||||
document.body.classList.add('loadingInProgress');
|
||||
this.bar.classList.remove('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
return ProgressBar;
|
||||
})();
|
936
attachment_preview/static/lib/ViewerJS/webodf.js
Normal file
134
attachment_preview/static/src/js/attachmentPreviewWidget.esm.js
Normal file
@ -0,0 +1,134 @@
|
||||
/** @odoo-module */
|
||||
import Widget from "web.Widget";
|
||||
|
||||
var active_attachment_index = 0;
|
||||
var is_first_click = true;
|
||||
|
||||
var AttachmentPreviewWidget = Widget.extend({
|
||||
template: "attachment_preview.AttachmentPreviewWidget",
|
||||
activeIndex: 0,
|
||||
|
||||
events: {
|
||||
"click .attachment_preview_close": "_onCloseClick",
|
||||
"click .attachment_preview_previous": "_onPreviousClick",
|
||||
"click .attachment_preview_next": "_onNextClick",
|
||||
"click .attachment_preview_popout": "_onPopoutClick",
|
||||
},
|
||||
|
||||
start: function () {
|
||||
// First_click = true;
|
||||
var res = this._super.apply(this, arguments);
|
||||
this.$overlay = $(".attachment_preview_overlay");
|
||||
this.$iframe = $(".attachment_preview_iframe");
|
||||
this.$current = $(".attachment_preview_current");
|
||||
return res;
|
||||
},
|
||||
|
||||
_onCloseClick: function () {
|
||||
this.hide();
|
||||
},
|
||||
|
||||
_onPreviousClick: function () {
|
||||
this.previous();
|
||||
},
|
||||
|
||||
_onNextClick: function () {
|
||||
this.next();
|
||||
},
|
||||
|
||||
_onPopoutClick: function () {
|
||||
if (!this.attachments[this.activeIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.open(this.attachments[this.activeIndex].previewUrl);
|
||||
},
|
||||
|
||||
next: function () {
|
||||
if (is_first_click) {
|
||||
is_first_click = !is_first_click;
|
||||
}
|
||||
var index = this.activeIndex + 1;
|
||||
if (index >= this.attachments.length) {
|
||||
index = 0;
|
||||
}
|
||||
this.activeIndex = index;
|
||||
this.updatePaginator();
|
||||
this.loadPreview();
|
||||
},
|
||||
|
||||
previous: function () {
|
||||
if (is_first_click) {
|
||||
is_first_click = !is_first_click;
|
||||
}
|
||||
var index = this.activeIndex - 1;
|
||||
if (index < 0) {
|
||||
index = this.attachments.length - 1;
|
||||
}
|
||||
this.activeIndex = index;
|
||||
this.updatePaginator();
|
||||
this.loadPreview();
|
||||
},
|
||||
|
||||
show: function () {
|
||||
this.$el.removeClass("d-none");
|
||||
this.trigger("shown");
|
||||
},
|
||||
|
||||
hide: function () {
|
||||
is_first_click = true;
|
||||
this.$el.addClass("d-none");
|
||||
this.trigger("hidden");
|
||||
},
|
||||
|
||||
updatePaginator: function () {
|
||||
var value = _.str.sprintf(
|
||||
"%s / %s",
|
||||
this.activeIndex + 1,
|
||||
this.attachments.length
|
||||
);
|
||||
this.$overlay = $(".attachment_preview_overlay");
|
||||
this.$iframe = $(".attachment_preview_iframe");
|
||||
this.$current = $(".attachment_preview_current");
|
||||
this.$current.html(value);
|
||||
},
|
||||
|
||||
loadPreview: function () {
|
||||
if (this.attachments.length === 0) {
|
||||
this.$iframe.attr("src", "about:blank");
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_first_click) {
|
||||
for (let i = 0; i < this.attachments.length; i++) {
|
||||
if (
|
||||
parseInt(this.attachments[i].id, 10) === this.active_attachment_id
|
||||
) {
|
||||
active_attachment_index = i;
|
||||
is_first_click = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
active_attachment_index = this.activeIndex;
|
||||
}
|
||||
|
||||
var att = this.attachments[active_attachment_index];
|
||||
this.$iframe.attr("src", att.previewUrl);
|
||||
},
|
||||
|
||||
setAttachments: function (attachments, active_attachment_id, first_click) {
|
||||
is_first_click = first_click;
|
||||
|
||||
if (active_attachment_id) {
|
||||
this.active_attachment_id = active_attachment_id;
|
||||
}
|
||||
if (attachments) {
|
||||
this.attachments = attachments;
|
||||
this.activeIndex = 0;
|
||||
this.updatePaginator();
|
||||
this.loadPreview();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default AttachmentPreviewWidget;
|
@ -0,0 +1,277 @@
|
||||
/** @odoo-module **/
|
||||
import AttachmentPreviewWidget from "../../attachmentPreviewWidget";
|
||||
import {Chatter} from "@mail/components/chatter/chatter";
|
||||
import {patch} from "web.utils";
|
||||
|
||||
odoo.define("attachment_preview.chatter", function (require) {
|
||||
const components = {Chatter};
|
||||
var rpc = require("web.rpc");
|
||||
var basic_fields = require("web.basic_fields");
|
||||
var core = require("web.core");
|
||||
var _t = core._t;
|
||||
|
||||
function getUrl(
|
||||
attachment_id,
|
||||
attachment_url,
|
||||
attachment_extension,
|
||||
attachment_title
|
||||
) {
|
||||
var url = "";
|
||||
if (attachment_url) {
|
||||
if (attachment_url.slice(0, 21) === "/web/static/lib/pdfjs") {
|
||||
url = (window.location.origin || "") + attachment_url;
|
||||
} else {
|
||||
url =
|
||||
(window.location.origin || "") +
|
||||
"/attachment_preview/static/lib/ViewerJS/index.html" +
|
||||
"?type=" +
|
||||
encodeURIComponent(attachment_extension) +
|
||||
"&zoom=automatic" +
|
||||
"&title=" +
|
||||
encodeURIComponent(attachment_title) +
|
||||
"#" +
|
||||
attachment_url.replace(window.location.origin, "");
|
||||
}
|
||||
} else {
|
||||
url =
|
||||
(window.location.origin || "") +
|
||||
"/attachment_preview/static/lib/ViewerJS/index.html" +
|
||||
"?type=" +
|
||||
encodeURIComponent(attachment_extension) +
|
||||
"&title=" +
|
||||
encodeURIComponent(attachment_title) +
|
||||
"&zoom=automatic" +
|
||||
"#" +
|
||||
"/web/content/" +
|
||||
attachment_id +
|
||||
"?model%3Dir.attachment";
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
function canPreview(extension) {
|
||||
return (
|
||||
$.inArray(extension, [
|
||||
"odt",
|
||||
"odp",
|
||||
"ods",
|
||||
"fodt",
|
||||
"pdf",
|
||||
"ott",
|
||||
"fodp",
|
||||
"otp",
|
||||
"fods",
|
||||
"ots",
|
||||
]) > -1
|
||||
);
|
||||
}
|
||||
|
||||
patch(
|
||||
components.Chatter.prototype,
|
||||
"attachment_preview/static/src/js/components/chatter/chatter.js",
|
||||
{
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
constructor(...args) {
|
||||
this._super(...args);
|
||||
|
||||
this._showPreview = this._showPreview.bind(this);
|
||||
this.canPreview = this.canPreview.bind(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_update() {
|
||||
// Var res = this._super.apply(this, arguments);
|
||||
var self = this;
|
||||
self._getPreviewableAttachments().then(function (atts) {
|
||||
self.previewableAttachments = atts;
|
||||
self._updatePreviewButtons(self.previewableAttachments);
|
||||
if (!self.attachmentPreviewWidget) {
|
||||
self.attachmentPreviewWidget = new AttachmentPreviewWidget(
|
||||
self
|
||||
);
|
||||
self.attachmentPreviewWidget.setAttachments(atts);
|
||||
}
|
||||
self.previewableAttachments = atts;
|
||||
// ChatterpreviewableAttachments = atts;
|
||||
self.attachmentPreviewWidget.setAttachments(atts);
|
||||
});
|
||||
},
|
||||
|
||||
_getPreviewableAttachments: function () {
|
||||
var self = this;
|
||||
var deferred = $.Deferred();
|
||||
const chatter = self.messaging.models["mail.chatter"].get(
|
||||
self.props.chatterLocalId
|
||||
);
|
||||
const thread = chatter ? chatter.thread : undefined;
|
||||
|
||||
var attachments = {};
|
||||
if (thread) {
|
||||
attachments = thread.allAttachments;
|
||||
}
|
||||
|
||||
attachments = _.object(
|
||||
attachments.map((attachment) => {
|
||||
return attachment.id;
|
||||
}),
|
||||
attachments.map((attachment) => {
|
||||
if (attachment.defaultSource) {
|
||||
return {
|
||||
url: attachment.defaultSource,
|
||||
extension: attachment.extension,
|
||||
title: attachment.name,
|
||||
};
|
||||
}
|
||||
return {
|
||||
url: "/web/content?id=" + attachment.id + "&download=true",
|
||||
extension: attachment.extension,
|
||||
title: attachment.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
rpc.query({
|
||||
model: "ir.attachment",
|
||||
method: "get_attachment_extension",
|
||||
args: [
|
||||
_.map(_.keys(attachments), function (id) {
|
||||
return parseInt(id, 10);
|
||||
}),
|
||||
],
|
||||
}).then(
|
||||
function (extensions) {
|
||||
var reviewableAttachments = _.map(
|
||||
_.keys(
|
||||
_.pick(extensions, function (extension) {
|
||||
return canPreview(extension);
|
||||
})
|
||||
),
|
||||
function (id) {
|
||||
return {
|
||||
id: id,
|
||||
url: attachments[id].url,
|
||||
extension: extensions[id],
|
||||
title: attachments[id].title,
|
||||
previewUrl: getUrl(
|
||||
id,
|
||||
attachments[id].url,
|
||||
extensions[id],
|
||||
attachments[id].title
|
||||
),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
deferred.resolve(reviewableAttachments);
|
||||
},
|
||||
|
||||
function () {
|
||||
deferred.reject();
|
||||
}
|
||||
);
|
||||
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
_updatePreviewButtons: function (previewableAttachments) {
|
||||
$(this)
|
||||
.find(".o_attachment_preview")
|
||||
.each(function () {
|
||||
var $this = $(this);
|
||||
var att = _.findWhere(previewableAttachments, {
|
||||
id: $this.attr("data-id"),
|
||||
});
|
||||
if (att) {
|
||||
$this.attr("data-extension", att.extension);
|
||||
} else {
|
||||
$this.remove();
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
basic_fields.FieldBinaryFile.include(components.Chatter);
|
||||
basic_fields.FieldBinaryFile.include({
|
||||
showPreview(
|
||||
attachment_id,
|
||||
attachment_url,
|
||||
attachment_extension,
|
||||
attachment_title,
|
||||
split_screen
|
||||
) {
|
||||
if (!canPreview(attachment_extension)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var url = getUrl(
|
||||
attachment_id,
|
||||
attachment_url,
|
||||
attachment_extension,
|
||||
attachment_title
|
||||
);
|
||||
|
||||
if (split_screen) {
|
||||
this.trigger("onAttachmentPreview", {url: url});
|
||||
} else {
|
||||
window.open(url);
|
||||
}
|
||||
},
|
||||
|
||||
_renderReadonly: function () {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
if (this.recordData.id) {
|
||||
this._getBinaryExtension().then(function (extension) {
|
||||
if (canPreview(extension)) {
|
||||
// Self._renderPreviewButton(extension, recordData);
|
||||
self._renderPreviewButton(extension);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_renderPreviewButton: function (extension) {
|
||||
this.$previewBtn = $("<a/>");
|
||||
this.$previewBtn.addClass("fa fa-external-link mr-2");
|
||||
this.$previewBtn.attr("href");
|
||||
this.$previewBtn.attr(
|
||||
"title",
|
||||
_.str.sprintf(_t("Preview %s"), this.field.string)
|
||||
);
|
||||
this.$previewBtn.attr("data-extension", extension);
|
||||
this.$el.find(".fa-download").after(this.$previewBtn);
|
||||
this.$previewBtn.on("click", this._onPreview.bind(this));
|
||||
},
|
||||
|
||||
_getBinaryExtension: function () {
|
||||
return rpc.query({
|
||||
model: "ir.attachment",
|
||||
method: "get_binary_extension",
|
||||
args: [this.model, this.recordData.id, this.name, this.attrs.filename],
|
||||
});
|
||||
},
|
||||
|
||||
_onPreview: function (event) {
|
||||
this.showPreview(
|
||||
null,
|
||||
_.str.sprintf(
|
||||
"/web/content?model=%s&field=%s&id=%d",
|
||||
this.model,
|
||||
this.name,
|
||||
this.recordData.id
|
||||
),
|
||||
$(event.currentTarget).attr("data-extension"),
|
||||
_.str.sprintf(_t("Preview %s"), this.field.string),
|
||||
false
|
||||
);
|
||||
event.stopPropagation();
|
||||
},
|
||||
});
|
||||
});
|
@ -0,0 +1,265 @@
|
||||
/** @odoo-module **/
|
||||
import AttachmentPreviewWidget from "../../attachmentPreviewWidget.esm";
|
||||
import FormRenderer from "web.FormRenderer";
|
||||
import {registerInstancePatchModel} from "@mail/model/model_core";
|
||||
|
||||
odoo.define("attachment_preview.attachment_card", function (require) {
|
||||
var rpc = require("web.rpc");
|
||||
|
||||
var chatterpreviewableAttachments = [];
|
||||
var active_attachment_id = 0;
|
||||
var first_click = true;
|
||||
|
||||
FormRenderer.include({
|
||||
custom_events: _.extend({}, FormRenderer.prototype.custom_events, {
|
||||
onAttachmentPreview: "_onAttachmentPreview",
|
||||
}),
|
||||
attachmentPreviewWidget: null,
|
||||
|
||||
init: function () {
|
||||
var res = this._super(...arguments);
|
||||
this.attachmentPreviewWidget = new AttachmentPreviewWidget(this);
|
||||
this.attachmentPreviewWidget.on(
|
||||
"hidden",
|
||||
this,
|
||||
this._attachmentPreviewWidgetHidden
|
||||
);
|
||||
return res;
|
||||
},
|
||||
|
||||
start: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
self.attachmentPreviewWidget.insertAfter(self.$el);
|
||||
});
|
||||
},
|
||||
|
||||
_attachmentPreviewWidgetHidden: function () {
|
||||
this.$el.removeClass("attachment_preview");
|
||||
},
|
||||
|
||||
showAttachmentPreviewWidget: function (first_c) {
|
||||
this.$el.addClass("attachment_preview");
|
||||
|
||||
this.attachmentPreviewWidget.setAttachments(
|
||||
chatterpreviewableAttachments,
|
||||
active_attachment_id,
|
||||
first_c
|
||||
);
|
||||
this.attachmentPreviewWidget.show();
|
||||
},
|
||||
|
||||
on_detach_callback: function () {
|
||||
this.attachmentPreviewWidget.hide();
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
_onAttachmentPreview: function () {
|
||||
first_click = true;
|
||||
this.showAttachmentPreviewWidget(first_click);
|
||||
},
|
||||
});
|
||||
|
||||
function canPreview(extension) {
|
||||
return (
|
||||
$.inArray(extension, [
|
||||
"odt",
|
||||
"odp",
|
||||
"ods",
|
||||
"fodt",
|
||||
"pdf",
|
||||
"ott",
|
||||
"fodp",
|
||||
"otp",
|
||||
"fods",
|
||||
"ots",
|
||||
]) > -1
|
||||
);
|
||||
}
|
||||
|
||||
function getUrl(
|
||||
attachment_id,
|
||||
attachment_url,
|
||||
attachment_extension,
|
||||
attachment_title
|
||||
) {
|
||||
var url = "";
|
||||
if (attachment_url) {
|
||||
if (attachment_url.slice(0, 21) === "/web/static/lib/pdfjs") {
|
||||
url = (window.location.origin || "") + attachment_url;
|
||||
} else {
|
||||
url =
|
||||
(window.location.origin || "") +
|
||||
"/attachment_preview/static/lib/ViewerJS/index.html" +
|
||||
"?type=" +
|
||||
encodeURIComponent(attachment_extension) +
|
||||
"&title=" +
|
||||
encodeURIComponent(attachment_title) +
|
||||
"&zoom=automatic" +
|
||||
"#" +
|
||||
attachment_url.replace(window.location.origin, "");
|
||||
}
|
||||
return url;
|
||||
}
|
||||
url =
|
||||
(window.location.origin || "") +
|
||||
"/attachment_preview/static/lib/ViewerJS/index.html" +
|
||||
"?type=" +
|
||||
encodeURIComponent(attachment_extension) +
|
||||
"&title=" +
|
||||
encodeURIComponent(attachment_title) +
|
||||
"&zoom=automatic" +
|
||||
"#" +
|
||||
"/web/content/" +
|
||||
attachment_id +
|
||||
"?model%3Dir.attachment";
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
registerInstancePatchModel(
|
||||
"mail.attachment_card",
|
||||
"attachment_preview/static/src/js/models/attachment_card/attachment_card.js",
|
||||
{
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_created() {
|
||||
this._super();
|
||||
this._onPreviewAttachment = this._onPreviewAttachment.bind(this);
|
||||
|
||||
var attachments = _.object(
|
||||
this.attachmentList.attachments.map((attachment) => {
|
||||
console.log("attachment", attachment);
|
||||
return attachment.id;
|
||||
}),
|
||||
this.attachmentList.attachments.map((attachment) => {
|
||||
if (
|
||||
attachment.defaultSource &&
|
||||
attachment.defaultSource.length > 38
|
||||
) {
|
||||
return {
|
||||
url: attachment.defaultSource,
|
||||
extension: attachment.extension,
|
||||
title: attachment.name,
|
||||
};
|
||||
}
|
||||
return {
|
||||
url: "/web/content?id=" + attachment.id + "&download=true",
|
||||
extension: attachment.extension,
|
||||
title: attachment.name,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
rpc.query({
|
||||
model: "ir.attachment",
|
||||
method: "get_attachment_extension",
|
||||
args: [
|
||||
_.map(_.keys(attachments), function (id) {
|
||||
return parseInt(id, 10);
|
||||
}),
|
||||
],
|
||||
}).then(function (extensions) {
|
||||
var reviewableAttachments = _.map(
|
||||
_.keys(
|
||||
_.pick(extensions, function (extension) {
|
||||
return canPreview(extension);
|
||||
})
|
||||
),
|
||||
function (id) {
|
||||
return {
|
||||
id: id,
|
||||
url: attachments[id].url,
|
||||
extension: extensions[id],
|
||||
title: attachments[id].title,
|
||||
previewUrl: getUrl(
|
||||
id,
|
||||
attachments[id].url,
|
||||
extensions[id],
|
||||
attachments[id].title
|
||||
),
|
||||
};
|
||||
}
|
||||
);
|
||||
chatterpreviewableAttachments = reviewableAttachments;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {attachment_id} attachment_id of the attachment
|
||||
* @param {attachment_url} attachment_url of the attachment
|
||||
* @param {attachment_extension} attachment_extension of the attachment
|
||||
* @param {attachment_title} attachment_title of the attachment
|
||||
* @param {split_screen} split_screen of the attachment
|
||||
*/
|
||||
_showPreview(
|
||||
attachment_id,
|
||||
attachment_url,
|
||||
attachment_extension,
|
||||
attachment_title,
|
||||
split_screen
|
||||
) {
|
||||
// Let active_attURL = "";
|
||||
// this.attachmentList.attachments.forEach((att) => {
|
||||
// if (
|
||||
// parseInt(att.localId.slice(20).slice(0, -1)) === attachment_id
|
||||
// ) {
|
||||
// if (att.__values.url === undefined) {
|
||||
// att.__values.url = attachment_url.slice(
|
||||
// window.location.origin.length
|
||||
// );
|
||||
// active_attURL = att.__values.url;
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
var url = getUrl(
|
||||
attachment_id,
|
||||
attachment_url,
|
||||
attachment_extension,
|
||||
attachment_title
|
||||
);
|
||||
|
||||
if (split_screen) {
|
||||
this.component.trigger("onAttachmentPreview", {
|
||||
url: url,
|
||||
active_attachment_id: active_attachment_id,
|
||||
});
|
||||
} else {
|
||||
window.open(url);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {event} event
|
||||
*/
|
||||
_onPreviewAttachment(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var self = this,
|
||||
$target = $(event.currentTarget),
|
||||
split_screen = $target.attr("data-target") !== "new",
|
||||
attachment_id = this.attachment.id,
|
||||
attachment_title = this.attachment.filename,
|
||||
attachment_url = this.attachment.defaultSource;
|
||||
active_attachment_id = attachment_id;
|
||||
|
||||
rpc.query({
|
||||
model: "ir.attachment",
|
||||
method: "get_attachment_extension",
|
||||
args: [attachment_id],
|
||||
}).then(function (extension) {
|
||||
self._showPreview(
|
||||
attachment_id,
|
||||
attachment_url,
|
||||
extension,
|
||||
attachment_title,
|
||||
split_screen
|
||||
);
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
15
attachment_preview/static/src/js/viewerjs_tweaks.js
Normal file
@ -0,0 +1,15 @@
|
||||
/* Copyright 2014 Therp BV (<http://therp.nl>)
|
||||
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
|
||||
// This file contains tweaks for viewerjs itself and is not meant to be run in
|
||||
// OpenERP's context
|
||||
(function (original_Viewer) {
|
||||
"use strict";
|
||||
window.Viewer = function (plugin, parameters) {
|
||||
if (!plugin) {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert("Unsupported file type");
|
||||
}
|
||||
return original_Viewer(plugin, parameters);
|
||||
};
|
||||
})(window.Viewer);
|
64
attachment_preview/static/src/scss/attachment_preview.scss
Normal file
@ -0,0 +1,64 @@
|
||||
.o_attachments_list .o_attachment_wrap {
|
||||
.o_attachment_preview {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
|
||||
&.o_attachment_preview_new_tab {
|
||||
right: 43px;
|
||||
}
|
||||
}
|
||||
|
||||
.open .dropdown-menu > li > a {
|
||||
padding-right: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_uri .fa-search {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
/* Preview widget */
|
||||
|
||||
.o_form_view.attachment_preview {
|
||||
width: 60%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.o_form_view + .attachment_preview_widget {
|
||||
width: 40%;
|
||||
height: 100%;
|
||||
float: right;
|
||||
position: relative;
|
||||
z-index: 999;
|
||||
|
||||
> .attachment_preview_iframe {
|
||||
width: 100%;
|
||||
height: calc(100% - 30px);
|
||||
float: left;
|
||||
}
|
||||
|
||||
.attachment_preview_buttons {
|
||||
padding: 4px;
|
||||
float: left;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-left: 1px solid $gray-800;
|
||||
|
||||
button {
|
||||
min-width: 25px;
|
||||
min-height: 25px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_group {
|
||||
.mr-2,
|
||||
.mx-2 {
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
}
|
71
attachment_preview/static/src/xml/attachment_preview.xml
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates>
|
||||
<t
|
||||
t-name="Attachment"
|
||||
t-inherit="mail.AttachmentCard"
|
||||
t-inherit-mode="extension"
|
||||
owl="1"
|
||||
>
|
||||
<xpath expr="//div[hasclass('o_AttachmentCard_aside')]" position="before">
|
||||
<div
|
||||
class="o_AttachmentCard_aside position-relative overflow-hidden"
|
||||
t-att-class="{ 'o-has-multiple-action d-flex flex-column': !attachmentCard.attachmentList.composerView and attachmentCard.attachment.isEditable }"
|
||||
>
|
||||
<div
|
||||
t-if="attachmentCard.attachment.downloadUrl"
|
||||
class="o_AttachmentCard_asideItem d-flex justify-content-center align-items-center ml4"
|
||||
t-att-data-id="attachmentCard.attachment.id"
|
||||
t-att-data-url="attachmentCard.attachment.defaultSource"
|
||||
t-on-click="attachmentCard._onPreviewAttachment"
|
||||
t-att-data-original-title="attachmentCard.attachment.name"
|
||||
t-attf-title="Preview {{attachmentCard.attachment.displayName}} in side panel"
|
||||
tabindex="0"
|
||||
role="menuitem"
|
||||
aria-label="Preview"
|
||||
>
|
||||
<i class="fa fa-search" />
|
||||
</div>
|
||||
<div
|
||||
t-if="attachmentCard.attachment.downloadUrl"
|
||||
class="o_AttachmentCard_asideItem d-flex justify-content-center align-items-center ml4 o_attachment_preview_new_tab"
|
||||
data-target="new"
|
||||
t-att-data-id="attachmentCard.attachment.id"
|
||||
t-att-data-url="attachmentCard.attachment.defaultSource"
|
||||
t-on-click="attachmentCard._onPreviewAttachment"
|
||||
t-att-data-original-title="attachmentCard.attachment.name"
|
||||
t-attf-title="Open preview {{attachmentCard.attachment.name}} in a new tab"
|
||||
tabindex="0"
|
||||
role="menuitem"
|
||||
aria-label="Open in new page"
|
||||
>
|
||||
<i class="fa fa-external-link" />
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="attachment_preview.AttachmentPreviewWidget">
|
||||
<div class="attachment_preview_widget d-none">
|
||||
<div class="attachment_preview_buttons">
|
||||
<div class="button-group pull-left">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary attachment_preview_previous"
|
||||
><i class="fa fa-chevron-left" /></button>
|
||||
<button
|
||||
class="btn btn-sm btn-secondary disabled attachment_preview_current"
|
||||
>1 / 5</button>
|
||||
<button class="btn btn-sm btn-secondary attachment_preview_next"><i
|
||||
class="fa fa-chevron-right"
|
||||
/></button>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-sm btn-secondary pull-left ml8 attachment_preview_popout"
|
||||
><i class="fa fa-external-link" /></button>
|
||||
<button
|
||||
class="btn btn-sm btn-secondary pull-right attachment_preview_close"
|
||||
><i class="fa fa-times" /></button>
|
||||
</div>
|
||||
<iframe class="attachment_preview_iframe" />
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
4
attachment_preview/tests/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# Copyright 2018 Onestein
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import test_attachment_preview
|
43
attachment_preview/tests/test_attachment_preview.py
Normal file
@ -0,0 +1,43 @@
|
||||
# Copyright 2018 Onestein
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import base64
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestAttachmentPreview(TransactionCase):
|
||||
def test_get_extension(self):
|
||||
attachment = self.env["ir.attachment"].create(
|
||||
{
|
||||
"datas": base64.b64encode(b"from this, to that."),
|
||||
"name": "doc.txt",
|
||||
}
|
||||
)
|
||||
attachment2 = self.env["ir.attachment"].create(
|
||||
{
|
||||
"datas": base64.b64encode(b"Png"),
|
||||
"name": "image.png",
|
||||
}
|
||||
)
|
||||
res = self.env["ir.attachment"].get_attachment_extension(attachment.id)
|
||||
self.assertEqual(res, "txt")
|
||||
|
||||
res = self.env["ir.attachment"].get_attachment_extension(
|
||||
[attachment.id, attachment2.id]
|
||||
)
|
||||
self.assertEqual(res[attachment.id], "txt")
|
||||
self.assertEqual(res[attachment2.id], "png")
|
||||
|
||||
res2 = self.env["ir.attachment"].get_binary_extension(
|
||||
"ir.attachment", attachment.id, "datas"
|
||||
)
|
||||
self.assertTrue(res2)
|
||||
|
||||
module = (
|
||||
self.env["ir.module.module"].search([]).filtered(lambda m: m.icon_image)[0]
|
||||
)
|
||||
res3 = self.env["ir.attachment"].get_binary_extension(
|
||||
"ir.module.module", module.id, "icon_image"
|
||||
)
|
||||
self.assertTrue(res3)
|
1
setup/attachment_preview/odoo/addons/attachment_preview
Symbolic link
@ -0,0 +1 @@
|
||||
../../../../attachment_preview
|
6
setup/attachment_preview/setup.py
Normal file
@ -0,0 +1,6 @@
|
||||
import setuptools
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['setuptools-odoo'],
|
||||
odoo_addon=True,
|
||||
)
|