collective.collabora 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- collective/collabora/__init__.py +12 -0
- collective/collabora/adapters.py +82 -0
- collective/collabora/browser/__init__.py +0 -0
- collective/collabora/browser/configure.zcml +49 -0
- collective/collabora/browser/edit.py +257 -0
- collective/collabora/browser/static/edit.css +32 -0
- collective/collabora/browser/static/edit.js +92 -0
- collective/collabora/browser/templates/edit.pt +105 -0
- collective/collabora/browser/wopi.py +205 -0
- collective/collabora/configure.zcml +40 -0
- collective/collabora/interfaces.py +38 -0
- collective/collabora/locales/README.rst +65 -0
- collective/collabora/locales/__init__.py +0 -0
- collective/collabora/locales/collective.collabora.pot +123 -0
- collective/collabora/locales/de/LC_MESSAGES/collective.collabora.po +120 -0
- collective/collabora/locales/en/LC_MESSAGES/collective.collabora.po +120 -0
- collective/collabora/locales/es/LC_MESSAGES/collective.collabora.po +125 -0
- collective/collabora/locales/fr/LC_MESSAGES/collective.collabora.po +120 -0
- collective/collabora/locales/nl/LC_MESSAGES/collective.collabora.po +120 -0
- collective/collabora/locales/update.py +80 -0
- collective/collabora/locales/update.sh +12 -0
- collective/collabora/profiles/default/browserlayer.xml +6 -0
- collective/collabora/profiles/default/metadata.xml +7 -0
- collective/collabora/profiles/default/registry.xml +29 -0
- collective/collabora/profiles/default/types/File.xml +32 -0
- collective/collabora/profiles/default/types.xml +8 -0
- collective/collabora/profiles/uninstall/browserlayer.xml +6 -0
- collective/collabora/setuphandlers.py +23 -0
- collective/collabora/testdata/server_discovery_xml +467 -0
- collective/collabora/testdata/testfile.docx +0 -0
- collective/collabora/testing.py +148 -0
- collective/collabora/tests/__init__.py +0 -0
- collective/collabora/tests/test_adapters.py +58 -0
- collective/collabora/tests/test_edit.py +275 -0
- collective/collabora/tests/test_setup.py +135 -0
- collective/collabora/tests/test_utils.py +90 -0
- collective/collabora/tests/test_wopi.py +321 -0
- collective/collabora/utils.py +78 -0
- collective.collabora-1.0.0-py3.10-nspkg.pth +1 -0
- collective.collabora-1.0.0.dist-info/LICENSE.GPL +339 -0
- collective.collabora-1.0.0.dist-info/LICENSE.rst +15 -0
- collective.collabora-1.0.0.dist-info/METADATA +174 -0
- collective.collabora-1.0.0.dist-info/RECORD +47 -0
- collective.collabora-1.0.0.dist-info/WHEEL +5 -0
- collective.collabora-1.0.0.dist-info/entry_points.txt +5 -0
- collective.collabora-1.0.0.dist-info/namespace_packages.txt +1 -0
- collective.collabora-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Init and utils."""
|
|
3
|
+
from __future__ import unicode_literals
|
|
4
|
+
|
|
5
|
+
from future import standard_library
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
standard_library.install_aliases()
|
|
9
|
+
from zope.i18nmessageid import MessageFactory
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_ = MessageFactory("collective.collabora")
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import unicode_literals
|
|
3
|
+
|
|
4
|
+
from future import standard_library
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
standard_library.install_aliases()
|
|
8
|
+
|
|
9
|
+
from collective.collabora import utils
|
|
10
|
+
from collective.collabora.interfaces import IStoredFile
|
|
11
|
+
from plone.app.contenttypes.interfaces import IFile
|
|
12
|
+
from plone.namedfile.file import NamedBlobFile
|
|
13
|
+
from zope.component import adapter
|
|
14
|
+
from zope.interface import implementer
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from Products.ATContentTypes.interfaces import IATFile
|
|
19
|
+
from StringIO import StringIO # py27 only
|
|
20
|
+
except ImportError:
|
|
21
|
+
from collective.collabora.interfaces import IDummy as IATFile
|
|
22
|
+
|
|
23
|
+
StringIO = utils.disallow
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@adapter(IFile)
|
|
27
|
+
@implementer(IStoredFile)
|
|
28
|
+
class DXStoredFile(object):
|
|
29
|
+
"""Access the file storage on a Dexterity content object.
|
|
30
|
+
|
|
31
|
+
i.e. the file attribute on the content object.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, context):
|
|
35
|
+
self.context = context
|
|
36
|
+
self.filename = context.file.filename
|
|
37
|
+
self.contentType = context.file.contentType
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def data(self):
|
|
41
|
+
return self.context.file.data
|
|
42
|
+
|
|
43
|
+
@data.setter
|
|
44
|
+
def data(self, data):
|
|
45
|
+
self.context.file = NamedBlobFile(
|
|
46
|
+
data=data, filename=self.filename, contentType=self.contentType
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def getSize(self):
|
|
50
|
+
return self.context.file.getSize()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@adapter(IATFile)
|
|
54
|
+
@implementer(IStoredFile)
|
|
55
|
+
class ATStoredFile(object):
|
|
56
|
+
"""Access the file storage on a Archetypes content object.
|
|
57
|
+
|
|
58
|
+
i.e. the File field on the content object.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, context):
|
|
62
|
+
self.context = context
|
|
63
|
+
self.file = context.getField("file")
|
|
64
|
+
# This weirdness is why we adopt the content type, rather than the blob
|
|
65
|
+
# field directly - these accessors on the field need the parent context
|
|
66
|
+
self.filename = self.file.getFilename(context)
|
|
67
|
+
self.contentType = self.file.getContentType(context)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def data(self):
|
|
71
|
+
return self.context.data
|
|
72
|
+
|
|
73
|
+
@data.setter
|
|
74
|
+
def data(self, data):
|
|
75
|
+
# This follows the README.txt in
|
|
76
|
+
# https://github.com/plone/plone.app.blob/tree/master/src/plone/app/blob
|
|
77
|
+
data_wrapper = StringIO(data)
|
|
78
|
+
data_wrapper.filename = self.filename
|
|
79
|
+
self.context.setFile(data_wrapper)
|
|
80
|
+
|
|
81
|
+
def getSize(self):
|
|
82
|
+
return self.file.get_size(self.context)
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<configure
|
|
2
|
+
xmlns="http://namespaces.zope.org/zope"
|
|
3
|
+
xmlns:browser="http://namespaces.zope.org/browser"
|
|
4
|
+
xmlns:zcml="http://namespaces.zope.org/zcml"
|
|
5
|
+
i18n_domain="collective.collabora"
|
|
6
|
+
>
|
|
7
|
+
|
|
8
|
+
<browser:resourceDirectory
|
|
9
|
+
name="collective.collabora"
|
|
10
|
+
directory="./static"
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
<browser:page
|
|
14
|
+
name="collabora-edit"
|
|
15
|
+
for="plone.app.contenttypes.interfaces.IFile"
|
|
16
|
+
class=".edit.CollaboraEditView"
|
|
17
|
+
template="templates/edit.pt"
|
|
18
|
+
permission="zope2.View"
|
|
19
|
+
layer="collective.collabora.interfaces.ICollectiveCollaboraLayer"
|
|
20
|
+
/>
|
|
21
|
+
|
|
22
|
+
<browser:page
|
|
23
|
+
name="collabora-wopi"
|
|
24
|
+
for="plone.app.contenttypes.interfaces.IFile"
|
|
25
|
+
class=".wopi.CollaboraWOPIView"
|
|
26
|
+
permission="zope2.View"
|
|
27
|
+
layer="collective.collabora.interfaces.ICollectiveCollaboraLayer"
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
<configure zcml:condition="installed Products.ATContentTypes">
|
|
31
|
+
<browser:page
|
|
32
|
+
name="collabora-edit"
|
|
33
|
+
for="Products.ATContentTypes.interfaces.IATFile"
|
|
34
|
+
class=".edit.CollaboraEditView"
|
|
35
|
+
template="templates/edit.pt"
|
|
36
|
+
permission="zope2.View"
|
|
37
|
+
layer="collective.collabora.interfaces.ICollectiveCollaboraLayer"
|
|
38
|
+
/>
|
|
39
|
+
|
|
40
|
+
<browser:page
|
|
41
|
+
name="collabora-wopi"
|
|
42
|
+
for="Products.ATContentTypes.interfaces.IATFile"
|
|
43
|
+
class=".wopi.CollaboraWOPIView"
|
|
44
|
+
permission="zope2.View"
|
|
45
|
+
layer="collective.collabora.interfaces.ICollectiveCollaboraLayer"
|
|
46
|
+
/>
|
|
47
|
+
</configure>
|
|
48
|
+
|
|
49
|
+
</configure>
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import unicode_literals
|
|
3
|
+
|
|
4
|
+
from builtins import dict
|
|
5
|
+
from builtins import filter
|
|
6
|
+
from builtins import next
|
|
7
|
+
from builtins import super
|
|
8
|
+
from future import standard_library
|
|
9
|
+
from future.utils import bytes_to_native_str as n
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
standard_library.install_aliases()
|
|
13
|
+
|
|
14
|
+
from collective.collabora import _
|
|
15
|
+
from collective.collabora import utils
|
|
16
|
+
from collective.collabora.adapters import IStoredFile
|
|
17
|
+
from logging import getLogger
|
|
18
|
+
from lxml import etree
|
|
19
|
+
from plone import api
|
|
20
|
+
from plone.app.contenttypes.browser.file import FileView
|
|
21
|
+
from plone.memoize.view import memoize
|
|
22
|
+
from plone.uuid.interfaces import IUUID
|
|
23
|
+
from Products.PluggableAuthService.interfaces.plugins import IAuthenticationPlugin
|
|
24
|
+
from urllib.parse import urlencode
|
|
25
|
+
from urllib.parse import urlparse
|
|
26
|
+
|
|
27
|
+
import requests
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
error_server_discovery = _(
|
|
33
|
+
"error_server_discovery", default="Collabora server is not responding."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CollaboraEditView(FileView):
|
|
38
|
+
"""User interface for interacting with Collabora Online."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, context, request):
|
|
41
|
+
# newsuper throws an infinite loop in py27
|
|
42
|
+
super(FileView, self).__init__(context, request)
|
|
43
|
+
self.error_msg = None
|
|
44
|
+
self.stored_file = IStoredFile(context)
|
|
45
|
+
|
|
46
|
+
def __call__(self):
|
|
47
|
+
if not all([self.plone_server_url, self.collabora_server_url, self.wopi_url]):
|
|
48
|
+
# accessing those detects errors and sets self.error_msg
|
|
49
|
+
pass
|
|
50
|
+
return super(FileView, self).__call__()
|
|
51
|
+
|
|
52
|
+
def human_readable_size(self):
|
|
53
|
+
"""Use our own reimplementation, against our own adapter"""
|
|
54
|
+
return utils.human_readable_size(self.stored_file.getSize())
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
@memoize
|
|
58
|
+
def plone_version(self):
|
|
59
|
+
"""Get the major version we're running in."""
|
|
60
|
+
return "plone%i" % utils.PLONE_VERSION
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
@memoize
|
|
64
|
+
def can_edit(self):
|
|
65
|
+
return api.user.has_permission(
|
|
66
|
+
"Modify portal content", user=api.user.get_current(), obj=self.context
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
@memoize
|
|
71
|
+
def download_url(self):
|
|
72
|
+
return "/".join(
|
|
73
|
+
(
|
|
74
|
+
self.context.absolute_url(),
|
|
75
|
+
"@@download",
|
|
76
|
+
"file",
|
|
77
|
+
self.stored_file.filename,
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
@memoize
|
|
83
|
+
def portal_url(self):
|
|
84
|
+
"""The 'normal' Plone portal url, as used in the"""
|
|
85
|
+
return api.portal.get().absolute_url()
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
@memoize
|
|
89
|
+
def plone_server_url(self):
|
|
90
|
+
"""The base URL for Collabora to reach Plone on"""
|
|
91
|
+
plone_server_url = api.portal.get_registry_record(
|
|
92
|
+
n(b"collective.collabora.plone_server_url"), default=None
|
|
93
|
+
)
|
|
94
|
+
if not plone_server_url:
|
|
95
|
+
plone_server_url = self.portal_url
|
|
96
|
+
parts = urlparse(plone_server_url)
|
|
97
|
+
# This assumes you are running Collabora in a docker container,
|
|
98
|
+
# which means it will hit itself and not Plone when trying to contact localhost.
|
|
99
|
+
if parts.hostname in ("localhost", "127.0.0.1"):
|
|
100
|
+
self.error_msg = _(
|
|
101
|
+
"error_plone_server_url",
|
|
102
|
+
default=(
|
|
103
|
+
"When Plone is supposed to be accessed on localhost, "
|
|
104
|
+
"Collabora cannot call back. "
|
|
105
|
+
"Use host.docker.internal or a public FQDN instead."
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
logger.error("When Plone runs on localhost, Collabora cannot call back.")
|
|
109
|
+
return ""
|
|
110
|
+
return plone_server_url
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
@memoize
|
|
114
|
+
def collabora_server_url(self):
|
|
115
|
+
collabora_server_url = api.portal.get_registry_record(
|
|
116
|
+
n(b"collective.collabora.collabora_server_url"), default=None
|
|
117
|
+
)
|
|
118
|
+
if not collabora_server_url:
|
|
119
|
+
self.error_msg = _(
|
|
120
|
+
"error_collabora_server_url_empty",
|
|
121
|
+
default="collective.collabora.collabora_server_url is not configured.",
|
|
122
|
+
)
|
|
123
|
+
logger.error("collective.collabora.collabora_server_url is not configured.")
|
|
124
|
+
if collabora_server_url and not collabora_server_url.startswith("http"):
|
|
125
|
+
self.error_msg = _(
|
|
126
|
+
"error_collabora_server_url_invalid",
|
|
127
|
+
default=(
|
|
128
|
+
"collective.collabora.collabora_server_url "
|
|
129
|
+
"needs to be a full URL."
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
logger.error(
|
|
133
|
+
"collective.collabora.collabora_server_url %s needs to be a full URL",
|
|
134
|
+
collabora_server_url,
|
|
135
|
+
)
|
|
136
|
+
return "" # Don't let server discovery error msg obscure this one
|
|
137
|
+
return collabora_server_url
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
@memoize
|
|
141
|
+
def server_discovery_xml(self):
|
|
142
|
+
if not self.collabora_server_url:
|
|
143
|
+
return
|
|
144
|
+
try:
|
|
145
|
+
response = requests.get("%s/hosting/discovery" % self.collabora_server_url)
|
|
146
|
+
except requests.exceptions.RequestException as e:
|
|
147
|
+
self.error_msg = error_server_discovery
|
|
148
|
+
logger.error(e)
|
|
149
|
+
return
|
|
150
|
+
if response.status_code == 200:
|
|
151
|
+
return response.text
|
|
152
|
+
self.error_msg = error_server_discovery
|
|
153
|
+
logger.error(
|
|
154
|
+
"Server discovery error %i: %s. Verify your proxy configuration.",
|
|
155
|
+
response.status_code,
|
|
156
|
+
response.reason,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
@memoize
|
|
161
|
+
def editor_url(self):
|
|
162
|
+
"""Return the URL of the LibreOffice / Collabora editor.
|
|
163
|
+
|
|
164
|
+
- Call wopi_discovery.
|
|
165
|
+
- Get the right URL depending on the file extension from the XML
|
|
166
|
+
"""
|
|
167
|
+
if not self.server_discovery_xml:
|
|
168
|
+
return
|
|
169
|
+
parser = etree.XMLParser()
|
|
170
|
+
tree = etree.fromstring(self.server_discovery_xml, parser=parser)
|
|
171
|
+
mime_type = self.stored_file.contentType
|
|
172
|
+
action = tree.xpath("//app[@name='%s']/action" % mime_type)
|
|
173
|
+
action = action[0] if len(action) else None
|
|
174
|
+
if action is None:
|
|
175
|
+
self.error_msg = _(
|
|
176
|
+
"error_editor_mimetype",
|
|
177
|
+
default="Collabora does not support mimetype ${mimetype}.",
|
|
178
|
+
mapping={"mimetype": mime_type},
|
|
179
|
+
)
|
|
180
|
+
logger.error("Collabora does not support mimetype %r.", mime_type)
|
|
181
|
+
return
|
|
182
|
+
urlsrc = action.get("urlsrc")
|
|
183
|
+
if not urlsrc:
|
|
184
|
+
self.error_msg = _(
|
|
185
|
+
"error_editor_urlsrc", default="Cannot extract url source from action"
|
|
186
|
+
)
|
|
187
|
+
logger.error("Cannot extract urlsrc from action %r", action)
|
|
188
|
+
return urlsrc
|
|
189
|
+
|
|
190
|
+
@property
|
|
191
|
+
@memoize
|
|
192
|
+
def jwt_token(self):
|
|
193
|
+
"""Return a JWT token from the plone.restapi JWT PAS plugin.
|
|
194
|
+
|
|
195
|
+
This token is used by the WOPI client (LibreOffice / Collabora)
|
|
196
|
+
to authenticate against the WOPI host (Quaive).
|
|
197
|
+
"""
|
|
198
|
+
acl_users = api.portal.get_tool("acl_users")
|
|
199
|
+
plugins = acl_users.plugins.listPlugins(IAuthenticationPlugin)
|
|
200
|
+
plugins = filter(
|
|
201
|
+
lambda it: it[1].meta_type == "JWT Authentication Plugin", plugins
|
|
202
|
+
)
|
|
203
|
+
try:
|
|
204
|
+
jwt_plugin = next(plugins)[1]
|
|
205
|
+
except StopIteration:
|
|
206
|
+
self.error_msg = _(
|
|
207
|
+
"error_jwt_plugin",
|
|
208
|
+
default=(
|
|
209
|
+
"JWT Authentication Plugin not found. "
|
|
210
|
+
"Is plone.restapi installed?"
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
logger.error("JWT Authentication Plugin not found.")
|
|
214
|
+
return
|
|
215
|
+
return jwt_plugin.create_token(api.user.get_current().getId())
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
@memoize
|
|
219
|
+
def wopi_url(self):
|
|
220
|
+
"""Return the URL to load the document in LibreOffice / Collabora."""
|
|
221
|
+
if not self.editor_url or not self.jwt_token:
|
|
222
|
+
return
|
|
223
|
+
document_url = self.context.absolute_url()
|
|
224
|
+
|
|
225
|
+
# optionally enable direct backend-to-backend connection
|
|
226
|
+
if self.plone_server_url != self.portal_url:
|
|
227
|
+
document_url = document_url.replace(self.portal_url, self.plone_server_url)
|
|
228
|
+
|
|
229
|
+
uuid = IUUID(self.context)
|
|
230
|
+
args = dict(
|
|
231
|
+
WOPISrc="%s/@@collabora-wopi/files/%s" % (document_url, uuid),
|
|
232
|
+
access_token=self.jwt_token, # login credentials
|
|
233
|
+
# https://sdk.collaboraonline.com/docs/theming.html
|
|
234
|
+
ui_defaults=api.portal.get_registry_record(
|
|
235
|
+
n(b"collective.collabora.ui_defaults"),
|
|
236
|
+
default="UIMode=compact;TextSidebar=false;TextRuler=false;PresentationStatusbar=false;SpreadsheetSidebar=false;", # noqa: E501
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
quoted_args = urlencode(args)
|
|
240
|
+
return "%s%s" % (self.editor_url, quoted_args)
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
@memoize
|
|
244
|
+
def iframe_is_cors(self):
|
|
245
|
+
"""
|
|
246
|
+
Requesting fullscreen works only when CORS protection is not in play,
|
|
247
|
+
i.e. when running Collabora Online via a reverse proxy on the same
|
|
248
|
+
domain and port as Plone itself - that is the recommended way of running
|
|
249
|
+
in production.
|
|
250
|
+
"""
|
|
251
|
+
return utils.collabora_is_cors()
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def css_import(self):
|
|
255
|
+
return (
|
|
256
|
+
"@import url(%s/++resource++collective.collabora/edit.css" % self.portal_url
|
|
257
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
.section-collabora-actions {
|
|
2
|
+
text-align: right;
|
|
3
|
+
background-color: #d6d6d6;
|
|
4
|
+
margin-bottom: 0;
|
|
5
|
+
padding: 0.5rem;
|
|
6
|
+
}
|
|
7
|
+
.section-collabora-actions.plone6 .btn {
|
|
8
|
+
font-size: 0.9rem;
|
|
9
|
+
}
|
|
10
|
+
.section-collabora-actions.plone4 {
|
|
11
|
+
display: flex;
|
|
12
|
+
justify-content: flex-end;
|
|
13
|
+
}
|
|
14
|
+
#content .section-collabora-actions.plone4 a.btn {
|
|
15
|
+
font-size: 0.9rem;
|
|
16
|
+
margin: 0 0.5rem;
|
|
17
|
+
padding: 0.25rem 0.5rem;
|
|
18
|
+
border-radius: 0.25rem;
|
|
19
|
+
background-color: #6c757d; color:white;
|
|
20
|
+
}
|
|
21
|
+
#content .section-collabora-actions.plone4 a.btn:hover {
|
|
22
|
+
background-color: #555;
|
|
23
|
+
}
|
|
24
|
+
#content .section-collabora-actions.plone4 a.btn.fullscreen {
|
|
25
|
+
margin-right: auto;
|
|
26
|
+
}
|
|
27
|
+
.section-collabora-actions .fullscreen {
|
|
28
|
+
float: left;
|
|
29
|
+
}
|
|
30
|
+
.section-collabora-main iframe {
|
|
31
|
+
border: 0
|
|
32
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//
|
|
2
|
+
// --- host to collabora ---
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
function collabora_postMessage(message_id, values={}) {
|
|
7
|
+
var iframe = document.getElementById("cool-iframe");
|
|
8
|
+
var targetOrigin = iframe.getAttribute("collabora_server_url");
|
|
9
|
+
var msg = {
|
|
10
|
+
"MessageId": message_id,
|
|
11
|
+
"SendTime": Date.now(),
|
|
12
|
+
"Values": values
|
|
13
|
+
}
|
|
14
|
+
console.log(msg);
|
|
15
|
+
window.frames[0].postMessage(JSON.stringify(msg), targetOrigin);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
function collabora_action_fullscreen() {
|
|
20
|
+
// Requesting fullscreen works only when CORS protection is not in play, i.e.
|
|
21
|
+
// when running Collabora via a reverse proxy on the same domain and port as
|
|
22
|
+
// Plone itself.
|
|
23
|
+
collabora_postMessage("Action_Fullscreen");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
function collabora_action_save() {
|
|
28
|
+
collabora_postMessage("Action_Save", {"DontSaveIfUnmodified": "true"});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function collabora_action_close() {
|
|
32
|
+
collabora_postMessage("Action_Close");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function collabora_action_save_and_close() {
|
|
36
|
+
collabora_action_save();
|
|
37
|
+
collabora_action_close();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//
|
|
41
|
+
// --- collabora to host ---
|
|
42
|
+
//
|
|
43
|
+
|
|
44
|
+
function isValidJSON(text) {
|
|
45
|
+
try {
|
|
46
|
+
JSON.parse(text);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resize_iframe() {
|
|
54
|
+
var iframe = document.getElementById("cool-iframe");
|
|
55
|
+
var plone_version = iframe.getAttribute("plone_version");
|
|
56
|
+
|
|
57
|
+
console.log("Resizing iframe on document loaded");
|
|
58
|
+
if (plone_version == "quaive") {
|
|
59
|
+
var offset = iframe.offsetTop + 80;
|
|
60
|
+
} else if (plone_version == "plone6") {
|
|
61
|
+
var offset = iframe.offsetTop + 5;
|
|
62
|
+
} else if (plone_version == "plone5") {
|
|
63
|
+
var offset = window.document.getElementById("main-container").offsetTop + 55;
|
|
64
|
+
} else {
|
|
65
|
+
var offset = window.document.getElementById("content").offsetTop + 210;
|
|
66
|
+
}
|
|
67
|
+
iframe.style.height = 'calc(100vh - ' + offset + 'px)';
|
|
68
|
+
console.log("Resized cool-iframe");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// https://sdk.collaboraonline.com/docs/postmessage_api.html
|
|
72
|
+
function handlePostMessage(e) {
|
|
73
|
+
// The actual message is contained in the data property of the event.
|
|
74
|
+
if (! isValidJSON(e.data)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
var msg = JSON.parse(e.data);
|
|
78
|
+
var msgId = msg.MessageId;
|
|
79
|
+
var msgData = msg.Values;
|
|
80
|
+
console.log('Received message: ' + msgId);
|
|
81
|
+
console.log(msgData);
|
|
82
|
+
if (msgData.Status == 'Frame_Ready') {
|
|
83
|
+
collabora_postMessage("Host_PostmessageReady");
|
|
84
|
+
}
|
|
85
|
+
if (msgData.Status == 'Document_Loaded') {
|
|
86
|
+
resize_iframe();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
91
|
+
window.addEventListener('message', handlePostMessage, false);
|
|
92
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<html xmlns="http://www.w3.org/1999/xhtml"
|
|
2
|
+
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
|
|
3
|
+
xmlns:metal="http://xml.zope.org/namespaces/metal"
|
|
4
|
+
xmlns:tal="http://xml.zope.org/namespaces/tal"
|
|
5
|
+
lang="en"
|
|
6
|
+
metal:use-macro="context/main_template/macros/master"
|
|
7
|
+
xml:lang="en"
|
|
8
|
+
i18n:domain="collective.collabora"
|
|
9
|
+
>
|
|
10
|
+
<body>
|
|
11
|
+
|
|
12
|
+
<metal:css fill-slot="style_slot">
|
|
13
|
+
<style type="text/css"
|
|
14
|
+
tal:content="view/css_import"
|
|
15
|
+
>
|
|
16
|
+
</style>
|
|
17
|
+
</metal:css>
|
|
18
|
+
<metal:js fill-slot="javascript_head_slot">
|
|
19
|
+
<script tal:attributes="
|
|
20
|
+
src python: view.portal_url + '/++resource++collective.collabora/edit.js';
|
|
21
|
+
"></script>
|
|
22
|
+
</metal:js>
|
|
23
|
+
|
|
24
|
+
<metal:main fill-slot="main">
|
|
25
|
+
<metal:block define-macro="main"
|
|
26
|
+
tal:define="
|
|
27
|
+
error_msg view/error_msg;
|
|
28
|
+
"
|
|
29
|
+
>
|
|
30
|
+
<div tal:condition="error_msg">
|
|
31
|
+
<h2 i18n:translate="label_error">Error</h2>
|
|
32
|
+
<p class="alert alert-warning"
|
|
33
|
+
tal:content="error_msg"
|
|
34
|
+
>error_msg</p>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<section tal:condition="not: view/error_msg"
|
|
38
|
+
tal:attributes="
|
|
39
|
+
class python:'section section-collabora-actions ' + view.plone_version;
|
|
40
|
+
"
|
|
41
|
+
>
|
|
42
|
+
<a class="btn btn-secondary standalone fullscreen"
|
|
43
|
+
onclick="collabora_action_fullscreen(); return false;"
|
|
44
|
+
tal:condition="not: view/iframe_is_cors"
|
|
45
|
+
i18n:translate=""
|
|
46
|
+
>Fullscreen</a>
|
|
47
|
+
<a class="btn btn-secondary standalone download"
|
|
48
|
+
tal:attributes="
|
|
49
|
+
href view/download_url;
|
|
50
|
+
"
|
|
51
|
+
>
|
|
52
|
+
<span i18n:translate="">Download</span>
|
|
53
|
+
<span tal:content="python: view.human_readable_size()"
|
|
54
|
+
i18n:ignore=""
|
|
55
|
+
>size</span>
|
|
56
|
+
</a>
|
|
57
|
+
<a class="btn btn-secondary standalone edit"
|
|
58
|
+
tal:condition="view/can_edit"
|
|
59
|
+
tal:attributes="
|
|
60
|
+
href python:context.absolute_url() + '/@@edit';
|
|
61
|
+
"
|
|
62
|
+
i18n:translate=""
|
|
63
|
+
>Edit metadata</a>
|
|
64
|
+
<a class="btn btn-primary context"
|
|
65
|
+
onclick="javascript:collabora_action_save_and_close(); return true;"
|
|
66
|
+
tal:condition="view/can_edit"
|
|
67
|
+
tal:attributes="
|
|
68
|
+
href python:context.absolute_url() + '/view';
|
|
69
|
+
"
|
|
70
|
+
i18n:translate=""
|
|
71
|
+
>Save & Close</a>
|
|
72
|
+
<a class="btn btn-primary context"
|
|
73
|
+
onclick="javascript:collabora_action_close(); return true;"
|
|
74
|
+
tal:condition="not:view/can_edit"
|
|
75
|
+
tal:attributes="
|
|
76
|
+
href python:context.absolute_url() + '/view';
|
|
77
|
+
"
|
|
78
|
+
i18n:translate=""
|
|
79
|
+
>Close</a>
|
|
80
|
+
</section>
|
|
81
|
+
|
|
82
|
+
<section tal:condition="not: view/error_msg"
|
|
83
|
+
tal:attributes="
|
|
84
|
+
class python:'section section-collabora-main ' + view.plone_version;
|
|
85
|
+
"
|
|
86
|
+
>
|
|
87
|
+
<iframe id="cool-iframe"
|
|
88
|
+
allowfullscreen="true"
|
|
89
|
+
height="100%"
|
|
90
|
+
name="cool-iframe"
|
|
91
|
+
scrolling="no"
|
|
92
|
+
width="100%"
|
|
93
|
+
tal:attributes="
|
|
94
|
+
collabora_server_url view/collabora_server_url;
|
|
95
|
+
plone_version view/plone_version;
|
|
96
|
+
src view/wopi_url;
|
|
97
|
+
"
|
|
98
|
+
></iframe>
|
|
99
|
+
</section>
|
|
100
|
+
|
|
101
|
+
</metal:block>
|
|
102
|
+
</metal:main>
|
|
103
|
+
|
|
104
|
+
</body>
|
|
105
|
+
</html>
|