Merge pull request #16 from hbrunn/7.0-attachment_preview
[ADD] attachment_preview
21
attachment_preview/__init__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
from . import model
|
63
attachment_preview/__openerp__.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
{
|
||||||
|
"name": "Preview attachments",
|
||||||
|
"version": "1.0",
|
||||||
|
"author": "Therp BV",
|
||||||
|
"license": "AGPL-3",
|
||||||
|
"complexity": "normal",
|
||||||
|
"description": """
|
||||||
|
This addon allows to preview attachments supported by http://viewerjs.org.
|
||||||
|
|
||||||
|
Currently, that's most Libreoffice files and PDFs.
|
||||||
|
|
||||||
|
For filetype recognition, you'll get the best results by installing
|
||||||
|
``python-magic``.
|
||||||
|
|
||||||
|
Acknowledgements
|
||||||
|
================
|
||||||
|
|
||||||
|
Addon icon courtesy of http://commons.wikimedia.org/wiki/Crystal_Clear
|
||||||
|
""",
|
||||||
|
"category": "Knowledge Management",
|
||||||
|
"depends": [
|
||||||
|
'web'
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
],
|
||||||
|
"js": [
|
||||||
|
'static/src/js/attachment_preview.js',
|
||||||
|
],
|
||||||
|
"css": [
|
||||||
|
'static/src/css/attachment_preview.css',
|
||||||
|
],
|
||||||
|
"qweb": [
|
||||||
|
'static/src/xml/attachment_preview.xml',
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
],
|
||||||
|
"auto_install": False,
|
||||||
|
"installable": True,
|
||||||
|
"application": False,
|
||||||
|
"external_dependencies": {
|
||||||
|
'python': [],
|
||||||
|
},
|
||||||
|
}
|
21
attachment_preview/model/__init__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
from . import ir_attachment
|
68
attachment_preview/model/ir_attachment.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
import collections
|
||||||
|
import os.path
|
||||||
|
import mimetypes
|
||||||
|
import base64
|
||||||
|
from openerp.osv.orm import Model
|
||||||
|
|
||||||
|
|
||||||
|
class IrAttachment(Model):
|
||||||
|
_inherit = 'ir.attachment'
|
||||||
|
|
||||||
|
def get_binary_extension(
|
||||||
|
self, cr, uid, model, ids, binary_field, filename_field=None,
|
||||||
|
context=None):
|
||||||
|
result = {}
|
||||||
|
for this in self.pool[model].browse(
|
||||||
|
cr, uid,
|
||||||
|
ids if isinstance(ids, collections.Iterable) else [ids],
|
||||||
|
context=context):
|
||||||
|
if not this.id:
|
||||||
|
result[this.id] = False
|
||||||
|
continue
|
||||||
|
extension = ''
|
||||||
|
if filename_field and this[filename_field]:
|
||||||
|
filename, extension = os.path.splitext(this[filename_field])
|
||||||
|
if not this[binary_field]:
|
||||||
|
result[this.id] = False
|
||||||
|
continue
|
||||||
|
if not extension:
|
||||||
|
try:
|
||||||
|
import magic
|
||||||
|
ms = magic.open(
|
||||||
|
hasattr(magic, 'MAGIC_MIME_TYPE')
|
||||||
|
and magic.MAGIC_MIME_TYPE or magic.MAGIC_MIME)
|
||||||
|
ms.load()
|
||||||
|
mimetype = ms.buffer(
|
||||||
|
base64.b64decode(this[binary_field]))
|
||||||
|
except ImportError:
|
||||||
|
(mimetype, encoding) = mimetypes.guess_type(
|
||||||
|
'data:;base64,' + this[binary_field], strict=False)
|
||||||
|
extension = mimetypes.guess_extension(
|
||||||
|
mimetype.split(';')[0], strict=False)
|
||||||
|
|
||||||
|
result[this.id] = (extension or '').lstrip('.').lower()
|
||||||
|
return result if isinstance(ids, collections.Iterable) else result[ids]
|
||||||
|
|
||||||
|
def get_attachment_extension(self, cr, uid, ids, context=None):
|
||||||
|
return self.get_binary_extension(
|
||||||
|
cr, uid, self._name, ids, 'datas', 'datas_fname', context=context)
|
661
attachment_preview/static/lib/ViewerJS/AGPL-3.0.txt
Normal file
@ -0,0 +1,661 @@
|
|||||||
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works, specifically designed to ensure
|
||||||
|
cooperation with the community in the case of network server software.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
our General Public Licenses are intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
Developers that use our General Public Licenses protect your rights
|
||||||
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
|
you this License which gives you legal permission to copy, distribute
|
||||||
|
and/or modify the software.
|
||||||
|
|
||||||
|
A secondary benefit of defending all users' freedom is that
|
||||||
|
improvements made in alternate versions of the program, if they
|
||||||
|
receive widespread use, become available for other developers to
|
||||||
|
incorporate. Many developers of free software are heartened and
|
||||||
|
encouraged by the resulting cooperation. However, in the case of
|
||||||
|
software used on network servers, this result may fail to come about.
|
||||||
|
The GNU General Public License permits making a modified version and
|
||||||
|
letting the public access it on a server without ever releasing its
|
||||||
|
source code to the public.
|
||||||
|
|
||||||
|
The GNU Affero General Public License is designed specifically to
|
||||||
|
ensure that, in such cases, the modified source code becomes available
|
||||||
|
to the community. It requires the operator of a network server to
|
||||||
|
provide the source code of the modified version running there to the
|
||||||
|
users of that server. Therefore, public use of a modified version, on
|
||||||
|
a publicly accessible server, gives the public access to the source
|
||||||
|
code of the modified version.
|
||||||
|
|
||||||
|
An older license, called the Affero General Public License and
|
||||||
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
|
this license.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, if you modify the
|
||||||
|
Program, your modified version must prominently offer all users
|
||||||
|
interacting with it remotely through a computer network (if your version
|
||||||
|
supports such interaction) an opportunity to receive the Corresponding
|
||||||
|
Source of your version by providing access to the Corresponding Source
|
||||||
|
from a network server at no charge, through some standard or customary
|
||||||
|
means of facilitating copying of software. This Corresponding Source
|
||||||
|
shall include the Corresponding Source for any work covered by version 3
|
||||||
|
of the GNU General Public License that is incorporated pursuant to the
|
||||||
|
following paragraph.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the work with which it is combined will remain governed by version
|
||||||
|
3 of the GNU General Public License.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU Affero General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU Affero General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU Affero General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If your software can interact with users remotely through a computer
|
||||||
|
network, you should also make sure that it provides a way for users to
|
||||||
|
get its source. For example, if your program is a web application, its
|
||||||
|
interface could display a "Source" link that leads users to an archive
|
||||||
|
of the code. There are many ways you could offer source, and different
|
||||||
|
solutions will be better for different programs; see section 13 for the
|
||||||
|
specific requirements.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
29
attachment_preview/static/lib/ViewerJS/ODFViewerPlugin.css
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2013-2014 KO GmbH <copyright@kogmbh.com>
|
||||||
|
*
|
||||||
|
* @licstart
|
||||||
|
* This file is part of WebODF.
|
||||||
|
*
|
||||||
|
* WebODF is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License (GNU AGPL)
|
||||||
|
* as published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* WebODF is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* @licend
|
||||||
|
*
|
||||||
|
* @source: http://www.webodf.org/
|
||||||
|
* @source: https://github.com/kogmbh/WebODF/
|
||||||
|
*/
|
||||||
|
|
||||||
|
@namespace cursor url(urn:webodf:names:cursor);
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
219
attachment_preview/static/lib/ViewerJS/ODFViewerPlugin.js
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
||||||
|
*
|
||||||
|
* @licstart
|
||||||
|
* This file is part of WebODF.
|
||||||
|
*
|
||||||
|
* WebODF is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License (GNU AGPL)
|
||||||
|
* as published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* WebODF is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* @licend
|
||||||
|
*
|
||||||
|
* @source: http://www.webodf.org/
|
||||||
|
* @source: https://github.com/kogmbh/WebODF/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*global runtime, document, odf, gui, console, webodf*/
|
||||||
|
|
||||||
|
function ODFViewerPlugin() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function init(callback) {
|
||||||
|
var lib = document.createElement('script'),
|
||||||
|
pluginCSS;
|
||||||
|
|
||||||
|
lib.async = false;
|
||||||
|
lib.src = './webodf.js';
|
||||||
|
lib.type = 'text/javascript';
|
||||||
|
lib.onload = function () {
|
||||||
|
runtime.loadClass('gui.HyperlinkClickHandler');
|
||||||
|
runtime.loadClass('odf.OdfCanvas');
|
||||||
|
runtime.loadClass('ops.Session');
|
||||||
|
runtime.loadClass('gui.CaretManager');
|
||||||
|
runtime.loadClass("gui.HyperlinkTooltipView");
|
||||||
|
runtime.loadClass('gui.SessionController');
|
||||||
|
runtime.loadClass('gui.SvgSelectionView');
|
||||||
|
runtime.loadClass('gui.SelectionViewManager');
|
||||||
|
runtime.loadClass('gui.ShadowCursor');
|
||||||
|
runtime.loadClass('gui.SessionView');
|
||||||
|
|
||||||
|
callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(lib);
|
||||||
|
|
||||||
|
pluginCSS = document.createElement('link');
|
||||||
|
pluginCSS.setAttribute("rel", "stylesheet");
|
||||||
|
pluginCSS.setAttribute("type", "text/css");
|
||||||
|
pluginCSS.setAttribute("href", "./ODFViewerPlugin.css");
|
||||||
|
document.head.appendChild(pluginCSS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// that should probably be provided by webodf
|
||||||
|
function nsResolver(prefix) {
|
||||||
|
var ns = {
|
||||||
|
'draw' : "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0",
|
||||||
|
'presentation' : "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0",
|
||||||
|
'text' : "urn:oasis:names:tc:opendocument:xmlns:text:1.0",
|
||||||
|
'office' : "urn:oasis:names:tc:opendocument:xmlns:office:1.0"
|
||||||
|
};
|
||||||
|
return ns[prefix] || console.log('prefix [' + prefix + '] unknown.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
pluginName = "WebODF",
|
||||||
|
pluginURL = "http://webodf.org",
|
||||||
|
odfCanvas = null,
|
||||||
|
odfElement = null,
|
||||||
|
initialized = false,
|
||||||
|
root = null,
|
||||||
|
documentType = null,
|
||||||
|
pages = [],
|
||||||
|
currentPage = null;
|
||||||
|
|
||||||
|
this.initialize = function (viewerElement, documentUrl) {
|
||||||
|
// If the URL has a fragment (#...), try to load the file it represents
|
||||||
|
init(function () {
|
||||||
|
var session,
|
||||||
|
sessionController,
|
||||||
|
sessionView,
|
||||||
|
odtDocument,
|
||||||
|
shadowCursor,
|
||||||
|
selectionViewManager,
|
||||||
|
caretManager,
|
||||||
|
localMemberId = 'localuser',
|
||||||
|
hyperlinkTooltipView,
|
||||||
|
eventManager;
|
||||||
|
|
||||||
|
odfElement = document.getElementById('canvas');
|
||||||
|
odfCanvas = new odf.OdfCanvas(odfElement);
|
||||||
|
odfCanvas.load(documentUrl);
|
||||||
|
|
||||||
|
odfCanvas.addListener('statereadychange', function () {
|
||||||
|
root = odfCanvas.odfContainer().rootElement;
|
||||||
|
initialized = true;
|
||||||
|
documentType = odfCanvas.odfContainer().getDocumentType(root);
|
||||||
|
if (documentType === 'text') {
|
||||||
|
odfCanvas.enableAnnotations(true, false);
|
||||||
|
|
||||||
|
session = new ops.Session(odfCanvas);
|
||||||
|
odtDocument = session.getOdtDocument();
|
||||||
|
shadowCursor = new gui.ShadowCursor(odtDocument);
|
||||||
|
sessionController = new gui.SessionController(session, localMemberId, shadowCursor, {});
|
||||||
|
eventManager = sessionController.getEventManager();
|
||||||
|
caretManager = new gui.CaretManager(sessionController);
|
||||||
|
selectionViewManager = new gui.SelectionViewManager(gui.SvgSelectionView);
|
||||||
|
sessionView = new gui.SessionView({
|
||||||
|
caretAvatarsInitiallyVisible: false
|
||||||
|
}, localMemberId, session, sessionController.getSessionConstraints(), caretManager, selectionViewManager);
|
||||||
|
selectionViewManager.registerCursor(shadowCursor);
|
||||||
|
hyperlinkTooltipView = new gui.HyperlinkTooltipView(odfCanvas,
|
||||||
|
sessionController.getHyperlinkClickHandler().getModifier);
|
||||||
|
eventManager.subscribe("mousemove", hyperlinkTooltipView.showTooltip);
|
||||||
|
eventManager.subscribe("mouseout", hyperlinkTooltipView.hideTooltip);
|
||||||
|
|
||||||
|
var op = new ops.OpAddMember();
|
||||||
|
op.init({
|
||||||
|
memberid: localMemberId,
|
||||||
|
setProperties: {
|
||||||
|
fillName: runtime.tr("Unknown Author"),
|
||||||
|
color: "blue"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
session.enqueue([op]);
|
||||||
|
sessionController.insertLocalCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onLoad();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isSlideshow = function () {
|
||||||
|
return documentType === 'presentation';
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onLoad = function () {};
|
||||||
|
|
||||||
|
this.getWidth = function () {
|
||||||
|
return odfElement.clientWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getHeight = function () {
|
||||||
|
return odfElement.clientHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fitToWidth = function (width) {
|
||||||
|
odfCanvas.fitToWidth(width);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fitToHeight = function (height) {
|
||||||
|
odfCanvas.fitToHeight(height);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fitToPage = function (width, height) {
|
||||||
|
odfCanvas.fitToContainingElement(width, height);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fitSmart = function (width) {
|
||||||
|
odfCanvas.fitSmart(width);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getZoomLevel = function () {
|
||||||
|
return odfCanvas.getZoomLevel();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setZoomLevel = function (value) {
|
||||||
|
odfCanvas.setZoomLevel(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// return a list of tuples (pagename, pagenode)
|
||||||
|
this.getPages = function () {
|
||||||
|
var pageNodes = Array.prototype.slice.call(root.getElementsByTagNameNS(nsResolver('draw'), 'page')),
|
||||||
|
pages = [],
|
||||||
|
i,
|
||||||
|
tuple;
|
||||||
|
|
||||||
|
for (i = 0; i < pageNodes.length; i += 1) {
|
||||||
|
tuple = [
|
||||||
|
pageNodes[i].getAttribute('draw:name'),
|
||||||
|
pageNodes[i]
|
||||||
|
];
|
||||||
|
pages.push(tuple);
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.showPage = function (n) {
|
||||||
|
odfCanvas.showPage(n);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getPluginName = function () {
|
||||||
|
return pluginName;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getPluginVersion = function () {
|
||||||
|
var version;
|
||||||
|
|
||||||
|
if (String(typeof webodf) !== "undefined") {
|
||||||
|
version = webodf.Version;
|
||||||
|
} else {
|
||||||
|
version = "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
return version;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getPluginURL = function () {
|
||||||
|
return pluginURL;
|
||||||
|
};
|
||||||
|
}
|
36
attachment_preview/static/lib/ViewerJS/PDFViewerPlugin.css
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
.page {
|
||||||
|
margin: 7px auto 7px auto;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background-clip: content-box;
|
||||||
|
background-color: white;
|
||||||
|
|
||||||
|
box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
-webkit-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
-moz-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
-ms-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
-o-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
.textLayer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
color: #000;
|
||||||
|
font-family: sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textLayer > div {
|
||||||
|
color: transparent;
|
||||||
|
position: absolute;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: pre;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection { background:rgba(0,0,255,0.3); }
|
||||||
|
::-moz-selection { background:rgba(0,0,255,0.3); }
|
||||||
|
|
364
attachment_preview/static/lib/ViewerJS/PDFViewerPlugin.js
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (C) 2013-2014 KO GmbH <copyright@kogmbh.com>
|
||||||
|
*
|
||||||
|
* @licstart
|
||||||
|
* The JavaScript code in this page is free software: you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Affero General Public License
|
||||||
|
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version. The code is distributed
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this code. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* As additional permission under GNU AGPL version 3 section 7, you
|
||||||
|
* may distribute non-source (e.g., minimized or compacted) forms of
|
||||||
|
* that code without the copy of the GNU GPL normally required by
|
||||||
|
* section 4, provided you include this license notice and a URL
|
||||||
|
* through which recipients can access the Corresponding Source.
|
||||||
|
*
|
||||||
|
* As a special exception to the AGPL, any HTML file which merely makes function
|
||||||
|
* calls to this code, and for that purpose includes it by reference shall be
|
||||||
|
* deemed a separate work for copyright law purposes. In addition, the copyright
|
||||||
|
* holders of this code give you permission to combine this code with free
|
||||||
|
* software libraries that are released under the GNU LGPL. You may copy and
|
||||||
|
* distribute such a system following the terms of the GNU AGPL for this code
|
||||||
|
* and the LGPL for the libraries. If you modify this code, you may extend this
|
||||||
|
* exception to your version of the code, but you are not obligated to do so.
|
||||||
|
* If you do not wish to do so, delete this exception statement from your
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* This license applies to this entire compilation.
|
||||||
|
* @licend
|
||||||
|
* @source: http://viewerjs.org/
|
||||||
|
* @source: http://github.com/kogmbh/ViewerJS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*global document, PDFJS, console, TextLayerBuilder*/
|
||||||
|
|
||||||
|
|
||||||
|
function PDFViewerPlugin() {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function loadScript(path, callback) {
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.async = false;
|
||||||
|
script.src = path;
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.onload = callback || script.onload;
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(callback) {
|
||||||
|
var pdfLib, textLayerLib, pluginCSS;
|
||||||
|
|
||||||
|
loadScript('./compatibility.js', function () {
|
||||||
|
loadScript('./pdf.js');
|
||||||
|
loadScript('./pdf_find_bar.js');
|
||||||
|
loadScript('./pdf_find_controller.js');
|
||||||
|
loadScript('./ui_utils.js');
|
||||||
|
loadScript('./text_layer_builder.js');
|
||||||
|
loadScript('./pdfjsversion.js', callback);
|
||||||
|
});
|
||||||
|
|
||||||
|
pluginCSS = document.createElement('link');
|
||||||
|
pluginCSS.setAttribute("rel", "stylesheet");
|
||||||
|
pluginCSS.setAttribute("type", "text/css");
|
||||||
|
pluginCSS.setAttribute("href", "./PDFViewerPlugin.css");
|
||||||
|
document.head.appendChild(pluginCSS);
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
pages = [],
|
||||||
|
domPages = [],
|
||||||
|
pageText = [],
|
||||||
|
renderingStates = [],
|
||||||
|
RENDERING = {
|
||||||
|
BLANK: 0,
|
||||||
|
RUNNING: 1,
|
||||||
|
FINISHED: 2
|
||||||
|
},
|
||||||
|
startedTextExtraction = false,
|
||||||
|
container = null,
|
||||||
|
initialized = false,
|
||||||
|
pdfDocument = null,
|
||||||
|
pageViewScroll = null,
|
||||||
|
isPresentationMode = false,
|
||||||
|
scale = 1,
|
||||||
|
currentPage = 1,
|
||||||
|
pageWidth,
|
||||||
|
pageHeight,
|
||||||
|
createdPageCount = 0;
|
||||||
|
|
||||||
|
function scrollIntoView(elem) {
|
||||||
|
elem.parentNode.scrollTop = elem.offsetTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isScrolledIntoView(elem) {
|
||||||
|
var docViewTop = container.scrollTop,
|
||||||
|
docViewBottom = docViewTop + container.clientHeight,
|
||||||
|
elemTop = elem.offsetTop,
|
||||||
|
elemBottom = elemTop + elem.clientHeight;
|
||||||
|
|
||||||
|
// Is in view if either the top or the bottom of the page is between the
|
||||||
|
// document viewport bounds,
|
||||||
|
// or if the top is above the viewport and the bottom is below it.
|
||||||
|
return (elemTop >= docViewTop && elemTop < docViewBottom)
|
||||||
|
|| (elemBottom >= docViewTop && elemBottom < docViewBottom)
|
||||||
|
|| (elemTop < docViewTop && elemBottom >= docViewBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDomPage(page) {
|
||||||
|
return domPages[page.pageInfo.pageIndex];
|
||||||
|
}
|
||||||
|
function getPageText(page) {
|
||||||
|
return pageText[page.pageInfo.pageIndex];
|
||||||
|
}
|
||||||
|
function getRenderingStatus(page) {
|
||||||
|
return renderingStates[page.pageInfo.pageIndex];
|
||||||
|
}
|
||||||
|
function setRenderingStatus(page, renderStatus) {
|
||||||
|
renderingStates[page.pageInfo.pageIndex] = renderStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePageDimensions(page, width, height) {
|
||||||
|
var domPage = getDomPage(page),
|
||||||
|
canvas = domPage.getElementsByTagName('canvas')[0],
|
||||||
|
textLayer = domPage.getElementsByTagName('div')[0],
|
||||||
|
cssScale = 'scale(' + scale + ', ' + scale + ')';
|
||||||
|
|
||||||
|
domPage.style.width = width + "px";
|
||||||
|
domPage.style.height = height + "px";
|
||||||
|
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
|
||||||
|
textLayer.style.width = width + "px";
|
||||||
|
textLayer.style.height = height + "px";
|
||||||
|
|
||||||
|
CustomStyle.setProp('transform', textLayer, cssScale);
|
||||||
|
CustomStyle.setProp('transformOrigin', textLayer, '0% 0%');
|
||||||
|
|
||||||
|
// Once the page dimension is updated, the rendering state is blank.
|
||||||
|
setRenderingStatus(page, RENDERING.BLANK);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPage(page) {
|
||||||
|
var domPage = getDomPage(page),
|
||||||
|
textLayer = getPageText(page),
|
||||||
|
canvas = domPage.getElementsByTagName('canvas')[0];
|
||||||
|
|
||||||
|
if (getRenderingStatus(page) === RENDERING.BLANK) {
|
||||||
|
setRenderingStatus(page, RENDERING.RUNNING);
|
||||||
|
page.render({
|
||||||
|
canvasContext: canvas.getContext('2d'),
|
||||||
|
textLayer: textLayer,
|
||||||
|
viewport: page.getViewport(scale)
|
||||||
|
}).promise.then(function () {
|
||||||
|
setRenderingStatus(page, RENDERING.FINISHED);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPage(page) {
|
||||||
|
var pageNumber,
|
||||||
|
textLayerDiv,
|
||||||
|
textLayer,
|
||||||
|
canvas,
|
||||||
|
domPage,
|
||||||
|
viewport;
|
||||||
|
|
||||||
|
pageNumber = page.pageInfo.pageIndex + 1;
|
||||||
|
|
||||||
|
viewport = page.getViewport(scale);
|
||||||
|
|
||||||
|
domPage = document.createElement('div');
|
||||||
|
domPage.id = 'pageContainer' + pageNumber;
|
||||||
|
domPage.className = 'page';
|
||||||
|
|
||||||
|
canvas = document.createElement('canvas');
|
||||||
|
canvas.id = 'canvas' + pageNumber;
|
||||||
|
|
||||||
|
textLayerDiv = document.createElement('div');
|
||||||
|
textLayerDiv.className = 'textLayer';
|
||||||
|
textLayerDiv.id = 'textLayer' + pageNumber;
|
||||||
|
|
||||||
|
container.appendChild(domPage);
|
||||||
|
domPage.appendChild(canvas);
|
||||||
|
domPage.appendChild(textLayerDiv);
|
||||||
|
|
||||||
|
pages.push(page);
|
||||||
|
domPages.push(domPage);
|
||||||
|
renderingStates.push(RENDERING.BLANK);
|
||||||
|
|
||||||
|
updatePageDimensions(page, viewport.width, viewport.height);
|
||||||
|
pageWidth = viewport.width;
|
||||||
|
pageHeight = viewport.height;
|
||||||
|
|
||||||
|
textLayer = new TextLayerBuilder({
|
||||||
|
textLayerDiv: textLayerDiv,
|
||||||
|
pageIndex: pageNumber - 1
|
||||||
|
});
|
||||||
|
page.getTextContent().then(function (textContent) {
|
||||||
|
textLayer.setTextContent(textContent);
|
||||||
|
});
|
||||||
|
pageText.push(textLayer);
|
||||||
|
|
||||||
|
createdPageCount += 1;
|
||||||
|
if (createdPageCount === (pdfDocument.numPages)) {
|
||||||
|
if (self.isSlideshow()) {
|
||||||
|
domPages.forEach(function (pageElement) {
|
||||||
|
pageElement.style.display = "none";
|
||||||
|
});
|
||||||
|
self.showPage(1);
|
||||||
|
}
|
||||||
|
self.onLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialize = function (viewContainer, location) {
|
||||||
|
var self = this,
|
||||||
|
i,
|
||||||
|
pluginCSS;
|
||||||
|
|
||||||
|
init(function () {
|
||||||
|
PDFJS.workerSrc = "./pdf.worker.js";
|
||||||
|
PDFJS.getDocument(location).then(function loadPDF(doc) {
|
||||||
|
pdfDocument = doc;
|
||||||
|
container = viewContainer;
|
||||||
|
|
||||||
|
for (i = 0; i < pdfDocument.numPages; i += 1) {
|
||||||
|
pdfDocument.getPage(i + 1).then(createPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isSlideshow = function () {
|
||||||
|
// A very simple but generally true guess - if the width is greater than the height, treat it as a slideshow
|
||||||
|
return pageWidth > pageHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onLoad = function () {};
|
||||||
|
|
||||||
|
this.getPages = function () {
|
||||||
|
return domPages;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getWidth = function () {
|
||||||
|
return pageWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getHeight = function () {
|
||||||
|
return pageHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fitToWidth = function (width) {
|
||||||
|
var zoomLevel;
|
||||||
|
|
||||||
|
if (self.getWidth() === width) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zoomLevel = width / pageWidth;
|
||||||
|
self.setZoomLevel(zoomLevel);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fitToHeight = function (height) {
|
||||||
|
var zoomLevel;
|
||||||
|
|
||||||
|
if (self.getHeight() === height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zoomLevel = height / pageHeight;
|
||||||
|
self.setZoomLevel(zoomLevel);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fitToPage = function (width, height) {
|
||||||
|
var zoomLevel = width / pageWidth;
|
||||||
|
if (height / pageHeight < zoomLevel) {
|
||||||
|
zoomLevel = height / pageHeight;
|
||||||
|
}
|
||||||
|
self.setZoomLevel(zoomLevel);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fitSmart = function (width, height) {
|
||||||
|
var zoomLevel = width / pageWidth;
|
||||||
|
if (height && (height / pageHeight) < zoomLevel) {
|
||||||
|
zoomLevel = height / pageHeight;
|
||||||
|
}
|
||||||
|
zoomLevel = Math.min(1.0, zoomLevel);
|
||||||
|
self.setZoomLevel(zoomLevel);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setZoomLevel = function (zoomLevel) {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
if (scale !== zoomLevel) {
|
||||||
|
scale = zoomLevel;
|
||||||
|
|
||||||
|
for (i = 0; i < pages.length; i += 1) {
|
||||||
|
updatePageDimensions(pages[i], pageWidth * scale, pageHeight * scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getZoomLevel = function () {
|
||||||
|
return scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onScroll = function () {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
for (i = 0; i < domPages.length; i += 1) {
|
||||||
|
if (isScrolledIntoView(domPages[i])) {
|
||||||
|
if (getRenderingStatus(pages[i]) === RENDERING.BLANK) {
|
||||||
|
renderPage(pages[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getPageInView = function () {
|
||||||
|
var i;
|
||||||
|
|
||||||
|
if (self.isSlideshow()) {
|
||||||
|
return currentPage;
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < domPages.length; i += 1) {
|
||||||
|
if (isScrolledIntoView(domPages[i])) {
|
||||||
|
return i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.showPage = function (n) {
|
||||||
|
if (self.isSlideshow()) {
|
||||||
|
domPages[currentPage - 1].style.display = "none";
|
||||||
|
currentPage = n;
|
||||||
|
domPages[n - 1].style.display = "block";
|
||||||
|
} else {
|
||||||
|
scrollIntoView(domPages[n - 1]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getPluginName = function () {
|
||||||
|
return "PDF.js"
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getPluginVersion = function () {
|
||||||
|
var version = (String(typeof pdfjs_version) !== "undefined"
|
||||||
|
? pdfjs_version
|
||||||
|
: "From Source"
|
||||||
|
);
|
||||||
|
return version;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getPluginURL = function () {
|
||||||
|
return "https://github.com/mozilla/pdf.js/";
|
||||||
|
};
|
||||||
|
}
|
86
attachment_preview/static/lib/ViewerJS/PluginLoader.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright (C) 2012 KO GmbH <copyright@kogmbh.com>
|
||||||
|
*
|
||||||
|
* @licstart
|
||||||
|
* The JavaScript code in this page is free software: you can redistribute it
|
||||||
|
* and/or modify it under the terms of the GNU Affero General Public License
|
||||||
|
* (GNU AGPL) as published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version. The code is distributed
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU AGPL for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this code. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* As additional permission under GNU AGPL version 3 section 7, you
|
||||||
|
* may distribute non-source (e.g., minimized or compacted) forms of
|
||||||
|
* that code without the copy of the GNU GPL normally required by
|
||||||
|
* section 4, provided you include this license notice and a URL
|
||||||
|
* through which recipients can access the Corresponding Source.
|
||||||
|
*
|
||||||
|
* As a special exception to the AGPL, any HTML file which merely makes function
|
||||||
|
* calls to this code, and for that purpose includes it by reference shall be
|
||||||
|
* deemed a separate work for copyright law purposes. In addition, the copyright
|
||||||
|
* holders of this code give you permission to combine this code with free
|
||||||
|
* software libraries that are released under the GNU LGPL. You may copy and
|
||||||
|
* distribute such a system following the terms of the GNU AGPL for this code
|
||||||
|
* and the LGPL for the libraries. If you modify this code, you may extend this
|
||||||
|
* exception to your version of the code, but you are not obligated to do so.
|
||||||
|
* If you do not wish to do so, delete this exception statement from your
|
||||||
|
* version.
|
||||||
|
*
|
||||||
|
* This license applies to this entire compilation.
|
||||||
|
* @licend
|
||||||
|
* @source: http://viewerjs.org/
|
||||||
|
* @source: http://github.com/kogmbh/ViewerJS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*global document, window, Viewer, ODFViewerPlugin, PDFViewerPlugin*/
|
||||||
|
|
||||||
|
var viewer;
|
||||||
|
|
||||||
|
function loadPlugin(pluginName, callback) {
|
||||||
|
"use strict";
|
||||||
|
var script, style;
|
||||||
|
|
||||||
|
// Load script
|
||||||
|
script = document.createElement('script');
|
||||||
|
script.async = false;
|
||||||
|
script.onload = callback;
|
||||||
|
script.src = pluginName + '.js';
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadDocument(documentUrl) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
if (documentUrl) {
|
||||||
|
var extension = documentUrl.split('.').pop(),
|
||||||
|
Plugin;
|
||||||
|
extension = extension.toLowerCase();
|
||||||
|
|
||||||
|
switch (extension) {
|
||||||
|
case 'odt':
|
||||||
|
case 'odp':
|
||||||
|
case 'ods':
|
||||||
|
case 'fodt':
|
||||||
|
loadPlugin('./ODFViewerPlugin', function () {
|
||||||
|
Plugin = ODFViewerPlugin;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'pdf':
|
||||||
|
loadPlugin('./PDFViewerPlugin', function () {
|
||||||
|
Plugin = PDFViewerPlugin;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
if (Plugin) {
|
||||||
|
viewer = new Viewer(new Plugin());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
491
attachment_preview/static/lib/ViewerJS/compatibility.js
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
/* -*- 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
|
||||||
|
(function checkTypedArrayCompatibility() {
|
||||||
|
if (typeof Uint8Array !== 'undefined') {
|
||||||
|
// some mobile versions do not support subarray (e.g. safari 5 / iOS)
|
||||||
|
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));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// some mobile version might not support Float64Array
|
||||||
|
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;
|
||||||
|
if (typeof arg1 === 'number') {
|
||||||
|
result = [];
|
||||||
|
for (var i = 0; i < arg1; ++i)
|
||||||
|
result[i] = 0;
|
||||||
|
} else if ('slice' in arg1) {
|
||||||
|
result = arg1.slice(0);
|
||||||
|
} else {
|
||||||
|
result = [];
|
||||||
|
for (var 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;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
(function normalizeURLObject() {
|
||||||
|
if (!window.URL) {
|
||||||
|
window.URL = window.webkitURL;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Object.create() ?
|
||||||
|
(function checkObjectCreateCompatibility() {
|
||||||
|
if (typeof Object.create !== 'undefined')
|
||||||
|
return;
|
||||||
|
|
||||||
|
Object.create = function objectCreate(proto) {
|
||||||
|
function Constructor() {}
|
||||||
|
Constructor.prototype = proto;
|
||||||
|
return new Constructor();
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Object.defineProperty() ?
|
||||||
|
(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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Object.keys() ?
|
||||||
|
(function checkObjectKeysCompatibility() {
|
||||||
|
if (typeof Object.keys !== 'undefined')
|
||||||
|
return;
|
||||||
|
|
||||||
|
Object.keys = function objectKeys(obj) {
|
||||||
|
var result = [];
|
||||||
|
for (var i in obj) {
|
||||||
|
if (obj.hasOwnProperty(i))
|
||||||
|
result.push(i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// No readAsArrayBuffer ?
|
||||||
|
(function checkFileReaderReadAsArrayBuffer() {
|
||||||
|
if (typeof FileReader === 'undefined')
|
||||||
|
return; // FileReader is not implemented
|
||||||
|
var frPrototype = FileReader.prototype;
|
||||||
|
// Older versions of Firefox might not have readAsArrayBuffer
|
||||||
|
if ('readAsArrayBuffer' in frPrototype)
|
||||||
|
return; // readAsArrayBuffer is implemented
|
||||||
|
Object.defineProperty(frPrototype, 'readAsArrayBuffer', {
|
||||||
|
value: function fileReaderReadAsArrayBuffer(blob) {
|
||||||
|
var fileReader = new FileReader();
|
||||||
|
var originalReader = this;
|
||||||
|
fileReader.onload = function fileReaderOnload(evt) {
|
||||||
|
var data = evt.target.result;
|
||||||
|
var buffer = new ArrayBuffer(data.length);
|
||||||
|
var uint8Array = new Uint8Array(buffer);
|
||||||
|
|
||||||
|
for (var i = 0, ii = data.length; i < ii; i++)
|
||||||
|
uint8Array[i] = data.charCodeAt(i);
|
||||||
|
|
||||||
|
Object.defineProperty(originalReader, 'result', {
|
||||||
|
value: buffer,
|
||||||
|
enumerable: true,
|
||||||
|
writable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var event = document.createEvent('HTMLEvents');
|
||||||
|
event.initEvent('load', false, false);
|
||||||
|
originalReader.dispatchEvent(event);
|
||||||
|
};
|
||||||
|
fileReader.readAsBinaryString(blob);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
// No XMLHttpRequest.response ?
|
||||||
|
(function checkXMLHttpRequestResponseCompatibility() {
|
||||||
|
var xhrPrototype = XMLHttpRequest.prototype;
|
||||||
|
if (!('overrideMimeType' in xhrPrototype)) {
|
||||||
|
// IE10 might have response, but not overrideMimeType
|
||||||
|
Object.defineProperty(xhrPrototype, 'overrideMimeType', {
|
||||||
|
value: function xmlHttpRequestOverrideMimeType(mimeType) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ('response' in xhrPrototype ||
|
||||||
|
'mozResponseArrayBuffer' in xhrPrototype ||
|
||||||
|
'mozResponse' in xhrPrototype ||
|
||||||
|
'responseArrayBuffer' in xhrPrototype)
|
||||||
|
return;
|
||||||
|
// IE9 ?
|
||||||
|
if (typeof VBArray !== 'undefined') {
|
||||||
|
Object.defineProperty(xhrPrototype, 'response', {
|
||||||
|
get: function xmlHttpRequestResponseGet() {
|
||||||
|
return new Uint8Array(new VBArray(this.responseBody).toArray());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// other browsers
|
||||||
|
function responseTypeSetter() {
|
||||||
|
// will be only called to set "arraybuffer"
|
||||||
|
this.overrideMimeType('text/plain; charset=x-user-defined');
|
||||||
|
}
|
||||||
|
if (typeof xhrPrototype.overrideMimeType === 'function') {
|
||||||
|
Object.defineProperty(xhrPrototype, 'responseType',
|
||||||
|
{ set: responseTypeSetter });
|
||||||
|
}
|
||||||
|
function responseGetter() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
Object.defineProperty(xhrPrototype, 'response', { get: responseGetter });
|
||||||
|
})();
|
||||||
|
|
||||||
|
// window.btoa (base64 encode function) ?
|
||||||
|
(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) ?
|
||||||
|
(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 ?
|
||||||
|
(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 = Array.prototype.concat.apply(headArgs, arguments);
|
||||||
|
return fn.apply(obj, args);
|
||||||
|
};
|
||||||
|
return bound;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// HTMLElement dataset property
|
||||||
|
(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
|
||||||
|
(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
|
||||||
|
(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
|
||||||
|
(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()
|
||||||
|
(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;
|
||||||
|
Object.defineProperty(navigator, 'language', {
|
||||||
|
get: function navigatorLanguage() {
|
||||||
|
var language = navigator.userLanguage || 'en-US';
|
||||||
|
return language.substring(0, 2).toLowerCase() +
|
||||||
|
language.substring(2).toUpperCase();
|
||||||
|
},
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
(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.
|
||||||
|
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.
|
||||||
|
var regex = /Android\s[0-2][^\d]/;
|
||||||
|
var isOldAndroid = regex.test(navigator.userAgent);
|
||||||
|
|
||||||
|
if (isSafari || isOldAndroid) {
|
||||||
|
PDFJS.disableRange = true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Check if the browser supports manipulation of the history.
|
||||||
|
(function checkHistoryManipulation() {
|
||||||
|
if (!window.history.pushState) {
|
||||||
|
PDFJS.disableHistory = true;
|
||||||
|
}
|
||||||
|
})();
|
27
attachment_preview/static/lib/ViewerJS/example.local.css
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/* This is just a sample file with CSS rules. You should write your own @font-face declarations
|
||||||
|
* to add support for your desired fonts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Novecentowide Book';
|
||||||
|
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot");
|
||||||
|
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot?#iefix") format("embedded-opentype"),
|
||||||
|
url("/ViewerJS/fonts/Novecentowide-Bold-webfont.woff") format("woff"),
|
||||||
|
url("/fonts/Novecentowide-Bold-webfont.ttf") format("truetype"),
|
||||||
|
url("/fonts/Novecentowide-Bold-webfont.svg#NovecentowideBookBold") format("svg");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'exotica';
|
||||||
|
src: url('/ViewerJS/fonts/Exotica-webfont.eot');
|
||||||
|
src: url('/ViewerJS/fonts/Exotica-webfont.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('/ViewerJS/fonts/Exotica-webfont.woff') format('woff'),
|
||||||
|
url('/ViewerJS/fonts/Exotica-webfont.ttf') format('truetype'),
|
||||||
|
url('/ViewerJS/fonts/Exotica-webfont.svg#exoticamedium') format('svg');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
BIN
attachment_preview/static/lib/ViewerJS/images/kogmbh.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
attachment_preview/static/lib/ViewerJS/images/nlnet.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
attachment_preview/static/lib/ViewerJS/images/texture.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 512 B |
After Width: | Height: | Size: 491 B |
After Width: | Height: | Size: 237 B |
After Width: | Height: | Size: 353 B |
After Width: | Height: | Size: 344 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 143 B |
131
attachment_preview/static/lib/ViewerJS/index.html
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2012-2014 KO GmbH <copyright@kogmbh.com>
|
||||||
|
|
||||||
|
@licstart
|
||||||
|
This file is part of WebODF.
|
||||||
|
|
||||||
|
WebODF is free software: you can redistribute it and/or modify it
|
||||||
|
under the terms of the GNU Affero General Public License (GNU AGPL)
|
||||||
|
as published by the Free Software Foundation, either version 3 of
|
||||||
|
the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
WebODF is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
@licend
|
||||||
|
|
||||||
|
@source: http://www.webodf.org/
|
||||||
|
@source: https://github.com/kogmbh/WebODF/
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This file is a derivative from a part of Mozilla's PDF.js project. The
|
||||||
|
original license header follows.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<html dir="ltr" lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<title>ViewerJS</title>
|
||||||
|
<!-- If you want to use custom CSS (@font-face rules, for example) you should uncomment
|
||||||
|
the following reference and use a local.css file for that. See the example.local.css
|
||||||
|
file for a sample.
|
||||||
|
<link rel="stylesheet" type="text/css" href="local.css" media="screen"/>
|
||||||
|
-->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||||
|
<link rel="stylesheet" type="text/css" href="viewer.css" media="screen"/>
|
||||||
|
<script src="viewer.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
<script src="PluginLoader.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
<!-- load some small tweaks for the Odoo integration /-->
|
||||||
|
<script src="../../src/js/viewerjs_tweaks.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
<script>
|
||||||
|
loadDocument(window.location.hash);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id = "viewer">
|
||||||
|
<div id = "titlebar">
|
||||||
|
<div id = "documentName"></div>
|
||||||
|
<div id = "toolbarRight">
|
||||||
|
<button id = "presentation" class = "toolbarButton presentation" title = "Presentation"></button>
|
||||||
|
<button id = "fullscreen" class = "toolbarButton fullscreen" title = "Fullscreen"></button>
|
||||||
|
<button id = "download" class = "toolbarButton download" title = "Download"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id = "toolbarContainer">
|
||||||
|
<div id = "toolbar">
|
||||||
|
<div id = "toolbarLeft">
|
||||||
|
<div id = "navButtons" class = "splitToolbarButton">
|
||||||
|
<button id = "previous" class = "toolbarButton pageUp" title = "Previous Page"></button>
|
||||||
|
<div class="splitToolbarButtonSeparator"></div>
|
||||||
|
<button id = "next" class = "toolbarButton pageDown" title = "Next Page"></button>
|
||||||
|
</div>
|
||||||
|
<label id = "pageNumberLabel" class = "toolbarLabel" for = "pageNumber">Page:</label>
|
||||||
|
<input type = "number" id = "pageNumber" class = "toolbarField pageNumber"/>
|
||||||
|
<span id = "numPages" class = "toolbarLabel"></span>
|
||||||
|
</div>
|
||||||
|
<div id = "toolbarMiddleContainer" class = "outerCenter">
|
||||||
|
<div id = "toolbarMiddle" class = "innerCenter">
|
||||||
|
<div id = 'zoomButtons' class = "splitToolbarButton">
|
||||||
|
<button id = "zoomOut" class = "toolbarButton zoomOut" title = "Zoom Out"></button>
|
||||||
|
<div class="splitToolbarButtonSeparator"></div>
|
||||||
|
<button id = "zoomIn" class = "toolbarButton zoomIn" title = "Zoom In"></button>
|
||||||
|
</div>
|
||||||
|
<span id="scaleSelectContainer" class="dropdownToolbarButton">
|
||||||
|
<select id="scaleSelect" title="Zoom" oncontextmenu="return false;">
|
||||||
|
<option id="pageAutoOption" value="auto" selected>Automatic</option>
|
||||||
|
<option id="pageActualOption" value="page-actual">Actual Size</option>
|
||||||
|
<option id="pageWidthOption" value="page-width">Full Width</option>
|
||||||
|
<option id="customScaleOption" value="custom"> </option>
|
||||||
|
<option value="0.5">50%</option>
|
||||||
|
<option value="0.75">75%</option>
|
||||||
|
<option value="1">100%</option>
|
||||||
|
<option value="1.25">125%</option>
|
||||||
|
<option value="1.5">150%</option>
|
||||||
|
<option value="2">200%</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
<div id = "sliderContainer">
|
||||||
|
<div id = "slider"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id = "canvasContainer">
|
||||||
|
<div id = "canvas"></div>
|
||||||
|
</div>
|
||||||
|
<div id = "overlayNavigator">
|
||||||
|
<div id = "previousPage"></div>
|
||||||
|
<div id = "nextPage"></div>
|
||||||
|
</div>
|
||||||
|
<div id = "overlayCloseButton">
|
||||||
|
✖
|
||||||
|
</div>
|
||||||
|
<div id = "dialogOverlay"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
7651
attachment_preview/static/lib/ViewerJS/pdf.js
Normal file
39900
attachment_preview/static/lib/ViewerJS/pdf.worker.js
vendored
Normal file
175
attachment_preview/static/lib/ViewerJS/pdf_find_bar.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
/* -*- 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';
|
||||||
|
|
||||||
|
/* globals PDFFindController, FindStates, mozL10n */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a "search bar" given set of DOM elements
|
||||||
|
* that act as controls for searching, or for setting
|
||||||
|
* search preferences in the UI. This object also sets
|
||||||
|
* up the appropriate events for the controls. Actual
|
||||||
|
* searching is done by PDFFindController
|
||||||
|
*/
|
||||||
|
var PDFFindBar = {
|
||||||
|
|
||||||
|
opened: false,
|
||||||
|
bar: null,
|
||||||
|
toggleButton: null,
|
||||||
|
findField: null,
|
||||||
|
highlightAll: null,
|
||||||
|
caseSensitive: null,
|
||||||
|
findMsg: null,
|
||||||
|
findStatusIcon: null,
|
||||||
|
findPreviousButton: null,
|
||||||
|
findNextButton: null,
|
||||||
|
|
||||||
|
initialize: function(options) {
|
||||||
|
if(typeof PDFFindController === 'undefined' || PDFFindController === null) {
|
||||||
|
throw 'PDFFindBar cannot be initialized ' +
|
||||||
|
'without a PDFFindController instance.';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bar = options.bar;
|
||||||
|
this.toggleButton = options.toggleButton;
|
||||||
|
this.findField = options.findField;
|
||||||
|
this.highlightAll = options.highlightAllCheckbox;
|
||||||
|
this.caseSensitive = options.caseSensitiveCheckbox;
|
||||||
|
this.findMsg = options.findMsg;
|
||||||
|
this.findStatusIcon = options.findStatusIcon;
|
||||||
|
this.findPreviousButton = options.findPreviousButton;
|
||||||
|
this.findNextButton = options.findNextButton;
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.toggleButton.addEventListener('click', function() {
|
||||||
|
self.toggle();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.findField.addEventListener('input', function() {
|
||||||
|
self.dispatchEvent('');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bar.addEventListener('keydown', function(evt) {
|
||||||
|
switch (evt.keyCode) {
|
||||||
|
case 13: // Enter
|
||||||
|
if (evt.target === self.findField) {
|
||||||
|
self.dispatchEvent('again', evt.shiftKey);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 27: // Escape
|
||||||
|
self.close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.findPreviousButton.addEventListener('click',
|
||||||
|
function() { self.dispatchEvent('again', true); }
|
||||||
|
);
|
||||||
|
|
||||||
|
this.findNextButton.addEventListener('click', function() {
|
||||||
|
self.dispatchEvent('again', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.highlightAll.addEventListener('click', function() {
|
||||||
|
self.dispatchEvent('highlightallchange');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.caseSensitive.addEventListener('click', function() {
|
||||||
|
self.dispatchEvent('casesensitivitychange');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
dispatchEvent: function(aType, aFindPrevious) {
|
||||||
|
var event = document.createEvent('CustomEvent');
|
||||||
|
event.initCustomEvent('find' + aType, true, true, {
|
||||||
|
query: this.findField.value,
|
||||||
|
caseSensitive: this.caseSensitive.checked,
|
||||||
|
highlightAll: this.highlightAll.checked,
|
||||||
|
findPrevious: aFindPrevious
|
||||||
|
});
|
||||||
|
return window.dispatchEvent(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateUIState: function(state, previous) {
|
||||||
|
var notFound = false;
|
||||||
|
var findMsg = '';
|
||||||
|
var status = '';
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case FindStates.FIND_FOUND:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FindStates.FIND_PENDING:
|
||||||
|
status = 'pending';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FindStates.FIND_NOTFOUND:
|
||||||
|
findMsg = mozL10n.get('find_not_found', null, 'Phrase not found');
|
||||||
|
notFound = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FindStates.FIND_WRAPPED:
|
||||||
|
if (previous) {
|
||||||
|
findMsg = mozL10n.get('find_reached_top', null,
|
||||||
|
'Reached top of document, continued from bottom');
|
||||||
|
} else {
|
||||||
|
findMsg = mozL10n.get('find_reached_bottom', null,
|
||||||
|
'Reached end of document, continued from top');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notFound) {
|
||||||
|
this.findField.classList.add('notFound');
|
||||||
|
} else {
|
||||||
|
this.findField.classList.remove('notFound');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.findField.setAttribute('data-status', status);
|
||||||
|
this.findMsg.textContent = findMsg;
|
||||||
|
},
|
||||||
|
|
||||||
|
open: function() {
|
||||||
|
if (!this.opened) {
|
||||||
|
this.opened = true;
|
||||||
|
this.toggleButton.classList.add('toggled');
|
||||||
|
this.bar.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.findField.select();
|
||||||
|
this.findField.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
close: function() {
|
||||||
|
if (!this.opened) return;
|
||||||
|
|
||||||
|
this.opened = false;
|
||||||
|
this.toggleButton.classList.remove('toggled');
|
||||||
|
this.bar.classList.add('hidden');
|
||||||
|
|
||||||
|
PDFFindController.active = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
toggle: function() {
|
||||||
|
if (this.opened) {
|
||||||
|
this.close();
|
||||||
|
} else {
|
||||||
|
this.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
355
attachment_preview/static/lib/ViewerJS/pdf_find_controller.js
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
/* -*- 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';
|
||||||
|
|
||||||
|
/* globals PDFFindBar, PDFJS, FindStates, FirefoxCom, Promise */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a "search" or "find" functionality for the PDF.
|
||||||
|
* This object actually performs the search for a given string.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var PDFFindController = {
|
||||||
|
startedTextExtraction: false,
|
||||||
|
|
||||||
|
extractTextPromises: [],
|
||||||
|
|
||||||
|
pendingFindMatches: {},
|
||||||
|
|
||||||
|
// If active, find results will be highlighted.
|
||||||
|
active: false,
|
||||||
|
|
||||||
|
// Stores the text for each page.
|
||||||
|
pageContents: [],
|
||||||
|
|
||||||
|
pageMatches: [],
|
||||||
|
|
||||||
|
// Currently selected match.
|
||||||
|
selected: {
|
||||||
|
pageIdx: -1,
|
||||||
|
matchIdx: -1
|
||||||
|
},
|
||||||
|
|
||||||
|
// Where find algorithm currently is in the document.
|
||||||
|
offset: {
|
||||||
|
pageIdx: null,
|
||||||
|
matchIdx: null
|
||||||
|
},
|
||||||
|
|
||||||
|
resumePageIdx: null,
|
||||||
|
|
||||||
|
state: null,
|
||||||
|
|
||||||
|
dirtyMatch: false,
|
||||||
|
|
||||||
|
findTimeout: null,
|
||||||
|
|
||||||
|
pdfPageSource: null,
|
||||||
|
|
||||||
|
integratedFind: false,
|
||||||
|
|
||||||
|
initialize: function(options) {
|
||||||
|
if(typeof PDFFindBar === 'undefined' || PDFFindBar === null) {
|
||||||
|
throw 'PDFFindController cannot be initialized ' +
|
||||||
|
'without a PDFFindController instance';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pdfPageSource = options.pdfPageSource;
|
||||||
|
this.integratedFind = options.integratedFind;
|
||||||
|
|
||||||
|
var events = [
|
||||||
|
'find',
|
||||||
|
'findagain',
|
||||||
|
'findhighlightallchange',
|
||||||
|
'findcasesensitivitychange'
|
||||||
|
];
|
||||||
|
|
||||||
|
this.firstPagePromise = new Promise(function (resolve) {
|
||||||
|
this.resolveFirstPage = resolve;
|
||||||
|
}.bind(this));
|
||||||
|
this.handleEvent = this.handleEvent.bind(this);
|
||||||
|
|
||||||
|
for (var i = 0; i < events.length; i++) {
|
||||||
|
window.addEventListener(events[i], this.handleEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function pdfFindControllerReset() {
|
||||||
|
this.startedTextExtraction = false;
|
||||||
|
this.extractTextPromises = [];
|
||||||
|
this.active = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
calcFindMatch: function(pageIndex) {
|
||||||
|
var pageContent = this.pageContents[pageIndex];
|
||||||
|
var query = this.state.query;
|
||||||
|
var caseSensitive = this.state.caseSensitive;
|
||||||
|
var queryLen = query.length;
|
||||||
|
|
||||||
|
if (queryLen === 0) {
|
||||||
|
// Do nothing the matches should be wiped out already.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!caseSensitive) {
|
||||||
|
pageContent = pageContent.toLowerCase();
|
||||||
|
query = query.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
var matches = [];
|
||||||
|
|
||||||
|
var matchIdx = -queryLen;
|
||||||
|
while (true) {
|
||||||
|
matchIdx = pageContent.indexOf(query, matchIdx + queryLen);
|
||||||
|
if (matchIdx === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches.push(matchIdx);
|
||||||
|
}
|
||||||
|
this.pageMatches[pageIndex] = matches;
|
||||||
|
this.updatePage(pageIndex);
|
||||||
|
if (this.resumePageIdx === pageIndex) {
|
||||||
|
this.resumePageIdx = null;
|
||||||
|
this.nextPageMatch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
extractText: function() {
|
||||||
|
if (this.startedTextExtraction) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.startedTextExtraction = true;
|
||||||
|
|
||||||
|
this.pageContents = [];
|
||||||
|
var extractTextPromisesResolves = [];
|
||||||
|
for (var i = 0, ii = this.pdfPageSource.pdfDocument.numPages; i < ii; i++) {
|
||||||
|
this.extractTextPromises.push(new Promise(function (resolve) {
|
||||||
|
extractTextPromisesResolves.push(resolve);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
function extractPageText(pageIndex) {
|
||||||
|
self.pdfPageSource.pages[pageIndex].getTextContent().then(
|
||||||
|
function textContentResolved(bidiTexts) {
|
||||||
|
var str = '';
|
||||||
|
|
||||||
|
for (var i = 0; i < bidiTexts.length; i++) {
|
||||||
|
str += bidiTexts[i].str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the pageContent as a string.
|
||||||
|
self.pageContents.push(str);
|
||||||
|
|
||||||
|
extractTextPromisesResolves[pageIndex](pageIndex);
|
||||||
|
if ((pageIndex + 1) < self.pdfPageSource.pages.length)
|
||||||
|
extractPageText(pageIndex + 1);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
extractPageText(0);
|
||||||
|
},
|
||||||
|
|
||||||
|
handleEvent: function(e) {
|
||||||
|
if (this.state === null || e.type !== 'findagain') {
|
||||||
|
this.dirtyMatch = true;
|
||||||
|
}
|
||||||
|
this.state = e.detail;
|
||||||
|
this.updateUIState(FindStates.FIND_PENDING);
|
||||||
|
|
||||||
|
this.firstPagePromise.then(function() {
|
||||||
|
this.extractText();
|
||||||
|
|
||||||
|
clearTimeout(this.findTimeout);
|
||||||
|
if (e.type === 'find') {
|
||||||
|
// Only trigger the find action after 250ms of silence.
|
||||||
|
this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
|
||||||
|
} else {
|
||||||
|
this.nextMatch();
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePage: function(idx) {
|
||||||
|
var page = this.pdfPageSource.pages[idx];
|
||||||
|
|
||||||
|
if (this.selected.pageIdx === idx) {
|
||||||
|
// If the page is selected, scroll the page into view, which triggers
|
||||||
|
// rendering the page, which adds the textLayer. Once the textLayer is
|
||||||
|
// build, it will scroll onto the selected match.
|
||||||
|
page.scrollIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (page.textLayer) {
|
||||||
|
page.textLayer.updateMatches();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
nextMatch: function() {
|
||||||
|
var previous = this.state.findPrevious;
|
||||||
|
var currentPageIndex = this.pdfPageSource.page - 1;
|
||||||
|
var numPages = this.pdfPageSource.pages.length;
|
||||||
|
|
||||||
|
this.active = true;
|
||||||
|
|
||||||
|
if (this.dirtyMatch) {
|
||||||
|
// Need to recalculate the matches, reset everything.
|
||||||
|
this.dirtyMatch = false;
|
||||||
|
this.selected.pageIdx = this.selected.matchIdx = -1;
|
||||||
|
this.offset.pageIdx = currentPageIndex;
|
||||||
|
this.offset.matchIdx = null;
|
||||||
|
this.hadMatch = false;
|
||||||
|
this.resumePageIdx = null;
|
||||||
|
this.pageMatches = [];
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
for (var i = 0; i < numPages; i++) {
|
||||||
|
// Wipe out any previous highlighted matches.
|
||||||
|
this.updatePage(i);
|
||||||
|
|
||||||
|
// As soon as the text is extracted start finding the matches.
|
||||||
|
if (!(i in this.pendingFindMatches)) {
|
||||||
|
this.pendingFindMatches[i] = true;
|
||||||
|
this.extractTextPromises[i].then(function(pageIdx) {
|
||||||
|
delete self.pendingFindMatches[pageIdx];
|
||||||
|
self.calcFindMatch(pageIdx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no query there's no point in searching.
|
||||||
|
if (this.state.query === '') {
|
||||||
|
this.updateUIState(FindStates.FIND_FOUND);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're waiting on a page, we return since we can't do anything else.
|
||||||
|
if (this.resumePageIdx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = this.offset;
|
||||||
|
// If there's already a matchIdx that means we are iterating through a
|
||||||
|
// page's matches.
|
||||||
|
if (offset.matchIdx !== null) {
|
||||||
|
var numPageMatches = this.pageMatches[offset.pageIdx].length;
|
||||||
|
if ((!previous && offset.matchIdx + 1 < numPageMatches) ||
|
||||||
|
(previous && offset.matchIdx > 0)) {
|
||||||
|
// The simple case, we just have advance the matchIdx to select the next
|
||||||
|
// match on the page.
|
||||||
|
this.hadMatch = true;
|
||||||
|
offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
|
||||||
|
this.updateMatch(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// We went beyond the current page's matches, so we advance to the next
|
||||||
|
// page.
|
||||||
|
this.advanceOffsetPage(previous);
|
||||||
|
}
|
||||||
|
// Start searching through the page.
|
||||||
|
this.nextPageMatch();
|
||||||
|
},
|
||||||
|
|
||||||
|
matchesReady: function(matches) {
|
||||||
|
var offset = this.offset;
|
||||||
|
var numMatches = matches.length;
|
||||||
|
var previous = this.state.findPrevious;
|
||||||
|
if (numMatches) {
|
||||||
|
// There were matches for the page, so initialize the matchIdx.
|
||||||
|
this.hadMatch = true;
|
||||||
|
offset.matchIdx = previous ? numMatches - 1 : 0;
|
||||||
|
this.updateMatch(true);
|
||||||
|
// matches were found
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// No matches attempt to search the next page.
|
||||||
|
this.advanceOffsetPage(previous);
|
||||||
|
if (offset.wrapped) {
|
||||||
|
offset.matchIdx = null;
|
||||||
|
if (!this.hadMatch) {
|
||||||
|
// No point in wrapping there were no matches.
|
||||||
|
this.updateMatch(false);
|
||||||
|
// while matches were not found, searching for a page
|
||||||
|
// with matches should nevertheless halt.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// matches were not found (and searching is not done)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
nextPageMatch: function() {
|
||||||
|
if (this.resumePageIdx !== null) {
|
||||||
|
console.error('There can only be one pending page.');
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
var pageIdx = this.offset.pageIdx;
|
||||||
|
var matches = this.pageMatches[pageIdx];
|
||||||
|
if (!matches) {
|
||||||
|
// The matches don't exist yet for processing by "matchesReady",
|
||||||
|
// so set a resume point for when they do exist.
|
||||||
|
this.resumePageIdx = pageIdx;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (!this.matchesReady(matches));
|
||||||
|
},
|
||||||
|
|
||||||
|
advanceOffsetPage: function(previous) {
|
||||||
|
var offset = this.offset;
|
||||||
|
var numPages = this.extractTextPromises.length;
|
||||||
|
offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
|
||||||
|
offset.matchIdx = null;
|
||||||
|
if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
|
||||||
|
offset.pageIdx = previous ? numPages - 1 : 0;
|
||||||
|
offset.wrapped = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMatch: function(found) {
|
||||||
|
var state = FindStates.FIND_NOTFOUND;
|
||||||
|
var wrapped = this.offset.wrapped;
|
||||||
|
this.offset.wrapped = false;
|
||||||
|
if (found) {
|
||||||
|
var previousPage = this.selected.pageIdx;
|
||||||
|
this.selected.pageIdx = this.offset.pageIdx;
|
||||||
|
this.selected.matchIdx = this.offset.matchIdx;
|
||||||
|
state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND;
|
||||||
|
// Update the currently selected page to wipe out any selected matches.
|
||||||
|
if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
|
||||||
|
this.updatePage(previousPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateUIState(state, this.state.findPrevious);
|
||||||
|
if (this.selected.pageIdx !== -1) {
|
||||||
|
this.updatePage(this.selected.pageIdx, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateUIState: function(state, previous) {
|
||||||
|
if (this.integratedFind) {
|
||||||
|
FirefoxCom.request('updateFindControlState',
|
||||||
|
{result: state, findPrevious: previous});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PDFFindBar.updateUIState(state, previous);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
1
attachment_preview/static/lib/ViewerJS/pdfjsversion.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
var /**@const{!string}*/pdfjs_version = "d45d7bc";
|
385
attachment_preview/static/lib/ViewerJS/text_layer_builder.js
Normal file
@ -0,0 +1,385 @@
|
|||||||
|
/* -*- 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, PDFFindController, scrollIntoView */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var FIND_SCROLL_OFFSET_TOP = -50;
|
||||||
|
var FIND_SCROLL_OFFSET_LEFT = -400;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextLayerBuilder provides text-selection
|
||||||
|
* functionality for the PDF. It does this
|
||||||
|
* by creating overlay divs over the PDF
|
||||||
|
* text. This divs contain text that matches
|
||||||
|
* the PDF text they are overlaying. This
|
||||||
|
* object also provides for a way to highlight
|
||||||
|
* text that is being searched for.
|
||||||
|
*/
|
||||||
|
var TextLayerBuilder = function textLayerBuilder(options) {
|
||||||
|
var textLayerFrag = document.createDocumentFragment();
|
||||||
|
|
||||||
|
this.textLayerDiv = options.textLayerDiv;
|
||||||
|
this.layoutDone = false;
|
||||||
|
this.divContentDone = false;
|
||||||
|
this.pageIdx = options.pageIndex;
|
||||||
|
this.matches = [];
|
||||||
|
this.lastScrollSource = options.lastScrollSource;
|
||||||
|
this.viewport = options.viewport;
|
||||||
|
this.isViewerInPresentationMode = options.isViewerInPresentationMode;
|
||||||
|
|
||||||
|
if(typeof PDFFindController === 'undefined') {
|
||||||
|
window.PDFFindController = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof this.lastScrollSource === 'undefined') {
|
||||||
|
this.lastScrollSource = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.beginLayout = function textLayerBuilderBeginLayout() {
|
||||||
|
this.textDivs = [];
|
||||||
|
this.renderingDone = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.endLayout = function textLayerBuilderEndLayout() {
|
||||||
|
this.layoutDone = true;
|
||||||
|
this.insertDivContent();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.renderLayer = function textLayerBuilderRenderLayer() {
|
||||||
|
var self = this;
|
||||||
|
var textDivs = this.textDivs;
|
||||||
|
var bidiTexts = this.textContent;
|
||||||
|
var textLayerDiv = this.textLayerDiv;
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// No point in rendering so many divs as it'd make the browser unusable
|
||||||
|
// even after the divs are rendered
|
||||||
|
var MAX_TEXT_DIVS_TO_RENDER = 100000;
|
||||||
|
if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (var i = 0, ii = textDivs.length; i < ii; i++) {
|
||||||
|
var textDiv = textDivs[i];
|
||||||
|
if ('isWhitespace' in textDiv.dataset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
|
||||||
|
var width = ctx.measureText(textDiv.textContent).width;
|
||||||
|
|
||||||
|
if (width > 0) {
|
||||||
|
textLayerFrag.appendChild(textDiv);
|
||||||
|
var textScale = textDiv.dataset.canvasWidth / width;
|
||||||
|
var rotation = textDiv.dataset.angle;
|
||||||
|
var transform = 'scale(' + textScale + ', 1)';
|
||||||
|
transform = 'rotate(' + rotation + 'deg) ' + transform;
|
||||||
|
CustomStyle.setProp('transform' , textDiv, transform);
|
||||||
|
CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textLayerDiv.appendChild(textLayerFrag);
|
||||||
|
this.renderingDone = true;
|
||||||
|
this.updateMatches();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
|
||||||
|
// Schedule renderLayout() if user has been scrolling, otherwise
|
||||||
|
// run it right away
|
||||||
|
var RENDER_DELAY = 200; // in ms
|
||||||
|
var self = this;
|
||||||
|
var lastScroll = this.lastScrollSource === null ?
|
||||||
|
0 : this.lastScrollSource.lastScroll;
|
||||||
|
|
||||||
|
if (Date.now() - lastScroll > RENDER_DELAY) {
|
||||||
|
// Render right away
|
||||||
|
this.renderLayer();
|
||||||
|
} else {
|
||||||
|
// Schedule
|
||||||
|
if (this.renderTimer)
|
||||||
|
clearTimeout(this.renderTimer);
|
||||||
|
this.renderTimer = setTimeout(function() {
|
||||||
|
self.setupRenderLayoutTimer();
|
||||||
|
}, RENDER_DELAY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.appendText = function textLayerBuilderAppendText(geom) {
|
||||||
|
var textDiv = document.createElement('div');
|
||||||
|
|
||||||
|
// vScale and hScale already contain the scaling to pixel units
|
||||||
|
var fontHeight = geom.fontSize * Math.abs(geom.vScale);
|
||||||
|
textDiv.dataset.canvasWidth = geom.canvasWidth * Math.abs(geom.hScale);
|
||||||
|
textDiv.dataset.fontName = geom.fontName;
|
||||||
|
textDiv.dataset.angle = geom.angle * (180 / Math.PI);
|
||||||
|
|
||||||
|
textDiv.style.fontSize = fontHeight + 'px';
|
||||||
|
textDiv.style.fontFamily = geom.fontFamily;
|
||||||
|
var fontAscent = geom.ascent ? geom.ascent * fontHeight :
|
||||||
|
geom.descent ? (1 + geom.descent) * fontHeight : fontHeight;
|
||||||
|
textDiv.style.left = (geom.x + (fontAscent * Math.sin(geom.angle))) + 'px';
|
||||||
|
textDiv.style.top = (geom.y - (fontAscent * Math.cos(geom.angle))) + 'px';
|
||||||
|
|
||||||
|
// The content of the div is set in the `setTextContent` function.
|
||||||
|
|
||||||
|
this.textDivs.push(textDiv);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.insertDivContent = function textLayerUpdateTextContent() {
|
||||||
|
// Only set the content of the divs once layout has finished, the content
|
||||||
|
// for the divs is available and content is not yet set on the divs.
|
||||||
|
if (!this.layoutDone || this.divContentDone || !this.textContent)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.divContentDone = true;
|
||||||
|
|
||||||
|
var textDivs = this.textDivs;
|
||||||
|
var bidiTexts = this.textContent;
|
||||||
|
|
||||||
|
for (var i = 0; i < bidiTexts.length; i++) {
|
||||||
|
var bidiText = bidiTexts[i];
|
||||||
|
var textDiv = textDivs[i];
|
||||||
|
if (!/\S/.test(bidiText.str)) {
|
||||||
|
textDiv.dataset.isWhitespace = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
textDiv.textContent = bidiText.str;
|
||||||
|
// TODO refactor text layer to use text content position
|
||||||
|
/**
|
||||||
|
* var arr = this.viewport.convertToViewportPoint(bidiText.x, bidiText.y);
|
||||||
|
* textDiv.style.left = arr[0] + 'px';
|
||||||
|
* textDiv.style.top = arr[1] + 'px';
|
||||||
|
*/
|
||||||
|
// bidiText.dir may be 'ttb' for vertical texts.
|
||||||
|
textDiv.dir = bidiText.dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setupRenderLayoutTimer();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
|
||||||
|
this.textContent = textContent;
|
||||||
|
this.insertDivContent();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.convertMatches = function textLayerBuilderConvertMatches(matches) {
|
||||||
|
var i = 0;
|
||||||
|
var iIndex = 0;
|
||||||
|
var bidiTexts = this.textContent;
|
||||||
|
var end = bidiTexts.length - 1;
|
||||||
|
var queryLen = PDFFindController === null ?
|
||||||
|
0 : PDFFindController.state.query.length;
|
||||||
|
|
||||||
|
var lastDivIdx = -1;
|
||||||
|
var pos;
|
||||||
|
|
||||||
|
var ret = [];
|
||||||
|
|
||||||
|
// Loop over all the matches.
|
||||||
|
for (var m = 0; m < matches.length; m++) {
|
||||||
|
var matchIdx = matches[m];
|
||||||
|
// # Calculate the begin position.
|
||||||
|
|
||||||
|
// Loop over the divIdxs.
|
||||||
|
while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
|
||||||
|
iIndex += bidiTexts[i].str.length;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Do proper handling here if something goes wrong.
|
||||||
|
if (i == bidiTexts.length) {
|
||||||
|
console.error('Could not find matching mapping');
|
||||||
|
}
|
||||||
|
|
||||||
|
var match = {
|
||||||
|
begin: {
|
||||||
|
divIdx: i,
|
||||||
|
offset: matchIdx - iIndex
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// # Calculate the end position.
|
||||||
|
matchIdx += queryLen;
|
||||||
|
|
||||||
|
// Somewhat same array as above, but use a > 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.renderMatches = function textLayerBuilder_renderMatches(matches) {
|
||||||
|
// Early exit if there is nothing to render.
|
||||||
|
if (matches.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bidiTexts = this.textContent;
|
||||||
|
var textDivs = this.textDivs;
|
||||||
|
var prevEnd = null;
|
||||||
|
var isSelectedPage = PDFFindController === null ?
|
||||||
|
false : (this.pageIdx === PDFFindController.selected.pageIdx);
|
||||||
|
|
||||||
|
var selectedMatchIdx = PDFFindController === null ?
|
||||||
|
-1 : PDFFindController.selected.matchIdx;
|
||||||
|
|
||||||
|
var highlightAll = PDFFindController === null ?
|
||||||
|
false : PDFFindController.state.highlightAll;
|
||||||
|
|
||||||
|
var infty = {
|
||||||
|
divIdx: -1,
|
||||||
|
offset: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
function beginText(begin, className) {
|
||||||
|
var divIdx = begin.divIdx;
|
||||||
|
var div = textDivs[divIdx];
|
||||||
|
div.textContent = '';
|
||||||
|
|
||||||
|
var content = bidiTexts[divIdx].str.substring(0, begin.offset);
|
||||||
|
var node = document.createTextNode(content);
|
||||||
|
if (className) {
|
||||||
|
var isSelected = isSelectedPage &&
|
||||||
|
divIdx === selectedMatchIdx;
|
||||||
|
var span = document.createElement('span');
|
||||||
|
span.className = className + (isSelected ? ' selected' : '');
|
||||||
|
span.appendChild(node);
|
||||||
|
div.appendChild(span);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
div.appendChild(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendText(from, to, className) {
|
||||||
|
var divIdx = from.divIdx;
|
||||||
|
var div = textDivs[divIdx];
|
||||||
|
|
||||||
|
var content = bidiTexts[divIdx].str.substring(from.offset, to.offset);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightDiv(divIdx, className) {
|
||||||
|
textDivs[divIdx].className = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
var i0 = selectedMatchIdx, i1 = i0 + 1, i;
|
||||||
|
|
||||||
|
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 (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 (isSelected && !this.isViewerInPresentationMode) {
|
||||||
|
scrollIntoView(textDivs[begin.divIdx], { top: FIND_SCROLL_OFFSET_TOP,
|
||||||
|
left: FIND_SCROLL_OFFSET_LEFT });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
appendText(prevEnd, infty);
|
||||||
|
}
|
||||||
|
// clears the divs and set the content until the begin point.
|
||||||
|
beginText(begin);
|
||||||
|
} else {
|
||||||
|
appendText(prevEnd, begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (begin.divIdx === end.divIdx) {
|
||||||
|
appendText(begin, end, 'highlight' + highlightSuffix);
|
||||||
|
} else {
|
||||||
|
appendText(begin, infty, 'highlight begin' + highlightSuffix);
|
||||||
|
for (var n = begin.divIdx + 1; n < end.divIdx; n++) {
|
||||||
|
highlightDiv(n, 'highlight middle' + highlightSuffix);
|
||||||
|
}
|
||||||
|
beginText(end, 'highlight end' + highlightSuffix);
|
||||||
|
}
|
||||||
|
prevEnd = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevEnd) {
|
||||||
|
appendText(prevEnd, infty);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateMatches = function textLayerUpdateMatches() {
|
||||||
|
// Only show matches, once all rendering is done.
|
||||||
|
if (!this.renderingDone)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Clear out all matches.
|
||||||
|
var matches = this.matches;
|
||||||
|
var textDivs = this.textDivs;
|
||||||
|
var bidiTexts = this.textContent;
|
||||||
|
var clearedUntilDivIdx = -1;
|
||||||
|
|
||||||
|
// Clear out all current matches.
|
||||||
|
for (var i = 0; i < matches.length; i++) {
|
||||||
|
var match = matches[i];
|
||||||
|
var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
|
||||||
|
for (var n = begin; n <= match.end.divIdx; n++) {
|
||||||
|
var div = textDivs[n];
|
||||||
|
div.textContent = bidiTexts[n].str;
|
||||||
|
div.className = '';
|
||||||
|
}
|
||||||
|
clearedUntilDivIdx = match.end.divIdx + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PDFFindController === null || !PDFFindController.active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Convert the matches on the page controller into the match format used
|
||||||
|
// for the textLayer.
|
||||||
|
this.matches = matches =
|
||||||
|
this.convertMatches(PDFFindController === null ?
|
||||||
|
[] : (PDFFindController.pageMatches[this.pageIdx] || []));
|
||||||
|
|
||||||
|
this.renderMatches(this.matches);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
270
attachment_preview/static/lib/ViewerJS/ui_utils.js
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
/* -*- 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';
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
this.bar.classList.add('hidden');
|
||||||
|
this.bar.removeAttribute('style');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return ProgressBar;
|
||||||
|
})();
|
||||||
|
|
||||||
|
var Cache = function cacheCache(size) {
|
||||||
|
var data = [];
|
||||||
|
this.push = function cachePush(view) {
|
||||||
|
var i = data.indexOf(view);
|
||||||
|
if (i >= 0)
|
||||||
|
data.splice(i);
|
||||||
|
data.push(view);
|
||||||
|
if (data.length > size)
|
||||||
|
data.shift().destroy();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
//#if !(FIREFOX || MOZCENTRAL || B2G)
|
||||||
|
var isLocalStorageEnabled = (function isLocalStorageEnabledClosure() {
|
||||||
|
// Feature test as per http://diveintohtml5.info/storage.html
|
||||||
|
// The additional localStorage call is to get around a FF quirk, see
|
||||||
|
// bug #495747 in bugzilla
|
||||||
|
try {
|
||||||
|
return ('localStorage' in window && window['localStorage'] !== null &&
|
||||||
|
localStorage);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
//#endif
|
801
attachment_preview/static/lib/ViewerJS/viewer.css
Normal file
@ -0,0 +1,801 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2012-2014 KO GmbH <copyright@kogmbh.com>
|
||||||
|
*
|
||||||
|
* @licstart
|
||||||
|
* This file is part of WebODF.
|
||||||
|
*
|
||||||
|
* WebODF is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License (GNU AGPL)
|
||||||
|
* as published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* WebODF is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with WebODF. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* @licend
|
||||||
|
*
|
||||||
|
* @source: http://www.webodf.org/
|
||||||
|
* @source: https://github.com/kogmbh/WebODF/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is a derivative from a part of Mozilla's PDF.js project. The
|
||||||
|
* original license header follows.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
* {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html > body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar > span,
|
||||||
|
.toolbarLabel,
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
font: message-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#titlebar {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
height: 32px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0px 1px 3px rgba(50, 50, 50, 0.75);
|
||||||
|
-moz-box-shadow: 0px 1px 3px rgba(50, 50, 50, 0.75);
|
||||||
|
box-shadow: 0px 1px 3px rgba(50, 50, 50, 0.75);
|
||||||
|
|
||||||
|
background-image: url(images/texture.png), linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99));
|
||||||
|
background-image: url(images/texture.png), -webkit-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99));
|
||||||
|
background-image: url(images/texture.png), -moz-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99));
|
||||||
|
background-image: url(images/texture.png), -ms-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99));
|
||||||
|
background-image: url(images/texture.png), -o-linear-gradient(rgba(69, 69, 69, .95), rgba(82, 82, 82, .99));
|
||||||
|
}
|
||||||
|
|
||||||
|
#titlebar a, #aboutDialog a, #titlebar a:visited, #aboutDialog a:visited {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#documentName {
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #F2F2F2;
|
||||||
|
line-height: 14px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
#documentName {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbarContainer {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
height: 32px;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
-webkit-box-shadow: 0px -1px 3px rgba(50, 50, 50, 0.75);
|
||||||
|
-moz-box-shadow: 0px -1px 3px rgba(50, 50, 50, 0.75);
|
||||||
|
box-shadow: 0px -1px 3px rgba(50, 50, 50, 0.75);
|
||||||
|
|
||||||
|
background-image: url(images/texture.png), linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95));
|
||||||
|
background-image: url(images/texture.png), -webkit-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95));
|
||||||
|
background-image: url(images/texture.png), -moz-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95));
|
||||||
|
background-image: url(images/texture.png), -ms-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95));
|
||||||
|
background-image: url(images/texture.png), -o-linear-gradient(rgba(82, 82, 82, .99), rgba(69, 69, 69, .95));
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbar {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toolbarMiddleContainer, #toolbarLeft {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir='ltr'] #toolbarLeft {
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] #toolbarRight {
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
|
html[dir='ltr'] #toolbarLeft,
|
||||||
|
html[dir='rtl'] #toolbarRight {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
html[dir='ltr'] #toolbarRight,
|
||||||
|
html[dir='rtl'] #toolbarLeft {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
html[dir='ltr'] #toolbarLeft > *,
|
||||||
|
html[dir='ltr'] #toolbarMiddle > *,
|
||||||
|
html[dir='ltr'] #toolbarRight > * {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] #toolbarLeft > *,
|
||||||
|
html[dir='rtl'] #toolbarMiddle > *,
|
||||||
|
html[dir='rtl'] #toolbarRight > * {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* outer/inner center provides horizontal center */
|
||||||
|
html[dir='ltr'] .outerCenter {
|
||||||
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
right: 50%;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] .outerCenter {
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
html[dir='ltr'] .innerCenter {
|
||||||
|
float: right;
|
||||||
|
position: relative;
|
||||||
|
right: -50%;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] .innerCenter {
|
||||||
|
float: left;
|
||||||
|
position: relative;
|
||||||
|
left: -50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir='ltr'] .splitToolbarButton {
|
||||||
|
margin: 3px 2px 4px 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] .splitToolbarButton {
|
||||||
|
margin: 3px 0 4px 2px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
html[dir='ltr'] .splitToolbarButton > .toolbarButton {
|
||||||
|
border-radius: 0;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] .splitToolbarButton > .toolbarButton {
|
||||||
|
border-radius: 0;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitToolbarButton.toggled .toolbarButton {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton {
|
||||||
|
border: 0 none;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
min-width: 32px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir='ltr'] .toolbarButton,
|
||||||
|
html[dir='ltr'] .dropdownToolbarButton {
|
||||||
|
margin: 3px 2px 4px 0;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] .toolbarButton,
|
||||||
|
html[dir='rtl'] .dropdownToolbarButton {
|
||||||
|
margin: 3px 0 4px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton:hover,
|
||||||
|
.toolbarButton:focus,
|
||||||
|
.dropdownToolbarButton {
|
||||||
|
background-color: hsla(0,0%,0%,.12);
|
||||||
|
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid hsla(0,0%,0%,.35);
|
||||||
|
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||||
|
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||||
|
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||||
|
0 1px 0 hsla(0,0%,100%,.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton:hover:active,
|
||||||
|
.dropdownToolbarButton:hover:active {
|
||||||
|
background-color: hsla(0,0%,0%,.2);
|
||||||
|
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45);
|
||||||
|
box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
|
||||||
|
0 0 1px hsla(0,0%,0%,.2) inset,
|
||||||
|
0 1px 0 hsla(0,0%,100%,.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitToolbarButton:hover > .toolbarButton,
|
||||||
|
.splitToolbarButton:focus > .toolbarButton,
|
||||||
|
.splitToolbarButton.toggled > .toolbarButton,
|
||||||
|
.toolbarButton.textButton {
|
||||||
|
background-color: hsla(0,0%,0%,.12);
|
||||||
|
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid hsla(0,0%,0%,.35);
|
||||||
|
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||||
|
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||||
|
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||||
|
0 1px 0 hsla(0,0%,100%,.05);
|
||||||
|
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-webkit-transition-duration: 150ms;
|
||||||
|
-webkit-transition-timing-function: ease;
|
||||||
|
-moz-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-moz-transition-duration: 150ms;
|
||||||
|
-moz-transition-timing-function: ease;
|
||||||
|
-ms-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-ms-transition-duration: 150ms;
|
||||||
|
-ms-transition-timing-function: ease;
|
||||||
|
-o-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-o-transition-duration: 150ms;
|
||||||
|
-o-transition-timing-function: ease;
|
||||||
|
transition-property: background-color, border-color, box-shadow;
|
||||||
|
transition-duration: 150ms;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
|
||||||
|
}
|
||||||
|
.splitToolbarButton > .toolbarButton:hover,
|
||||||
|
.splitToolbarButton > .toolbarButton:focus,
|
||||||
|
.dropdownToolbarButton:hover,
|
||||||
|
.toolbarButton.textButton:hover,
|
||||||
|
.toolbarButton.textButton:focus {
|
||||||
|
background-color: hsla(0,0%,0%,.2);
|
||||||
|
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||||
|
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||||
|
0 0 1px hsla(0,0%,0%,.05);
|
||||||
|
z-index: 199;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.splitToolbarButton:hover > .toolbarButton,
|
||||||
|
.splitToolbarButton:focus > .toolbarButton,
|
||||||
|
.splitToolbarButton.toggled > .toolbarButton,
|
||||||
|
.toolbarButton.textButton {
|
||||||
|
background-color: hsla(0,0%,0%,.12);
|
||||||
|
background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid hsla(0,0%,0%,.35);
|
||||||
|
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||||
|
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||||
|
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||||
|
0 1px 0 hsla(0,0%,100%,.05);
|
||||||
|
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-webkit-transition-duration: 150ms;
|
||||||
|
-webkit-transition-timing-function: ease;
|
||||||
|
-moz-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-moz-transition-duration: 150ms;
|
||||||
|
-moz-transition-timing-function: ease;
|
||||||
|
-ms-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-ms-transition-duration: 150ms;
|
||||||
|
-ms-transition-timing-function: ease;
|
||||||
|
-o-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-o-transition-duration: 150ms;
|
||||||
|
-o-transition-timing-function: ease;
|
||||||
|
transition-property: background-color, border-color, box-shadow;
|
||||||
|
transition-duration: 150ms;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
|
||||||
|
}
|
||||||
|
.splitToolbarButton > .toolbarButton:hover,
|
||||||
|
.splitToolbarButton > .toolbarButton:focus,
|
||||||
|
.dropdownToolbarButton:hover,
|
||||||
|
.toolbarButton.textButton:hover,
|
||||||
|
.toolbarButton.textButton:focus {
|
||||||
|
background-color: hsla(0,0%,0%,.2);
|
||||||
|
box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
|
||||||
|
0 0 1px hsla(0,0%,100%,.15) inset,
|
||||||
|
0 0 1px hsla(0,0%,0%,.05);
|
||||||
|
z-index: 199;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownToolbarButton {
|
||||||
|
border: 1px solid #333 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton,
|
||||||
|
.dropdownToolbarButton {
|
||||||
|
min-width: 16px;
|
||||||
|
padding: 2px 6px 2px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: hsl(0,0%,95%);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
-webkit-user-select:none;
|
||||||
|
-moz-user-select:none;
|
||||||
|
-ms-user-select:none;
|
||||||
|
/* Opera does not support user-select, use <... unselectable="on"> instead */
|
||||||
|
cursor: default;
|
||||||
|
-webkit-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-webkit-transition-duration: 150ms;
|
||||||
|
-webkit-transition-timing-function: ease;
|
||||||
|
-moz-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-moz-transition-duration: 150ms;
|
||||||
|
-moz-transition-timing-function: ease;
|
||||||
|
-ms-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-ms-transition-duration: 150ms;
|
||||||
|
-ms-transition-timing-function: ease;
|
||||||
|
-o-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-o-transition-duration: 150ms;
|
||||||
|
-o-transition-timing-function: ease;
|
||||||
|
transition-property: background-color, border-color, box-shadow;
|
||||||
|
transition-duration: 150ms;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir='ltr'] .toolbarButton,
|
||||||
|
html[dir='ltr'] .dropdownToolbarButton {
|
||||||
|
margin: 3px 2px 4px 0;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] .toolbarButton,
|
||||||
|
html[dir='rtl'] .dropdownToolbarButton {
|
||||||
|
margin: 3px 0 4px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitToolbarButton:hover > .splitToolbarButtonSeparator,
|
||||||
|
.splitToolbarButton.toggled > .splitToolbarButtonSeparator {
|
||||||
|
padding: 12px 0;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: 0 0 0 1px hsla(0,0%,100%,.03);
|
||||||
|
-webkit-transition-property: padding;
|
||||||
|
-webkit-transition-duration: 10ms;
|
||||||
|
-webkit-transition-timing-function: ease;
|
||||||
|
-moz-transition-property: padding;
|
||||||
|
-moz-transition-duration: 10ms;
|
||||||
|
-moz-transition-timing-function: ease;
|
||||||
|
-ms-transition-property: padding;
|
||||||
|
-ms-transition-duration: 10ms;
|
||||||
|
-ms-transition-timing-function: ease;
|
||||||
|
-o-transition-property: padding;
|
||||||
|
-o-transition-duration: 10ms;
|
||||||
|
-o-transition-timing-function: ease;
|
||||||
|
transition-property: padding;
|
||||||
|
transition-duration: 10ms;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton.toggled:hover:active,
|
||||||
|
.splitToolbarButton > .toolbarButton:hover:active {
|
||||||
|
background-color: hsla(0,0%,0%,.4);
|
||||||
|
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.5) hsla(0,0%,0%,.55);
|
||||||
|
box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset,
|
||||||
|
0 0 1px hsla(0,0%,0%,.3) inset,
|
||||||
|
0 1px 0 hsla(0,0%,100%,.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir='ltr'] .splitToolbarButton > .toolbarButton:first-child,
|
||||||
|
html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: -1px;
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
|
border-right-color: transparent;
|
||||||
|
}
|
||||||
|
html[dir='ltr'] .splitToolbarButton > .toolbarButton:last-child,
|
||||||
|
html[dir='rtl'] .splitToolbarButton > .toolbarButton:first-child {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
margin-left: -1px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
border-left-color: transparent;
|
||||||
|
}
|
||||||
|
.splitToolbarButtonSeparator {
|
||||||
|
padding: 8px 0;
|
||||||
|
width: 1px;
|
||||||
|
background-color: hsla(0,0%,00%,.5);
|
||||||
|
z-index: 99;
|
||||||
|
box-shadow: 0 0 0 1px hsla(0,0%,100%,.08);
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
html[dir='ltr'] .splitToolbarButtonSeparator {
|
||||||
|
float:left;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] .splitToolbarButtonSeparator {
|
||||||
|
float:right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownToolbarButton {
|
||||||
|
min-width: 120px;
|
||||||
|
max-width: 120px;
|
||||||
|
padding: 4px 2px 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: url(images/toolbarButton-menuArrows.png) no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownToolbarButton > select {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none; /* in the future this might matter, see bugzilla bug #649849 */
|
||||||
|
min-width: 140px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: hsl(0,0%,95%);
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
border:none;
|
||||||
|
background: rgba(0,0,0,0); /* Opera does not support 'transparent' <select> background */
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownToolbarButton > select > option {
|
||||||
|
background: hsl(0,0%,24%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pageWidthOption {
|
||||||
|
border-bottom: 1px rgba(255, 255, 255, .5) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html[dir='ltr'] .dropdownToolbarButton {
|
||||||
|
background-position: 95%;
|
||||||
|
}
|
||||||
|
html[dir='rtl'] .dropdownToolbarButton {
|
||||||
|
background-position: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.toolbarButton.fullscreen::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: url(images/toolbarButton-fullscreen.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton.presentation::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: url(images/toolbarButton-presentation.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton.download::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: url(images/toolbarButton-download.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton.about {
|
||||||
|
color: #F2F2F2;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 14px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
.toolbarButton.about::before {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton.zoomOut::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: url(images/toolbarButton-zoomOut.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton.zoomIn::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: url(images/toolbarButton-zoomIn.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton.pageUp::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: url(images/toolbarButton-pageUp.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarButton.pageDown::before {
|
||||||
|
display: inline-block;
|
||||||
|
content: url(images/toolbarButton-pageDown.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarField.pageNumber {
|
||||||
|
min-width: 16px;
|
||||||
|
text-align: right;
|
||||||
|
width: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarField {
|
||||||
|
padding: 3px 6px;
|
||||||
|
margin: 4px 0 4px 0;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: hsla(0,0%,100%,.09);
|
||||||
|
background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid hsla(0,0%,0%,.35);
|
||||||
|
border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
|
||||||
|
box-shadow: 0 1px 0 hsla(0,0%,0%,.05) inset,
|
||||||
|
0 1px 0 hsla(0,0%,100%,.05);
|
||||||
|
color: hsl(0,0%,95%);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
outline-style: none;
|
||||||
|
-moz-transition-property: background-color, border-color, box-shadow;
|
||||||
|
-moz-transition-duration: 150ms;
|
||||||
|
-moz-transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarField.pageNumber::-webkit-inner-spin-button,
|
||||||
|
.toolbarField.pageNumber::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarField:hover {
|
||||||
|
background-color: hsla(0,0%,100%,.11);
|
||||||
|
border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.43) hsla(0,0%,0%,.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarField:focus {
|
||||||
|
background-color: hsla(0,0%,100%,.15);
|
||||||
|
border-color: hsla(204,100%,65%,.8) hsla(204,100%,65%,.85) hsla(204,100%,65%,.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbarLabel {
|
||||||
|
min-width: 16px;
|
||||||
|
padding: 3px 6px 3px 2px;
|
||||||
|
margin: 4px 2px 4px 0;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 2px;
|
||||||
|
color: hsl(0,0%,85%);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
text-align: left;
|
||||||
|
-webkit-user-select:none;
|
||||||
|
-moz-user-select:none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvasContainer {
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
padding-top: 6px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
position: absolute;
|
||||||
|
top: 32px;
|
||||||
|
right: 0;
|
||||||
|
bottom: 32px;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
background-color: #888;
|
||||||
|
background-image: url(images/texture.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvasContainer.slideshow {
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvasContainer.slideshow > * {
|
||||||
|
margin: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentationMode {
|
||||||
|
top: 0 !important;
|
||||||
|
bottom: 0 !important;
|
||||||
|
background-color: black !important;
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
-webkit-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
-moz-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
-ms-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
-o-box-shadow: 0px 0px 7px rgba(0, 0, 0, 0.75);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hide the canvas overflow because otherwise the CSS-scaled 'sizer' child
|
||||||
|
* of the canvas will still advertise the original size in Firefox, causing
|
||||||
|
* strange scrollbar behavior.
|
||||||
|
*/
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sliderContainer {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlayNavigator {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
top: calc(50% - 50px);
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
z-index: 3;
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition: opacity 1s ease-out;
|
||||||
|
-moz-transition: opacity 1s ease-out;
|
||||||
|
transition: opacity 1s ease-out;
|
||||||
|
}
|
||||||
|
#previousPage {
|
||||||
|
float: left;
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
/* CSS triangle */
|
||||||
|
border-top: 50px solid transparent;
|
||||||
|
border-bottom: 50px solid transparent;
|
||||||
|
border-right: 50px solid black;
|
||||||
|
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
#nextPage {
|
||||||
|
float: right;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
/* CSS triangle */
|
||||||
|
border-top: 50px solid transparent;
|
||||||
|
border-bottom: 50px solid transparent;
|
||||||
|
border-left: 50px solid black;
|
||||||
|
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
#previousPage:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
#nextPage:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
#overlayCloseButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 3;
|
||||||
|
font-size: 35px;
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
opacity: 0.5;
|
||||||
|
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
-webkit-border-radius: 20px;
|
||||||
|
-moz-border-radius: 20px;
|
||||||
|
border-radius: 20px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#overlayCloseButton:active {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
#aboutDialogCentererTable {
|
||||||
|
display: table;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#aboutDialogCentererCell {
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align:center;
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
#aboutDialog {
|
||||||
|
width: 280px;
|
||||||
|
background-color: #666;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0px 1px 6px black;
|
||||||
|
padding: 5px;
|
||||||
|
font-style: sans-serif;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#aboutDialog h1 {
|
||||||
|
font-size: 25pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#aboutDialog p {
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#aboutDialog > * {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialogOverlay {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
z-index: 3;
|
||||||
|
background-color: rgba(0,0,0,.5);
|
||||||
|
overflow: auto;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-device-width: 800px) and (max-device-height: 800px) {
|
||||||
|
#canvasContainer {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlayNavigator {
|
||||||
|
height: 100px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
#nextPage, #previousPage {
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
#titlebar, #toolbarContainer {
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
background-image: none;
|
||||||
|
-webkit-transition: all 0.5s;
|
||||||
|
-moz-transition: all 0.5s;
|
||||||
|
transition: all 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#titlebar {
|
||||||
|
top: -32px;
|
||||||
|
}
|
||||||
|
#titlebar.viewer-touched {
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
#toolbarContainer {
|
||||||
|
bottom: -32px;
|
||||||
|
}
|
||||||
|
#toolbarContainer.viewer-touched {
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-touched {
|
||||||
|
display: block;
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#next, #previous {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
16
attachment_preview/static/lib/ViewerJS/viewer.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
function Viewer(c){function M(){var a,b,A,d,f;c&&(A=c.getPluginName(),d=c.getPluginVersion(),f=c.getPluginURL());a=document.createElement("div");a.id="aboutDialogCentererTable";b=document.createElement("div");b.id="aboutDialogCentererCell";n=document.createElement("div");n.id="aboutDialog";n.innerHTML='<h1>ViewerJS</h1><p>Open Source document viewer for webpages, built with HTML and JavaScript.</p><p>Learn more and get your own copy on the <a href="http://viewerjs.org/" target="_blank">ViewerJS website</a>.</p>'+
|
||||||
|
(c?'<p>Using the <a href = "'+f+'" target="_blank">'+A+'</a> (<span id = "pluginVersion">'+d+"</span>) plugin to show you this document.</p>":"")+'<p>Supported by <a href="http://nlnet.nl" target="_blank"><br><img src="images/nlnet.png" width="160" height="60" alt="NLnet Foundation"></a></p><p>Made by <a href="http://kogmbh.com" target="_blank"><br><img src="images/kogmbh.png" width="172" height="40" alt="KO GmbH"></a></p><button id = "aboutDialogCloseButton" class = "toolbarButton textButton">Close</button>';
|
||||||
|
u.appendChild(a);a.appendChild(b);b.appendChild(n);a=document.createElement("button");a.id="about";a.className="toolbarButton textButton about";a.title="About";a.innerHTML="ViewerJS";N.appendChild(a);a.addEventListener("click",function(){u.style.display="block"});document.getElementById("aboutDialogCloseButton").addEventListener("click",function(){u.style.display="none"})}function B(a){var b=O.options,c,d=!1,f;for(f=0;f<b.length;f+=1)c=b[f],c.value!==a?c.selected=!1:d=c.selected=!0;return d}function C(a,
|
||||||
|
c,d){a!==b.getZoomLevel()&&(b.setZoomLevel(a),d=document.createEvent("UIEvents"),d.initUIEvent("scalechange",!1,!1,window,0),d.scale=a,d.resetAutoSettings=c,window.dispatchEvent(d))}function D(){var a;if(c.onScroll)c.onScroll();c.getPageInView&&(a=c.getPageInView())&&(k=a,document.getElementById("pageNumber").value=a)}function E(a){window.clearTimeout(F);F=window.setTimeout(function(){D()},a)}function e(a,b,g){var e,f;if(e="custom"===a?parseFloat(document.getElementById("customScaleOption").textContent)/
|
||||||
|
100:parseFloat(a))C(e,!0,g);else{e=d.clientWidth-p;f=d.clientHeight-p;switch(a){case "page-actual":C(1,b,g);break;case "page-width":c.fitToWidth(e);break;case "page-height":c.fitToHeight(f);break;case "page-fit":c.fitToPage(e,f);break;case "auto":c.isSlideshow()?c.fitToPage(e+p,f+p):c.fitSmart(e)}B(a)}E(300)}function q(){l=!l;r&&!l&&b.togglePresentationMode()}function v(){s&&(w.className="viewer-touched",window.clearTimeout(G),G=window.setTimeout(function(){w.className=""},5E3))}function x(){h.classList.add("viewer-touched");
|
||||||
|
m.classList.add("viewer-touched");window.clearTimeout(H);H=window.setTimeout(function(){I()},5E3)}function I(){h.classList.remove("viewer-touched");m.classList.remove("viewer-touched")}function P(){h.classList.contains("viewer-touched")?I():x()}var b=this,p=40,r=!1,l=!1,J=!1,s=!1,y,g=document.getElementById("viewer"),d=document.getElementById("canvasContainer"),w=document.getElementById("overlayNavigator"),h=document.getElementById("titlebar"),m=document.getElementById("toolbarContainer"),K=document.getElementById("toolbarLeft"),
|
||||||
|
Q=document.getElementById("toolbarMiddleContainer"),O=document.getElementById("scaleSelect"),u=document.getElementById("dialogOverlay"),N=document.getElementById("toolbarRight"),n,L,t=[],k,F,G,H;this.initialize=function(){var a=String(document.location),z=a.indexOf("#"),a=a.substr(z+1);-1===z||0===a.length?console.log("Could not parse file path argument."):(y=a,L=y.replace(/^.*[\\\/]/,""),document.title=L,document.getElementById("documentName").innerHTML=document.title,c.onLoad=function(){document.getElementById("pluginVersion").innerHTML=
|
||||||
|
c.getPluginVersion();(s=c.isSlideshow())?(d.classList.add("slideshow"),K.style.visibility="visible"):(Q.style.visibility="visible",c.getPageInView&&(K.style.visibility="visible"));J=!0;t=c.getPages();document.getElementById("numPages").innerHTML="of "+t.length;b.showPage(1);e("auto");d.onscroll=D;E()},c.initialize(d,a))};this.showPage=function(a){0>=a?a=1:a>t.length&&(a=t.length);c.showPage(a);k=a;document.getElementById("pageNumber").value=k};this.showNextPage=function(){b.showPage(k+1)};this.showPreviousPage=
|
||||||
|
function(){b.showPage(k-1)};this.download=function(){var a=y.split("#")[0];window.open(a+"#viewer.action=download","_parent")};this.toggleFullScreen=function(){l?document.cancelFullScreen?document.cancelFullScreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.webkitCancelFullScreen?document.webkitCancelFullScreen():document.msExitFullscreen&&document.msExitFullscreen():g.requestFullScreen?g.requestFullScreen():g.mozRequestFullScreen?g.mozRequestFullScreen():g.webkitRequestFullScreen?
|
||||||
|
g.webkitRequestFullScreen():g.msRequestFullscreen&&g.msRequestFullscreen()};this.togglePresentationMode=function(){var a=document.getElementById("overlayCloseButton");r?(h.style.display=m.style.display="block",a.style.display="none",d.classList.remove("presentationMode"),d.onmouseup=function(){},d.oncontextmenu=function(){},d.onmousedown=function(){},e("auto"),s=c.isSlideshow()):(h.style.display=m.style.display="none",a.style.display="block",d.classList.add("presentationMode"),s=!0,d.onmousedown=
|
||||||
|
function(a){a.preventDefault()},d.oncontextmenu=function(a){a.preventDefault()},d.onmouseup=function(a){a.preventDefault();1===a.which?b.showNextPage():b.showPreviousPage()},e("page-fit"));r=!r};this.getZoomLevel=function(){return c.getZoomLevel()};this.setZoomLevel=function(a){c.setZoomLevel(a)};this.zoomOut=function(){var a=(b.getZoomLevel()/1.1).toFixed(2),a=Math.max(0.25,a);e(a,!0)};this.zoomIn=function(){var a=(1.1*b.getZoomLevel()).toFixed(2),a=Math.min(4,a);e(a,!0)};(function(){M();c&&(b.initialize(),
|
||||||
|
document.cancelFullScreen||document.mozCancelFullScreen||document.webkitCancelFullScreen||document.msExitFullscreen||(document.getElementById("fullscreen").style.visibility="hidden",document.getElementById("presentation").style.visibility="hidden"),document.getElementById("overlayCloseButton").addEventListener("click",b.toggleFullScreen),document.getElementById("fullscreen").addEventListener("click",b.toggleFullScreen),document.getElementById("presentation").addEventListener("click",function(){l||
|
||||||
|
b.toggleFullScreen();b.togglePresentationMode()}),document.addEventListener("fullscreenchange",q),document.addEventListener("webkitfullscreenchange",q),document.addEventListener("mozfullscreenchange",q),document.addEventListener("MSFullscreenChange",q),document.getElementById("download").addEventListener("click",function(){b.download()}),document.getElementById("zoomOut").addEventListener("click",function(){b.zoomOut()}),document.getElementById("zoomIn").addEventListener("click",function(){b.zoomIn()}),
|
||||||
|
document.getElementById("previous").addEventListener("click",function(){b.showPreviousPage()}),document.getElementById("next").addEventListener("click",function(){b.showNextPage()}),document.getElementById("previousPage").addEventListener("click",function(){b.showPreviousPage()}),document.getElementById("nextPage").addEventListener("click",function(){b.showNextPage()}),document.getElementById("pageNumber").addEventListener("change",function(){b.showPage(this.value)}),document.getElementById("scaleSelect").addEventListener("change",
|
||||||
|
function(){e(this.value)}),d.addEventListener("click",v),w.addEventListener("click",v),d.addEventListener("click",P),h.addEventListener("click",x),m.addEventListener("click",x),window.addEventListener("scalechange",function(a){var b=document.getElementById("customScaleOption"),c=B(String(a.scale));b.selected=!1;c||(b.textContent=Math.round(1E4*a.scale)/100+"%",b.selected=!0)},!0),window.addEventListener("resize",function(a){J&&(document.getElementById("pageWidthOption").selected||document.getElementById("pageAutoOption").selected)&&
|
||||||
|
e(document.getElementById("scaleSelect").value);v()}),window.addEventListener("keydown",function(a){var c=a.shiftKey;switch(a.keyCode){case 33:case 38:case 37:b.showPreviousPage();break;case 34:case 40:case 39:b.showNextPage();break;case 32:c?b.showPreviousPage():b.showNextPage()}}))})()};
|
631
attachment_preview/static/lib/ViewerJS/webodf.js
Normal file
26
attachment_preview/static/src/css/attachment_preview.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.openerp .oe_sidebar .oe_dropdown_menu li .oe-sidebar-attachment-preview
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
right: 18px;
|
||||||
|
top: 0px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.openerp .oe_sidebar .oe_dropdown_menu li:hover .oe-sidebar-attachment-preview
|
||||||
|
{
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
.openerp .oe_sidebar .oe_dropdown_menu li .oe-sidebar-attachment-preview:hover
|
||||||
|
{
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
background: #8786b7;
|
||||||
|
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
|
||||||
|
-moz-border-radius: 2px;
|
||||||
|
-webkit-border-radius: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.openerp .oe-binary-preview
|
||||||
|
{
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
BIN
attachment_preview/static/src/img/icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
242
attachment_preview/static/src/js/attachment_preview.js
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
//-*- coding: utf-8 -*-
|
||||||
|
//############################################################################
|
||||||
|
//
|
||||||
|
// OpenERP, Open Source Management Solution
|
||||||
|
// This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
//############################################################################
|
||||||
|
|
||||||
|
openerp.attachment_preview = function(instance)
|
||||||
|
{
|
||||||
|
var _t = instance.web._t;
|
||||||
|
openerp.attachment_preview.show_preview = function(
|
||||||
|
attachment_id, attachment_url, attachment_extension,
|
||||||
|
attachment_title)
|
||||||
|
{
|
||||||
|
var url = (window.location.origin || '') +
|
||||||
|
'/attachment_preview/static/lib/ViewerJS/index.html#' +
|
||||||
|
attachment_url.replace(window.location.origin, '') +
|
||||||
|
'&title=' + encodeURIComponent(attachment_title) +
|
||||||
|
'&ext=.' + encodeURIComponent(attachment_extension);
|
||||||
|
window.open(url);
|
||||||
|
};
|
||||||
|
openerp.attachment_preview.can_preview = function(extension)
|
||||||
|
{
|
||||||
|
return jQuery.inArray(
|
||||||
|
extension, ['odt', 'odp', 'ods', 'fodt', 'pdf']) > -1;
|
||||||
|
};
|
||||||
|
instance.web.Sidebar.include(
|
||||||
|
{
|
||||||
|
init: function(parent, form_view)
|
||||||
|
{
|
||||||
|
this.attachment_title_preview = function(name)
|
||||||
|
{
|
||||||
|
return _.str.sprintf(
|
||||||
|
instance.web._t("Preview attachment %s"), name);
|
||||||
|
};
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
on_attachments_loaded: function(attachments)
|
||||||
|
{
|
||||||
|
var result = this._super.apply(this, arguments);
|
||||||
|
this.$el.find('.oe-sidebar-attachment-preview')
|
||||||
|
.click(this.on_attachment_preview);
|
||||||
|
this.update_preview_buttons();
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
on_attachment_preview: function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
var self = this,
|
||||||
|
$target = jQuery(e.currentTarget),
|
||||||
|
attachment_id = parseInt($target.attr('data-id')),
|
||||||
|
attachment_url = $target.attr('data-url'),
|
||||||
|
attachment_extension = $target.attr('data-extension'),
|
||||||
|
attachment_title = $target.attr('original-title');
|
||||||
|
if(attachment_extension)
|
||||||
|
{
|
||||||
|
openerp.attachment_preview.show_preview(
|
||||||
|
attachment_id, attachment_url, attachment_extension,
|
||||||
|
attachment_title);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(new instance.web.Model('ir.attachment')).call(
|
||||||
|
'get_attachment_extension', [attachment_id], {})
|
||||||
|
.then(function(extension)
|
||||||
|
{
|
||||||
|
openerp.attachment_preview.show_preview(
|
||||||
|
attachment_id, attachment_url, extension);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update_preview_buttons: function()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
return (new instance.web.Model('ir.attachment')).call(
|
||||||
|
'get_attachment_extension',
|
||||||
|
[
|
||||||
|
this.$el.find('.oe-sidebar-attachment-preview')
|
||||||
|
.map(function()
|
||||||
|
{
|
||||||
|
return parseInt(jQuery(this).attr('data-id'));
|
||||||
|
})
|
||||||
|
.get()
|
||||||
|
],
|
||||||
|
{})
|
||||||
|
.then(function(extensions)
|
||||||
|
{
|
||||||
|
_(extensions).each(function(extension, id)
|
||||||
|
{
|
||||||
|
var $element = jQuery(
|
||||||
|
'a.oe-sidebar-attachment-preview[data-id="'
|
||||||
|
+ id + '"]');
|
||||||
|
if(openerp.attachment_preview.can_preview(extension))
|
||||||
|
{
|
||||||
|
$element.attr('data-extension', extension);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$element.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
instance.web.ListView.include(
|
||||||
|
{
|
||||||
|
reload_content: function()
|
||||||
|
{
|
||||||
|
var deferred = this._super.apply(this, arguments),
|
||||||
|
self = this;
|
||||||
|
deferred.then(function()
|
||||||
|
{
|
||||||
|
var $elements = self.$el.find('.oe-binary-preview');
|
||||||
|
if(!$elements.length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$elements.click(function(e)
|
||||||
|
{
|
||||||
|
e.stopPropagation();
|
||||||
|
var $target = jQuery(e.currentTarget),
|
||||||
|
attachment_id = parseInt($target.attr('data-id')),
|
||||||
|
attachment_extension = $target.attr('data-extension');
|
||||||
|
openerp.attachment_preview.show_preview(
|
||||||
|
attachment_id,
|
||||||
|
$target.siblings('a').attr('href'),
|
||||||
|
attachment_extension,
|
||||||
|
_t('Preview'));
|
||||||
|
});
|
||||||
|
return (new instance.web.Model('ir.attachment')).call(
|
||||||
|
'get_binary_extension',
|
||||||
|
[
|
||||||
|
$elements.attr('data-model'),
|
||||||
|
$elements
|
||||||
|
.map(function()
|
||||||
|
{
|
||||||
|
return parseInt(jQuery(this).attr('data-id'));
|
||||||
|
})
|
||||||
|
.get(),
|
||||||
|
$elements.attr('data-field'),
|
||||||
|
$elements.attr('data-filename'),
|
||||||
|
],
|
||||||
|
{})
|
||||||
|
.then(function(extensions)
|
||||||
|
{
|
||||||
|
_(extensions).each(function(extension, id)
|
||||||
|
{
|
||||||
|
var $element = $elements.filter(
|
||||||
|
'[data-id="' + id + '"]');
|
||||||
|
if(openerp.attachment_preview.can_preview(extension))
|
||||||
|
{
|
||||||
|
$element.attr('data-extension', extension);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$element.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return deferred;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
instance.web.list.Binary.include(
|
||||||
|
{
|
||||||
|
_format: function (row_data, options)
|
||||||
|
{
|
||||||
|
var link = this._super.apply(this, arguments);
|
||||||
|
link += _.template(
|
||||||
|
'<img class="oe-binary-preview" alt="<%-preview_text%>" data-id="<%-preview_id%>" data-model="<%-preview_model%>" data-field="<%-preview_field%>" data-filename="<%-preview_filename%>" src="/web/static/src/img/icons/gtk-print-preview.png" />',
|
||||||
|
{
|
||||||
|
preview_id: options.id,
|
||||||
|
preview_text: _t('Preview'),
|
||||||
|
preview_model: options.model,
|
||||||
|
preview_field: this.id,
|
||||||
|
preview_filename: this.filename || '',
|
||||||
|
});
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
instance.web.form.FieldBinaryFile.include(
|
||||||
|
{
|
||||||
|
render_value: function()
|
||||||
|
{
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
if(this.get("effective_readonly") && this.get('value'))
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
(new instance.web.Model('ir.attachment')).call(
|
||||||
|
'get_binary_extension', [
|
||||||
|
this.view.dataset.model,
|
||||||
|
this.view.datarecord.id ? [this.view.datarecord.id] : [],
|
||||||
|
this.name,
|
||||||
|
this.node.attrs.filename,
|
||||||
|
],
|
||||||
|
{})
|
||||||
|
.then(function(extensions)
|
||||||
|
{
|
||||||
|
_(extensions).each(function(extension)
|
||||||
|
{
|
||||||
|
var $element = self.$el.find('.oe-binary-preview');
|
||||||
|
if(openerp.attachment_preview.can_preview(extension))
|
||||||
|
{
|
||||||
|
$element.click(function()
|
||||||
|
{
|
||||||
|
openerp.attachment_preview.show_preview(
|
||||||
|
null,
|
||||||
|
_.str.sprintf(
|
||||||
|
'/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d',
|
||||||
|
instance.session.session_id,
|
||||||
|
self.view.dataset.model,
|
||||||
|
self.name,
|
||||||
|
self.view.datarecord.id),
|
||||||
|
extension,
|
||||||
|
self.view.datarecord[self.node.attrs.filename]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$element.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
40
attachment_preview/static/src/js/viewerjs_tweaks.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
//-*- coding: utf-8 -*-
|
||||||
|
//############################################################################
|
||||||
|
//
|
||||||
|
// OpenERP, Open Source Management Solution
|
||||||
|
// This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
//############################################################################
|
||||||
|
|
||||||
|
// This file contains tweaks for viewerjs itself and is not meant to be run in
|
||||||
|
// OpenERP's context
|
||||||
|
var original_loadDocument = loadDocument;
|
||||||
|
loadDocument = function(documentUrl)
|
||||||
|
{
|
||||||
|
original_loadDocument.apply(this, arguments);
|
||||||
|
var original_onload = window.onload;
|
||||||
|
window.onload = function()
|
||||||
|
{
|
||||||
|
original_onload();
|
||||||
|
var matches = (/&title=([^&]+)&/).exec(window.location.hash);
|
||||||
|
if(matches && matches.length > 1)
|
||||||
|
{
|
||||||
|
document.title = decodeURIComponent(matches[1]);
|
||||||
|
document.getElementById('documentName').innerHTML = document.title;
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
15
attachment_preview/static/src/xml/attachment_preview.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates>
|
||||||
|
<t t-extend="Sidebar">
|
||||||
|
<t t-jquery="a.oe_sidebar_delete_item" t-operation="before">
|
||||||
|
<a t-if="section.name == 'files' and !item.callback and item.url" href="#" class="oe-sidebar-attachment-preview" t-att-data-id="item.id" t-att-data-url="item.url" t-attf-title="Preview #{item.name}">
|
||||||
|
<img t-att-src='_s + "/web/static/src/img/icons/gtk-print-preview.png"' width="12" height="12" border="0"/>
|
||||||
|
</a>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
<t t-extend="FieldBinaryFile">
|
||||||
|
<t t-jquery="a.oe_form_uri" t-operation="after">
|
||||||
|
<img class="oe-binary-preview" t-att-src='_s + "/web/static/src/img/icons/gtk-print-preview.png"'/>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</templates>
|