Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/richdocuments.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulius Härtl <jus@bitgrid.net>2020-05-14 09:00:33 +0300
committerJulius Härtl <jus@bitgrid.net>2020-05-22 09:42:33 +0300
commitcedbdb296c69d8a91f1369d2abde605670d3b429 (patch)
tree54eb25a2ec5a700f4e2e3cacd739ace2aead927e
parentd0eba26c7c54b93329b24907240480ecda44dd78 (diff)
Basic viewer integration
Signed-off-by: Julius Härtl <jus@bitgrid.net>
-rw-r--r--appinfo/app.php5
-rw-r--r--lib/AppInfo/Application.php9
-rw-r--r--src/files.js517
-rw-r--r--src/view/FilesAppIntegration.js4
-rw-r--r--src/view/Office.vue166
-rw-r--r--src/viewer.js518
-rw-r--r--templates/documents.php2
-rw-r--r--webpack.common.js1
8 files changed, 710 insertions, 512 deletions
diff --git a/appinfo/app.php b/appinfo/app.php
index 9785bc7d..087b31de 100644
--- a/appinfo/app.php
+++ b/appinfo/app.php
@@ -37,14 +37,13 @@ $eventDispatcher = \OC::$server->getEventDispatcher();
$eventDispatcher->addListener(
'OCA\Files::loadAdditionalScripts',
function() {
- \OCP\Util::addScript('richdocuments', 'viewer');
- \OCP\Util::addStyle('richdocuments', 'viewer');
+ \OCP\Util::addScript('richdocuments', 'files');
}
);
$eventDispatcher->addListener(
'OCA\Files_Sharing::loadAdditionalScripts',
function() {
- \OCP\Util::addScript('richdocuments', 'viewer');
+ \OCP\Util::addScript('richdocuments', 'files');
}
);
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index b9ca89f5..bf8237f0 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -34,7 +34,9 @@ use OCA\Richdocuments\Preview\OOXML;
use OCA\Richdocuments\Preview\OpenDocument;
use OCA\Richdocuments\Preview\Pdf;
use OCA\Richdocuments\Service\FederationService;
+use OCA\Viewer\Event\LoadViewer;
use OCP\AppFramework\App;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\IPreview;
class Application extends App {
@@ -44,6 +46,12 @@ class Application extends App {
public function __construct(array $urlParams = array()) {
parent::__construct(self::APPNAME, $urlParams);
+ /** @var IEventDispatcher $eventDispatcher */
+ $eventDispatcher = $this->getContainer()->getServer()->query(IEventDispatcher::class);
+ $eventDispatcher->addListener(LoadViewer::class, function () {
+ \OCP\Util::addScript('richdocuments', 'viewer');
+ });
+
$this->getContainer()->registerCapability(Capabilities::class);
}
@@ -93,6 +101,7 @@ class Application extends App {
$cspManager = $container->getServer()->getContentSecurityPolicyManager();
$policy = new ContentSecurityPolicy();
if ($publicWopiUrl !== '') {
+ $policy->addAllowedFrameDomain('\'self\'');
$policy->addAllowedFrameDomain($publicWopiUrl);
if (method_exists($policy, 'addAllowedFormActionDomain')) {
$policy->addAllowedFormActionDomain($publicWopiUrl);
diff --git a/src/files.js b/src/files.js
new file mode 100644
index 00000000..1d8f862a
--- /dev/null
+++ b/src/files.js
@@ -0,0 +1,517 @@
+import { getDocumentUrlFromTemplate, getDocumentUrlForPublicFile, getDocumentUrlForFile, getSearchParam } from './helpers/url'
+import PostMessageService from './services/postMessage'
+import Config from './services/config'
+import Types from './helpers/types'
+import FilesAppIntegration from './view/FilesAppIntegration'
+import '../css/viewer.scss'
+
+const FRAME_DOCUMENT = 'FRAME_DOCUMENT'
+const PostMessages = new PostMessageService({
+ FRAME_DOCUMENT: () => document.getElementById('richdocumentsframe').contentWindow
+})
+
+const preloadCreate = getSearchParam('richdocuments_create')
+const preloadOpen = getSearchParam('richdocuments_open')
+const Preload = {}
+
+if (preloadCreate) {
+ Preload.create = {
+ type: getSearchParam('richdocuments_create'),
+ filename: getSearchParam('richdocuments_filename')
+ }
+}
+
+if (preloadOpen) {
+ Preload.open = {
+ filename: preloadOpen,
+ id: getSearchParam('richdocuments_fileId'),
+ dir: getSearchParam('dir')
+ }
+}
+
+const isDownloadHidden = document.getElementById('hideDownload') && document.getElementById('hideDownload').value === 'true'
+
+const isPublic = document.getElementById('isPublic') && document.getElementById('isPublic').value === '1'
+
+const odfViewer = {
+
+ open: false,
+ receivedLoading: false,
+ isCollaboraConfigured: typeof OC.getCapabilities().richdocuments.collabora === 'object' && OC.getCapabilities().richdocuments.collabora.length !== 0,
+ supportedMimes: OC.getCapabilities().richdocuments.mimetypes.concat(OC.getCapabilities().richdocuments.mimetypesNoDefaultOpen),
+ excludeMimeFromDefaultOpen: OC.getCapabilities().richdocuments.mimetypesNoDefaultOpen,
+ hideDownloadMimes: ['image/jpeg', 'image/svg+xml', 'image/cgm', 'image/vnd.dxf', 'image/x-emf', 'image/x-wmf', 'image/x-wpg', 'image/x-freehand', 'image/bmp', 'image/png', 'image/gif', 'image/tiff', 'image/jpg', 'image/jpeg', 'text/plain', 'application/pdf'],
+
+ register() {
+ const EDIT_ACTION_NAME = 'Edit with ' + OC.getCapabilities().richdocuments.productName
+ for (let mime of odfViewer.supportedMimes) {
+ OCA.Files.fileActions.register(
+ mime,
+ EDIT_ACTION_NAME,
+ 0,
+ OC.imagePath('core', 'actions/rename'),
+ this.onEdit,
+ t('richdocuments', 'Edit with {productName}', { productName: OC.getCapabilities().richdocuments.productName })
+ )
+ if (odfViewer.excludeMimeFromDefaultOpen.indexOf(mime) === -1 || isDownloadHidden) {
+ OCA.Files.fileActions.setDefault(mime, EDIT_ACTION_NAME)
+ }
+ }
+ },
+
+ onEdit: function(fileName, context) {
+ if (!odfViewer.isCollaboraConfigured) {
+ const setupUrl = OC.generateUrl('/settings/admin/richdocuments')
+ const installHint = OC.isUserAdmin()
+ ? `<a href="${setupUrl}">Collabora Online is not setup yet. <br />Click here to configure your own server or connect to a demo server.</a>`
+ : t('richdocuments', 'Collabora Online is not setup yet. Please contact your administrator.')
+
+ if (OCP.Toast) {
+ OCP.Toast.error(installHint, {
+ isHTML: true,
+ timeout: 0
+ })
+ } else {
+ OC.Notification.showHtml(installHint)
+ }
+ return
+ }
+ if (odfViewer.open === true) {
+ return
+ }
+ odfViewer.open = true
+ let fileList = null
+ if (context) {
+ fileList = context.fileList
+ var fileDir = context.dir
+ var fileId = context.fileId || context.$file.attr('data-id')
+ var templateId = context.templateId
+ if (context.fileList) {
+ context.fileList.setViewerMode(true)
+ context.fileList.setPageTitle(fileName)
+ context.fileList.showMask()
+ }
+ }
+ odfViewer.receivedLoading = false
+
+ let documentUrl = getDocumentUrlForFile(fileDir, fileId)
+ if (isPublic) {
+ documentUrl = getDocumentUrlForPublicFile(fileName, fileId)
+ }
+ if (typeof (templateId) !== 'undefined') {
+ documentUrl = getDocumentUrlFromTemplate(templateId, fileName, fileDir)
+ }
+
+ /**
+ * We need to reload the page to set a proper CSP if the file is federated
+ * and the reload didn't happen for the exact same file
+ */
+ const canAccessCSP = (url, callback) => {
+ let canEmbed = false
+ let frame = document.createElement('iframe')
+ frame.setAttribute('src', url)
+ frame.setAttribute('onload', () => {
+ canEmbed = true
+ })
+ document.body.appendChild(frame)
+ setTimeout(() => {
+ if (!canEmbed) {
+ callback()
+ }
+ document.body.removeChild(frame)
+ }, 50)
+
+ }
+
+ const reloadForFederationCSP = (fileName) => {
+ const preloadId = Preload.open ? parseInt(Preload.open.id) : -1
+ const fileModel = fileList.findFile(fileName)
+ const shareOwnerId = fileModel.shareOwnerId
+ if (typeof shareOwnerId !== 'undefined') {
+ const lastIndex = shareOwnerId.lastIndexOf('@')
+ // only redirect if remote file, not opened though reload and csp blocks the request
+ if (shareOwnerId.substr(lastIndex).indexOf('/') !== -1 && fileModel.id !== preloadId) {
+ canAccessCSP('https://' + shareOwnerId.substr(lastIndex) + '/status.php', () => {
+ window.location = OC.generateUrl('/apps/richdocuments/open?fileId=' + fileId)
+ })
+ }
+ }
+ return false
+ }
+
+ if (context) {
+ reloadForFederationCSP(fileName)
+ }
+
+ OC.addStyle('richdocuments', 'mobile')
+
+ var $iframe = $('<iframe id="richdocumentsframe" nonce="' + btoa(OC.requestToken) + '" scrolling="no" allowfullscreen src="' + documentUrl + '" />')
+ odfViewer.loadingTimeout = setTimeout(function() {
+ if (!odfViewer.receivedLoading) {
+ odfViewer.onClose()
+ OC.Notification.showTemporary(t('richdocuments', 'Failed to load {productName} - please try again later', { productName: OC.getCapabilities().richdocuments.productName || 'Collabora Online' }))
+ }
+ }, 15000)
+ $iframe.src = documentUrl
+
+ $('body').css('overscroll-behavior-y', 'none')
+ var viewport = document.querySelector('meta[name=viewport]')
+ viewport.setAttribute('content', 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no')
+ if (isPublic) {
+ // force the preview to adjust its height
+ $('#preview').append($iframe).css({ height: '100%' })
+ $('body').css({ height: '100%' })
+ $('#content').addClass('full-height')
+ $('footer').addClass('hidden')
+ $('#imgframe').addClass('hidden')
+ $('.directLink').addClass('hidden')
+ $('.directDownload').addClass('hidden')
+ $('#controls').addClass('hidden')
+ $('#content').addClass('loading')
+ } else {
+ $('body').css('overflow', 'hidden')
+ $('#app-content').append($iframe)
+ $iframe.hide()
+ }
+
+ $('#app-content #controls').addClass('hidden')
+ setTimeout(() => {
+ FilesAppIntegration.init({
+ fileName,
+ fileId,
+ fileList,
+ sendPostMessage: (msgId, values) => {
+ PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, msgId, values)
+ }
+ })
+ })
+ },
+
+ onReceiveLoading() {
+ odfViewer.receivedLoading = true
+ $('#richdocumentsframe').show()
+ $('html, body').scrollTop(0)
+ $('#content').removeClass('loading')
+ FilesAppIntegration.initAfterReady()
+ },
+
+ onClose: function() {
+ odfViewer.open = false
+ clearTimeout(odfViewer.loadingTimeout)
+ odfViewer.receivedLoading = false
+ $('link[href*="richdocuments/css/mobile"]').remove()
+ $('#app-content #controls').removeClass('hidden')
+ $('#richdocumentsframe').remove()
+ $('.searchbox').show()
+ $('body').css('overflow', 'auto')
+
+ if (isPublic) {
+ $('#content').removeClass('full-height')
+ $('footer').removeClass('hidden')
+ $('#imgframe').removeClass('hidden')
+ $('.directLink').removeClass('hidden')
+ $('.directDownload').removeClass('hidden')
+ }
+
+ OC.Util.History.replaceState()
+
+ FilesAppIntegration.close()
+ },
+
+ registerFilesMenu: function() {
+
+ const registerFilesMenu = (OCA) => {
+ OCA.FilesLOMenu = {
+ attach: function(newFileMenu) {
+ var self = this
+ const document = Types.getFileType('document')
+ const spreadsheet = Types.getFileType('spreadsheet')
+ const presentation = Types.getFileType('presentation')
+
+ newFileMenu.addMenuEntry({
+ id: 'add-' + document.extension,
+ displayName: t('richdocuments', 'New Document'),
+ templateName: t('richdocuments', 'New Document') + '.' + document.extension,
+ iconClass: 'icon-filetype-document',
+ fileType: 'x-office-document',
+ actionHandler: function(filename) {
+ if (OC.getCapabilities().richdocuments.templates) {
+ self._openTemplatePicker('document', document.mime, filename)
+ } else {
+ self._createDocument(document.mime, filename)
+ }
+ }
+ })
+
+ newFileMenu.addMenuEntry({
+ id: 'add-' + spreadsheet.extension,
+ displayName: t('richdocuments', 'New Spreadsheet'),
+ templateName: t('richdocuments', 'New Spreadsheet') + '.' + spreadsheet.extension,
+ iconClass: 'icon-filetype-spreadsheet',
+ fileType: 'x-office-spreadsheet',
+ actionHandler: function(filename) {
+ if (OC.getCapabilities().richdocuments.templates) {
+ self._openTemplatePicker('spreadsheet', spreadsheet.mime, filename)
+ } else {
+ self._createDocument(spreadsheet.mime, filename)
+ }
+ }
+ })
+
+ newFileMenu.addMenuEntry({
+ id: 'add-' + presentation.extension,
+ displayName: t('richdocuments', 'New Presentation'),
+ templateName: t('richdocuments', 'New Presentation') + '.' + presentation.extension,
+ iconClass: 'icon-filetype-presentation',
+ fileType: 'x-office-presentation',
+ actionHandler: function(filename) {
+ if (OC.getCapabilities().richdocuments.templates) {
+ self._openTemplatePicker('presentation', presentation.mime, filename)
+ } else {
+ self._createDocument(presentation.mime, filename)
+ }
+ }
+ })
+ },
+
+ _createDocument: function(mimetype, filename) {
+ OCA.Files.Files.isFileNameValid(filename)
+ filename = FileList.getUniqueName(filename)
+
+ $.post(
+ OC.generateUrl('apps/richdocuments/ajax/documents/create'),
+ { mimetype: mimetype, filename: filename, dir: $('#dir').val() },
+ function(response) {
+ if (response && response.status === 'success') {
+ FileList.add(response.data, { animate: true, scrollTo: true })
+ } else {
+ OC.dialogs.alert(response.data.message, t('core', 'Could not create file'))
+ }
+ }
+ )
+ },
+
+ _createDocumentFromTemplate: function(templateId, mimetype, filename) {
+ OCA.Files.Files.isFileNameValid(filename)
+ filename = FileList.getUniqueName(filename)
+ $.post(
+ OC.generateUrl('apps/richdocuments/ajax/documents/create'),
+ { mimetype: mimetype, filename: filename, dir: $('#dir').val() },
+ function(response) {
+ if (response && response.status === 'success') {
+ FileList.add(response.data, { animate: false, scrollTo: false })
+ odfViewer.onEdit(filename, {
+ fileId: -1,
+ dir: $('#dir').val(),
+ templateId: templateId,
+ fileList: FileList
+ })
+ } else {
+ OC.dialogs.alert(response.data.message, t('core', 'Could not create file'))
+ }
+ }
+ )
+ },
+
+ _openTemplatePicker: function(type, mimetype, filename) {
+ var self = this
+ $.ajax({
+ url: OC.linkToOCS('apps/richdocuments/api/v1/templates', 2) + type,
+ dataType: 'json'
+ }).then(function(response) {
+ if (response.ocs.data.length === 1) {
+ const { id } = response.ocs.data[0]
+ self._createDocumentFromTemplate(id, mimetype, filename)
+ return
+ }
+ self._buildTemplatePicker(response.ocs.data)
+ .then(function() {
+ var buttonlist = [{
+ text: t('core', 'Cancel'),
+ classes: 'cancel',
+ click: function() {
+ $(this).ocdialog('close')
+ }
+ }, {
+ text: t('richdocuments', 'Create'),
+ classes: 'primary',
+ click: function() {
+ var templateId = this.dataset.templateId
+ self._createDocumentFromTemplate(templateId, mimetype, filename)
+ $(this).ocdialog('close')
+ }
+ }]
+
+ $('#template-picker').ocdialog({
+ closeOnEscape: true,
+ modal: true,
+ buttons: buttonlist
+ })
+ })
+ })
+ },
+
+ _buildTemplatePicker: function(data) {
+ var self = this
+ return $.get(OC.filePath('richdocuments', 'templates', 'templatePicker.html'), function(tmpl) {
+ var $tmpl = $(tmpl)
+ // init template picker
+ var $dlg = $tmpl.octemplate({
+ dialog_name: 'template-picker',
+ dialog_title: t('richdocuments', 'Select template')
+ })
+
+ // create templates list
+ var templates = _.values(data)
+ templates.forEach(function(template) {
+ self._appendTemplateFromData($dlg[0], template)
+ })
+
+ $('body').append($dlg)
+ })
+ },
+
+ _appendTemplateFromData: function(dlg, data) {
+ var template = dlg.querySelector('.template-model').cloneNode(true)
+ template.className = ''
+ template.querySelector('img').src = OC.generateUrl('apps/richdocuments/template/preview/' + data.id)
+ template.querySelector('h2').textContent = data.name
+ template.onclick = function() {
+ dlg.dataset.templateId = data.id
+ }
+ if (!dlg.dataset.templateId) {
+ dlg.dataset.templateId = data.id
+ }
+
+ dlg.querySelector('.template-container').appendChild(template)
+ }
+ }
+ }
+ registerFilesMenu(OCA)
+
+ OC.Plugins.register('OCA.Files.NewFileMenu', OCA.FilesLOMenu)
+
+ // Open the template picker if there was a create parameter detected on load
+ if (Preload.create && Preload.create.type && Preload.create.filename) {
+ const fileType = Types.getFileType(Preload.create.type, Config.get('ooxml'))
+ OCA.FilesLOMenu._openTemplatePicker(Preload.create.type, fileType.mime, Preload.create.filename + '.' + fileType.extension)
+ }
+
+ if (Preload.open) {
+ FileList.$fileList.one('updated', function() {
+ odfViewer.onEdit(Preload.open.filename, {
+ fileId: Preload.open.id,
+ dir: document.getElementById('dir').value,
+ fileList: FileList
+ })
+ })
+ }
+ }
+}
+
+const settings = OC.getCapabilities()['richdocuments']['config'] || {}
+Config.update('ooxml', settings['doc_format'] === 'ooxml')
+
+window.OCA.RichDocuments = {
+ config: {
+ create: Types.getFileTypes()
+ }
+}
+
+$(document).ready(function() {
+ // register file actions and menu
+ if (typeof OCA !== 'undefined'
+ && typeof OCA.Files !== 'undefined'
+ && typeof OCA.Files.fileActions !== 'undefined'
+ ) {
+ // check if texteditor app is enabled and loaded...
+ if (typeof OCA.Files_Texteditor === 'undefined' && typeof OCA.Text === 'undefined') {
+ odfViewer.supportedMimes.push('text/plain')
+ }
+
+ odfViewer.registerFilesMenu()
+ odfViewer.register()
+ }
+
+ // Open documents if a public page is opened for a supported mimetype
+ const isSupportedMime = isPublic && odfViewer.supportedMimes.indexOf($('#mimetype').val()) !== -1 && odfViewer.excludeMimeFromDefaultOpen.indexOf($('#mimetype').val()) === -1
+ const showSecureView = isPublic && isDownloadHidden && odfViewer.hideDownloadMimes.indexOf($('#mimetype').val()) !== -1
+ if (isSupportedMime || showSecureView) {
+ odfViewer.onEdit(document.getElementById('filename').value)
+ }
+
+ PostMessages.registerPostMessageHandler(({ parsed }) => {
+ console.debug('[viewer] Received post message', parsed)
+ const { msgId, args, deprecated } = parsed
+ if (deprecated) { return }
+
+ switch (msgId) {
+ case 'loading':
+ odfViewer.onReceiveLoading()
+ break
+ case 'App_LoadingStatus':
+ if (args.Status === 'Timeout') {
+ odfViewer.onClose()
+ OC.Notification.showTemporary(t('richdocuments', 'Failed to connect to {productName}. Please try again later or contact your server administrator.',
+ { productName: OC.getCapabilities().richdocuments.productName }
+ ))
+ }
+ break
+ case 'UI_Share':
+ FilesAppIntegration.share()
+ break
+ case 'UI_CreateFile':
+ FilesAppIntegration.createNewFile(args.DocumentType)
+ break
+ case 'UI_InsertGraphic':
+ FilesAppIntegration.insertGraphic((filename, url) => {
+ PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, 'postAsset', { FileName: filename, Url: url })
+ })
+ break
+ case 'File_Rename':
+ FileList.reload()
+ OC.Apps.hideAppSidebar()
+ FilesAppIntegration.fileName = args.NewName
+ break
+ case 'close':
+ odfViewer.onClose()
+ break
+ case 'Get_Views_Resp':
+ case 'Views_List':
+ FilesAppIntegration.setViews(args)
+ break
+ case 'UI_FileVersions':
+ case 'rev-history':
+ FilesAppIntegration.showRevHistory()
+ break
+ case 'App_VersionRestore':
+ if (args.Status === 'Pre_Restore_Ack') {
+ FilesAppIntegration.restoreVersionExecute()
+ }
+ break
+ }
+
+ // legacy view handling
+ if (msgId === 'View_Added') {
+ FilesAppIntegration.views[args.ViewId] = args
+ FilesAppIntegration.renderAvatars()
+ } else if (msgId === 'View_Removed') {
+ delete FilesAppIntegration.views[args.ViewId]
+ FilesAppIntegration.renderAvatars()
+ } else if (msgId === 'FollowUser_Changed') {
+ if (args.IsFollowEditor) {
+ FilesAppIntegration.followingEditor = true
+ } else {
+ FilesAppIntegration.followingEditor = false
+ }
+ if (args.IsFollowUser) {
+ FilesAppIntegration.following = args.FollowedViewId
+ } else {
+ FilesAppIntegration.following = null
+ }
+ FilesAppIntegration.renderAvatars()
+ }
+
+ })
+ window.FilesAppIntegration = FilesAppIntegration
+})
diff --git a/src/view/FilesAppIntegration.js b/src/view/FilesAppIntegration.js
index f6cba7d7..22cfd86d 100644
--- a/src/view/FilesAppIntegration.js
+++ b/src/view/FilesAppIntegration.js
@@ -106,10 +106,10 @@ export default {
if (this.fileList) {
return this.fileList
}
- if (OCA.Files.App) {
+ if (OCA.Files && OCA.Files.App) {
return OCA.Files.App.fileList
}
- if (OCA.Sharing.PublicApp) {
+ if (OCA.Sharing && OCA.Sharing.PublicApp) {
return OCA.Sharing.PublicApp.fileList
}
return null
diff --git a/src/view/Office.vue b/src/view/Office.vue
new file mode 100644
index 00000000..44397594
--- /dev/null
+++ b/src/view/Office.vue
@@ -0,0 +1,166 @@
+<!--
+ - @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ -
+ - @author Julius Härtl <jus@bitgrid.net>
+ -
+ - @license GNU AGPL version 3 or any later version
+ -
+ - 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/>.
+ -
+ -->
+
+<template>
+ <transition name="fade" appear>
+ <div v-show="loading" id="richdocuments-wrapper">
+ <div class="header">
+ <!-- This is obviously not the way to go since it would require absolute positioning and therefore not be compatible with viewer actions/sidebar -->
+ <div class="avatars">
+ <avatar v-for="view in avatarViews" :key="view.ViewId" :user="view.UserId"
+ :display-name="view.UserName"
+ :style="viewColor(view)" />
+ </div>
+ </div>
+ <iframe id="collaboraframe" ref="documentFrame" :src="src" />
+ </div>
+ </transition>
+</template>
+
+<script>
+import Avatar from '@nextcloud/vue/dist/Components/Avatar'
+
+import { getDocumentUrlForFile } from '../helpers/url'
+import PostMessageService from '../services/postMessage'
+import FilesAppIntegration from './FilesAppIntegration'
+
+const FRAME_DOCUMENT = 'FRAME_DOCUMENT'
+const PostMessages = new PostMessageService({
+ FRAME_DOCUMENT: () => document.getElementById('collaboraframe').contentWindow
+})
+
+export default {
+ name: 'Office',
+ components: {
+ Avatar
+ },
+ props: {
+ filename: {
+ type: String,
+ default: null
+ },
+ fileid: {
+ type: Number,
+ default: null
+ },
+ hasPreview: {
+ type: Boolean,
+ required: false,
+ default: () => false
+ }
+ },
+ data() {
+ return {
+ src: null,
+ loading: false,
+ views: []
+ }
+ },
+ computed: {
+ avatarViews() {
+ return this.views
+ },
+ viewColor() {
+ return view => ({
+ 'border-color': '#' + ('000000' + Number(view.Color).toString(16)).substr(-6),
+ 'border-width': '2px',
+ 'border-style': 'solid'
+ })
+ }
+ },
+ mounted() {
+ PostMessages.registerPostMessageHandler(({ parsed }) => {
+ console.debug('[viewer] Received post message', parsed)
+ const { msgId, args, deprecated } = parsed
+ if (deprecated) { return }
+
+ switch (msgId) {
+ case 'loading':
+ break
+ case 'close':
+ this.$parent.close()
+ break
+ case 'Get_Views_Resp':
+ case 'Views_List':
+ this.views = args
+ break
+ case 'UI_InsertGraphic':
+ FilesAppIntegration.insertGraphic((filename, url) => {
+ PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, 'postAsset', { FileName: filename, Url: url })
+ })
+ break
+ }
+ })
+ this.load()
+ },
+ methods: {
+ async load() {
+ let documentUrl = getDocumentUrlForFile(this.filename, this.fileid) + '&path=' + this.filename
+ this.$emit('update:loaded', true)
+ this.src = documentUrl
+ this.loading = true
+ }
+ }
+}
+</script>
+<style lang="scss">
+ .header {
+ position: absolute;
+ right: 100px;
+ top: -50px;
+
+ .avatars {
+ display: flex;
+ padding: 9px;
+
+ .avatardiv {
+ margin-left: -15px;
+ box-shadow: 0 0 3px var(--color-box-shadow);
+ }
+
+ }
+ }
+
+ #richdocuments-wrapper {
+ width: 100vw;
+ height: calc(100vh - 50px);
+ top: 50px;
+ left: 0;
+ position: absolute;
+ z-index: 100000;
+ max-width: 100%;
+ display: flex;
+ flex-direction: column;
+ background-color: var(--color-main-background);
+ transition: opacity .25s;
+ }
+ iframe {
+ width: 100%;
+ flex-grow: 1;
+ }
+ .fade-enter-active, .fade-leave-active {
+ transition: opacity .25s;
+ }
+ .fade-enter, .fade-leave-to {
+ opacity: 0;
+ }
+</style>
diff --git a/src/viewer.js b/src/viewer.js
index 5c9e2b4d..4c1d7662 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -1,516 +1,22 @@
-import { getDocumentUrlFromTemplate, getDocumentUrlForPublicFile, getDocumentUrlForFile, getSearchParam } from './helpers/url'
-import PostMessageService from './services/postMessage'
-import Config from './services/config'
-import Types from './helpers/types'
-import FilesAppIntegration from './view/FilesAppIntegration'
-import '../css/viewer.scss'
+import Office from './view/Office'
-const FRAME_DOCUMENT = 'FRAME_DOCUMENT'
-const PostMessages = new PostMessageService({
- FRAME_DOCUMENT: () => document.getElementById('richdocumentsframe').contentWindow
-})
-
-const preloadCreate = getSearchParam('richdocuments_create')
-const preloadOpen = getSearchParam('richdocuments_open')
-const Preload = {}
-
-if (preloadCreate) {
- Preload.create = {
- type: getSearchParam('richdocuments_create'),
- filename: getSearchParam('richdocuments_filename')
- }
-}
-
-if (preloadOpen) {
- Preload.open = {
- filename: preloadOpen,
- id: getSearchParam('richdocuments_fileId'),
- dir: getSearchParam('dir')
- }
-}
-
-const isDownloadHidden = document.getElementById('hideDownload') && document.getElementById('hideDownload').value === 'true'
-
-const isPublic = document.getElementById('isPublic') && document.getElementById('isPublic').value === '1'
-
-const odfViewer = {
-
- open: false,
- receivedLoading: false,
- isCollaboraConfigured: typeof OC.getCapabilities().richdocuments.collabora === 'object' && OC.getCapabilities().richdocuments.collabora.length !== 0,
- supportedMimes: OC.getCapabilities().richdocuments.mimetypes.concat(OC.getCapabilities().richdocuments.mimetypesNoDefaultOpen),
- excludeMimeFromDefaultOpen: OC.getCapabilities().richdocuments.mimetypesNoDefaultOpen,
- hideDownloadMimes: ['image/jpeg', 'image/svg+xml', 'image/cgm', 'image/vnd.dxf', 'image/x-emf', 'image/x-wmf', 'image/x-wpg', 'image/x-freehand', 'image/bmp', 'image/png', 'image/gif', 'image/tiff', 'image/jpg', 'image/jpeg', 'text/plain', 'application/pdf'],
-
- register() {
- const EDIT_ACTION_NAME = 'Edit with ' + OC.getCapabilities().richdocuments.productName
- for (let mime of odfViewer.supportedMimes) {
- OCA.Files.fileActions.register(
- mime,
- EDIT_ACTION_NAME,
- 0,
- OC.imagePath('core', 'actions/rename'),
- this.onEdit,
- t('richdocuments', 'Edit with {productName}', { productName: OC.getCapabilities().richdocuments.productName })
- )
- if (odfViewer.excludeMimeFromDefaultOpen.indexOf(mime) === -1 || isDownloadHidden) {
- OCA.Files.fileActions.setDefault(mime, EDIT_ACTION_NAME)
- }
- }
- },
-
- onEdit: function(fileName, context) {
- if (!odfViewer.isCollaboraConfigured) {
- const setupUrl = OC.generateUrl('/settings/admin/richdocuments')
- const installHint = OC.isUserAdmin()
- ? `<a href="${setupUrl}">Collabora Online is not setup yet. <br />Click here to configure your own server or connect to a demo server.</a>`
- : t('richdocuments', 'Collabora Online is not setup yet. Please contact your administrator.')
-
- if (OCP.Toast) {
- OCP.Toast.error(installHint, {
- isHTML: true,
- timeout: 0
- })
- } else {
- OC.Notification.showHtml(installHint)
- }
- return
- }
- if (odfViewer.open === true) {
- return
- }
- odfViewer.open = true
- let fileList = null
- if (context) {
- fileList = context.fileList
- var fileDir = context.dir
- var fileId = context.fileId || context.$file.attr('data-id')
- var templateId = context.templateId
- if (context.fileList) {
- context.fileList.setViewerMode(true)
- context.fileList.setPageTitle(fileName)
- context.fileList.showMask()
- }
- }
- odfViewer.receivedLoading = false
-
- let documentUrl = getDocumentUrlForFile(fileDir, fileId)
- if (isPublic) {
- documentUrl = getDocumentUrlForPublicFile(fileName, fileId)
- }
- if (typeof (templateId) !== 'undefined') {
- documentUrl = getDocumentUrlFromTemplate(templateId, fileName, fileDir)
- }
-
- /**
- * We need to reload the page to set a proper CSP if the file is federated
- * and the reload didn't happen for the exact same file
- */
- const canAccessCSP = (url, callback) => {
- let canEmbed = false
- let frame = document.createElement('iframe')
- frame.setAttribute('src', url)
- frame.setAttribute('onload', () => {
- canEmbed = true
- })
- document.body.appendChild(frame)
- setTimeout(() => {
- if (!canEmbed) {
- callback()
- }
- document.body.removeChild(frame)
- }, 50)
-
- }
-
- const reloadForFederationCSP = (fileName) => {
- const preloadId = Preload.open ? parseInt(Preload.open.id) : -1
- const fileModel = fileList.findFile(fileName)
- const shareOwnerId = fileModel.shareOwnerId
- if (typeof shareOwnerId !== 'undefined') {
- const lastIndex = shareOwnerId.lastIndexOf('@')
- // only redirect if remote file, not opened though reload and csp blocks the request
- if (shareOwnerId.substr(lastIndex).indexOf('/') !== -1 && fileModel.id !== preloadId) {
- canAccessCSP('https://' + shareOwnerId.substr(lastIndex) + '/status.php', () => {
- window.location = OC.generateUrl('/apps/richdocuments/open?fileId=' + fileId)
- })
- }
- }
- return false
- }
-
- if (context) {
- reloadForFederationCSP(fileName)
- }
-
- OC.addStyle('richdocuments', 'mobile')
-
- var $iframe = $('<iframe id="richdocumentsframe" nonce="' + btoa(OC.requestToken) + '" scrolling="no" allowfullscreen src="' + documentUrl + '" />')
- odfViewer.loadingTimeout = setTimeout(function() {
- if (!odfViewer.receivedLoading) {
- odfViewer.onClose()
- OC.Notification.showTemporary(t('richdocuments', 'Failed to load {productName} - please try again later', { productName: OC.getCapabilities().richdocuments.productName || 'Collabora Online' }))
- }
- }, 15000)
- $iframe.src = documentUrl
-
- $('body').css('overscroll-behavior-y', 'none')
- var viewport = document.querySelector('meta[name=viewport]')
- viewport.setAttribute('content', 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no')
- if (isPublic) {
- // force the preview to adjust its height
- $('#preview').append($iframe).css({ height: '100%' })
- $('body').css({ height: '100%' })
- $('#content').addClass('full-height')
- $('footer').addClass('hidden')
- $('#imgframe').addClass('hidden')
- $('.directLink').addClass('hidden')
- $('.directDownload').addClass('hidden')
- $('#controls').addClass('hidden')
- $('#content').addClass('loading')
- } else {
- $('body').css('overflow', 'hidden')
- $('#app-content').append($iframe)
- $iframe.hide()
- }
-
- $('#app-content #controls').addClass('hidden')
- setTimeout(() => {
- FilesAppIntegration.init({
- fileName,
- fileId,
- fileList,
- sendPostMessage: (msgId, values) => {
- PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, msgId, values)
- }
- })
- })
- },
-
- onReceiveLoading() {
- odfViewer.receivedLoading = true
- $('#richdocumentsframe').show()
- $('html, body').scrollTop(0)
- $('#content').removeClass('loading')
- FilesAppIntegration.initAfterReady()
- },
-
- onClose: function() {
- odfViewer.open = false
- clearTimeout(odfViewer.loadingTimeout)
- odfViewer.receivedLoading = false
- $('link[href*="richdocuments/css/mobile"]').remove()
- $('#app-content #controls').removeClass('hidden')
- $('#richdocumentsframe').remove()
- $('.searchbox').show()
- $('body').css('overflow', 'auto')
-
- if (isPublic) {
- $('#content').removeClass('full-height')
- $('footer').removeClass('hidden')
- $('#imgframe').removeClass('hidden')
- $('.directLink').removeClass('hidden')
- $('.directDownload').removeClass('hidden')
- }
-
- OC.Util.History.replaceState()
-
- FilesAppIntegration.close()
- },
+const supportedMimes = OC.getCapabilities().richdocuments.mimetypes.concat(OC.getCapabilities().richdocuments.mimetypesNoDefaultOpen)
- registerFilesMenu: function() {
-
- const registerFilesMenu = (OCA) => {
- OCA.FilesLOMenu = {
- attach: function(newFileMenu) {
- var self = this
- const document = Types.getFileType('document')
- const spreadsheet = Types.getFileType('spreadsheet')
- const presentation = Types.getFileType('presentation')
-
- newFileMenu.addMenuEntry({
- id: 'add-' + document.extension,
- displayName: t('richdocuments', 'New Document'),
- templateName: t('richdocuments', 'New Document') + '.' + document.extension,
- iconClass: 'icon-filetype-document',
- fileType: 'x-office-document',
- actionHandler: function(filename) {
- if (OC.getCapabilities().richdocuments.templates) {
- self._openTemplatePicker('document', document.mime, filename)
- } else {
- self._createDocument(document.mime, filename)
- }
- }
- })
-
- newFileMenu.addMenuEntry({
- id: 'add-' + spreadsheet.extension,
- displayName: t('richdocuments', 'New Spreadsheet'),
- templateName: t('richdocuments', 'New Spreadsheet') + '.' + spreadsheet.extension,
- iconClass: 'icon-filetype-spreadsheet',
- fileType: 'x-office-spreadsheet',
- actionHandler: function(filename) {
- if (OC.getCapabilities().richdocuments.templates) {
- self._openTemplatePicker('spreadsheet', spreadsheet.mime, filename)
- } else {
- self._createDocument(spreadsheet.mime, filename)
- }
- }
- })
-
- newFileMenu.addMenuEntry({
- id: 'add-' + presentation.extension,
- displayName: t('richdocuments', 'New Presentation'),
- templateName: t('richdocuments', 'New Presentation') + '.' + presentation.extension,
- iconClass: 'icon-filetype-presentation',
- fileType: 'x-office-presentation',
- actionHandler: function(filename) {
- if (OC.getCapabilities().richdocuments.templates) {
- self._openTemplatePicker('presentation', presentation.mime, filename)
- } else {
- self._createDocument(presentation.mime, filename)
- }
- }
- })
- },
-
- _createDocument: function(mimetype, filename) {
- OCA.Files.Files.isFileNameValid(filename)
- filename = FileList.getUniqueName(filename)
-
- $.post(
- OC.generateUrl('apps/richdocuments/ajax/documents/create'),
- { mimetype: mimetype, filename: filename, dir: $('#dir').val() },
- function(response) {
- if (response && response.status === 'success') {
- FileList.add(response.data, { animate: true, scrollTo: true })
- } else {
- OC.dialogs.alert(response.data.message, t('core', 'Could not create file'))
- }
- }
- )
- },
-
- _createDocumentFromTemplate: function(templateId, mimetype, filename) {
- OCA.Files.Files.isFileNameValid(filename)
- filename = FileList.getUniqueName(filename)
- $.post(
- OC.generateUrl('apps/richdocuments/ajax/documents/create'),
- { mimetype: mimetype, filename: filename, dir: $('#dir').val() },
- function(response) {
- if (response && response.status === 'success') {
- FileList.add(response.data, { animate: false, scrollTo: false })
- odfViewer.onEdit(filename, {
- fileId: -1,
- dir: $('#dir').val(),
- templateId: templateId,
- fileList: FileList
- })
- } else {
- OC.dialogs.alert(response.data.message, t('core', 'Could not create file'))
- }
- }
- )
- },
-
- _openTemplatePicker: function(type, mimetype, filename) {
- var self = this
- $.ajax({
- url: OC.linkToOCS('apps/richdocuments/api/v1/templates', 2) + type,
- dataType: 'json'
- }).then(function(response) {
- if (response.ocs.data.length === 1) {
- const { id } = response.ocs.data[0]
- self._createDocumentFromTemplate(id, mimetype, filename)
- return
- }
- self._buildTemplatePicker(response.ocs.data)
- .then(function() {
- var buttonlist = [{
- text: t('core', 'Cancel'),
- classes: 'cancel',
- click: function() {
- $(this).ocdialog('close')
- }
- }, {
- text: t('richdocuments', 'Create'),
- classes: 'primary',
- click: function() {
- var templateId = this.dataset.templateId
- self._createDocumentFromTemplate(templateId, mimetype, filename)
- $(this).ocdialog('close')
- }
- }]
-
- $('#template-picker').ocdialog({
- closeOnEscape: true,
- modal: true,
- buttons: buttonlist
- })
- })
- })
- },
-
- _buildTemplatePicker: function(data) {
- var self = this
- return $.get(OC.filePath('richdocuments', 'templates', 'templatePicker.html'), function(tmpl) {
- var $tmpl = $(tmpl)
- // init template picker
- var $dlg = $tmpl.octemplate({
- dialog_name: 'template-picker',
- dialog_title: t('richdocuments', 'Select template')
- })
-
- // create templates list
- var templates = _.values(data)
- templates.forEach(function(template) {
- self._appendTemplateFromData($dlg[0], template)
- })
-
- $('body').append($dlg)
- })
- },
-
- _appendTemplateFromData: function(dlg, data) {
- var template = dlg.querySelector('.template-model').cloneNode(true)
- template.className = ''
- template.querySelector('img').src = OC.generateUrl('apps/richdocuments/template/preview/' + data.id)
- template.querySelector('h2').textContent = data.name
- template.onclick = function() {
- dlg.dataset.templateId = data.id
- }
- if (!dlg.dataset.templateId) {
- dlg.dataset.templateId = data.id
- }
-
- dlg.querySelector('.template-container').appendChild(template)
- }
- }
- }
- registerFilesMenu(OCA)
-
- OC.Plugins.register('OCA.Files.NewFileMenu', OCA.FilesLOMenu)
-
- // Open the template picker if there was a create parameter detected on load
- if (Preload.create && Preload.create.type && Preload.create.filename) {
- const fileType = Types.getFileType(Preload.create.type, Config.get('ooxml'))
- OCA.FilesLOMenu._openTemplatePicker(Preload.create.type, fileType.mime, Preload.create.filename + '.' + fileType.extension)
- }
-
- if (Preload.open) {
- FileList.$fileList.one('updated', function() {
- odfViewer.onEdit(Preload.open.filename, {
- fileId: Preload.open.id,
- dir: document.getElementById('dir').value,
- fileList: FileList
- })
- })
- }
- }
-}
-
-const settings = OC.getCapabilities()['richdocuments']['config'] || {}
-Config.update('ooxml', settings['doc_format'] === 'ooxml')
-
-window.OCA.RichDocuments = {
- config: {
- create: Types.getFileTypes()
- }
-}
-
-$(document).ready(function() {
- // register file actions and menu
+document.addEventListener('DOMContentLoaded', function(event) {
+ // Only use it outside the files app for now
if (typeof OCA !== 'undefined'
&& typeof OCA.Files !== 'undefined'
&& typeof OCA.Files.fileActions !== 'undefined'
) {
- // check if texteditor app is enabled and loaded...
- if (typeof OCA.Files_Texteditor === 'undefined' && typeof OCA.Text === 'undefined') {
- odfViewer.supportedMimes.push('text/plain')
- }
- odfViewer.register()
- odfViewer.registerFilesMenu()
+ return
}
- // Open documents if a public page is opened for a supported mimetype
- const isSupportedMime = isPublic && odfViewer.supportedMimes.indexOf($('#mimetype').val()) !== -1 && odfViewer.excludeMimeFromDefaultOpen.indexOf($('#mimetype').val()) === -1
- const showSecureView = isPublic && isDownloadHidden && odfViewer.hideDownloadMimes.indexOf($('#mimetype').val()) !== -1
- if (isSupportedMime || showSecureView) {
- odfViewer.onEdit(document.getElementById('filename').value)
+ if (OCA.Viewer) {
+ OCA.Viewer.registerHandler({
+ id: 'richdocuments',
+ group: null,
+ mimes: supportedMimes,
+ component: Office
+ })
}
-
- PostMessages.registerPostMessageHandler(({ parsed }) => {
- console.debug('[viewer] Received post message', parsed)
- const { msgId, args, deprecated } = parsed
- if (deprecated) { return }
-
- switch (msgId) {
- case 'loading':
- odfViewer.onReceiveLoading()
- break
- case 'App_LoadingStatus':
- if (args.Status === 'Timeout') {
- odfViewer.onClose()
- OC.Notification.showTemporary(t('richdocuments', 'Failed to connect to {productName}. Please try again later or contact your server administrator.',
- { productName: OC.getCapabilities().richdocuments.productName }
- ))
- }
- break
- case 'UI_Share':
- FilesAppIntegration.share()
- break
- case 'UI_CreateFile':
- FilesAppIntegration.createNewFile(args.DocumentType)
- break
- case 'UI_InsertGraphic':
- FilesAppIntegration.insertGraphic((filename, url) => {
- PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, 'postAsset', { FileName: filename, Url: url })
- })
- break
- case 'File_Rename':
- FileList.reload()
- OC.Apps.hideAppSidebar()
- FilesAppIntegration.fileName = args.NewName
- break
- case 'close':
- odfViewer.onClose()
- break
- case 'Get_Views_Resp':
- case 'Views_List':
- FilesAppIntegration.setViews(args)
- break
- case 'UI_FileVersions':
- case 'rev-history':
- FilesAppIntegration.showRevHistory()
- break
- case 'App_VersionRestore':
- if (args.Status === 'Pre_Restore_Ack') {
- FilesAppIntegration.restoreVersionExecute()
- }
- break
- }
-
- // legacy view handling
- if (msgId === 'View_Added') {
- FilesAppIntegration.views[args.ViewId] = args
- FilesAppIntegration.renderAvatars()
- } else if (msgId === 'View_Removed') {
- delete FilesAppIntegration.views[args.ViewId]
- FilesAppIntegration.renderAvatars()
- } else if (msgId === 'FollowUser_Changed') {
- if (args.IsFollowEditor) {
- FilesAppIntegration.followingEditor = true
- } else {
- FilesAppIntegration.followingEditor = false
- }
- if (args.IsFollowUser) {
- FilesAppIntegration.following = args.FollowedViewId
- } else {
- FilesAppIntegration.following = null
- }
- FilesAppIntegration.renderAvatars()
- }
-
- })
- window.FilesAppIntegration = FilesAppIntegration
})
diff --git a/templates/documents.php b/templates/documents.php
index 50d7b1a3..877cc7a9 100644
--- a/templates/documents.php
+++ b/templates/documents.php
@@ -14,5 +14,5 @@
<?php
script('richdocuments', 'document');
?>
-<div id="loadingContainer" class="icon-loading"></div>
+<div id="loadingContainer"></div>
<div id="documents-content"></div>
diff --git a/webpack.common.js b/webpack.common.js
index d905fe36..db3a21a8 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -6,6 +6,7 @@ const StyleLintPlugin = require('stylelint-webpack-plugin');
module.exports = {
entry: {
viewer: path.join(__dirname, 'src', 'viewer.js'),
+ files: path.join(__dirname, 'src', 'files.js'),
document: path.join(__dirname, 'src', 'document.js'),
admin: path.join(__dirname, 'src', 'admin.js'),
personal: path.join(__dirname, 'src', 'personal.js'),