[ADD] preliminary port of attachment_preview
46
attachment_preview/README.rst
Normal file
@ -0,0 +1,46 @@
|
||||
Preview attachments
|
||||
===================
|
||||
|
||||
This addon allows to preview attachments supported by http://viewerjs.org.
|
||||
|
||||
Currently, that's most Libreoffice files and PDFs.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
For filetype recognition, you'll get the best results by installing
|
||||
``python-magic``::
|
||||
|
||||
sudo apt-get install python-magic
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
The module adds a little print preview icon right of download links for
|
||||
attachments or binary fields.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Addon icon
|
||||
----------
|
||||
|
||||
* courtesy of http://commons.wikimedia.org/wiki/Crystal_Clear
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Holger Brunn <hbrunn@therp.nl>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
.. image:: http://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: http://odoo-community.org
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
|
||||
|
||||
To contribute to this module, please visit http://odoo-community.org.
|
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
|
46
attachment_preview/__openerp__.py
Normal file
@ -0,0 +1,46 @@
|
||||
# -*- 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.1",
|
||||
"author": "Therp BV",
|
||||
"license": "AGPL-3",
|
||||
"complexity": "normal",
|
||||
'summary': 'Preview attachments supported by Viewer.js',
|
||||
"category": "Knowledge Management",
|
||||
"depends": [
|
||||
'web',
|
||||
],
|
||||
"data": [
|
||||
"view/attachment_preview.xml",
|
||||
],
|
||||
"qweb": [
|
||||
'static/src/xml/attachment_preview.xml',
|
||||
],
|
||||
"test": [
|
||||
],
|
||||
"auto_install": False,
|
||||
"installable": True,
|
||||
"application": False,
|
||||
"external_dependencies": {
|
||||
'python': [],
|
||||
},
|
||||
}
|
23
attachment_preview/i18n/attachment_preview.pot
Normal file
@ -0,0 +1,23 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 8.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-15 11:04+0000\n"
|
||||
"PO-Revision-Date: 2015-01-15 11:04+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:multiple:42
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr ""
|
23
attachment_preview/i18n/nl.po
Normal file
@ -0,0 +1,23 @@
|
||||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * attachment_preview
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 8.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-01-15 11:04+0000\n"
|
||||
"PO-Revision-Date: 2015-01-15 11:04+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: attachment_preview
|
||||
#. openerp-web
|
||||
#: code:multiple:42
|
||||
#, python-format
|
||||
msgid "Preview %s"
|
||||
msgstr "Voorbeeld %s"
|
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)
|
BIN
attachment_preview/static/description/icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
574
attachment_preview/static/lib/ViewerJS/compatibility.js
Normal file
@ -0,0 +1,574 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* globals VBArray, PDFJS */
|
||||
|
||||
'use strict';
|
||||
|
||||
// Initializing PDFJS global object here, it case if we need to change/disable
|
||||
// some PDF.js features, e.g. range requests
|
||||
if (typeof PDFJS === 'undefined') {
|
||||
(typeof window !== 'undefined' ? window : this).PDFJS = {};
|
||||
}
|
||||
|
||||
// Checking if the typed arrays are supported
|
||||
// Support: iOS<6.0 (subarray), IE<10, Android<4.0
|
||||
(function checkTypedArrayCompatibility() {
|
||||
if (typeof Uint8Array !== 'undefined') {
|
||||
// Support: iOS<6.0
|
||||
if (typeof Uint8Array.prototype.subarray === 'undefined') {
|
||||
Uint8Array.prototype.subarray = function subarray(start, end) {
|
||||
return new Uint8Array(this.slice(start, end));
|
||||
};
|
||||
Float32Array.prototype.subarray = function subarray(start, end) {
|
||||
return new Float32Array(this.slice(start, end));
|
||||
};
|
||||
}
|
||||
|
||||
// Support: Android<4.1
|
||||
if (typeof Float64Array === 'undefined') {
|
||||
window.Float64Array = Float32Array;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
function subarray(start, end) {
|
||||
return new TypedArray(this.slice(start, end));
|
||||
}
|
||||
|
||||
function setArrayOffset(array, offset) {
|
||||
if (arguments.length < 2) {
|
||||
offset = 0;
|
||||
}
|
||||
for (var i = 0, n = array.length; i < n; ++i, ++offset) {
|
||||
this[offset] = array[i] & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
function TypedArray(arg1) {
|
||||
var result, i, n;
|
||||
if (typeof arg1 === 'number') {
|
||||
result = [];
|
||||
for (i = 0; i < arg1; ++i) {
|
||||
result[i] = 0;
|
||||
}
|
||||
} else if ('slice' in arg1) {
|
||||
result = arg1.slice(0);
|
||||
} else {
|
||||
result = [];
|
||||
for (i = 0, n = arg1.length; i < n; ++i) {
|
||||
result[i] = arg1[i];
|
||||
}
|
||||
}
|
||||
|
||||
result.subarray = subarray;
|
||||
result.buffer = result;
|
||||
result.byteLength = result.length;
|
||||
result.set = setArrayOffset;
|
||||
|
||||
if (typeof arg1 === 'object' && arg1.buffer) {
|
||||
result.buffer = arg1.buffer;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
window.Uint8Array = TypedArray;
|
||||
window.Int8Array = TypedArray;
|
||||
|
||||
// we don't need support for set, byteLength for 32-bit array
|
||||
// so we can use the TypedArray as well
|
||||
window.Uint32Array = TypedArray;
|
||||
window.Int32Array = TypedArray;
|
||||
window.Uint16Array = TypedArray;
|
||||
window.Float32Array = TypedArray;
|
||||
window.Float64Array = TypedArray;
|
||||
})();
|
||||
|
||||
// URL = URL || webkitURL
|
||||
// Support: Safari<7, Android 4.2+
|
||||
(function normalizeURLObject() {
|
||||
if (!window.URL) {
|
||||
window.URL = window.webkitURL;
|
||||
}
|
||||
})();
|
||||
|
||||
// Object.defineProperty()?
|
||||
// Support: Android<4.0, Safari<5.1
|
||||
(function checkObjectDefinePropertyCompatibility() {
|
||||
if (typeof Object.defineProperty !== 'undefined') {
|
||||
var definePropertyPossible = true;
|
||||
try {
|
||||
// some browsers (e.g. safari) cannot use defineProperty() on DOM objects
|
||||
// and thus the native version is not sufficient
|
||||
Object.defineProperty(new Image(), 'id', { value: 'test' });
|
||||
// ... another test for android gb browser for non-DOM objects
|
||||
var Test = function Test() {};
|
||||
Test.prototype = { get id() { } };
|
||||
Object.defineProperty(new Test(), 'id',
|
||||
{ value: '', configurable: true, enumerable: true, writable: false });
|
||||
} catch (e) {
|
||||
definePropertyPossible = false;
|
||||
}
|
||||
if (definePropertyPossible) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty = function objectDefineProperty(obj, name, def) {
|
||||
delete obj[name];
|
||||
if ('get' in def) {
|
||||
obj.__defineGetter__(name, def['get']);
|
||||
}
|
||||
if ('set' in def) {
|
||||
obj.__defineSetter__(name, def['set']);
|
||||
}
|
||||
if ('value' in def) {
|
||||
obj.__defineSetter__(name, function objectDefinePropertySetter(value) {
|
||||
this.__defineGetter__(name, function objectDefinePropertyGetter() {
|
||||
return value;
|
||||
});
|
||||
return value;
|
||||
});
|
||||
obj[name] = def.value;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
// No XMLHttpRequest#response?
|
||||
// Support: IE<11, Android <4.0
|
||||
(function checkXMLHttpRequestResponseCompatibility() {
|
||||
var xhrPrototype = XMLHttpRequest.prototype;
|
||||
var xhr = new XMLHttpRequest();
|
||||
if (!('overrideMimeType' in xhr)) {
|
||||
// IE10 might have response, but not overrideMimeType
|
||||
// Support: IE10
|
||||
Object.defineProperty(xhrPrototype, 'overrideMimeType', {
|
||||
value: function xmlHttpRequestOverrideMimeType(mimeType) {}
|
||||
});
|
||||
}
|
||||
if ('responseType' in xhr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The worker will be using XHR, so we can save time and disable worker.
|
||||
PDFJS.disableWorker = true;
|
||||
|
||||
Object.defineProperty(xhrPrototype, 'responseType', {
|
||||
get: function xmlHttpRequestGetResponseType() {
|
||||
return this._responseType || 'text';
|
||||
},
|
||||
set: function xmlHttpRequestSetResponseType(value) {
|
||||
if (value === 'text' || value === 'arraybuffer') {
|
||||
this._responseType = value;
|
||||
if (value === 'arraybuffer' &&
|
||||
typeof this.overrideMimeType === 'function') {
|
||||
this.overrideMimeType('text/plain; charset=x-user-defined');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Support: IE9
|
||||
if (typeof VBArray !== 'undefined') {
|
||||
Object.defineProperty(xhrPrototype, 'response', {
|
||||
get: function xmlHttpRequestResponseGet() {
|
||||
if (this.responseType === 'arraybuffer') {
|
||||
return new Uint8Array(new VBArray(this.responseBody).toArray());
|
||||
} else {
|
||||
return this.responseText;
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(xhrPrototype, 'response', {
|
||||
get: function xmlHttpRequestResponseGet() {
|
||||
if (this.responseType !== 'arraybuffer') {
|
||||
return this.responseText;
|
||||
}
|
||||
var text = this.responseText;
|
||||
var i, n = text.length;
|
||||
var result = new Uint8Array(n);
|
||||
for (i = 0; i < n; ++i) {
|
||||
result[i] = text.charCodeAt(i) & 0xFF;
|
||||
}
|
||||
return result.buffer;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// window.btoa (base64 encode function) ?
|
||||
// Support: IE<10
|
||||
(function checkWindowBtoaCompatibility() {
|
||||
if ('btoa' in window) {
|
||||
return;
|
||||
}
|
||||
|
||||
var digits =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
|
||||
window.btoa = function windowBtoa(chars) {
|
||||
var buffer = '';
|
||||
var i, n;
|
||||
for (i = 0, n = chars.length; i < n; i += 3) {
|
||||
var b1 = chars.charCodeAt(i) & 0xFF;
|
||||
var b2 = chars.charCodeAt(i + 1) & 0xFF;
|
||||
var b3 = chars.charCodeAt(i + 2) & 0xFF;
|
||||
var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
|
||||
var d3 = i + 1 < n ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
|
||||
var d4 = i + 2 < n ? (b3 & 0x3F) : 64;
|
||||
buffer += (digits.charAt(d1) + digits.charAt(d2) +
|
||||
digits.charAt(d3) + digits.charAt(d4));
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
})();
|
||||
|
||||
// window.atob (base64 encode function)?
|
||||
// Support: IE<10
|
||||
(function checkWindowAtobCompatibility() {
|
||||
if ('atob' in window) {
|
||||
return;
|
||||
}
|
||||
|
||||
// https://github.com/davidchambers/Base64.js
|
||||
var digits =
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
|
||||
window.atob = function (input) {
|
||||
input = input.replace(/=+$/, '');
|
||||
if (input.length % 4 === 1) {
|
||||
throw new Error('bad atob input');
|
||||
}
|
||||
for (
|
||||
// initialize result and counters
|
||||
var bc = 0, bs, buffer, idx = 0, output = '';
|
||||
// get next character
|
||||
buffer = input.charAt(idx++);
|
||||
// character found in table?
|
||||
// initialize bit storage and add its ascii value
|
||||
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
|
||||
// and if not first of each 4 characters,
|
||||
// convert the first 8 bits to one ascii character
|
||||
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
|
||||
) {
|
||||
// try to find character in table (0-63, not found => -1)
|
||||
buffer = digits.indexOf(buffer);
|
||||
}
|
||||
return output;
|
||||
};
|
||||
})();
|
||||
|
||||
// Function.prototype.bind?
|
||||
// Support: Android<4.0, iOS<6.0
|
||||
(function checkFunctionPrototypeBindCompatibility() {
|
||||
if (typeof Function.prototype.bind !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
Function.prototype.bind = function functionPrototypeBind(obj) {
|
||||
var fn = this, headArgs = Array.prototype.slice.call(arguments, 1);
|
||||
var bound = function functionPrototypeBindBound() {
|
||||
var args = headArgs.concat(Array.prototype.slice.call(arguments));
|
||||
return fn.apply(obj, args);
|
||||
};
|
||||
return bound;
|
||||
};
|
||||
})();
|
||||
|
||||
// HTMLElement dataset property
|
||||
// Support: IE<11, Safari<5.1, Android<4.0
|
||||
(function checkDatasetProperty() {
|
||||
var div = document.createElement('div');
|
||||
if ('dataset' in div) {
|
||||
return; // dataset property exists
|
||||
}
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'dataset', {
|
||||
get: function() {
|
||||
if (this._dataset) {
|
||||
return this._dataset;
|
||||
}
|
||||
|
||||
var dataset = {};
|
||||
for (var j = 0, jj = this.attributes.length; j < jj; j++) {
|
||||
var attribute = this.attributes[j];
|
||||
if (attribute.name.substring(0, 5) !== 'data-') {
|
||||
continue;
|
||||
}
|
||||
var key = attribute.name.substring(5).replace(/\-([a-z])/g,
|
||||
function(all, ch) {
|
||||
return ch.toUpperCase();
|
||||
});
|
||||
dataset[key] = attribute.value;
|
||||
}
|
||||
|
||||
Object.defineProperty(this, '_dataset', {
|
||||
value: dataset,
|
||||
writable: false,
|
||||
enumerable: false
|
||||
});
|
||||
return dataset;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
|
||||
// HTMLElement classList property
|
||||
// Support: IE<10, Android<4.0, iOS<5.0
|
||||
(function checkClassListProperty() {
|
||||
var div = document.createElement('div');
|
||||
if ('classList' in div) {
|
||||
return; // classList property exists
|
||||
}
|
||||
|
||||
function changeList(element, itemName, add, remove) {
|
||||
var s = element.className || '';
|
||||
var list = s.split(/\s+/g);
|
||||
if (list[0] === '') {
|
||||
list.shift();
|
||||
}
|
||||
var index = list.indexOf(itemName);
|
||||
if (index < 0 && add) {
|
||||
list.push(itemName);
|
||||
}
|
||||
if (index >= 0 && remove) {
|
||||
list.splice(index, 1);
|
||||
}
|
||||
element.className = list.join(' ');
|
||||
return (index >= 0);
|
||||
}
|
||||
|
||||
var classListPrototype = {
|
||||
add: function(name) {
|
||||
changeList(this.element, name, true, false);
|
||||
},
|
||||
contains: function(name) {
|
||||
return changeList(this.element, name, false, false);
|
||||
},
|
||||
remove: function(name) {
|
||||
changeList(this.element, name, false, true);
|
||||
},
|
||||
toggle: function(name) {
|
||||
changeList(this.element, name, true, true);
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'classList', {
|
||||
get: function() {
|
||||
if (this._classList) {
|
||||
return this._classList;
|
||||
}
|
||||
|
||||
var classList = Object.create(classListPrototype, {
|
||||
element: {
|
||||
value: this,
|
||||
writable: false,
|
||||
enumerable: true
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, '_classList', {
|
||||
value: classList,
|
||||
writable: false,
|
||||
enumerable: false
|
||||
});
|
||||
return classList;
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
})();
|
||||
|
||||
// Check console compatibility
|
||||
// In older IE versions the console object is not available
|
||||
// unless console is open.
|
||||
// Support: IE<10
|
||||
(function checkConsoleCompatibility() {
|
||||
if (!('console' in window)) {
|
||||
window.console = {
|
||||
log: function() {},
|
||||
error: function() {},
|
||||
warn: function() {}
|
||||
};
|
||||
} else if (!('bind' in console.log)) {
|
||||
// native functions in IE9 might not have bind
|
||||
console.log = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.log);
|
||||
console.error = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.error);
|
||||
console.warn = (function(fn) {
|
||||
return function(msg) { return fn(msg); };
|
||||
})(console.warn);
|
||||
}
|
||||
})();
|
||||
|
||||
// Check onclick compatibility in Opera
|
||||
// Support: Opera<15
|
||||
(function checkOnClickCompatibility() {
|
||||
// workaround for reported Opera bug DSK-354448:
|
||||
// onclick fires on disabled buttons with opaque content
|
||||
function ignoreIfTargetDisabled(event) {
|
||||
if (isDisabled(event.target)) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
function isDisabled(node) {
|
||||
return node.disabled || (node.parentNode && isDisabled(node.parentNode));
|
||||
}
|
||||
if (navigator.userAgent.indexOf('Opera') !== -1) {
|
||||
// use browser detection since we cannot feature-check this bug
|
||||
document.addEventListener('click', ignoreIfTargetDisabled, true);
|
||||
}
|
||||
})();
|
||||
|
||||
// Checks if possible to use URL.createObjectURL()
|
||||
// Support: IE
|
||||
(function checkOnBlobSupport() {
|
||||
// sometimes IE loosing the data created with createObjectURL(), see #3977
|
||||
if (navigator.userAgent.indexOf('Trident') >= 0) {
|
||||
PDFJS.disableCreateObjectURL = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Checks if navigator.language is supported
|
||||
(function checkNavigatorLanguage() {
|
||||
if ('language' in navigator &&
|
||||
/^[a-z]+(-[A-Z]+)?$/.test(navigator.language)) {
|
||||
return;
|
||||
}
|
||||
function formatLocale(locale) {
|
||||
var split = locale.split(/[-_]/);
|
||||
split[0] = split[0].toLowerCase();
|
||||
if (split.length > 1) {
|
||||
split[1] = split[1].toUpperCase();
|
||||
}
|
||||
return split.join('-');
|
||||
}
|
||||
var language = navigator.language || navigator.userLanguage || 'en-US';
|
||||
PDFJS.locale = formatLocale(language);
|
||||
})();
|
||||
|
||||
(function checkRangeRequests() {
|
||||
// Safari has issues with cached range requests see:
|
||||
// https://github.com/mozilla/pdf.js/issues/3260
|
||||
// Last tested with version 6.0.4.
|
||||
// Support: Safari 6.0+
|
||||
var isSafari = Object.prototype.toString.call(
|
||||
window.HTMLElement).indexOf('Constructor') > 0;
|
||||
|
||||
// Older versions of Android (pre 3.0) has issues with range requests, see:
|
||||
// https://github.com/mozilla/pdf.js/issues/3381.
|
||||
// Make sure that we only match webkit-based Android browsers,
|
||||
// since Firefox/Fennec works as expected.
|
||||
// Support: Android<3.0
|
||||
var regex = /Android\s[0-2][^\d]/;
|
||||
var isOldAndroid = regex.test(navigator.userAgent);
|
||||
|
||||
if (isSafari || isOldAndroid) {
|
||||
PDFJS.disableRange = true;
|
||||
PDFJS.disableStream = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Check if the browser supports manipulation of the history.
|
||||
// Support: IE<10, Android<4.2
|
||||
(function checkHistoryManipulation() {
|
||||
// Android 2.x has so buggy pushState support that it was removed in
|
||||
// Android 3.0 and restored as late as in Android 4.2.
|
||||
// Support: Android 2.x
|
||||
if (!history.pushState || navigator.userAgent.indexOf('Android 2.') >= 0) {
|
||||
PDFJS.disableHistory = true;
|
||||
}
|
||||
})();
|
||||
|
||||
// Support: IE<11, Chrome<21, Android<4.4, Safari<6
|
||||
(function checkSetPresenceInImageData() {
|
||||
// IE < 11 will use window.CanvasPixelArray which lacks set function.
|
||||
if (window.CanvasPixelArray) {
|
||||
if (typeof window.CanvasPixelArray.prototype.set !== 'function') {
|
||||
window.CanvasPixelArray.prototype.set = function(arr) {
|
||||
for (var i = 0, ii = this.length; i < ii; i++) {
|
||||
this[i] = arr[i];
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Old Chrome and Android use an inaccessible CanvasPixelArray prototype.
|
||||
// Because we cannot feature detect it, we rely on user agent parsing.
|
||||
var polyfill = false, versionMatch;
|
||||
if (navigator.userAgent.indexOf('Chrom') >= 0) {
|
||||
versionMatch = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
|
||||
// Chrome < 21 lacks the set function.
|
||||
polyfill = versionMatch && parseInt(versionMatch[2]) < 21;
|
||||
} else if (navigator.userAgent.indexOf('Android') >= 0) {
|
||||
// Android < 4.4 lacks the set function.
|
||||
// Android >= 4.4 will contain Chrome in the user agent,
|
||||
// thus pass the Chrome check above and not reach this block.
|
||||
polyfill = /Android\s[0-4][^\d]/g.test(navigator.userAgent);
|
||||
} else if (navigator.userAgent.indexOf('Safari') >= 0) {
|
||||
versionMatch = navigator.userAgent.
|
||||
match(/Version\/([0-9]+)\.([0-9]+)\.([0-9]+) Safari\//);
|
||||
// Safari < 6 lacks the set function.
|
||||
polyfill = versionMatch && parseInt(versionMatch[1]) < 6;
|
||||
}
|
||||
|
||||
if (polyfill) {
|
||||
var contextPrototype = window.CanvasRenderingContext2D.prototype;
|
||||
contextPrototype._createImageData = contextPrototype.createImageData;
|
||||
contextPrototype.createImageData = function(w, h) {
|
||||
var imageData = this._createImageData(w, h);
|
||||
imageData.data.set = function(arr) {
|
||||
for (var i = 0, ii = this.length; i < ii; i++) {
|
||||
this[i] = arr[i];
|
||||
}
|
||||
};
|
||||
return imageData;
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Support: IE<10, Android<4.0, iOS
|
||||
(function checkRequestAnimationFrame() {
|
||||
function fakeRequestAnimationFrame(callback) {
|
||||
window.setTimeout(callback, 20);
|
||||
}
|
||||
|
||||
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
|
||||
if (isIOS) {
|
||||
// requestAnimationFrame on iOS is broken, replacing with fake one.
|
||||
window.requestAnimationFrame = fakeRequestAnimationFrame;
|
||||
return;
|
||||
}
|
||||
if ('requestAnimationFrame' in window) {
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame =
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
fakeRequestAnimationFrame;
|
||||
})();
|
||||
|
||||
(function checkCanvasSizeLimitation() {
|
||||
var isIOS = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
|
||||
var isAndroid = /Android/g.test(navigator.userAgent);
|
||||
if (isIOS || isAndroid) {
|
||||
// 5MP
|
||||
PDFJS.maxCanvasPixels = 5242880;
|
||||
}
|
||||
})();
|
27
attachment_preview/static/lib/ViewerJS/example.local.css
Normal file
@ -0,0 +1,27 @@
|
||||
/* This is just a sample file with CSS rules. You should write your own @font-face declarations
|
||||
* to add support for your desired fonts.
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Novecentowide Book';
|
||||
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot");
|
||||
src: url("/ViewerJS/fonts/Novecentowide-Bold-webfont.eot?#iefix") format("embedded-opentype"),
|
||||
url("/ViewerJS/fonts/Novecentowide-Bold-webfont.woff") format("woff"),
|
||||
url("/fonts/Novecentowide-Bold-webfont.ttf") format("truetype"),
|
||||
url("/fonts/Novecentowide-Bold-webfont.svg#NovecentowideBookBold") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'exotica';
|
||||
src: url('/ViewerJS/fonts/Exotica-webfont.eot');
|
||||
src: url('/ViewerJS/fonts/Exotica-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('/ViewerJS/fonts/Exotica-webfont.woff') format('woff'),
|
||||
url('/ViewerJS/fonts/Exotica-webfont.ttf') format('truetype'),
|
||||
url('/ViewerJS/fonts/Exotica-webfont.svg#exoticamedium') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
}
|
||||
|
BIN
attachment_preview/static/lib/ViewerJS/images/kogmbh.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
attachment_preview/static/lib/ViewerJS/images/nlnet.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
attachment_preview/static/lib/ViewerJS/images/texture.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 512 B |
After Width: | Height: | Size: 491 B |
After Width: | Height: | Size: 237 B |
After Width: | Height: | Size: 353 B |
After Width: | Height: | Size: 344 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 228 B |
After Width: | Height: | Size: 143 B |
147
attachment_preview/static/lib/ViewerJS/index.html
Normal file
7847
attachment_preview/static/lib/ViewerJS/pdf.js
Normal file
39112
attachment_preview/static/lib/ViewerJS/pdf.worker.js
vendored
Normal file
1
attachment_preview/static/lib/ViewerJS/pdfjsversion.js
Normal file
@ -0,0 +1 @@
|
||||
var /**@const{!string}*/pdfjs_version = "v1.0.1040";
|
389
attachment_preview/static/lib/ViewerJS/text_layer_builder.js
Normal file
@ -0,0 +1,389 @@
|
||||
/* -*- 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, scrollIntoView, PDFJS */
|
||||
|
||||
'use strict';
|
||||
|
||||
var FIND_SCROLL_OFFSET_TOP = -50;
|
||||
var FIND_SCROLL_OFFSET_LEFT = -400;
|
||||
var MAX_TEXT_DIVS_TO_RENDER = 100000;
|
||||
var RENDER_DELAY = 200; // ms
|
||||
|
||||
var NonWhitespaceRegexp = /\S/;
|
||||
|
||||
function isAllWhitespace(str) {
|
||||
return !NonWhitespaceRegexp.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} TextLayerBuilderOptions
|
||||
* @property {HTMLDivElement} textLayerDiv - The text layer container.
|
||||
* @property {number} pageIndex - The page index.
|
||||
* @property {PageViewport} viewport - The viewport of the text layer.
|
||||
* @property {ILastScrollSource} lastScrollSource - The object that records when
|
||||
* last time scroll happened.
|
||||
* @property {boolean} isViewerInPresentationMode
|
||||
* @property {PDFFindController} findController
|
||||
*/
|
||||
|
||||
/**
|
||||
* TextLayerBuilder provides text-selection functionality for the PDF.
|
||||
* It does this by creating overlay divs over the PDF text. These divs
|
||||
* contain text that matches the PDF text they are overlaying. This object
|
||||
* also provides a way to highlight text that is being searched for.
|
||||
* @class
|
||||
*/
|
||||
var TextLayerBuilder = (function TextLayerBuilderClosure() {
|
||||
function TextLayerBuilder(options) {
|
||||
this.textLayerDiv = options.textLayerDiv;
|
||||
this.layoutDone = false;
|
||||
this.divContentDone = false;
|
||||
this.pageIdx = options.pageIndex;
|
||||
this.matches = [];
|
||||
this.lastScrollSource = options.lastScrollSource || null;
|
||||
this.viewport = options.viewport;
|
||||
this.isViewerInPresentationMode = options.isViewerInPresentationMode;
|
||||
this.textDivs = [];
|
||||
this.findController = options.findController || null;
|
||||
}
|
||||
|
||||
TextLayerBuilder.prototype = {
|
||||
renderLayer: function TextLayerBuilder_renderLayer() {
|
||||
var textLayerFrag = document.createDocumentFragment();
|
||||
var textDivs = this.textDivs;
|
||||
var textDivsLength = textDivs.length;
|
||||
var canvas = document.createElement('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
|
||||
// No point in rendering many divs as it would make the browser
|
||||
// unusable even after the divs are rendered.
|
||||
if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lastFontSize;
|
||||
var lastFontFamily;
|
||||
for (var i = 0; i < textDivsLength; i++) {
|
||||
var textDiv = textDivs[i];
|
||||
if (textDiv.dataset.isWhitespace !== undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var fontSize = textDiv.style.fontSize;
|
||||
var fontFamily = textDiv.style.fontFamily;
|
||||
|
||||
// Only build font string and set to context if different from last.
|
||||
if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
|
||||
ctx.font = fontSize + ' ' + fontFamily;
|
||||
lastFontSize = fontSize;
|
||||
lastFontFamily = fontFamily;
|
||||
}
|
||||
|
||||
var width = ctx.measureText(textDiv.textContent).width;
|
||||
if (width > 0) {
|
||||
textLayerFrag.appendChild(textDiv);
|
||||
var transform;
|
||||
if (textDiv.dataset.canvasWidth !== undefined) {
|
||||
// Dataset values come of type string.
|
||||
var textScale = textDiv.dataset.canvasWidth / width;
|
||||
transform = 'scaleX(' + textScale + ')';
|
||||
} else {
|
||||
transform = '';
|
||||
}
|
||||
var rotation = textDiv.dataset.angle;
|
||||
if (rotation) {
|
||||
transform = 'rotate(' + rotation + 'deg) ' + transform;
|
||||
}
|
||||
if (transform) {
|
||||
CustomStyle.setProp('transform' , textDiv, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.textLayerDiv.appendChild(textLayerFrag);
|
||||
this.renderingDone = true;
|
||||
this.updateMatches();
|
||||
},
|
||||
|
||||
setupRenderLayoutTimer:
|
||||
function TextLayerBuilder_setupRenderLayoutTimer() {
|
||||
// Schedule renderLayout() if the user has been scrolling,
|
||||
// otherwise run it right away.
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
appendText: function TextLayerBuilder_appendText(geom, styles) {
|
||||
var style = styles[geom.fontName];
|
||||
var textDiv = document.createElement('div');
|
||||
this.textDivs.push(textDiv);
|
||||
if (isAllWhitespace(geom.str)) {
|
||||
textDiv.dataset.isWhitespace = true;
|
||||
return;
|
||||
}
|
||||
var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform);
|
||||
var angle = Math.atan2(tx[1], tx[0]);
|
||||
if (style.vertical) {
|
||||
angle += Math.PI / 2;
|
||||
}
|
||||
var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
|
||||
var fontAscent = fontHeight;
|
||||
if (style.ascent) {
|
||||
fontAscent = style.ascent * fontAscent;
|
||||
} else if (style.descent) {
|
||||
fontAscent = (1 + style.descent) * fontAscent;
|
||||
}
|
||||
|
||||
var left;
|
||||
var top;
|
||||
if (angle === 0) {
|
||||
left = tx[4];
|
||||
top = tx[5] - fontAscent;
|
||||
} else {
|
||||
left = tx[4] + (fontAscent * Math.sin(angle));
|
||||
top = tx[5] - (fontAscent * Math.cos(angle));
|
||||
}
|
||||
textDiv.style.left = left + 'px';
|
||||
textDiv.style.top = top + 'px';
|
||||
textDiv.style.fontSize = fontHeight + 'px';
|
||||
textDiv.style.fontFamily = style.fontFamily;
|
||||
|
||||
textDiv.textContent = geom.str;
|
||||
// |fontName| is only used by the Font Inspector. This test will succeed
|
||||
// when e.g. the Font Inspector is off but the Stepper is on, but it's
|
||||
// not worth the effort to do a more accurate test.
|
||||
if (PDFJS.pdfBug) {
|
||||
textDiv.dataset.fontName = geom.fontName;
|
||||
}
|
||||
// Storing into dataset will convert number into string.
|
||||
if (angle !== 0) {
|
||||
textDiv.dataset.angle = angle * (180 / Math.PI);
|
||||
}
|
||||
// We don't bother scaling single-char text divs, because it has very
|
||||
// little effect on text highlighting. This makes scrolling on docs with
|
||||
// lots of such divs a lot faster.
|
||||
if (textDiv.textContent.length > 1) {
|
||||
if (style.vertical) {
|
||||
textDiv.dataset.canvasWidth = geom.height * this.viewport.scale;
|
||||
} else {
|
||||
textDiv.dataset.canvasWidth = geom.width * this.viewport.scale;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setTextContent: function TextLayerBuilder_setTextContent(textContent) {
|
||||
this.textContent = textContent;
|
||||
|
||||
var textItems = textContent.items;
|
||||
for (var i = 0, len = textItems.length; i < len; i++) {
|
||||
this.appendText(textItems[i], textContent.styles);
|
||||
}
|
||||
this.divContentDone = true;
|
||||
this.setupRenderLayoutTimer();
|
||||
},
|
||||
|
||||
convertMatches: function TextLayerBuilder_convertMatches(matches) {
|
||||
var i = 0;
|
||||
var iIndex = 0;
|
||||
var bidiTexts = this.textContent.items;
|
||||
var end = bidiTexts.length - 1;
|
||||
var queryLen = (this.findController === null ?
|
||||
0 : this.findController.state.query.length);
|
||||
var ret = [];
|
||||
|
||||
for (var m = 0, len = matches.length; m < len; m++) {
|
||||
// Calculate the start position.
|
||||
var matchIdx = matches[m];
|
||||
|
||||
// Loop over the divIdxs.
|
||||
while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
|
||||
iIndex += bidiTexts[i].str.length;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i === bidiTexts.length) {
|
||||
console.error('Could not find a matching mapping');
|
||||
}
|
||||
|
||||
var match = {
|
||||
begin: {
|
||||
divIdx: i,
|
||||
offset: matchIdx - iIndex
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate the end position.
|
||||
matchIdx += queryLen;
|
||||
|
||||
// Somewhat the same array as above, but use > instead of >= to get
|
||||
// the end position right.
|
||||
while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
|
||||
iIndex += bidiTexts[i].str.length;
|
||||
i++;
|
||||
}
|
||||
|
||||
match.end = {
|
||||
divIdx: i,
|
||||
offset: matchIdx - iIndex
|
||||
};
|
||||
ret.push(match);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
renderMatches: function TextLayerBuilder_renderMatches(matches) {
|
||||
// Early exit if there is nothing to render.
|
||||
if (matches.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bidiTexts = this.textContent.items;
|
||||
var textDivs = this.textDivs;
|
||||
var prevEnd = null;
|
||||
var isSelectedPage = (this.findController === null ?
|
||||
false : (this.pageIdx === this.findController.selected.pageIdx));
|
||||
var selectedMatchIdx = (this.findController === null ?
|
||||
-1 : this.findController.selected.matchIdx);
|
||||
var highlightAll = (this.findController === null ?
|
||||
false : this.findController.state.highlightAll);
|
||||
var infinity = {
|
||||
divIdx: -1,
|
||||
offset: undefined
|
||||
};
|
||||
|
||||
function beginText(begin, className) {
|
||||
var divIdx = begin.divIdx;
|
||||
textDivs[divIdx].textContent = '';
|
||||
appendTextToDiv(divIdx, 0, begin.offset, className);
|
||||
}
|
||||
|
||||
function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
|
||||
var div = textDivs[divIdx];
|
||||
var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
|
||||
var node = document.createTextNode(content);
|
||||
if (className) {
|
||||
var span = document.createElement('span');
|
||||
span.className = className;
|
||||
span.appendChild(node);
|
||||
div.appendChild(span);
|
||||
return;
|
||||
}
|
||||
div.appendChild(node);
|
||||
}
|
||||
|
||||
var i0 = selectedMatchIdx, i1 = i0 + 1;
|
||||
if (highlightAll) {
|
||||
i0 = 0;
|
||||
i1 = matches.length;
|
||||
} else if (!isSelectedPage) {
|
||||
// Not highlighting all and this isn't the selected page, so do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = i0; i < i1; i++) {
|
||||
var match = matches[i];
|
||||
var begin = match.begin;
|
||||
var end = match.end;
|
||||
var isSelected = (isSelectedPage && i === selectedMatchIdx);
|
||||
var highlightSuffix = (isSelected ? ' selected' : '');
|
||||
|
||||
if (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) {
|
||||
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
|
||||
}
|
||||
// Clear the divs and set the content until the starting point.
|
||||
beginText(begin);
|
||||
} else {
|
||||
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
|
||||
}
|
||||
|
||||
if (begin.divIdx === end.divIdx) {
|
||||
appendTextToDiv(begin.divIdx, begin.offset, end.offset,
|
||||
'highlight' + highlightSuffix);
|
||||
} else {
|
||||
appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
|
||||
'highlight begin' + highlightSuffix);
|
||||
for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
|
||||
textDivs[n0].className = 'highlight middle' + highlightSuffix;
|
||||
}
|
||||
beginText(end, 'highlight end' + highlightSuffix);
|
||||
}
|
||||
prevEnd = end;
|
||||
}
|
||||
|
||||
if (prevEnd) {
|
||||
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
|
||||
}
|
||||
},
|
||||
|
||||
updateMatches: function TextLayerBuilder_updateMatches() {
|
||||
// Only show matches when all rendering is done.
|
||||
if (!this.renderingDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear all matches.
|
||||
var matches = this.matches;
|
||||
var textDivs = this.textDivs;
|
||||
var bidiTexts = this.textContent.items;
|
||||
var clearedUntilDivIdx = -1;
|
||||
|
||||
// Clear all current matches.
|
||||
for (var i = 0, len = matches.length; i < len; i++) {
|
||||
var match = matches[i];
|
||||
var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
|
||||
for (var n = begin, end = match.end.divIdx; n <= end; n++) {
|
||||
var div = textDivs[n];
|
||||
div.textContent = bidiTexts[n].str;
|
||||
div.className = '';
|
||||
}
|
||||
clearedUntilDivIdx = match.end.divIdx + 1;
|
||||
}
|
||||
|
||||
if (this.findController === null || !this.findController.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert the matches on the page controller into the match format
|
||||
// used for the textLayer.
|
||||
this.matches = this.convertMatches(this.findController === null ?
|
||||
[] : (this.findController.pageMatches[this.pageIdx] || []));
|
||||
this.renderMatches(this.matches);
|
||||
}
|
||||
};
|
||||
return TextLayerBuilder;
|
||||
})();
|
371
attachment_preview/static/lib/ViewerJS/ui_utils.js
Normal file
@ -0,0 +1,371 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* Copyright 2012 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var CSS_UNITS = 96.0 / 72.0;
|
||||
var DEFAULT_SCALE = 'auto';
|
||||
var UNKNOWN_SCALE = 0;
|
||||
var MAX_AUTO_SCALE = 1.25;
|
||||
var SCROLLBAR_PADDING = 40;
|
||||
var VERTICAL_PADDING = 5;
|
||||
var DEFAULT_CACHE_SIZE = 10;
|
||||
|
||||
// optimised CSS custom property getter/setter
|
||||
var CustomStyle = (function CustomStyleClosure() {
|
||||
|
||||
// As noted on: http://www.zachstronaut.com/posts/2009/02/17/
|
||||
// animate-css-transforms-firefox-webkit.html
|
||||
// in some versions of IE9 it is critical that ms appear in this list
|
||||
// before Moz
|
||||
var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
|
||||
var _cache = {};
|
||||
|
||||
function CustomStyle() {}
|
||||
|
||||
CustomStyle.getProp = function get(propName, element) {
|
||||
// check cache only when no element is given
|
||||
if (arguments.length === 1 && typeof _cache[propName] === 'string') {
|
||||
return _cache[propName];
|
||||
}
|
||||
|
||||
element = element || document.documentElement;
|
||||
var style = element.style, prefixed, uPropName;
|
||||
|
||||
// test standard property first
|
||||
if (typeof style[propName] === 'string') {
|
||||
return (_cache[propName] = propName);
|
||||
}
|
||||
|
||||
// capitalize
|
||||
uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
|
||||
|
||||
// test vendor specific properties
|
||||
for (var i = 0, l = prefixes.length; i < l; i++) {
|
||||
prefixed = prefixes[i] + uPropName;
|
||||
if (typeof style[prefixed] === 'string') {
|
||||
return (_cache[propName] = prefixed);
|
||||
}
|
||||
}
|
||||
|
||||
//if all fails then set to undefined
|
||||
return (_cache[propName] = 'undefined');
|
||||
};
|
||||
|
||||
CustomStyle.setProp = function set(propName, element, str) {
|
||||
var prop = this.getProp(propName);
|
||||
if (prop !== 'undefined') {
|
||||
element.style[prop] = str;
|
||||
}
|
||||
};
|
||||
|
||||
return CustomStyle;
|
||||
})();
|
||||
|
||||
function getFileName(url) {
|
||||
var anchor = url.indexOf('#');
|
||||
var query = url.indexOf('?');
|
||||
var end = Math.min(
|
||||
anchor > 0 ? anchor : url.length,
|
||||
query > 0 ? query : url.length);
|
||||
return url.substring(url.lastIndexOf('/', end) + 1, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns scale factor for the canvas. It makes sense for the HiDPI displays.
|
||||
* @return {Object} The object with horizontal (sx) and vertical (sy)
|
||||
scales. The scaled property is set to false if scaling is
|
||||
not required, true otherwise.
|
||||
*/
|
||||
function getOutputScale(ctx) {
|
||||
var devicePixelRatio = window.devicePixelRatio || 1;
|
||||
var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
|
||||
ctx.mozBackingStorePixelRatio ||
|
||||
ctx.msBackingStorePixelRatio ||
|
||||
ctx.oBackingStorePixelRatio ||
|
||||
ctx.backingStorePixelRatio || 1;
|
||||
var pixelRatio = devicePixelRatio / backingStoreRatio;
|
||||
return {
|
||||
sx: pixelRatio,
|
||||
sy: pixelRatio,
|
||||
scaled: pixelRatio !== 1
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls specified element into view of its parent.
|
||||
* element {Object} The element to be visible.
|
||||
* spot {Object} An object with optional top and left properties,
|
||||
* specifying the offset from the top left edge.
|
||||
*/
|
||||
function scrollIntoView(element, spot) {
|
||||
// Assuming offsetParent is available (it's not available when viewer is in
|
||||
// hidden iframe or object). We have to scroll: if the offsetParent is not set
|
||||
// producing the error. See also animationStartedClosure.
|
||||
var parent = element.offsetParent;
|
||||
var offsetY = element.offsetTop + element.clientTop;
|
||||
var offsetX = element.offsetLeft + element.clientLeft;
|
||||
if (!parent) {
|
||||
console.error('offsetParent is not set -- cannot scroll');
|
||||
return;
|
||||
}
|
||||
while (parent.clientHeight === parent.scrollHeight) {
|
||||
if (parent.dataset._scaleY) {
|
||||
offsetY /= parent.dataset._scaleY;
|
||||
offsetX /= parent.dataset._scaleX;
|
||||
}
|
||||
offsetY += parent.offsetTop;
|
||||
offsetX += parent.offsetLeft;
|
||||
parent = parent.offsetParent;
|
||||
if (!parent) {
|
||||
return; // no need to scroll
|
||||
}
|
||||
}
|
||||
if (spot) {
|
||||
if (spot.top !== undefined) {
|
||||
offsetY += spot.top;
|
||||
}
|
||||
if (spot.left !== undefined) {
|
||||
offsetX += spot.left;
|
||||
parent.scrollLeft = offsetX;
|
||||
}
|
||||
}
|
||||
parent.scrollTop = offsetY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to start monitoring the scroll event and converting them into
|
||||
* PDF.js friendly one: with scroll debounce and scroll direction.
|
||||
*/
|
||||
function watchScroll(viewAreaElement, callback) {
|
||||
var debounceScroll = function debounceScroll(evt) {
|
||||
if (rAF) {
|
||||
return;
|
||||
}
|
||||
// schedule an invocation of scroll for next animation frame.
|
||||
rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
|
||||
rAF = null;
|
||||
|
||||
var currentY = viewAreaElement.scrollTop;
|
||||
var lastY = state.lastY;
|
||||
if (currentY > lastY) {
|
||||
state.down = true;
|
||||
} else if (currentY < lastY) {
|
||||
state.down = false;
|
||||
}
|
||||
state.lastY = currentY;
|
||||
// else do nothing and use previous value
|
||||
callback(state);
|
||||
});
|
||||
};
|
||||
|
||||
var state = {
|
||||
down: true,
|
||||
lastY: viewAreaElement.scrollTop,
|
||||
_eventHandler: debounceScroll
|
||||
};
|
||||
|
||||
var rAF = null;
|
||||
viewAreaElement.addEventListener('scroll', debounceScroll, true);
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic helper to find out what elements are visible within a scroll pane.
|
||||
*/
|
||||
function getVisibleElements(scrollEl, views, sortByVisibility) {
|
||||
var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
|
||||
var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
|
||||
|
||||
var visible = [], view;
|
||||
var currentHeight, viewHeight, hiddenHeight, percentHeight;
|
||||
var currentWidth, viewWidth;
|
||||
for (var i = 0, ii = views.length; i < ii; ++i) {
|
||||
view = views[i];
|
||||
currentHeight = view.el.offsetTop + view.el.clientTop;
|
||||
viewHeight = view.el.clientHeight;
|
||||
if ((currentHeight + viewHeight) < top) {
|
||||
continue;
|
||||
}
|
||||
if (currentHeight > bottom) {
|
||||
break;
|
||||
}
|
||||
currentWidth = view.el.offsetLeft + view.el.clientLeft;
|
||||
viewWidth = view.el.clientWidth;
|
||||
if ((currentWidth + viewWidth) < left || currentWidth > right) {
|
||||
continue;
|
||||
}
|
||||
hiddenHeight = Math.max(0, top - currentHeight) +
|
||||
Math.max(0, currentHeight + viewHeight - bottom);
|
||||
percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
|
||||
|
||||
visible.push({ id: view.id, x: currentWidth, y: currentHeight,
|
||||
view: view, percent: percentHeight });
|
||||
}
|
||||
|
||||
var first = visible[0];
|
||||
var last = visible[visible.length - 1];
|
||||
|
||||
if (sortByVisibility) {
|
||||
visible.sort(function(a, b) {
|
||||
var pc = a.percent - b.percent;
|
||||
if (Math.abs(pc) > 0.001) {
|
||||
return -pc;
|
||||
}
|
||||
return a.id - b.id; // ensure stability
|
||||
});
|
||||
}
|
||||
return {first: first, last: last, views: visible};
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler to suppress context menu.
|
||||
*/
|
||||
function noContextMenuHandler(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filename or guessed filename from the url (see issue 3455).
|
||||
* url {String} The original PDF location.
|
||||
* @return {String} Guessed PDF file name.
|
||||
*/
|
||||
function getPDFFileNameFromURL(url) {
|
||||
var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
|
||||
// SCHEME HOST 1.PATH 2.QUERY 3.REF
|
||||
// Pattern to get last matching NAME.pdf
|
||||
var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
|
||||
var splitURI = reURI.exec(url);
|
||||
var suggestedFilename = reFilename.exec(splitURI[1]) ||
|
||||
reFilename.exec(splitURI[2]) ||
|
||||
reFilename.exec(splitURI[3]);
|
||||
if (suggestedFilename) {
|
||||
suggestedFilename = suggestedFilename[0];
|
||||
if (suggestedFilename.indexOf('%') !== -1) {
|
||||
// URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
|
||||
try {
|
||||
suggestedFilename =
|
||||
reFilename.exec(decodeURIComponent(suggestedFilename))[0];
|
||||
} catch(e) { // Possible (extremely rare) errors:
|
||||
// URIError "Malformed URI", e.g. for "%AA.pdf"
|
||||
// TypeError "null has no properties", e.g. for "%2F.pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
return suggestedFilename || 'document.pdf';
|
||||
}
|
||||
|
||||
var ProgressBar = (function ProgressBarClosure() {
|
||||
|
||||
function clamp(v, min, max) {
|
||||
return Math.min(Math.max(v, min), max);
|
||||
}
|
||||
|
||||
function ProgressBar(id, opts) {
|
||||
this.visible = true;
|
||||
|
||||
// Fetch the sub-elements for later.
|
||||
this.div = document.querySelector(id + ' .progress');
|
||||
|
||||
// Get the loading bar element, so it can be resized to fit the viewer.
|
||||
this.bar = this.div.parentNode;
|
||||
|
||||
// Get options, with sensible defaults.
|
||||
this.height = opts.height || 100;
|
||||
this.width = opts.width || 100;
|
||||
this.units = opts.units || '%';
|
||||
|
||||
// Initialize heights.
|
||||
this.div.style.height = this.height + this.units;
|
||||
this.percent = 0;
|
||||
}
|
||||
|
||||
ProgressBar.prototype = {
|
||||
|
||||
updateBar: function ProgressBar_updateBar() {
|
||||
if (this._indeterminate) {
|
||||
this.div.classList.add('indeterminate');
|
||||
this.div.style.width = this.width + this.units;
|
||||
return;
|
||||
}
|
||||
|
||||
this.div.classList.remove('indeterminate');
|
||||
var progressSize = this.width * this._percent / 100;
|
||||
this.div.style.width = progressSize + this.units;
|
||||
},
|
||||
|
||||
get percent() {
|
||||
return this._percent;
|
||||
},
|
||||
|
||||
set percent(val) {
|
||||
this._indeterminate = isNaN(val);
|
||||
this._percent = clamp(val, 0, 100);
|
||||
this.updateBar();
|
||||
},
|
||||
|
||||
setWidth: function ProgressBar_setWidth(viewer) {
|
||||
if (viewer) {
|
||||
var container = viewer.parentNode;
|
||||
var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
|
||||
if (scrollbarWidth > 0) {
|
||||
this.bar.setAttribute('style', 'width: calc(100% - ' +
|
||||
scrollbarWidth + 'px);');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
hide: function ProgressBar_hide() {
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
this.visible = false;
|
||||
this.bar.classList.add('hidden');
|
||||
document.body.classList.remove('loadingInProgress');
|
||||
},
|
||||
|
||||
show: function ProgressBar_show() {
|
||||
if (this.visible) {
|
||||
return;
|
||||
}
|
||||
this.visible = true;
|
||||
document.body.classList.add('loadingInProgress');
|
||||
this.bar.classList.remove('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
return ProgressBar;
|
||||
})();
|
||||
|
||||
var Cache = function cacheCache(size) {
|
||||
var data = [];
|
||||
this.push = function cachePush(view) {
|
||||
var i = data.indexOf(view);
|
||||
if (i >= 0) {
|
||||
data.splice(i, 1);
|
||||
}
|
||||
data.push(view);
|
||||
if (data.length > size) {
|
||||
data.shift().destroy();
|
||||
}
|
||||
};
|
||||
this.resize = function (newSize) {
|
||||
size = newSize;
|
||||
while (data.length > size) {
|
||||
data.shift().destroy();
|
||||
}
|
||||
};
|
||||
};
|
642
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;
|
||||
}
|
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', 'ott', 'fodp', 'otp',
|
||||
'fods', 'ots'
|
||||
]) > -1;
|
||||
};
|
||||
instance.web.Sidebar.include(
|
||||
{
|
||||
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('data-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,
|
||||
$target.attr('alt'));
|
||||
});
|
||||
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" title="<%-preview_text%>" 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: _.str.sprintf(_t('Preview %s'), this.string),
|
||||
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,
|
||||
_.str.sprintf(_t('Preview %s'), self.field.string));
|
||||
});
|
||||
$element.attr('title', _.str.sprintf(_t('Preview %s'), self.field.string));
|
||||
}
|
||||
else
|
||||
{
|
||||
$element.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
this.$el.find('.oe-binary-preview').remove();
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
33
attachment_preview/static/src/js/viewerjs_tweaks.js
Normal file
@ -0,0 +1,33 @@
|
||||
//-*- 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_Viewer = Viewer;
|
||||
Viewer = function(plugin, parameters)
|
||||
{
|
||||
var matches = (/&title=([^&]+)&/).exec(window.location.hash);
|
||||
if(matches && matches.length > 1)
|
||||
{
|
||||
parameters.title = decodeURIComponent(matches[1]);
|
||||
}
|
||||
return original_Viewer(plugin, parameters);
|
||||
}
|
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>
|
11
attachment_preview/view/attachment_preview.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="assets_backend" name="attachment_preview assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/attachment_preview/static/src/js/attachment_preview.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/attachment_preview/static/src/css/attachment_preview.css" />
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|