/* * @copyright Copyright (c) 2019 Julius Härtl * * @author Julius Härtl * * @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 . * */ import Preload from '../services/preload' import { splitPath } from '../helpers' import Types from '../helpers/types' import Config from '../services/config' import NewFileMenu from './NewFileMenu' const isPublic = document.getElementById('isPublic') && document.getElementById('isPublic').value === '1' export default { fileModel: null, fileList: undefined, /* Views: people currently editing the file */ views: {}, followingEditor: false, following: null, handlers: {}, startLoading() { if (this.getFileList()) { this.getFileList().setViewerMode && this.getFileList().setViewerMode(true) this.getFileList().showMask && this.getFileList().showMask() } }, init({ fileName, fileId, sendPostMessage, fileList, fileModel }) { this.fileName = fileName this.fileId = fileId this.fileList = fileList this.fileModel = fileModel this.sendPostMessage = sendPostMessage if (this.fileModel && this.fileModel.on) { this.fileModel.on('change', () => { this._addHeaderFileActions() }) } }, registerHandler(event, callback) { this.handlers[event] = callback }, initAfterReady() { if (this.handlers.initAfterReady && this.handlers.initAfterReady(this)) { return } if (this.getFileList()) { this.getFileModel() this.getFileList().hideMask && this.getFileList().hideMask() this.getFileList().setPageTitle && this.getFileList().setPageTitle(this.fileName) } const headerRight = document.querySelector('#header .header-right') if (!document.getElementById('richdocuments-header')) { const richdocumentsHeader = document.createElement('div') richdocumentsHeader.id = 'richdocuments-header' headerRight.insertBefore(richdocumentsHeader, headerRight.firstChild) this._addAvatarList() if (!isPublic) { this._addHeaderShareButton() this._addHeaderFileActions() this.addVersionSidebarEvents() } } }, close() { if (this.handlers.close && this.handlers.close(this)) { return } if (this.getFileList()) { this.getFileList().setViewerMode && this.getFileList().setViewerMode(false) this.getFileList().reload && this.getFileList().reload() } this.fileModel = null if (!isPublic) { this.removeVersionSidebarEvents() } $('#richdocuments-header').remove() }, saveAs() { if (this.handlers.saveAs && this.handlers.saveAs(this)) { return } if (this.getFileList()) { this.getFileList() .reload() } }, share() { if (this.handlers.share && this.handlers.share(this)) { return } if (isPublic || !this.getFileList()) { console.error('[FilesAppIntegration] Sharing is not supported') return } this.getFileList().showDetailsView && this.getFileList().showDetailsView(this.fileName, 'shareTabView') OC.Apps.showAppSidebar() }, rename(newName) { this.fileName = newName if (this.handlers.rename && this.handlers.rename(this)) { return } if (this.getFileList()) { this.getFileList().reload && this.getFileList().reload() OC.Apps.hideAppSidebar() } }, insertGraphic(insertFile) { if (isPublic) { console.error('[FilesAppIntegration] insertGraphic is not supported') } const insertFileFromPath = (path) => { const filename = path.substring(path.lastIndexOf('/') + 1) $.ajax({ type: 'POST', url: OC.generateUrl('apps/richdocuments/assets'), data: { path: path } }).done(function(resp) { insertFile(filename, resp.url) }) } if (this.handlers.insertGraphic && this.handlers.insertGraphic(this, { insertFileFromPath: insertFileFromPath })) { return } OC.dialogs.filepicker(t('richdocuments', 'Insert from {name}', { name: OC.theme.name }), function(path, type) { if (type === OC.dialogs.FILEPICKER_TYPE_CHOOSE) { insertFileFromPath(path) } }, false, ['image/png', 'image/gif', 'image/jpeg', 'image/svg'], true, OC.dialogs.FILEPICKER_TYPE_CHOOSE) }, getFileList() { if (this.fileList) { return this.fileList } if (OCA.Files && OCA.Files.App) { return OCA.Files.App.fileList } if (OCA.Sharing && OCA.Sharing.PublicApp) { return OCA.Sharing.PublicApp.fileList } return null }, getFileModel() { if (this.fileModel) { return this.fileModel } if (!this.getFileList()) { return null } this.getFileList()._updateDetailsView && this.getFileList()._updateDetailsView(this.fileName, false) this.fileModel = this.getFileList().getModelForFile(this.fileName) if (this.fileModel && this.fileModel.on) { 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 = $('
') avatarList.on('click', function(e) { e.stopPropagation() $('#editors-menu').toggle() }) $('#richdocuments-header').append(avatarList) }, _addHeaderShareButton() { if ($('header').length) { var $button = $('
') $('#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 = $('
    ') var actions = actionsContainer.find('#richdocuments-actions-menu').empty() var getContext = () => ({ '$file': this.getFileList().$el ? this.getFileList().$el.find('[data-id=' + this.fileId + ']').first() : null, 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 = $('
  • ').click((event) => { $favorite.find('a').removeClass('icon-starred').removeClass('icon-star-dark').addClass('icon-loading-small') if (this.handlers.actionFavorite && this.handlers.actionFavorite(this)) { return } this.getFileList().fileActions.triggerAction('Favorite', this.getFileModel(), this.getFileList()) this.getFileModel().trigger('change', this.getFileModel()) }) if (isFavorite(this.getFileModel())) { $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 = $('
  • ').click(() => { if (this.handlers.actionDetails && this.handlers.actionDetails(this)) { return } this.getFileList().fileActions.actions.all.Details.action(this.fileName, getContext()) OC.hideMenus() }) $info.find('a').text(t('files', 'Details')) var $download = $('
  • Download
  • ').click(() => { if (this.handlers.actionDownload && this.handlers.actionDownload(this)) { return } this.getFileList().fileActions.actions.all.Download.action(this.fileName, getContext()) 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 = $('
  • ') entry.append(this._avatarForView(view)) var label = $('
    ') 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) const isFileOwner = !isPublic && this.getFileModel() && typeof this.getFileModel().get('shareOwner') === 'undefined' const canEdit = this.getFileModel() && !!(this.getFileModel().get('permissions') & OC.PERMISSION_UPDATE) if (isFileOwner && canEdit && !view.IsCurrentView) { var removeButton = $('
    ') 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 = $('
    ') var avatar = avatarContainer.find('.avatar') avatar.css({ 'border-color': '#' + ('000000' + Number(view.Color).toString(16)).substr(-6), '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 = $('') 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++ < 4) { avatardiv.append(this._avatarForView(view)) } } var followCurrentEditor = $('
  • ') 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)) $(document.querySelector('#content')).on('click.revisionsTab', '#app-sidebar [data-tabid=versionsTabView]', this.addCurrentVersion.bind(this)) }, removeVersionSidebarEvents() { $(document.querySelector('#content')).off('click.revisions') $(document.querySelector('#content')).off('click.revisions') $(document.querySelector('#content')).off('mousedown.revisions') $(document.querySelector('#content')).off('click.revisionsTab') }, addCurrentVersion() { $('#lastSavedVersion').remove() $('#currentVersion').remove() if (this.getFileModel()) { const preview = OC.MimeType.getIconUrl(this.getFileModel().get('mimetype')) const mtime = this.getFileModel().get('mtime') $('#versionsTabView').prepend('') $('#versionsTabView').prepend('') $('.live-relative-timestamp').each(function() { $(this).text(OC.Util.relativeModifiedDate(parseInt($(this).attr('data-timestamp'), 10))) }) } }, showRevHistory() { if (this.handlers.showRevHistory && this.handlers.showRevHistory(this)) { return } if (this.getFileList()) { this.getFileList() .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) { e.preventDefault() e.stopPropagation() this.sendPostMessage('Host_VersionRestore', { Status: 'Pre_Restore' }) const version = e.currentTarget.parentElement.parentElement.dataset.revision this._restoreVersionCallback = () => { this._restoreDAV(version) this._restoreVersionCallback = null } return false }, restoreVersionExecute() { if (this._restoreVersionCallback !== null) { this._restoreVersionCallback() } }, restoreVersionAbort() { this._restoreVersionCallback = null }, _restoreSuccess: function(response) { if (response.status === 'error') { OC.Notification.showTemporary(t('richdocuments', 'Failed to revert the document to older version')) } // Reload the document frame to get the new file // TODO: ideally we should have a post messsage that can be sent to collabora to just reload the file once the restore is finished document.getElementById('richdocumentsframe').src = document.getElementById('richdocumentsframe').src 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/' + OC.getCurrentUser().uid + '/versions/' + this.fileId + '/' + version $.ajax({ type: 'MOVE', url: restoreUrl, headers: { Destination: OC.linkToRemote('dav') + '/versions/' + OC.getCurrentUser().uid + '/restore/target' }, success: this._restoreSuccess.bind(this), error: this._restoreError.bind(this) }) }, /** * Called when a new file creation has been triggered from collabora * * 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 with * FilesAppIntegration.preloadCreate */ createNewFile: function(type) { if (this.handlers.createNewFile && this.handlers.createNewFile(this, { type: type })) { return } 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')) }) }, /** * Automaically open a document on page load */ preloadOpen: function() { if (this.handlers.preloadOpen && this.handlers.preloadOpen(this)) { return } const fileId = Preload.open.id const path = Preload.open.filename setTimeout(function() { window.FileList.$fileList.one('updated', function() { const [, file] = splitPath(path) const fileModel = FileList.getModelForFile(file) OCA.RichDocuments.open({ path, fileId, fileModel: fileModel, fileList: window.FileList }) }) }, 250) }, /** * Automaically open a template picker on page load */ preloadCreate: function() { if (this.handlers.preloadCreate && this.handlers.preloadCreate(this)) { return } setTimeout(function() { window.FileList.$fileList.one('updated', function() { const fileType = Types.getFileType(Preload.create.type, Config.get('ooxml')) NewFileMenu._openTemplatePicker(Preload.create.type, fileType.mime, Preload.create.filename + '.' + fileType.extension) }) }, 250) } }