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
path: root/src
diff options
context:
space:
mode:
authorJulius Härtl <jus@bitgrid.net>2019-07-19 15:18:52 +0300
committerJulius Härtl <jus@bitgrid.net>2019-08-26 12:21:21 +0300
commit5ffb9634079eb3ff829f1987aa08608598003914 (patch)
tree26e7358f5b229c2effd389b0b0f0083f0e2d63c0 /src
parent7ca3f97975322a4c7c6383e86a63790894d4d375 (diff)
Implement post messages for across frame communication
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Diffstat (limited to 'src')
-rw-r--r--src/admin.js2
-rw-r--r--src/document.js853
-rw-r--r--src/helpers/guestName.js6
-rw-r--r--src/helpers/index.js7
-rw-r--r--src/helpers/mobile.js54
-rw-r--r--src/helpers/types.js60
-rw-r--r--src/helpers/url.js67
-rw-r--r--src/services/config.tsx53
-rw-r--r--src/services/postMessage.tsx107
-rw-r--r--src/view/FilesAppIntegration.js447
-rw-r--r--src/viewer.js326
11 files changed, 1129 insertions, 853 deletions
diff --git a/src/admin.js b/src/admin.js
index 1b2fda5d..2d3344d4 100644
--- a/src/admin.js
+++ b/src/admin.js
@@ -202,7 +202,6 @@ var documentsSettings = {
})
$(document).on('change', '#edit_group_select', function() {
- var element = page.find('input.edit-groups-enable')
var groups = $(this).val()
documentsSettings.saveGroups({ edit_groups: groups })
})
@@ -223,7 +222,6 @@ var documentsSettings = {
})
$(document).on('change', '#use_group_select', function() {
- var element = page.find('input.use-groups-enable')
var groups = $(this).val()
documentsSettings.saveGroups({ use_groups: groups })
})
diff --git a/src/document.js b/src/document.js
index 204415f8..70af94be 100644
--- a/src/document.js
+++ b/src/document.js
@@ -1,10 +1,20 @@
import { getRootUrl } from 'nextcloud-router'
-import { getRequestToken, getCurrentUser } from 'nextcloud-auth'
-import { languageToBCP47 } from './helpers'
-import { getGuestNameCookie, setGuestNameCookie, shouldAskForGuestName } from './helpers/guestName'
-
-/* TODO: move to one object */
-/* global richdocuments_directEdit richdocuments_fileId richdocuments_urlsrc richdocuments_token richdocuments_path richdocuments_permissions richdocuments_title getURLParameter richdocuments_canonical_webroot */
+import { getRequestToken } from 'nextcloud-auth'
+import Config from './services/config'
+import { setGuestNameCookie, shouldAskForGuestName } from './helpers/guestName'
+
+import PostMessageService from './services/postMessage'
+import {
+ callMobileMessage,
+ isDirectEditing,
+ isMobileInterfaceAvailable
+} from './helpers/mobile'
+import { getWopiUrl } from './helpers/url'
+
+const PostMessages = new PostMessageService({
+ parent: window.parent,
+ loolframe: () => document.getElementById('loleafletframe').contentWindow
+})
const showLoadingIndicator = () => document.getElementById('loadingContainer').classList.add('icon-loading')
const hideLoadingIndicator = () => document.getElementById('loadingContainer').classList.remove('icon-loading')
@@ -15,23 +25,20 @@ $.widget('oc.guestNamePicker', {
_create: function() {
hideLoadingIndicator()
- var text = document.createElement('div')
- $(text).attr('style', 'margin: 0 auto; margin-top: 100px; text-align: center;')
-
- var para = t('richdocuments', 'Please choose your nickname to continue as guest user.')
- text.innerHTML = para
+ const text = document.createElement('div')
+ text.setAttribute('style', 'margin: 0 auto; margin-top: 100px; text-align: center;')
+ text.innerHTML = t('richdocuments', 'Please choose your nickname to continue as guest user.')
- var div = document.createElement('div')
- $(div).attr('style', 'margin: 0 auto; width: 250px; display: flex;')
- var nick = '<input type="text" placeholder="' + t('richdocuments', 'Nickname') + '" id="nickname" style="flex-grow: 1; border-right:none; border-top-right-radius: 0; border-bottom-right-radius: 0">'
- var btn = '<input style="border-left:none; border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -3px" type="button" id="btn" type="button" value="' + t('richdocuments', 'Set') + '">'
+ const div = document.createElement('div')
+ div.setAttribute('style', 'margin: 0 auto; width: 250px; display: flex;')
+ const nick = '<input type="text" placeholder="' + t('richdocuments', 'Nickname') + '" id="nickname" style="flex-grow: 1; border-right:none; border-top-right-radius: 0; border-bottom-right-radius: 0">'
+ const btn = '<input style="border-left:none; border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: -3px" type="button" id="btn" type="button" value="' + t('richdocuments', 'Set') + '">'
div.innerHTML = nick + btn
$('#documents-content').prepend(div)
$('#documents-content').prepend(text)
- var that = this
const setGuestNameSubmit = () => {
- var username = $('#nickname').val()
+ const username = $('#nickname').val()
setGuestNameCookie(username)
window.location.reload(true)
}
@@ -66,40 +73,25 @@ const documentsMain = {
baseName: null,
canShare: false,
canEdit: false,
- loadError: false,
- loadErrorMessage: '',
- loadErrorHint: '',
- fileModel: null,
renderComplete: false, // false till page is rendered with all required data about the document(s)
- toolbar: '<div id="ocToolbar"><div id="ocToolbarInside"></div><span id="toolbar" class="claro"></span></div>',
$deferredVersionRestoreAck: null,
wopiClientFeatures: null,
// generates docKey for given fileId
_generateDocKey: function(wopiFileId) {
- var ocurl = getRootUrl() + '/index.php/apps/richdocuments/wopi/files/' + wopiFileId
- if (richdocuments_canonical_webroot) {
- if (!richdocuments_canonical_webroot.startsWith('/')) { richdocuments_canonical_webroot = '/' + richdocuments_canonical_webroot }
-
- ocurl = ocurl.replace(getRootUrl(), richdocuments_canonical_webroot)
+ let canonicalWebroot = Config.get('canonical_webroot')
+ let ocurl = getRootUrl() + '/index.php/apps/richdocuments/wopi/files/' + wopiFileId
+ if (canonicalWebroot) {
+ if (!canonicalWebroot.startsWith('/')) {
+ canonicalWebroot = '/' + canonicalWebroot
+ }
+ Config.update('canonical_webroot', canonicalWebroot)
+ ocurl = ocurl.replace(getRootUrl(), canonicalWebroot)
}
return ocurl
},
- getFileList: function() {
- if (window === parent) {
- return null
- }
- if (parent.OCA.Files.App) {
- return parent.OCA.Files.App.fileList
- }
- if (parent.OCA.Sharing.PublicApp) {
- return parent.OCA.Sharing.PublicApp.fileList
- }
- return null
- },
-
UI: {
/* Editor wrapper HTML */
container: '<div id="mainContainer" class="claro">'
@@ -109,230 +101,24 @@ const documentsMain = {
+ '<div id="revViewer"></div>'
+ '</div>',
- /* Previous window title */
- mainTitle: '',
- /* Number of revisions already loaded */
- revisionsStart: 0,
-
- /* Views: people currently editing the file */
- views: {},
-
- followingEditor: false,
-
- following: null,
-
- init: function() {
- if (!richdocuments_directEdit && parent.$('#richdocuments-avatars').length === 0) {
- documentsMain.UI.mainTitle = parent.document.title
-
- // Add the avatar toolbar if possible
- var avatarList = $('<div id="richdocuments-avatars">')
- avatarList.on('click', function(e) {
- e.stopPropagation()
- parent.$('#editors-menu').toggle()
- })
- var headerRight = parent.$('#header .header-right')
- headerRight.prepend(avatarList)
-
- this.addVersionSidebarEvents()
- }
- },
-
- _addHeaderFileActions: function() {
- parent.OC.unregisterMenu(parent.$('#richdocuments-actions .icon-more'), parent.$('#richdocuments-actions-menu'))
- parent.$('#richdocuments-actions').remove()
- var actionsContainer = $('<div id="richdocuments-actions"><div class="icon-more icon-white"></div><ul id="richdocuments-actions-menu" class="popovermenu"></ul></div>')
- var actions = actionsContainer.find('#richdocuments-actions-menu').empty()
-
- var context = {
- '$file': documentsMain.getFileList().$el.find('[data-id=' + documentsMain.originalFileId + ']').first(),
- fileActions: documentsMain.getFileList().fileActions,
- fileList: documentsMain.getFileList(),
- fileInfoModel: documentsMain.getFileModel()
- }
-
- var isFavorite = function(fileInfo) {
- return fileInfo.get('tags') && fileInfo.get('tags').indexOf(parent.OC.TAG_FAVORITE) >= 0
- }
- var $favorite = $('<li><a></a></li>').click(function(e) {
- $favorite.find('a').removeClass('icon-starred').removeClass('icon-star-dark').addClass('icon-loading-small')
- documentsMain.getFileList().fileActions.triggerAction('Favorite', documentsMain.getFileModel(), documentsMain.getFileList())
- documentsMain.getFileModel().trigger('change', documentsMain.getFileModel())
- })
- if (isFavorite(context.fileInfoModel)) {
- $favorite.find('a').text(parent.t('files', 'Remove from favorites'))
- $favorite.find('a').addClass('icon-starred')
- } else {
- $favorite.find('a').text(parent.t('files', 'Add to favorites'))
- $favorite.find('a').addClass('icon-star-dark')
- }
-
- var $info = $('<li><a class="icon-info"></a></li>').click(function() {
- documentsMain.getFileList().fileActions.actions.all.Details.action(documentsMain.fileName, context)
- parent.OC.hideMenus()
- })
- $info.find('a').text(parent.t('files', 'Details'))
- var $download = $('<li><a class="icon-download">Download</a></li>').click(function() {
- documentsMain.getFileList().fileActions.actions.all.Download.action(documentsMain.fileName, context)
- parent.OC.hideMenus()
- })
- $download.find('a').text(parent.t('files', 'Download'))
- actions.append($favorite).append($info).append($download)
- actionsContainer.insertAfter(parent.$('#header .richdocuments-sharing'))
- parent.OC.registerMenu(parent.$('#richdocuments-actions .icon-more'), parent.$('#richdocuments-actions-menu'), false, true)
- },
-
- /**
- * @param {View} view
- * @private
- */
- _userEntry: function(view) {
- var entry = $('<li></li>')
- entry.append(this._avatarForView(view))
-
- var label = $('<div class="label"></div>')
- label.text(view.UserName)
- if (view.ReadOnly === '1') {
- label.text(view.UserName + ' ' + t('richdocuments', '(read only)'))
-
- }
- label.click(function(event) {
- event.stopPropagation()
- documentsMain.UI.followView(view)
- })
- if (this.following === view.ViewId) {
- parent.$('#editors-menu').find('li').removeClass('active')
- entry.addClass('active')
- }
- entry.append(label)
-
- var isFileOwner = documentsMain.getFileModel() && typeof documentsMain.getFileModel().get('shareOwner') === 'undefined'
- if (documentsMain.canEdit && isFileOwner && !view.IsCurrentView) {
- var removeButton = $('<div class="icon-close" title="Remove user"/>')
- removeButton.click(function() {
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Action_RemoveView', { ViewId: view.ViewId })
- })
- entry.append(removeButton)
- }
- return entry
- },
-
- /**
- * @param {View} view
- * @returns {$|HTMLElement}
- * @private
- */
- _avatarForView: function(view) {
- var avatarContainer = $('<div class="richdocuments-avatar"><div class="avatar" title="' + view.UserName + '" data-user="' + view.UserId + '"></div></div>')
- var avatar = avatarContainer.find('.avatar')
- avatar.css({ 'border-color': view.Color,
- 'border-width': '2px',
- 'border-style': 'solid' })
- if (view.ReadOnly === '1') {
- avatarContainer.addClass('read-only')
- $(avatar).attr('title', view.UserName + ' ' + t('richdocuments', '(read only)'))
- } else {
- $(avatar).attr('title', view.UserName)
- }
- $(avatar).avatar(view.UserId, 32, undefined, true, undefined, view.UserName)
- if (parent.OC.currentUser !== null && view.UserId !== '') {
- // $(avatar).contactsMenu(view.UserId, 0, avatarContainer);
- }
- return avatarContainer
- },
-
- renderAvatars: function() {
- var avatardiv = parent.$('#header .header-right #richdocuments-avatars')
- avatardiv.empty()
- var popover = $('<div id="editors-menu" class="popovermenu menu-center"><ul></ul></div>')
-
- var users = []
- // Add new avatars
- var i = 0
- for (var viewId in this.views) {
- /**
- * @type {View}
- */
- var view = this.views[viewId]
- view.UserName = view.UserName !== '' ? view.UserName : t('richdocuments', 'Guest')
- popover.find('ul').append(this._userEntry(view))
-
- if (view.UserId === parent.OC.currentUser) {
- continue
- }
- if (view.UserId !== '' && users.indexOf(view.UserId) > -1) {
- continue
- }
- users.push(view.UserId)
- if (i++ < 3) {
- avatardiv.append(this._avatarForView(view))
- }
- }
- var followCurrentEditor = $('<li><input type="checkbox" class="checkbox" /><label class="label">' + t('richdocuments', 'Follow current editor') + '</label></li>')
- followCurrentEditor.find('label').click(function(event) {
- event.stopPropagation()
- if (documentsMain.UI.followingEditor) {
- documentsMain.UI.followReset()
- } else {
- documentsMain.UI.followCurrentEditor()
- }
- })
- followCurrentEditor.find('.checkbox').prop('checked', documentsMain.UI.followingEditor)
- popover.find('ul').append(followCurrentEditor)
- avatardiv.append(popover)
- },
- followReset: function(event) {
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Action_FollowUser', { Follow: false })
- this.following = null
- this.followingEditor = false
- this.renderAvatars()
- },
- followCurrentEditor: function(event) {
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Action_FollowUser', { Follow: true })
- this.following = null
- this.followingEditor = true
- this.renderAvatars()
- },
- followView: function(view) {
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Action_FollowUser', { ViewId: view.ViewId, Follow: true })
- documentsMain.UI.following = view.ViewId
- documentsMain.UI.followingEditor = false
- documentsMain.UI.renderAvatars()
- },
-
showViewer: function(fileId, title) {
// remove previous viewer, if open, and set a new one
if (documentsMain.isViewerMode) {
$('#revViewer').remove()
$('#revViewerContainer').prepend($('<div id="revViewer">'))
- } else {
- this.addCurrentVersion()
}
- // WOPISrc - URL that loolwsd will access (ie. pointing to ownCloud)
- // index.php is forced here to avoid different wopi srcs for the same document
- var wopiurl = window.location.protocol + '//' + window.location.host + getRootUrl() + '/index.php/apps/richdocuments/wopi/files/' + fileId
- var wopisrc = encodeURIComponent(wopiurl)
-
- // urlsrc - the URL from discovery xml that we access for the particular
- // document; we add various parameters to that.
- // The discovery is available at
- // https://<loolwsd-server>:9980/hosting/discovery
- var urlsrc = documentsMain.urlsrc
- + 'WOPISrc=' + wopisrc
- + '&title=' + encodeURIComponent(title)
- + '&lang=' + languageToBCP47()
- + '&permission=readonly'
+ const urlsrc = getWopiUrl({ fileId, title, readOnly: true })
// access_token - must be passed via a form post
- var accessToken = encodeURIComponent(documentsMain.token)
+ const accessToken = encodeURIComponent(documentsMain.token)
// form to post the access token for WOPISrc
- var form = '<form id="loleafletform_viewer" name="loleafletform_viewer" target="loleafletframe_viewer" action="' + urlsrc + '" method="post">'
+ const form = '<form id="loleafletform_viewer" name="loleafletform_viewer" target="loleafletframe_viewer" action="' + urlsrc + '" method="post">'
+ '<input name="access_token" value="' + accessToken + '" type="hidden"/></form>'
// iframe that contains the Collabora Online Viewer
- var frame = '<iframe id="loleafletframe_viewer" name="loleafletframe_viewer" nonce="' + btoa(getRequestToken()) + '" style="width:100%;height:100%;position:absolute;"/>'
+ const frame = '<iframe id="loleafletframe_viewer" name="loleafletframe_viewer" nonce="' + btoa(getRequestToken()) + '" style="width:100%;height:100%;position:absolute;"/>'
$('#revViewer').append(form)
$('#revViewer').append(frame)
@@ -340,7 +126,6 @@ const documentsMain = {
// submit that
$('#loleafletform_viewer').submit()
documentsMain.isViewerMode = true
-
// for closing revision mode
$('#revViewerContainer .closeButton').click(function(e) {
e.preventDefault()
@@ -351,134 +136,14 @@ const documentsMain = {
loadRevViewerContainer: function() {
if (!$('revViewerContainer').length) {
$(document.body).prepend(documentsMain.UI.viewContainer)
- var closeButton = $('<button class="icon-close closeButton" title="' + parent.t('richdocuments', 'Close version preview') + '"/>')
+ const closeButton = $('<button class="icon-close closeButton" title="' + t('richdocuments', 'Close version preview') + '"/>')
$('#revViewerContainer').prepend(closeButton)
}
},
- showRevHistory: function(documentPath) {
- // TODO: make sure this also works if using the sidebar with the share icon and navigating to versions then
- parent.FileList.showDetailsView(documentsMain.fileName, 'versionsTabView')
- this.loadRevViewerContainer()
- // Load current revision
- // TODO: add entry to versions
- var fileId = documentsMain.fileId
- var title = documentsMain.fileName
- documentsMain.UI.showViewer(
- fileId, title
- )
-
- },
-
- addVersionSidebarEvents: function() {
- $(parent.document.querySelector('#content')).on('click.revisions', '#app-sidebar .preview-container', this.showVersionPreview.bind(this))
- $(parent.document.querySelector('#content')).on('click.revisions', '#app-sidebar .downloadVersion', this.showVersionPreview.bind(this))
- // Use mousedown event to overwrite behavior of the versions app
- $(parent.document.querySelector('#content')).on('mousedown.revisions', '#app-sidebar .revertVersion', this.restoreVersion.bind(this))
- },
-
- removeVersionSidebarEvents: function() {
- $(parent.document.querySelector('#content')).off('click.revisions')
- $(parent.document.querySelector('#content')).off('click.revisions')
- $(parent.document.querySelector('#content')).off('mousedown.revisions')
- },
-
- addCurrentVersion: function() {
- if (documentsMain.fileModel) {
- var preview = OC.MimeType.getIconUrl(documentsMain.fileModel.get('mimetype'))
- parent.$('#versionsTabView').prepend('<ul id="lastSavedVersion"><li data-revision="0"><div><div class="preview-container"><img src="' + preview + '" width="44" /></div><div class="version-container">\n'
- + '<div><a class="downloadVersion"><span class="versiondate has-tooltip live-relative-timestamp" data-timestamp="1551294326000"></span></div></div></li></ul>')
- parent.$('#versionsTabView').prepend('<ul id="currentVersion"><li data-revision="" class="active"><div><div class="preview-container"><img src="' + preview + '" width="44" /></div><div class="version-container">\n'
- + '<div><a class="downloadVersion">' + t('richdocuments', 'Current version') + '</a></div></div></li></ul>')
- parent.$('.live-relative-timestamp').each(function() {
- $(this).text(OC.Util.relativeModifiedDate(parseInt($(this).attr('data-timestamp'), 10)))
- })
- }
- },
-
- showVersionPreview: function(e) {
- e.preventDefault()
- documentsMain.UI.loadRevViewerContainer()
- var element = e.currentTarget.parentElement.parentElement
- if ($(e.currentTarget).hasClass('downloadVersion')) {
- element = e.currentTarget.parentElement.parentElement.parentElement.parentElement
- }
- var version = element.dataset.revision
- var fileId = documentsMain.fileId
- var title = documentsMain.fileName
- if (version !== '') {
- fileId += '_' + version
- title += '_' + version
- }
- documentsMain.UI.showViewer(
- fileId, title
- )
-
- // mark only current <li> as active
- $(element.parentElement.parentElement).find('li').removeClass('active')
- $(element).addClass('active')
- },
-
- restoreVersion: function(e) {
- var self = this
- e.preventDefault()
- e.stopPropagation()
-
- documentsMain.onCloseViewer()
-
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Host_VersionRestore', { Status: 'Pre_Restore' })
-
- var version = e.currentTarget.parentElement.parentElement.dataset.revision
-
- documentsMain.$deferredVersionRestoreAck = $.Deferred()
- $.when(documentsMain.$deferredVersionRestoreAck).done(function(args) {
- self._restoreDAV(version)
- })
-
- // resolve the deferred object immediately if client doesn't support version states
- if (!documentsMain.wopiClientFeatures || !documentsMain.wopiClientFeatures.VersionStates) {
- documentsMain.$deferredVersionRestoreAck.resolve()
- }
-
- return false
- },
-
- _restoreSuccess: function(response) {
- if (response.status === 'error') {
- documentsMain.UI.notify(t('richdocuments', 'Failed to revert the document to older version'))
- }
-
- // load the file again, it should get reverted now
- window.location = $(parent.document.querySelector('#richdocumentsframe')).attr('src')
- parent.OC.Apps.hideAppSidebar()
- },
-
- _restoreError: function() {
- documentsMain.UI.notify(t('richdocuments', 'Failed to revert the document to older version'))
- },
-
- _restoreDAV: function(version) {
- var restoreUrl = OC.linkToRemoteBase('dav') + '/versions/' + getCurrentUser().uid
- + '/versions/' + documentsMain.originalFileId + '/' + version
- $.ajax({
- type: 'MOVE',
- url: restoreUrl,
- headers: {
- Destination: OC.linkToRemote('dav') + '/versions/' + getCurrentUser().uid + '/restore/target'
- },
- success: this._restoreSuccess,
- error: this._restoreError
- })
- },
-
showEditor: function(title, fileId, action) {
- if (documentsMain.loadError) {
- documentsMain.onEditorShutdown(documentsMain.loadErrorMessage + '\n' + documentsMain.loadErrorHint)
- return
- }
-
if (!documentsMain.renderComplete) {
- setTimeout(function() { documentsMain.UI.showEditor(title, action) }, 10)
+ setTimeout(function() { documentsMain.UI.showEditor(title, fileId, action) }, 10)
console.debug('Waiting for page to render…')
return
}
@@ -486,29 +151,13 @@ const documentsMain = {
OC.Util.History.addOnPopStateHandler(_.bind(documentsMain.onClose))
OC.Util.History.pushState()
- parent.postMessage('loading', '*')
+ PostMessages.sendPostMessage('parent', 'loading')
hideLoadingIndicator()
$(document.body).addClass('claro')
$(document.body).prepend(documentsMain.UI.container)
- // WOPISrc - URL that loolwsd will access (ie. pointing to ownCloud)
- var wopiurl = window.location.protocol + '//' + window.location.host + getRootUrl() + '/index.php/apps/richdocuments/wopi/files/' + documentsMain.fileId
- var wopisrc = encodeURIComponent(wopiurl)
-
- // urlsrc - the URL from discovery xml that we access for the particular
- // document; we add various parameters to that.
- // The discovery is available at
- // https://<loolwsd-server>:9980/hosting/discovery
- var urlsrc = documentsMain.urlsrc
- + 'WOPISrc=' + wopisrc
- + '&title=' + encodeURIComponent(title)
- + '&lang=' + languageToBCP47()
- + '&closebutton=1'
- + '&revisionhistory=1'
- if (!documentsMain.canEdit || action === 'view') {
- urlsrc += '&permission=readonly'
- }
+ const urlsrc = getWopiUrl({ fileId, title, readOnly: false, closeButton: true, revisionHistory: true })
// access_token - must be passed via a form post
var accessToken = encodeURIComponent(documentsMain.token)
@@ -525,55 +174,42 @@ const documentsMain = {
// Listen for App_LoadingStatus as soon as possible
$('#loleafletframe').ready(function() {
- var editorInitListener = function(e) {
- var msg = {}
- try {
- msg = JSON.parse(e.data)
- } catch (e) {
+ const editorInitListener = ({ parsed, data }) => {
+ console.debug('[document] editorInitListener: Received post message ', parsed)
+ const { msgId, args } = parsed
+
+ if (msgId !== 'App_LoadingStatus') {
return
}
- if (msg.MessageId === 'App_LoadingStatus') {
- if (msg.Values.Status === 'Frame_Ready') {
- documentsMain.isFrameReady = true
- documentsMain.wopiClientFeatures = msg.Values.Features
-
- // Forward to mobile handler
- if (window.RichDocumentsMobileInterface) {
- window.RichDocumentsMobileInterface.documentLoaded()
- }
- // iOS webkit fallback
- if (window.webkit
- && window.webkit.messageHandlers
- && window.webkit.messageHandlers.RichDocumentsMobileInterface) {
- window.webkit.messageHandlers.RichDocumentsMobileInterface.postMessage('documentLoaded')
- }
- } else if (msg.Values.Status === 'Document_Loaded') {
- window.removeEventListener('message', editorInitListener, false)
- if (documentsMain.getFileList()) {
- documentsMain.getFileModel()
- }
- // Hide buttons when using the mobile app integration
- if (
- window.RichDocumentsMobileInterface
- || (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.RichDocumentsMobileInterface)
- ) {
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Hide_Button', { id: 'fullscreen' })
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Hide_Menu_Item', { id: 'fullscreen' })
- }
- } else if (msg.Values.Status === 'Failed') {
- // Loading failed but editor shows the error
- documentsMain.isFrameReady = true
- } else if (msg.Values.Status === 'Timeout') {
- // Timeout - no response from the editor
- documentsMain.onClose()
- parent.OC.Notification.showTemporary(t('richdocuments', 'Failed to connect to {productName}. Please try again later or contact your server administrator.',
- { productName: OC.getCapabilities().richdocuments.productName }
- ))
+ // Pass though all messages to viewer.js if not direct editing
+ if (!isDirectEditing()) {
+ PostMessages.sendPostMessage('parent', data)
+ }
+
+ switch (args.Status) {
+ case 'Frame_Ready':
+ documentsMain.isFrameReady = true
+ documentsMain.wopiClientFeatures = args.Features
+ callMobileMessage('documentLoaded')
+ break
+ case 'Document_Loaded':
+ PostMessages.unregisterPostMessageHandler(editorInitListener)
+
+ // Hide buttons when using the mobile app integration
+ if (isDirectEditing) {
+ PostMessages.sendWOPIPostMessage('loolframe', 'Hide_Button', { id: 'fullscreen' })
+ PostMessages.sendWOPIPostMessage('loolframe', 'Hide_Menu_Item', { id: 'fullscreen' })
}
+ break
+ case 'Failed':
+ // Loading failed but editor shows the error
+ documentsMain.isFrameReady = true
+ break
}
}
- window.addEventListener('message', editorInitListener, false)
+
+ PostMessages.registerPostMessageHandler(editorInitListener)
// In case of editor inactivity
setTimeout(function() {
@@ -584,100 +220,111 @@ const documentsMain = {
})
$('#loleafletframe').load(function() {
- // And start listening to incoming post messages
- window.addEventListener('message', function(e) {
- if (documentsMain.isViewerMode) {
+ const ViewerToLool = [
+ 'Action_FollowUser',
+ 'Host_VersionRestore',
+ 'Action_RemoveView'
+ ]
+ PostMessages.registerPostMessageHandler(({ parsed, data }) => {
+ console.debug('[document] Received post message ', parsed)
+ const { msgId, args, deprecated } = parsed
+
+ if (deprecated) {
return
}
- try {
- var msg = JSON.parse(e.data)
- var msgId = msg.MessageId
- var args = msg.Values
- var deprecated = !!args.Deprecated
- } catch (exc) {
- msgId = e.data
- }
-
- if (msgId === 'Download_As') {
- console.debug('download for ' + args.Type + '. Use this url: ' + args.URL)
- if (
- window.RichDocumentsMobileInterface
- || (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.RichDocumentsMobileInterface)
- ) {
- documentsMain.callMobileMessage('downloadAs', args)
+ if (documentsMain.isViewerMode) {
+ let { fileId, title, version } = args
+ switch (parsed.msgId) {
+ case 'Action_loadRevViewer':
+ documentsMain.UI.loadRevViewerContainer()
+ if (fileId) {
+ fileId += '_' + Config.get('instanceId')
+ if (version) {
+ fileId += `_${version}`
+ title += `_${version}`
+ }
+ documentsMain.UI.showViewer(
+ fileId, title
+ )
+ }
+ break
+ case 'App_VersionRestore':
+ if (!documentsMain.$deferredVersionRestoreAck) {
+ console.warn('No version restore deferred object found.')
+ return
+ }
+ break
+ case 'Pre_Restore_Ack':
+ // user instructed to restore the version
+ documentsMain.$deferredVersionRestoreAck.resolve()
+ break
+ default:
return
}
- } else if (msgId === 'File_Rename') {
- documentsMain.fileModel = null
- documentsMain.fileName = args.NewName
- if (
- window.RichDocumentsMobileInterface
- || (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.RichDocumentsMobileInterface)
- ) {
- documentsMain.callMobileMessage('fileRename', args)
- } else {
- documentsMain.getFileList().reload()
- parent.OC.Apps.hideAppSidebar()
- }
- return
+
}
- // Check for webview handler
- if (window.RichDocumentsMobileInterface) {
- if (msgId === 'UI_Close') {
- window.RichDocumentsMobileInterface.close()
- } else if (msgId === 'UI_InsertGraphic') {
- window.RichDocumentsMobileInterface.insertGraphic()
- } else if (msgId === 'UI_Share') {
- window.RichDocumentsMobileInterface.share()
+ // Pass all messages to viewer if not direct editing or
+ if (!isDirectEditing() && ViewerToLool.indexOf(msgId) === -1) {
+ PostMessages.sendPostMessage('parent', data)
+ }
+ // Pass messages from viewer to lool
+ if (ViewerToLool.indexOf(msgId) >= 0) {
+ return PostMessages.sendPostMessage('loolframe', data)
+ }
+
+ if (isMobileInterfaceAvailable()) {
+ if (msgId === 'Download_As') {
+ return callMobileMessage('downloadAs', args)
}
- // Fallback to web UI for SaveAs
- if (msgId !== 'UI_SaveAs') {
+ if (msgId === 'File_Rename') {
+ return callMobileMessage('fileRename', args)
+ } else if (msgId === 'UI_Paste') {
+ documentsMain.callMobileMessage('paste')
return
}
- }
-
- // iOS webkit fallback
- if (window.webkit
- && window.webkit.messageHandlers
- && window.webkit.messageHandlers.RichDocumentsMobileInterface) {
if (msgId === 'UI_Close') {
- window.webkit.messageHandlers.RichDocumentsMobileInterface.postMessage('close')
+ callMobileMessage('close')
} else if (msgId === 'UI_InsertGraphic') {
- window.webkit.messageHandlers.RichDocumentsMobileInterface.postMessage('insertGraphic')
+ callMobileMessage('insertGraphic')
} else if (msgId === 'UI_Share') {
- window.webkit.messageHandlers.RichDocumentsMobileInterface.postMessage('share')
+ callMobileMessage('share')
}
- // Fallback to web UI for SaveAs
+ // Fallback to web UI for SaveAs, otherwise ignore other post messages
if (msgId !== 'UI_SaveAs') {
return
}
}
- if (msgId === 'UI_Close' || msgId === 'close' /* deprecated */) {
- // If a postmesage API is deprecated, we must ignore it and wait for the standard postmessage
- // (or it might already have been fired)
- if (deprecated) { return }
-
+ switch (parsed.msgId) {
+ case 'UI_Close':
+ case 'close':
documentsMain.onClose()
- } else if (msgId === 'UI_FileVersions' || msgId === 'rev-history' /* deprecated */) {
- if (deprecated) { return }
-
- documentsMain.UI.showRevHistory(documentsMain.fullPath)
- } else if (msgId === 'UI_Share') {
- if (documentsMain.getFileList()) {
- documentsMain.getFileList().showDetailsView(documentsMain.fileName, 'shareTabView')
- parent.OC.Apps.showAppSidebar()
- }
- } else if (msgId === 'UI_SaveAs') {
+ break
+ // Messages received from the viewer
+ case 'postAsset':
+ documentsMain.postAsset(args.FileName, args.Url)
+ break
+ case 'UI_FileVersions':
+ case 'rev-history':
+ documentsMain.UI.loadRevViewerContainer()
+ documentsMain.UI.showViewer(
+ documentsMain.fileId, documentsMain.title
+ )
+ break
+ default:
+ console.debug('[document] Unhandled post message', parsed)
+ }
+
+ if (msgId === 'UI_SaveAs') {
// TODO Move to file picker dialog with input field
OC.dialogs.prompt(
t('richdocuments', 'Please enter the filename to store the document as.'),
t('richdocuments', 'Save As'),
function(result, value) {
if (result === true && value) {
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Action_SaveAs', { 'Filename': value })
+ PostMessages.sendWOPIPostMessage('loolframe', 'Action_SaveAs', { 'Filename': value })
}
},
true,
@@ -689,221 +336,57 @@ const documentsMain = {
$buttons.eq(0).text(t('richdocuments', 'Cancel'))
$buttons.eq(1).text(t('richdocuments', 'Save'))
})
- } else if (msgId === 'UI_CreateFile') {
- documentsMain.UI.createNewFile(args.DocumentType)
-
- } else if (msgId === 'UI_InsertGraphic') {
- parent.OC.dialogs.filepicker(t('richdocuments', 'Insert from {name}', { name: OC.theme.name }), function(path, type) {
- if (type === OC.dialogs.FILEPICKER_TYPE_CHOOSE) {
- var filename = path.substring(path.lastIndexOf('/') + 1)
- $.ajax({
- type: 'POST',
- url: OC.generateUrl('apps/richdocuments/assets'),
- data: {
- path: path
- }
- }).done(function(resp) {
- documentsMain.postAsset(filename, resp.url)
- })
- }
- }, false, ['image/png', 'image/gif', 'image/jpeg', 'image/svg'], true, OC.dialogs.FILEPICKER_TYPE_CHOOSE)
- } else if (msgId === 'App_VersionRestore') {
- if (!documentsMain.$deferredVersionRestoreAck) {
- console.warn('No version restore deferred object found.')
- return
- }
-
- if (args.Status === 'Pre_Restore_Ack') {
- // user instructed to restore the version
- documentsMain.$deferredVersionRestoreAck.resolve()
- }
- } else if (msgId === 'View_Added') {
- if (deprecated) { return }
-
- documentsMain.UI.views[args.ViewId] = args
- documentsMain.UI.renderAvatars()
- } else if (msgId === 'View_Removed') {
- if (deprecated) { return }
-
- delete documentsMain.UI.views[args.ViewId]
- documentsMain.UI.renderAvatars()
- } else if (msgId === 'Get_Views_Resp' || msgId === 'Views_List') {
- documentsMain.UI.views = {}
- args.forEach(function(view) {
- documentsMain.UI.views[view.ViewId] = view
- })
- documentsMain.UI.renderAvatars()
- } else if (msgId === 'FollowUser_Changed') {
- if (args.IsFollowEditor) {
- documentsMain.UI.followingEditor = true
- } else {
- documentsMain.UI.followingEditor = false
- }
- if (args.IsFollowUser) {
- documentsMain.UI.following = args.FollowedViewId
- } else {
- documentsMain.UI.following = null
- }
- documentsMain.UI.renderAvatars()
}
})
// Tell the LOOL iframe that we are ready now
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Host_PostmessageReady', {})
+ PostMessages.sendWOPIPostMessage('loolframe', 'Host_PostmessageReady', {})
})
// submit that
$('#loleafletform').submit()
},
- /* Ask for a new filename and open the files app in a new tab
- * the parameters richdocuments_create and richdocuments_filename are
- * parsed by viewer.js and open a template picker in the new tab
- */
- createNewFile: function(type) {
- parent.OC.dialogs.prompt(
- t('richdocuments', 'Please enter the filename for the new document'),
- t('richdocuments', 'Save As'),
- function(result, value) {
- if (result === true && value) {
- if (type === 'text') {
- type = 'document'
- }
- var dir = parent.$('#dir').val()
- var url = OC.generateUrl('/apps/files/?dir=' + dir + '&richdocuments_create=' + type + '&richdocuments_filename=' + encodeURI(value))
- window.open(url, '_blank')
- }
- },
- true,
- t('richdocuments', 'New filename'),
- false
- ).then(function() {
- var $dialog = parent.$('.oc-dialog:visible')
- var $buttons = $dialog.find('button')
- $buttons.eq(0).text(t('richdocuments', 'Cancel'))
- $buttons.eq(1).text(t('richdocuments', 'Create a new document'))
- })
- },
-
hideEditor: function() {
// Fade out editor
$('#mainContainer').fadeOut('fast', function() {
$('#mainContainer').remove()
$('#content-wrapper').fadeIn('fast')
$(document.body).removeClass('claro')
- parent.document.title = documentsMain.UI.mainTitle
})
- },
-
- notify: function(message) {
- OC.Notification.show(message)
- setTimeout(OC.Notification.hide, 10000)
}
},
onStartup: function() {
var fileId
- documentsMain.UI.init()
// Does anything indicate that we need to autostart a session?
- fileId = getURLParameter('fileId').replace(/^\W*/, '')
+ fileId = window.getURLParameter('fileId').replace(/^\W*/, '')
if (fileId && Number.isInteger(Number(fileId)) && $('#nickname').length === 0) {
- documentsMain.prepareSession()
+ documentsMain.isEditorMode = true
documentsMain.originalFileId = fileId
}
documentsMain.ready = true
},
- WOPIPostMessage: function(iframe, msgId, values) {
- if (iframe) {
- var msg = {
- 'MessageId': msgId,
- 'SendTime': Date.now(),
- 'Values': values
- }
-
- iframe.contentWindow.postMessage(JSON.stringify(msg), '*')
- }
- },
-
- callMobileMessage: function(messageName, attributes) {
- var message = messageName
- if (typeof attributes !== 'undefined') {
- message = {
- MessageName: messageName,
- Values: attributes
- }
- }
- // Forward to mobile handler
- if (window.RichDocumentsMobileInterface && typeof window.RichDocumentsMobileInterface[messageName] === 'function') {
- window.RichDocumentsMobileInterface[messageName](JSON.stringify(attributes))
- }
-
- // iOS webkit fallback
- if (window.webkit
- && window.webkit.messageHandlers
- && window.webkit.messageHandlers.RichDocumentsMobileInterface) {
- window.webkit.messageHandlers.RichDocumentsMobileInterface.postMessage(message)
- }
- },
-
- prepareSession: function() {
- documentsMain.isEditorMode = true
- },
-
initSession: function() {
- documentsMain.urlsrc = richdocuments_urlsrc
- documentsMain.fullPath = richdocuments_path
- documentsMain.token = richdocuments_token
+ documentsMain.urlsrc = Config.get('urlsrc')
+ documentsMain.fullPath = Config.get('path')
+ documentsMain.token = Config.get('token')
+ documentsMain.fileId = Config.get('fileId')
+ documentsMain.fileName = Config.get('title')
+ documentsMain.canEdit = Boolean(Config.get('permissions') & OC.PERMISSION_UPDATE)
+ documentsMain.canShare = typeof OC.Share !== 'undefined' && Config.get('permissions') & OC.PERMISSION_SHARE
$('footer,nav').hide()
- $(documentsMain.toolbar).appendTo('#header')
-
- documentsMain.canShare = typeof OC.Share !== 'undefined' && richdocuments_permissions & OC.PERMISSION_SHARE
-
// fade out file list and show the document
$('#content-wrapper').fadeOut('fast').promise().done(function() {
-
- documentsMain.fileId = richdocuments_fileId
- documentsMain.fileName = richdocuments_title
-
- documentsMain.canEdit = Boolean(richdocuments_permissions & OC.PERMISSION_UPDATE)
-
documentsMain.loadDocument(documentsMain.fileName, documentsMain.fileId)
})
},
- getFileModel: function() {
- if (documentsMain.getFileList() && documentsMain.getFileList()._detailsView && documentsMain.getFileList()._detailsView.getFileInfo()) {
- if (documentsMain.fileModel && documentsMain.fileModel !== documentsMain.getFileList()._detailsView.getFileInfo()) {
- documentsMain.fileModel = documentsMain.getFileList()._detailsView.getFileInfo()
- documentsMain.fileModel.on('change', function() {
- documentsMain.UI._addHeaderFileActions()
- })
- }
- }
-
- if (documentsMain.fileModel) {
- return documentsMain.fileModel
- }
- if (documentsMain.getFileList()) {
- documentsMain.getFileList().scrollTo([documentsMain.fileName, ''])
- var fileModel = documentsMain.getFileList().getModelForFile(documentsMain.fileName)
-
- if (fileModel) {
- fileModel.on('change', function() {
- documentsMain.UI._addHeaderFileActions()
- })
- documentsMain.fileModel = fileModel
- documentsMain.UI._addHeaderFileActions()
- } else {
- setTimeout(documentsMain.getFileModel, 500)
- }
- }
- },
-
loadDocument: function(title, fileId) {
documentsMain.UI.showEditor(title, fileId, 'write')
},
@@ -915,7 +398,6 @@ const documentsMain = {
$(window).off('unload')
if (documentsMain.isEditorMode) {
documentsMain.isEditorMode = false
- parent.location.hash = ''
} else {
setTimeout(OC.Notification.hide, 7000)
}
@@ -928,15 +410,11 @@ const documentsMain = {
documentsMain.isEditorMode = false
$(window).off('beforeunload')
$(window).off('unload')
- parent.location.hash = ''
$('footer,nav').show()
documentsMain.UI.hideEditor()
- $('#ocToolbar').remove()
- parent.document.title = documentsMain.UI.mainTitle
- parent.postMessage('close', '*')
- documentsMain.UI.removeVersionSidebarEvents()
+ PostMessages.sendPostMessage('parent', 'close', '*')
},
onCloseViewer: function() {
@@ -945,22 +423,19 @@ const documentsMain = {
$('#revPanelContainer').remove()
$('#revViewerContainer').remove()
documentsMain.isViewerMode = false
- documentsMain.UI.revisionsStart = 0
- parent.$('#versionsTabView .active').removeClass('active')
- parent.$('#versionsTabView #currentVersion').remove()
- parent.OC.Apps.hideAppSidebar()
+
$('#loleafletframe').focus()
},
postAsset: function(filename, url) {
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Action_InsertGraphic', {
+ PostMessages.sendWOPIPostMessage('loolframe', 'Action_InsertGraphic', {
filename: filename,
url: url
})
},
postGrabFocus: function() {
- documentsMain.WOPIPostMessage($('#loleafletframe')[0], 'Grab_Focus')
+ PostMessages.sendWOPIPostMessage('loolframe', 'Grab_Focus')
}
}
@@ -987,4 +462,6 @@ $(document).ready(function() {
viewport.setAttribute('content', 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no')
documentsMain.onStartup()
+
+ window.documentsMain = documentsMain
})
diff --git a/src/helpers/guestName.js b/src/helpers/guestName.js
index d7c6aae4..806d8039 100644
--- a/src/helpers/guestName.js
+++ b/src/helpers/guestName.js
@@ -20,8 +20,7 @@
*
*/
-/* global richdocuments_permissions */
-
+import Config from './../services/config'
import { getCurrentUser } from 'nextcloud-auth'
import mobile from './mobile'
@@ -50,8 +49,9 @@ const setGuestNameCookie = function(username) {
const shouldAskForGuestName = () => {
return !mobile.isDirectEditing()
&& getCurrentUser().uid === null
+ && Config.get('userId') === null
&& getGuestNameCookie() === ''
- && (richdocuments_permissions & OC.PERMISSION_UPDATE)
+ && (Config.get('permissions') & OC.PERMISSION_UPDATE)
}
export {
diff --git a/src/helpers/index.js b/src/helpers/index.js
index 6f9ea750..177a935b 100644
--- a/src/helpers/index.js
+++ b/src/helpers/index.js
@@ -28,6 +28,11 @@ const languageToBCP47 = () => {
.replace(/^([a-z]{2}).*_([A-Z]{2})$/, (match, p1, p2) => p1 + '-' + p2.toLowerCase())
}
+const getNextcloudVersion = () => {
+ return parseInt(OC.config.version.split('.')[0])
+}
+
export {
- languageToBCP47
+ languageToBCP47,
+ getNextcloudVersion
}
diff --git a/src/helpers/mobile.js b/src/helpers/mobile.js
index 34211883..66979009 100644
--- a/src/helpers/mobile.js
+++ b/src/helpers/mobile.js
@@ -20,10 +20,58 @@
*
*/
-/* global richdocuments_directEdit */
+import Config from './../services/config'
-const isDirectEditing = () => richdocuments_directEdit
+const isDirectEditing = () => Config.get('directEdit')
+
+const isMobileInterfaceAvailable = () => window.RichDocumentsMobileInterface
+ || (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.RichDocumentsMobileInterface)
+
+const isMobileInterfaceOnIos = () => window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.RichDocumentsMobileInterface
+
+const isMobileInterfaceOnAndroid = () => window.RichDocumentsMobileInterface
+
+const callMobileMessage = (messageName, attributes) => {
+ console.debug('callMobileMessage', messageName, attributes)
+ let message = messageName
+ if (typeof attributes !== 'undefined') {
+ message = {
+ MessageName: messageName,
+ Values: attributes
+ }
+ }
+ let attributesString = null
+ try {
+ attributesString = JSON.stringify(attributes)
+ } catch (e) {
+ attributesString = null
+ }
+ // Forward to mobile handler
+ if (window.RichDocumentsMobileInterface && typeof window.RichDocumentsMobileInterface[messageName] === 'function') {
+ if (attributesString === null || typeof attributesString === 'undefined') {
+ window.RichDocumentsMobileInterface[messageName]()
+ } else {
+ window.RichDocumentsMobileInterface[messageName](attributesString)
+ }
+ }
+
+ // iOS webkit fallback
+ if (window.webkit
+ && window.webkit.messageHandlers
+ && window.webkit.messageHandlers.RichDocumentsMobileInterface) {
+ window.webkit.messageHandlers.RichDocumentsMobileInterface.postMessage(message)
+ }
+}
+
+export default {
+ isDirectEditing,
+ callMobileMessage
+}
export {
- isDirectEditing
+ isDirectEditing,
+ callMobileMessage,
+ isMobileInterfaceAvailable,
+ isMobileInterfaceOnAndroid,
+ isMobileInterfaceOnIos
}
diff --git a/src/helpers/types.js b/src/helpers/types.js
new file mode 100644
index 00000000..597f7c06
--- /dev/null
+++ b/src/helpers/types.js
@@ -0,0 +1,60 @@
+
+/*
+ * @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/>.
+ *
+ */
+
+const getFileType = (document, ooxml) => {
+ let documentTypes = {
+ document: {
+ extension: 'odt',
+ mime: 'application/vnd.oasis.opendocument.text'
+ },
+ spreadsheet: {
+ extension: 'ods',
+ mime: 'application/vnd.oasis.opendocument.spreadsheet'
+ },
+ presentation: {
+ extension: 'odp',
+ mime: 'application/vnd.oasis.opendocument.presentation'
+ }
+ }
+ if (ooxml) {
+ documentTypes = {
+ document: {
+ extension: 'docx',
+ mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+ },
+ spreadsheet: {
+ extension: 'xlsx',
+ mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+ },
+ presentation: {
+ extension: 'pptx',
+ mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
+ }
+ }
+ }
+ return documentTypes[document]
+}
+
+export default {
+ getFileType
+}
diff --git a/src/helpers/url.js b/src/helpers/url.js
index 49e9c006..777c0218 100644
--- a/src/helpers/url.js
+++ b/src/helpers/url.js
@@ -20,6 +20,10 @@
*
*/
+import { getRootUrl } from 'nextcloud-router'
+import { languageToBCP47 } from './index'
+import Config from './../services/config'
+
const getSearchParam = (name) => {
var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href)
if (results === null) {
@@ -28,4 +32,65 @@ const getSearchParam = (name) => {
return decodeURI(results[1]) || 0
}
-export { getSearchParam }
+const getWopiUrl = ({ fileId, title, readOnly, closeButton, revisionHistory }) => {
+ // WOPISrc - URL that loolwsd will access (ie. pointing to ownCloud)
+ // index.php is forced here to avoid different wopi srcs for the same document
+ const wopiurl = window.location.protocol + '//' + window.location.host + getRootUrl() + '/index.php/apps/richdocuments/wopi/files/' + fileId
+ console.debug('[getWopiUrl] ' + wopiurl)
+ const wopisrc = encodeURIComponent(wopiurl)
+
+ // urlsrc - the URL from discovery xml that we access for the particular
+ // document; we add various parameters to that.
+ // The discovery is available at
+ // https://<loolwsd-server>:9980/hosting/discovery
+ return Config.get('urlsrc')
+ + 'WOPISrc=' + wopisrc
+ + '&title=' + encodeURIComponent(title)
+ + '&lang=' + languageToBCP47()
+ + (closeButton ? '&closebutton=1' : '')
+ + (revisionHistory ? '&revisionhistory=1' : '')
+ + (readOnly ? '&permission=readonly' : '')
+}
+
+const getDocumentUrlFromTemplate = (templateId, fileName, fileDir, fillWithTemplate) => {
+ return OC.generateUrl(
+ 'apps/richdocuments/indexTemplate?templateId={templateId}&fileName={fileName}&dir={dir}&requesttoken={requesttoken}',
+ {
+ templateId: templateId,
+ fileName: fileName,
+ dir: fileDir,
+ requesttoken: OC.requestToken
+ }
+ )
+}
+
+const getDocumentUrlForPublicFile = (fileName, fileId) => {
+ return OC.generateUrl(
+ 'apps/richdocuments/public?shareToken={shareToken}&fileName={fileName}&requesttoken={requesttoken}&fileId={fileId}',
+ {
+ shareToken: document.getElementById('sharingToken').value,
+ fileName: fileName,
+ fileId: fileId,
+ requesttoken: OC.requestToken
+ }
+ )
+}
+
+const getDocumentUrlForFile = (fileDir, fileId) => {
+ return OC.generateUrl(
+ 'apps/richdocuments/index?fileId={fileId}&requesttoken={requesttoken}',
+ {
+ fileId: fileId,
+ dir: fileDir,
+ requesttoken: OC.requestToken
+ })
+}
+
+export {
+ getSearchParam,
+ getWopiUrl,
+
+ getDocumentUrlFromTemplate,
+ getDocumentUrlForPublicFile,
+ getDocumentUrlForFile
+}
diff --git a/src/services/config.tsx b/src/services/config.tsx
new file mode 100644
index 00000000..0789a97e
--- /dev/null
+++ b/src/services/config.tsx
@@ -0,0 +1,53 @@
+
+/*
+ * @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/>.
+ *
+ */
+
+class ConfigService {
+ private values: {[name: string]: string}
+ constructor () {
+ this.values = {}
+ this.loadFromGlobal('userId')
+ this.loadFromGlobal('urlsrc')
+ this.loadFromGlobal('directEdit')
+ this.loadFromGlobal('permissions')
+ this.loadFromGlobal('instanceId')
+
+ }
+ loadFromGlobal(key: string) {
+ // @ts-ignore
+ this.values[key] = window['richdocuments_' + key]
+ }
+ update(key: string, value: string) {
+ // @ts-ignore
+ this.values[key] = value
+ }
+ get(key: string) {
+ if (typeof this.values[key] === 'undefined') {
+ this.loadFromGlobal(key)
+ }
+ return this.values[key]
+ }
+}
+
+const Config = new ConfigService()
+
+export default Config
diff --git a/src/services/postMessage.tsx b/src/services/postMessage.tsx
new file mode 100644
index 00000000..ccdd6933
--- /dev/null
+++ b/src/services/postMessage.tsx
@@ -0,0 +1,107 @@
+/*
+ * @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/>.
+ *
+ */
+type MessageEventSource = Window | MessagePort | ServiceWorker;
+
+export interface WopiPost {
+ MessageId: string;
+ Values: WopiPostValues;
+}
+
+export interface WopiPostValues {
+ Deprecated?: boolean;
+}
+
+interface WindowCallbackHandler { (): Window}
+
+export default class PostMessageService {
+ private readonly targets: {[name: string]: (Window|WindowCallbackHandler)};
+ private postMessageHandlers: Function[] = [];
+
+ constructor(targets: {[name: string]: (Window|WindowCallbackHandler)}) {
+ this.targets = targets
+ window.addEventListener('message', (event: {source: MessageEventSource, data: any, origin: string}) => {
+ this.handlePostMessage(event.data)
+ }, false)
+ }
+
+ sendPostMessage(target: string, message: any, targetOrigin: string = '*') {
+ let targetElement: Window;
+ if (typeof this.targets[target] === 'function') {
+ targetElement = (this.targets[target] as WindowCallbackHandler)()
+ } else {
+ targetElement = this.targets[target] as Window
+ }
+ targetElement.postMessage(message, targetOrigin)
+ console.debug('PostMessageService.sendPostMessage', target, message)
+ }
+
+
+ sendWOPIPostMessage(target: string, msgId: string, values: any = {}) {
+ const msg = {
+ MessageId: msgId,
+ SendTime: Date.now(),
+ Values: values
+ }
+
+ this.sendPostMessage(target, JSON.stringify(msg))
+ }
+
+ private static parsePostMessage(data: any) {
+ let msgId: string,
+ args: WopiPostValues,
+ deprecated: boolean
+
+ try {
+ const msg: WopiPost = JSON.parse(data)
+ msgId = msg.MessageId
+ args = msg.Values
+ deprecated = !!msg.Values.Deprecated
+ } catch (exc) {
+ msgId = data
+ }
+ return { msgId, args, deprecated }
+ }
+
+ registerPostMessageHandler(callback: Function) {
+ this.postMessageHandlers.push(callback)
+ }
+
+ unregisterPostMessageHandler(callback: Function) {
+ const handlerIndex = this.postMessageHandlers.findIndex(cb => cb === callback)
+ delete this.postMessageHandlers[handlerIndex]
+ }
+
+ private handlePostMessage(data: any) {
+ this.postMessageHandlers.forEach((fn: Function): void => {
+ const parsed = PostMessageService.parsePostMessage(data);
+ if (parsed.deprecated) {
+ console.debug('PostMessageService.handlePostMessage', 'Ignoring deprecated post message', parsed.msgId)
+ return;
+ }
+ fn({
+ data: data,
+ parsed
+ })
+ })
+ }
+
+}
diff --git a/src/view/FilesAppIntegration.js b/src/view/FilesAppIntegration.js
new file mode 100644
index 00000000..53bee506
--- /dev/null
+++ b/src/view/FilesAppIntegration.js
@@ -0,0 +1,447 @@
+/*
+ * @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/>.
+ *
+ */
+
+import Config from '../services/config'
+
+let documentsMain = null
+const isPublic = document.getElementById('isPublic') && document.getElementById('isPublic').value === '1'
+
+export default {
+
+ fileModel: null,
+ /* Views: people currently editing the file */
+ views: {},
+
+ followingEditor: false,
+
+ following: null,
+
+ init({ fileName, fileId, sendPostMessage }) {
+ this.fileName = fileName
+ this.fileId = fileId
+ this.sendPostMessage = sendPostMessage
+
+ if (typeof this.getFileList() !== 'undefined') {
+ this.getFileModel()
+ }
+
+ const headerRight = document.querySelector('#header .header-right')
+ const richdocumentsHeader = document.createElement('div')
+ richdocumentsHeader.id = 'richdocuments-header'
+ headerRight.insertBefore(richdocumentsHeader, headerRight.firstChild)
+
+ this._addAvatarList()
+ if (!isPublic) {
+ this._addHeaderShareButton()
+ this._addHeaderFileActions()
+ this.addVersionSidebarEvents()
+ }
+ },
+
+ initAfterReady() {
+ documentsMain = document.getElementById('richdocumentsframe').contentWindow.documentsMain
+ },
+
+ close() {
+ this.fileModel = null
+ if (!isPublic) {
+ this.removeVersionSidebarEvents()
+ }
+ $('#richdocuments-header').remove()
+ },
+
+ share() {
+ if (isPublic) {
+ console.error('[FilesAppIntegration] Sharing is not supported')
+ }
+ FileList.showDetailsView(this.fileName, 'shareTabView')
+ OC.Apps.showAppSidebar()
+ },
+
+ insertGraphic(callback) {
+ if (isPublic) {
+ console.error('[FilesAppIntegration] insertGraphic is not supported')
+ }
+ OC.dialogs.filepicker(t('richdocuments', 'Insert from {name}', { name: OC.theme.name }), function(path, type) {
+ if (type === OC.dialogs.FILEPICKER_TYPE_CHOOSE) {
+ const filename = path.substring(path.lastIndexOf('/') + 1)
+ $.ajax({
+ type: 'POST',
+ url: OC.generateUrl('apps/richdocuments/assets'),
+ data: {
+ path: path
+ }
+ }).done(function(resp) {
+ callback(filename, resp.url)
+ })
+ }
+ }, false, ['image/png', 'image/gif', 'image/jpeg', 'image/svg'], true, OC.dialogs.FILEPICKER_TYPE_CHOOSE)
+ },
+
+ getFileList() {
+ if (OCA.Files.App) {
+ return OCA.Files.App.fileList
+ }
+ if (OCA.Sharing.PublicApp) {
+ return OCA.Sharing.PublicApp.fileList
+ }
+ return null
+ },
+
+ getFileModel() {
+ if (this.fileModel !== null) {
+ return this.fileModel
+ }
+ this.getFileList()._updateDetailsView(this.fileName, false)
+ this.fileModel = this.getFileList().getModelForFile(this.fileName)
+
+ if (this.fileModel !== null) {
+ this.fileModel.on('change', () => {
+ this._addHeaderFileActions()
+ })
+ }
+
+ return this.fileModel
+ },
+
+ setViews(views) {
+ this.views = {}
+ views.forEach((view) => {
+ this.views[view.ViewId] = view
+ })
+ this.renderAvatars()
+ },
+
+ followReset(event) {
+ this.sendPostMessage('Action_FollowUser', { Follow: false })
+ this.following = null
+ this.followingEditor = false
+ this.renderAvatars()
+ },
+ followCurrentEditor(event) {
+ this.sendPostMessage('Action_FollowUser', { Follow: true })
+ this.following = null
+ this.followingEditor = true
+ this.renderAvatars()
+ },
+ followView(view) {
+ this.sendPostMessage('Action_FollowUser', { ViewId: view.ViewId, Follow: true })
+ this.following = view.ViewId
+ this.followingEditor = false
+ this.renderAvatars()
+ },
+
+ _addAvatarList() {
+ // Add the avatar toolbar if possible
+ const avatarList = $('<div id="richdocuments-avatars">')
+ avatarList.on('click', function(e) {
+ e.stopPropagation()
+ $('#editors-menu').toggle()
+ })
+ $('#richdocuments-header').append(avatarList)
+ },
+
+ _addHeaderShareButton() {
+ if ($('header').length) {
+ var $button = $('<div id="richdocuments-sharing"><a class="icon-shared icon-white"></a></div>')
+ $('#richdocuments-header').append($button)
+ $button.on('click', () => {
+ if (!$('#app-sidebar').is(':visible')) {
+ return this.share()
+ }
+ OC.Apps.hideAppSidebar()
+ })
+ $('.searchbox').hide()
+ }
+ },
+
+ _addHeaderFileActions() {
+ console.debug('[FilesAppIntegration] Adding header file actions')
+ OC.unregisterMenu($('#richdocuments-actions .icon-more'), $('#richdocuments-actions-menu'))
+ $('#richdocuments-actions').remove()
+ var actionsContainer = $('<div id="richdocuments-actions"><div class="icon-more icon-white"></div><ul id="richdocuments-actions-menu" class="popovermenu"></ul></div>')
+ var actions = actionsContainer.find('#richdocuments-actions-menu').empty()
+
+ var context = {
+ '$file': this.getFileList().$el.find('[data-id=' + this.originalFileId + ']').first(),
+ fileActions: this.getFileList().fileActions,
+ fileList: this.getFileList(),
+ fileInfoModel: this.getFileModel()
+ }
+
+ const isFavorite = function(fileInfo) {
+ return fileInfo.get('tags') && fileInfo.get('tags').indexOf(OC.TAG_FAVORITE) >= 0
+ }
+ const $favorite = $('<li><a></a></li>').click((event) => {
+ $favorite.find('a').removeClass('icon-starred').removeClass('icon-star-dark').addClass('icon-loading-small')
+ this.getFileList().fileActions.triggerAction('Favorite', this.getFileModel(), this.getFileList())
+ this.getFileModel().trigger('change', this.getFileModel())
+ })
+ if (isFavorite(context.fileInfoModel)) {
+ $favorite.find('a').text(t('files', 'Remove from favorites'))
+ $favorite.find('a').addClass('icon-starred')
+ } else {
+ $favorite.find('a').text(t('files', 'Add to favorites'))
+ $favorite.find('a').addClass('icon-star-dark')
+ }
+
+ var $info = $('<li><a class="icon-info"></a></li>').click(() => {
+ this.getFileList().fileActions.actions.all.Details.action(this.fileName, context)
+ OC.hideMenus()
+ })
+ $info.find('a').text(t('files', 'Details'))
+ var $download = $('<li><a class="icon-download">Download</a></li>').click(() => {
+ this.getFileList().fileActions.actions.all.Download.action(this.fileName, context)
+ OC.hideMenus()
+ })
+ $download.find('a').text(t('files', 'Download'))
+ actions.append($favorite).append($info).append($download)
+ $('#richdocuments-header').append(actionsContainer)
+ OC.registerMenu($('#richdocuments-actions .icon-more'), $('#richdocuments-actions-menu'), false, true)
+ },
+
+ /**
+ * @param {View} view
+ * @private
+ */
+ _userEntry: function(view) {
+ var entry = $('<li></li>')
+ entry.append(this._avatarForView(view))
+
+ var label = $('<div class="label"></div>')
+ label.text(view.UserName)
+ if (view.ReadOnly === '1') {
+ label.text(view.UserName + ' ' + t('richdocuments', '(read only)'))
+
+ }
+ label.click((event) => {
+ event.stopPropagation()
+ this.followView(view)
+ })
+ if (this.following === view.ViewId) {
+ $('#editors-menu').find('li').removeClass('active')
+ entry.addClass('active')
+ }
+ entry.append(label)
+
+ var isFileOwner = !isPublic && this.getFileModel() && typeof this.getFileModel().get('shareOwner') === 'undefined'
+ if (documentsMain.canEdit && isFileOwner && !view.IsCurrentView) {
+ var removeButton = $('<div class="icon-close" title="Remove user"/>')
+ removeButton.click(() => {
+ this.sendPostMessage('Action_RemoveView', { ViewId: view.ViewId })
+ })
+ entry.append(removeButton)
+ }
+ return entry
+ },
+
+ /**
+ * @param {View} view
+ * @returns {$|HTMLElement}
+ * @private
+ */
+ _avatarForView: function(view) {
+ const userId = (view.UserId === '') ? view.UserName : view.UserId
+ var avatarContainer = $('<div class="richdocuments-avatar"><div class="avatar" title="' + view.UserName + '" data-user="' + userId + '"></div></div>')
+ var avatar = avatarContainer.find('.avatar')
+ avatar.css({ 'border-color': view.Color,
+ 'border-width': '2px',
+ 'border-style': 'solid' })
+ if (view.ReadOnly === '1') {
+ avatarContainer.addClass('read-only')
+ $(avatar).attr('title', view.UserName + ' ' + t('richdocuments', '(read only)'))
+ } else {
+ $(avatar).attr('title', view.UserName)
+ }
+
+ $(avatar).avatar(userId, 32, undefined, true, undefined, view.UserName)
+ return avatarContainer
+ },
+
+ renderAvatars: function() {
+ var avatardiv = $('#header .header-right #richdocuments-avatars')
+ avatardiv.empty()
+ var popover = $('<div id="editors-menu" class="popovermenu menu-center"><ul></ul></div>')
+
+ var users = []
+ // Add new avatars
+ var i = 0
+ for (var viewId in this.views) {
+ /**
+ * @type {View}
+ */
+ var view = this.views[viewId]
+ view.UserName = view.UserName !== '' ? view.UserName : t('richdocuments', 'Guest')
+ popover.find('ul').append(this._userEntry(view))
+
+ if (view.UserId === OC.currentUser) {
+ continue
+ }
+ if (view.UserId !== '' && users.indexOf(view.UserId) > -1) {
+ continue
+ }
+ users.push(view.UserId)
+ if (i++ < 3) {
+ avatardiv.append(this._avatarForView(view))
+ }
+ }
+ var followCurrentEditor = $('<li><input type="checkbox" class="checkbox" /><label class="label">' + t('richdocuments', 'Follow current editor') + '</label></li>')
+ followCurrentEditor.find('label').click((event) => {
+ event.stopPropagation()
+ if (this.followingEditor) {
+ this.followReset()
+ } else {
+ this.followCurrentEditor()
+ }
+ })
+ followCurrentEditor.find('.checkbox').prop('checked', this.followingEditor)
+ popover.find('ul').append(followCurrentEditor)
+ avatardiv.append(popover)
+ },
+
+ addVersionSidebarEvents() {
+ $(document.querySelector('#content')).on('click.revisions', '#app-sidebar .preview-container', this.showVersionPreview.bind(this))
+ $(document.querySelector('#content')).on('click.revisions', '#app-sidebar .downloadVersion', this.showVersionPreview.bind(this))
+ $(document.querySelector('#content')).on('mousedown.revisions', '#app-sidebar .revertVersion', this.restoreVersion.bind(this))
+ },
+
+ removeVersionSidebarEvents() {
+ $(document.querySelector('#content')).off('click.revisions')
+ $(document.querySelector('#content')).off('click.revisions')
+ $(document.querySelector('#content')).off('mousedown.revisions')
+ },
+
+ addCurrentVersion() {
+ if (this.getFileModel()) {
+ const preview = OC.MimeType.getIconUrl(this.getFileModel().get('mimetype'))
+ const mtime = this.getFileModel().get('mtime')
+ $('#versionsTabView').prepend('<ul id="lastSavedVersion"><li data-revision="0"><div><div class="preview-container"><img src="' + preview + '" width="44" /></div><div class="version-container">\n'
+ + '<div><a class="downloadVersion">' + t('richdocuments', 'Last saved version') + '<span class="versiondate has-tooltip live-relative-timestamp" data-timestamp="' + mtime + '"></span></div></div></li></ul>')
+ $('#versionsTabView').prepend('<ul id="currentVersion"><li data-revision="" class="active"><div><div class="preview-container"><img src="' + preview + '" width="44" /></div><div class="version-container">\n'
+ + '<div><a class="downloadVersion">' + t('richdocuments', 'Current version') + '</a></div></div></li></ul>')
+ $('.live-relative-timestamp').each(function() {
+ $(this).text(OC.Util.relativeModifiedDate(parseInt($(this).attr('data-timestamp'), 10)))
+ })
+ }
+ },
+
+ showRevHistory() {
+ FileList.showDetailsView(this.fileName, 'versionsTabView')
+ this.addCurrentVersion()
+ },
+
+ showVersionPreview: function(e) {
+ e.preventDefault()
+ var element = e.currentTarget.parentElement.parentElement
+ if ($(e.currentTarget).hasClass('downloadVersion')) {
+ element = e.currentTarget.parentElement.parentElement.parentElement.parentElement
+ }
+ var version = element.dataset.revision
+ var fileId = this.fileId
+ var title = this.fileName
+ console.debug('[FilesAppIntegration] showVersionPreview', version, fileId, title)
+ this.sendPostMessage('Action_loadRevViewer', { fileId, title, version })
+ $(element.parentElement.parentElement).find('li').removeClass('active')
+ $(element).addClass('active')
+ },
+
+ restoreVersion: function(e) {
+ var self = this
+ e.preventDefault()
+ e.stopPropagation()
+
+ documentsMain.onCloseViewer()
+
+ this.sendPostMessage('Host_VersionRestore', { Status: 'Pre_Restore' })
+
+ var version = e.currentTarget.parentElement.parentElement.dataset.revision
+
+ documentsMain.$deferredVersionRestoreAck = $.Deferred()
+ $.when(documentsMain.$deferredVersionRestoreAck).done(function(args) {
+ self._restoreDAV(version)
+ })
+
+ // resolve the deferred object immediately if client doesn't support version states
+ if (!documentsMain.wopiClientFeatures || !documentsMain.wopiClientFeatures.VersionStates) {
+ documentsMain.$deferredVersionRestoreAck.resolve()
+ }
+
+ return false
+ },
+
+ _restoreSuccess: function(response) {
+ if (response.status === 'error') {
+ OC.Notification.showTemporary(t('richdocuments', 'Failed to revert the document to older version'))
+ }
+
+ // load the file again, it should get reverted now
+ window.location = $(parent.document.querySelector('#richdocumentsframe')).attr('src')
+ parent.OC.Apps.hideAppSidebar()
+ },
+
+ _restoreError: function() {
+ OC.Notification.showTemporary(t('richdocuments', 'Failed to revert the document to older version'))
+ },
+
+ _restoreDAV: function(version) {
+ var restoreUrl = OC.linkToRemoteBase('dav') + '/versions/' + Config.get('userId')
+ + '/versions/' + this.fileId + '/' + version
+ $.ajax({
+ type: 'MOVE',
+ url: restoreUrl,
+ headers: {
+ Destination: OC.linkToRemote('dav') + '/versions/' + Config.get('userId') + '/restore/target'
+ },
+ success: this._restoreSuccess,
+ error: this._restoreError
+ })
+ },
+
+ /* Ask for a new filename and open the files app in a new tab
+ * the parameters richdocuments_create and richdocuments_filename are
+ * parsed by viewer.js and open a template picker in the new tab
+ */
+ createNewFile: function(type) {
+ OC.dialogs.prompt(
+ t('richdocuments', 'Please enter the filename for the new document'),
+ t('richdocuments', 'Save As'),
+ function(result, value) {
+ if (result === true && value) {
+ if (type === 'text') {
+ type = 'document'
+ }
+ var dir = parent.$('#dir').val()
+ var url = OC.generateUrl('/apps/files/?dir=' + dir + '&richdocuments_create=' + type + '&richdocuments_filename=' + encodeURI(value))
+ window.open(url, '_blank')
+ }
+ },
+ true,
+ t('richdocuments', 'New filename'),
+ false
+ ).then(function() {
+ var $dialog = parent.$('.oc-dialog:visible')
+ var $buttons = $dialog.find('button')
+ $buttons.eq(0).text(t('richdocuments', 'Cancel'))
+ $buttons.eq(1).text(t('richdocuments', 'Create a new document'))
+ })
+ }
+}
diff --git a/src/viewer.js b/src/viewer.js
index e70a6dca..1b4480a6 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -1,112 +1,85 @@
-import { getSearchParam } from './helpers/url'
+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'
+
+const FRAME_DOCUMENT = 'FRAME_DOCUMENT'
+const PostMessages = new PostMessageService({
+ FRAME_DOCUMENT: () => document.getElementById('richdocumentsframe').contentWindow
+})
-var preloadType = getSearchParam('richdocuments_create')
-var preloadFilename = getSearchParam('richdocuments_filename')
-var Preload = {
+const Preload = {
create: {
- type: preloadType,
- filename: preloadFilename
+ type: getSearchParam('richdocuments_create'),
+ filename: getSearchParam('richdocuments_filename')
}
}
-var odfViewer = {
- isDocuments: false,
- nextcloudVersion: 0,
+const isPublic = document.getElementById('isPublic') && document.getElementById('isPublic').value === '1'
+
+const odfViewer = {
+
+ open: false,
receivedLoading: false,
supportedMimes: OC.getCapabilities().richdocuments.mimetypes.concat(OC.getCapabilities().richdocuments.mimetypesNoDefaultOpen),
excludeMimeFromDefaultOpen: OC.getCapabilities().richdocuments.mimetypesNoDefaultOpen,
- register: function() {
- odfViewer.nextcloudVersion = parseInt(OC.config.version.split('.')[0])
- var i, mime
- var editActionName = 'Edit with ' + OC.getCapabilities().richdocuments.productName
- for (i = 0; i < odfViewer.supportedMimes.length; ++i) {
- mime = odfViewer.supportedMimes[i]
+
+ register() {
+ const EDIT_ACTION_NAME = 'Edit with ' + OC.getCapabilities().richdocuments.productName
+ for (let mime of odfViewer.supportedMimes) {
OCA.Files.fileActions.register(
mime,
- editActionName,
+ EDIT_ACTION_NAME,
OC.PERMISSION_UPDATE | OC.PERMISSION_READ,
OC.imagePath('core', 'actions/rename'),
- odfViewer.onEdit,
+ this.onEdit,
t('richdocuments', 'Edit with {productName}', { productName: OC.getCapabilities().richdocuments.productName })
)
if (odfViewer.excludeMimeFromDefaultOpen.indexOf(mime) === -1) {
- OCA.Files.fileActions.setDefault(mime, editActionName)
+ OCA.Files.fileActions.setDefault(mime, EDIT_ACTION_NAME)
}
}
},
- dispatch: function(filename) {
- odfViewer.onEdit(filename)
- },
-
- getNewDocumentFromTemplateUrl: function(templateId, fileName, fileDir, fillWithTemplate) {
- return OC.generateUrl(
- 'apps/richdocuments/indexTemplate?templateId={templateId}&fileName={fileName}&dir={dir}&requesttoken={requesttoken}',
- {
- templateId: templateId,
- fileName: fileName,
- dir: fileDir,
- requesttoken: OC.requestToken
- }
- )
- },
-
onEdit: function(fileName, context) {
+ if (odfViewer.open === true) {
+ return
+ }
+ odfViewer.open = true
if (context) {
var fileDir = context.dir
var fileId = context.fileId || context.$file.attr('data-id')
var templateId = context.templateId
+ FileList.setViewerMode(true)
+ FileList.setPageTitle(fileName)
+ FileList.showMask()
}
odfViewer.receivedLoading = false
- var viewer
- if ($('#isPublic').val() === '1') {
- viewer = OC.generateUrl(
- 'apps/richdocuments/public?shareToken={shareToken}&fileName={fileName}&requesttoken={requesttoken}&fileId={fileId}',
- {
- shareToken: $('#sharingToken').val(),
- fileName: fileName,
- fileId: fileId,
- requesttoken: OC.requestToken
- }
- )
- } else {
- // We are dealing with a template
- if (typeof (templateId) !== 'undefined') {
- viewer = this.getNewDocumentFromTemplateUrl(templateId, fileName, fileDir)
- } else {
- viewer = OC.generateUrl(
- 'apps/richdocuments/index?fileId={fileId}&requesttoken={requesttoken}',
- {
- fileId: fileId,
- dir: fileDir,
- requesttoken: OC.requestToken
- }
- )
- }
+ let documentUrl = getDocumentUrlForFile(fileDir, fileId)
+ if (isPublic) {
+ documentUrl = getDocumentUrlForPublicFile(fileName, fileId)
}
-
- if (context) {
- FileList.setViewerMode(true)
- FileList.setPageTitle(fileName)
- FileList.showMask()
+ if (typeof (templateId) !== 'undefined') {
+ documentUrl = getDocumentUrlFromTemplate(templateId, fileName, fileDir)
}
OC.addStyle('richdocuments', 'mobile')
- var $iframe = $('<iframe id="richdocumentsframe" nonce="' + btoa(OC.requestToken) + '" scrolling="no" allowfullscreen src="' + viewer + '" />')
+ 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 = viewer
+ $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').val()) {
+ if (isPublic) {
// force the preview to adjust its height
$('#preview').append($iframe).css({ height: '100%' })
$('body').css({ height: '100%' })
@@ -121,42 +94,43 @@ var odfViewer = {
$('body').css('overflow', 'hidden')
$('#app-content').append($iframe)
$iframe.hide()
- if ($('header').length) {
- var $button = $('<div class="richdocuments-sharing"><a class="icon-shared icon-white"></a></div>')
- $('.header-right').prepend($button)
- $button.on('click', function() {
- if ($('#app-sidebar').is(':visible')) {
- OC.Apps.hideAppSidebar()
- return
- }
- var frameFilename = $('#richdocumentsframe')[0].contentWindow.documentsMain.fileName
- FileList.showDetailsView(frameFilename || fileName, 'shareTabView')
- OC.Apps.showAppSidebar()
- })
- $('.searchbox').hide()
- }
}
$('#app-content #controls').addClass('hidden')
+ FilesAppIntegration.init({
+ fileName,
+ fileId,
+ sendPostMessage: (msgId, values) => PostMessages.sendWOPIPostMessage(FRAME_DOCUMENT, msgId, values)
+ })
+ },
+
+ onReceiveLoading() {
+ odfViewer.receivedLoading = true
+ $('#richdocumentsframe').show()
+ $('html, body').scrollTop(0)
+ $('#content').removeClass('loading')
+ if (typeof FileList !== 'undefined') {
+ FileList.hideMask()
+ }
+ FilesAppIntegration.initAfterReady()
},
onClose: function() {
+ odfViewer.open = false
clearTimeout(odfViewer.loadingTimeout)
if (typeof FileList !== 'undefined') {
FileList.setViewerMode(false)
FileList.reload()
+ // FileList.scrollTo()
}
odfViewer.receivedLoading = false
$('link[href*="richdocuments/css/mobile"]').remove()
$('#app-content #controls').removeClass('hidden')
$('#richdocumentsframe').remove()
- $('.richdocuments-sharing').remove()
- $('#richdocuments-avatars').remove()
- $('#richdocuments-actions').remove()
$('.searchbox').show()
$('body').css('overflow', 'auto')
- if ($('#isPublic').val()) {
+ if (isPublic) {
$('#content').removeClass('full-height')
$('footer').removeClass('hidden')
$('#imgframe').removeClass('hidden')
@@ -165,75 +139,64 @@ var odfViewer = {
}
OC.Util.History.replaceState()
+ location.hash = ''
+
+ FilesAppIntegration.close()
},
registerFilesMenu: function(response) {
- var ooxml = response.doc_format === 'ooxml'
-
- var docExt, spreadsheetExt, presentationExt
- var docMime, spreadsheetMime, presentationMime
- if (ooxml) {
- docExt = 'docx'
- spreadsheetExt = 'xlsx'
- presentationExt = 'pptx'
- docMime = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
- spreadsheetMime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
- presentationMime = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
- } else {
- docExt = 'odt'
- spreadsheetExt = 'ods'
- presentationExt = 'odp'
- docMime = 'application/vnd.oasis.opendocument.text'
- spreadsheetMime = 'application/vnd.oasis.opendocument.spreadsheet'
- presentationMime = 'application/vnd.oasis.opendocument.presentation'
- }
+ Config.update('ooxml', response.doc_format === 'ooxml')
- (function(OCA) {
+ const registerFilesMenu = (OCA) => {
OCA.FilesLOMenu = {
attach: function(newFileMenu) {
var self = this
+ const ooxml = Config.get('ooxml')
+ const document = Types.getFileType('document', ooxml)
+ const spreadsheet = Types.getFileType('spreadsheet', ooxml)
+ const presentation = Types.getFileType('presentation', ooxml)
newFileMenu.addMenuEntry({
- id: 'add-' + docExt,
+ id: 'add-' + document.extension,
displayName: t('richdocuments', 'New Document'),
- templateName: t('richdocuments', 'New Document') + '.' + docExt,
+ 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', docMime, filename)
+ self._openTemplatePicker('document', document.mime, filename)
} else {
- self._createDocument(docMime, filename)
+ self._createDocument(document.mime, filename)
}
}
})
newFileMenu.addMenuEntry({
- id: 'add-' + spreadsheetExt,
+ id: 'add-' + spreadsheet.extension,
displayName: t('richdocuments', 'New Spreadsheet'),
- templateName: t('richdocuments', 'New Spreadsheet') + '.' + spreadsheetExt,
+ 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', spreadsheetMime, filename)
+ self._openTemplatePicker('spreadsheet', spreadsheet.mime, filename)
} else {
- self._createDocument(spreadsheetMime, filename)
+ self._createDocument(spreadsheet.mime, filename)
}
}
})
newFileMenu.addMenuEntry({
- id: 'add-' + presentationExt,
+ id: 'add-' + presentation.extension,
displayName: t('richdocuments', 'New Presentation'),
- templateName: t('richdocuments', 'New Presentation') + '.' + presentationExt,
+ 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', presentationMime, filename)
+ self._openTemplatePicker('presentation', presentation.mime, filename)
} else {
- self._createDocument(presentationMime, filename)
+ self._createDocument(presentation.mime, filename)
}
}
})
@@ -259,11 +222,22 @@ var odfViewer = {
_createDocumentFromTemplate: function(templateId, mimetype, filename) {
OCA.Files.Files.isFileNameValid(filename)
filename = FileList.getUniqueName(filename)
- odfViewer.onEdit(filename, {
- fileId: -1,
- dir: $('#dir').val(),
- templateId: templateId
- })
+ $.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
+ })
+ } else {
+ OC.dialogs.alert(response.data.message, t('core', 'Could not create file'))
+ }
+ }
+ )
},
_openTemplatePicker: function(type, mimetype, filename) {
@@ -334,29 +308,15 @@ var odfViewer = {
dlg.querySelector('.template-container').appendChild(template)
}
}
- })(OCA)
+ }
+ 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) {
- var mimetype
- var ext
- switch (Preload.create.type) {
- case 'document':
- mimetype = docMime
- ext = docExt
- break
- case 'spreadsheet':
- mimetype = spreadsheetMime
- ext = spreadsheetExt
- break
- case 'presentation':
- mimetype = presentationMime
- ext = presentationExt
- break
- }
- OCA.FilesLOMenu._openTemplatePicker(Preload.create.type, mimetype, Preload.create.filename + '.' + ext)
+ const fileType = Types.getFileType(Preload.create.type, Config.get('ooxml'))
+ OCA.FilesLOMenu._openTemplatePicker(Preload.create.type, fileType.mime, Preload.create.filename + '.' + fileType.extension)
}
}
@@ -369,34 +329,90 @@ $(document).ready(function() {
&& typeof OCA.Files.fileActions !== 'undefined'
) {
// check if texteditor app is enabled and loaded...
- if (_.isUndefined(OCA.Files_Texteditor)) {
- // it is not, so we do open text files with this app too.
+ if (typeof OCA.Files_Texteditor === 'undefined' && typeof OCA.Text === 'undefined') {
odfViewer.supportedMimes.push('text/plain')
}
-
- // notice: when changing 'supportedMimes' interactively (e.g. dev console),
- // register() needs to be re-run to re-register the fileActions.
odfViewer.register()
$.get(OC.filePath('richdocuments', 'ajax', 'settings.php')).done(function(settings) {
+ // TODO: move ooxml setting to capabilities so we don't need this request
odfViewer.registerFilesMenu(settings)
})
}
// Open documents if a public page is opened for a supported mimetype
- if ($('#isPublic').val() && odfViewer.supportedMimes.indexOf($('#mimetype').val()) !== -1) {
- odfViewer.onEdit($('#filename').val())
+ if (isPublic && odfViewer.supportedMimes.indexOf($('#mimetype').val()) !== -1) {
+ odfViewer.onEdit(document.getElementById('filename').value)
}
- // listen to message from the viewer for closing/loading actions
- window.addEventListener('message', function(e) {
- if (e.data === 'close') {
+ 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()
- } else if (e.data === 'loading') {
- odfViewer.receivedLoading = true
- $('#content').removeClass('loading')
- FileList.hideMask()
+ break
+ case 'Get_Views_Resp':
+ case 'Views_List':
+ FilesAppIntegration.setViews(args)
+ break
+ case 'UI_FileVersions':
+ case 'rev-history':
+ FilesAppIntegration.showRevHistory()
+ break
}
- }, false)
+
+ // 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
})