This commit is contained in:
anusriNPS 2025-06-21 17:28:43 +02:00 committed by GitHub
commit 039a3af17d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 52136 additions and 0 deletions

View 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.

View 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

View 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,
}

View 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 ""

View 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 ""

View 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 ""

View 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 ""

View 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 ""

View 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 ""

View 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 ""

View 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"

View 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 ""

View 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 ""

View 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 ""

View 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

View 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")

View File

@ -0,0 +1,2 @@
* Holger Brunn <mail@hunki-enterprises.com>
* Dennis Sluijk <d.sluijk@onestein.nl>

View File

@ -0,0 +1,4 @@
Addon icon
----------
* courtesy of http://commons.wikimedia.org/wiki/Crystal_Clear

View 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%

View File

@ -0,0 +1,4 @@
For filetype recognition, you'll get the best results by installing
``python-magic``:
sudo apt-get install python-magic

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View 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&amp;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, thats 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, youll 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 its 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 &lt;<a class="reference external" href="mailto:mail&#64;hunki-enterprises.com">mail&#64;hunki-enterprises.com</a>&gt;</li>
<li>Dennis Sluijk &lt;<a class="reference external" href="mailto:d.sluijk&#64;onestein.nl">d.sluijk&#64;onestein.nl</a>&gt;</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

View 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;
}
})();

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
var /**@const{!string}*/pdfjs_version = "v1.1.114";

View 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
});
}
};

View 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;
})();

File diff suppressed because one or more lines are too long

View 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;

View File

@ -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();
},
});
});

View File

@ -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
);
});
},
}
);
});

View 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);

View 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;
}
}

View 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>

View 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

View 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)

View File

@ -0,0 +1 @@
../../../../attachment_preview

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)