diff --git a/apps/comments/src/comments-tab.js b/apps/comments/src/comments-tab.js index 2c81843291c..78bfb610af7 100644 --- a/apps/comments/src/comments-tab.js +++ b/apps/comments/src/comments-tab.js @@ -20,12 +20,15 @@ * */ +// eslint-disable-next-line node/no-missing-import, import/no-unresolved +import MessageReplyText from '@mdi/svg/svg/message-reply-text.svg?raw' + // Init Comments tab component let TabInstance = null const commentTab = new OCA.Files.Sidebar.Tab({ id: 'comments', name: t('comments', 'Comments'), - icon: 'icon-comment', + iconSvg: MessageReplyText, async mount(el, fileInfo, context) { if (TabInstance) { diff --git a/apps/files/src/components/SidebarTab.vue b/apps/files/src/components/SidebarTab.vue index c0f5a7d4416..ac3cfba7d02 100644 --- a/apps/files/src/components/SidebarTab.vue +++ b/apps/files/src/components/SidebarTab.vue @@ -26,6 +26,9 @@ :name="name" :icon="icon" @bottomReached="onScrollBottomReached"> + @@ -63,7 +66,7 @@ export default { }, icon: { type: String, - required: true, + required: false, }, /** diff --git a/apps/files/src/models/Tab.js b/apps/files/src/models/Tab.js index 4c41ec5a3b1..63d1ad97ff6 100644 --- a/apps/files/src/models/Tab.js +++ b/apps/files/src/models/Tab.js @@ -19,12 +19,14 @@ * along with this program. If not, see . * */ +import { sanitizeSVG } from '@skjnldsv/sanitize-svg' export default class Tab { _id _name _icon + _iconSvgSanitized _mount _update _destroy @@ -37,19 +39,20 @@ export default class Tab { * @param {object} options destructuring object * @param {string} options.id the unique id of this tab * @param {string} options.name the translated tab name - * @param {string} options.icon the vue component + * @param {?string} options.icon the icon css class + * @param {?string} options.iconSvg the icon in svg format * @param {Function} options.mount function to mount the tab * @param {Function} options.update function to update the tab * @param {Function} options.destroy function to destroy the tab * @param {Function} [options.enabled] define conditions whether this tab is active. Must returns a boolean * @param {Function} [options.scrollBottomReached] executed when the tab is scrolled to the bottom */ - constructor({ id, name, icon, mount, update, destroy, enabled, scrollBottomReached } = {}) { + constructor({ id, name, icon, iconSvg, mount, update, destroy, enabled, scrollBottomReached } = {}) { if (enabled === undefined) { enabled = () => true } if (scrollBottomReached === undefined) { - scrollBottomReached = () => {} + scrollBottomReached = () => { } } // Sanity checks @@ -59,8 +62,8 @@ export default class Tab { if (typeof name !== 'string' || name.trim() === '') { throw new Error('The name argument is not a valid string') } - if (typeof icon !== 'string' || icon.trim() === '') { - throw new Error('The icon argument is not a valid string') + if ((typeof icon !== 'string' || icon.trim() === '') && typeof iconSvg !== 'string') { + throw new Error('Missing valid string for icon or iconSvg argument') } if (typeof mount !== 'function') { throw new Error('The mount argument should be a function') @@ -87,6 +90,13 @@ export default class Tab { this._enabled = enabled this._scrollBottomReached = scrollBottomReached + if (typeof iconSvg === 'string') { + sanitizeSVG(iconSvg) + .then(sanitizedSvg => { + this._iconSvgSanitized = sanitizedSvg + }) + } + } get id() { @@ -101,6 +111,10 @@ export default class Tab { return this._icon } + get iconSvg() { + return this._iconSvgSanitized + } + get mount() { return this._mount } diff --git a/apps/files/src/views/Sidebar.vue b/apps/files/src/views/Sidebar.vue index d4bf8cfde40..4c29da59708 100644 --- a/apps/files/src/views/Sidebar.vue +++ b/apps/files/src/views/Sidebar.vue @@ -72,7 +72,12 @@ :on-update="tab.update" :on-destroy="tab.destroy" :on-scroll-bottom-reached="tab.scrollBottomReached" - :file-info="fileInfo" /> + :file-info="fileInfo"> + + @@ -508,5 +513,13 @@ export default { top: 0 !important; height: 100% !important; } + + .svg-icon { + ::v-deep svg { + width: 20px; + height: 20px; + fill: currentColor; + } + } } diff --git a/apps/files_sharing/src/files_sharing_tab.js b/apps/files_sharing/src/files_sharing_tab.js index c06ec051c4e..85bbd869932 100644 --- a/apps/files_sharing/src/files_sharing_tab.js +++ b/apps/files_sharing/src/files_sharing_tab.js @@ -25,11 +25,14 @@ import Vue from 'vue' import VueClipboard from 'vue-clipboard2' import { translate as t, translatePlural as n } from '@nextcloud/l10n' -import SharingTab from './views/SharingTab' -import ShareSearch from './services/ShareSearch' -import ExternalLinkActions from './services/ExternalLinkActions' -import ExternalShareActions from './services/ExternalShareActions' -import TabSections from './services/TabSections' +import SharingTab from './views/SharingTab.vue' +import ShareSearch from './services/ShareSearch.js' +import ExternalLinkActions from './services/ExternalLinkActions.js' +import ExternalShareActions from './services/ExternalShareActions.js' +import TabSections from './services/TabSections.js' + +// eslint-disable-next-line node/no-missing-import, import/no-unresolved +import ShareVariant from '@mdi/svg/svg/share-variant.svg?raw' // Init Sharing Tab Service if (!window.OCA.Sharing) { @@ -53,7 +56,7 @@ window.addEventListener('DOMContentLoaded', function() { OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({ id: 'sharing', name: t('files_sharing', 'Sharing'), - icon: 'icon-share', + iconSvg: ShareVariant, async mount(el, fileInfo, context) { if (TabInstance) { diff --git a/apps/files_versions/src/css/versions.css b/apps/files_versions/src/css/versions.css deleted file mode 100644 index 8a32b143f03..00000000000 --- a/apps/files_versions/src/css/versions.css +++ /dev/null @@ -1,74 +0,0 @@ -.versionsTabView .clear-float { - clear: both; -} - -.versionsTabView li { - width: 100%; - cursor: default; - height: 56px; - float: left; - border-bottom: 1px solid rgba(100,100,100,.1); -} -.versionsTabView li:last-child { - border-bottom: none; -} - -.versionsTabView a, -.versionsTabView div > span { - vertical-align: middle; - opacity: .5; -} - -.versionsTabView li a{ - padding: 15px 10px 11px; -} - -.versionsTabView a:hover, -.versionsTabView a:focus { - opacity: 1; -} - -.versionsTabView .preview-container { - display: inline-block; - vertical-align: top; -} - -.versionsTabView .version-container img, .revertVersion img { - filter: var(--background-invert-if-dark); -} - -.versionsTabView img { - cursor: pointer; - padding-right: 4px; -} - -.versionsTabView img.preview { - cursor: default; -} - -.versionsTabView .version-container { - display: inline-block; -} - -.versionsTabView .versiondate { - min-width: 100px; - vertical-align: super; -} - -.versionsTabView .version-details { - text-align: left; -} - -.versionsTabView .version-details > span { - padding: 0 10px; -} - -.versionsTabView .revertVersion { - cursor: pointer; - float: right; - margin-right: -10px; -} - -.versionsTabView .emptycontent { - margin-top: 50px !important; -} diff --git a/apps/files_versions/src/files_versions_tab.js b/apps/files_versions/src/files_versions_tab.js new file mode 100644 index 00000000000..8482247e672 --- /dev/null +++ b/apps/files_versions/src/files_versions_tab.js @@ -0,0 +1,70 @@ +/** + * @copyright 2022 Carl Schwan + * @license AGPL-3.0-or-later + * + * 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 Vue from 'vue' +import { translate as t, translatePlural as n } from '@nextcloud/l10n' + +import VersionTab from './views/VersionTab.vue' +import VTooltip from 'v-tooltip' +// eslint-disable-next-line node/no-missing-import, import/no-unresolved +import BackupRestore from '@mdi/svg/svg/backup-restore.svg?raw' + +Vue.prototype.t = t +Vue.prototype.n = n + +Vue.use(VTooltip) + +// Init Sharing tab component +const View = Vue.extend(VersionTab) +let TabInstance = null + +window.addEventListener('DOMContentLoaded', function() { + if (OCA.Files?.Sidebar === undefined) { + return + } + + OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({ + id: 'version_vue', + name: t('files_versions', 'Version'), + iconSvg: BackupRestore, + + async mount(el, fileInfo, context) { + if (TabInstance) { + TabInstance.$destroy() + } + TabInstance = new View({ + // Better integration with vue parent component + parent: context, + }) + // Only mount after we have all the info we need + await TabInstance.update(fileInfo) + TabInstance.$mount(el) + }, + update(fileInfo) { + TabInstance.update(fileInfo) + }, + destroy() { + TabInstance.$destroy() + TabInstance = null + }, + enabled(fileInfo) { + return !(fileInfo?.isDirectory() ?? true) + }, + })) +}) diff --git a/apps/files_versions/src/templates/item.handlebars b/apps/files_versions/src/templates/item.handlebars deleted file mode 100644 index 656cab22866..00000000000 --- a/apps/files_versions/src/templates/item.handlebars +++ /dev/null @@ -1,22 +0,0 @@ -
  • -
    -
    - -
    -
    - - {{#hasDetails}} -
    - {{humanReadableSize}} -
    - {{/hasDetails}} -
    - {{#canRevert}} - - {{/canRevert}} -
    -
  • diff --git a/apps/files_versions/src/templates/template.handlebars b/apps/files_versions/src/templates/template.handlebars deleted file mode 100644 index f01a6f41626..00000000000 --- a/apps/files_versions/src/templates/template.handlebars +++ /dev/null @@ -1,10 +0,0 @@ -
      -
      - - - diff --git a/apps/files_versions/src/filesplugin.js b/apps/files_versions/src/utils/davClient.js similarity index 53% rename from apps/files_versions/src/filesplugin.js rename to apps/files_versions/src/utils/davClient.js index 90dac2024c1..e4bfeb10411 100644 --- a/apps/files_versions/src/filesplugin.js +++ b/apps/files_versions/src/utils/davClient.js @@ -1,8 +1,7 @@ /** - * Copyright (c) 2015 + * @copyright 2022 Louis Chemineau * - * @author John Molakvoæ - * @author Vincent Petry + * @author Louis Chemineau * * @license AGPL-3.0-or-later * @@ -18,29 +17,18 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * */ -(function() { - OCA.Versions = OCA.Versions || {} +import { createClient, getPatcher } from 'webdav' +import { generateRemoteUrl } from '@nextcloud/router' +import axios from '@nextcloud/axios' - /** - * @namespace - */ - OCA.Versions.Util = { - /** - * Initialize the versions plugin. - * - * @param {OCA.Files.FileList} fileList file list to be extended - */ - attach(fileList) { - if (fileList.id === 'trashbin' || fileList.id === 'files.public') { - return - } +const rootPath = 'dav' - fileList.registerTabView(new OCA.Versions.VersionsTabView('versionsTabView', { order: -10 })) - }, - } -})() +// force our axios +const patcher = getPatcher() +patcher.patch('request', axios) -OC.Plugins.register('OCA.Files.FileList', OCA.Versions.Util) +// init webdav client on default dav endpoint +const remote = generateRemoteUrl(rootPath) +export default createClient(remote) diff --git a/apps/files_versions/src/utils/davRequest.js b/apps/files_versions/src/utils/davRequest.js new file mode 100644 index 00000000000..b77cd150643 --- /dev/null +++ b/apps/files_versions/src/utils/davRequest.js @@ -0,0 +1,33 @@ +/** + * @copyright Copyright (c) 2019 Louis Chmn + * + * @author Louis Chmn + * + * @license AGPL-3.0-or-later + * + * 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 . + * + */ + +export default ` + + + + + + +` diff --git a/apps/files_versions/src/files_versions.js b/apps/files_versions/src/utils/logger.js similarity index 70% rename from apps/files_versions/src/files_versions.js rename to apps/files_versions/src/utils/logger.js index 9f4ccc2c1bf..4f0356764d9 100644 --- a/apps/files_versions/src/files_versions.js +++ b/apps/files_versions/src/utils/logger.js @@ -1,7 +1,7 @@ /** - * @copyright Copyright (c) 2016 Roeland Jago Douma + * @copyright 2022 Louis Chemineau * - * @author Roeland Jago Douma + * @author Louis Chemineau * * @license AGPL-3.0-or-later * @@ -17,13 +17,11 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * */ -import './versionmodel' -import './versioncollection' -import './versionstabview' -import './filesplugin' -import './css/versions.css' +import { getLoggerBuilder } from '@nextcloud/logger' -window.OCA.Versions = OCA.Versions +export default getLoggerBuilder() + .setApp('files_version') + .detectUser() + .build() diff --git a/apps/files_versions/src/utils/versions.js b/apps/files_versions/src/utils/versions.js new file mode 100644 index 00000000000..8fe258119f7 --- /dev/null +++ b/apps/files_versions/src/utils/versions.js @@ -0,0 +1,124 @@ +/** + * @copyright 2022 Louis Chemineau + * + * @author Louis Chemineau + * + * @license AGPL-3.0-or-later + * + * 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 { getCurrentUser } from '@nextcloud/auth' +import client from '../utils/davClient.js' +import davRequest from '../utils/davRequest.js' +import logger from '../utils/logger.js' +import { basename, joinPaths } from '@nextcloud/paths' +import { generateUrl } from '@nextcloud/router' +import { translate } from '@nextcloud/l10n' +import moment from '@nextcloud/moment' + +/** + * @typedef {object} Version + * @property {string} title - 'Current version' or '' + * @property {string} fileName - File name relative to the version DAV endpoint + * @property {string} mimeType - Empty for the current version, else the actual mime type of the version + * @property {string} size - Human readable size + * @property {string} type - 'file' + * @property {number} mtime - Version creation date as a timestamp + * @property {string} preview - Preview URL of the version + * @property {string} url - Download URL of the version + * @property {string|null} fileVersion - The version id, null for the current version + * @property {boolean} isCurrent - Whether this is the current version of the file + */ + +/** + * @param fileInfo + * @return {Promise} + */ +export async function fetchVersions(fileInfo) { + const path = `/versions/${getCurrentUser()?.uid}/versions/${fileInfo.id}` + + try { + /** @type {import('webdav').FileStat[]} */ + const response = await client.getDirectoryContents(path, { + data: davRequest, + }) + return response.map(version => formatVersion(version, fileInfo)) + } catch (exception) { + logger.error('Could not fetch version', { exception }) + throw exception + } +} + +/** + * Restore the given version + * + * @param {Version} version + * @param {object} fileInfo + */ +export async function restoreVersion(version, fileInfo) { + try { + logger.debug('Restoring version', { url: version.url }) + await client.moveFile( + `/versions/${getCurrentUser()?.uid}/versions/${fileInfo.id}/${version.fileVersion}`, + `/versions/${getCurrentUser()?.uid}/restore/target` + ) + } catch (exception) { + logger.error('Could not restore version', { exception }) + throw exception + } +} + +/** + * Format version + * + * @param {object} version - raw version received from the versions DAV endpoint + * @param {object} fileInfo - file properties received from the files DAV endpoint + * @return {Version} + */ +function formatVersion(version, fileInfo) { + const isCurrent = version.mime === '' + const fileVersion = isCurrent ? null : basename(version.filename) + + let url = null + let preview = null + + if (isCurrent) { + // https://nextcloud_server2.test/remote.php/webdav/welcome.txt?downloadStartSecret=hl5awd7tbzg + url = joinPaths('/remote.php/webdav', fileInfo.path, fileInfo.name) + preview = generateUrl('/core/preview?fileId={fileId}&c={fileEtag}&x=250&y=250&forceIcon=0&a=0', { + fileId: fileInfo.id, + fileEtag: fileInfo.etag, + }) + } else { + url = joinPaths('/remote.php/dav', version.filename) + preview = generateUrl('/apps/files_versions/preview?file={file}&version={fileVersion}', { + file: joinPaths(fileInfo.path, fileInfo.name), + fileVersion, + }) + } + + return { + title: isCurrent ? translate('files_versions', 'Current version') : '', + fileName: version.filename, + mimeType: version.mime, + size: isCurrent ? fileInfo.size : version.size, + type: version.type, + mtime: moment(isCurrent ? fileInfo.mtime : version.lastmod).unix(), + preview, + url, + fileVersion, + isCurrent, + } +} diff --git a/apps/files_versions/src/versioncollection.js b/apps/files_versions/src/versioncollection.js deleted file mode 100644 index 592b4f8cda2..00000000000 --- a/apps/files_versions/src/versioncollection.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2015 - * - * @author John Molakvoæ - * @author Robin Appelman - * @author Vincent Petry - * - * @license AGPL-3.0-or-later - * - * 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 . - * - */ - -(function() { - /** - * @memberof OCA.Versions - */ - const VersionCollection = OC.Backbone.Collection.extend({ - model: OCA.Versions.VersionModel, - sync: OC.Backbone.davSync, - - /** - * @member OCA.Files.FileInfoModel - */ - _fileInfo: null, - - _currentUser: null, - - _client: null, - - setFileInfo(fileInfo) { - this._fileInfo = fileInfo - }, - - getFileInfo() { - return this._fileInfo - }, - - setCurrentUser(user) { - this._currentUser = user - }, - - getCurrentUser() { - return this._currentUser || OC.getCurrentUser().uid - }, - - setClient(client) { - this._client = client - }, - - getClient() { - return this._client || new OC.Files.Client({ - host: OC.getHost(), - root: OC.linkToRemoteBase('dav') + '/versions/' + this.getCurrentUser(), - useHTTPS: OC.getProtocol() === 'https', - }) - }, - - url() { - return OC.linkToRemoteBase('dav') + '/versions/' + this.getCurrentUser() + '/versions/' + this._fileInfo.get('id') - }, - - parse(result) { - const fullPath = this._fileInfo.getFullPath() - const fileId = this._fileInfo.get('id') - const name = this._fileInfo.get('name') - const user = this.getCurrentUser() - const client = this.getClient() - return _.map(result, function(version) { - version.fullPath = fullPath - version.fileId = fileId - version.name = name - version.timestamp = parseInt(moment(new Date(version.timestamp)).format('X'), 10) - version.id = OC.basename(version.href) - version.size = parseInt(version.size, 10) - version.user = user - version.client = client - return version - }) - }, - }) - - OCA.Versions = OCA.Versions || {} - - OCA.Versions.VersionCollection = VersionCollection -})() diff --git a/apps/files_versions/src/versionmodel.js b/apps/files_versions/src/versionmodel.js deleted file mode 100644 index 01914f702e2..00000000000 --- a/apps/files_versions/src/versionmodel.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2015 - * - * @author John Molakvoæ - * @author Robin Appelman - * @author Vincent Petry - * - * @license AGPL-3.0-or-later - * - * 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 . - * - */ - -(function() { - /** - * @memberof OCA.Versions - */ - const VersionModel = OC.Backbone.Model.extend({ - sync: OC.Backbone.davSync, - - davProperties: { - size: '{DAV:}getcontentlength', - mimetype: '{DAV:}getcontenttype', - timestamp: '{DAV:}getlastmodified', - }, - - /** - * Restores the original file to this revision - * - * @param {object} [options] options - * @return {Promise} - */ - revert(options) { - options = options ? _.clone(options) : {} - const model = this - - const client = this.get('client') - - return client.move('/versions/' + this.get('fileId') + '/' + this.get('id'), '/restore/target', true) - .done(function() { - if (options.success) { - options.success.call(options.context, model, {}, options) - } - model.trigger('revert', model, options) - }) - .fail(function() { - if (options.error) { - options.error.call(options.context, model, {}, options) - } - model.trigger('error', model, {}, options) - }) - }, - - getFullPath() { - return this.get('fullPath') - }, - - getPreviewUrl() { - const url = OC.generateUrl('/apps/files_versions/preview') - const params = { - file: this.get('fullPath'), - version: this.get('id'), - } - return url + '?' + OC.buildQueryString(params) - }, - - getDownloadUrl() { - return OC.linkToRemoteBase('dav') + '/versions/' + this.get('user') + '/versions/' + this.get('fileId') + '/' + this.get('id') - }, - }) - - OCA.Versions = OCA.Versions || {} - - OCA.Versions.VersionModel = VersionModel -})() diff --git a/apps/files_versions/src/versionstabview.js b/apps/files_versions/src/versionstabview.js deleted file mode 100644 index 9f76755a582..00000000000 --- a/apps/files_versions/src/versionstabview.js +++ /dev/null @@ -1,231 +0,0 @@ -/** - * Copyright (c) 2015 - * - * @author Jan-Christoph Borchardt - * @author John Molakvoæ - * @author Julius Härtl - * @author Michael Jobst - * @author noveens - * @author Robin Appelman - * @author Vincent Petry - * - * @license AGPL-3.0-or-later - * - * 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 ItemTemplate from './templates/item.handlebars' -import Template from './templates/template.handlebars'; - -(function() { - if (!OCA.Files.DetailTabView) { - // Only register the versions tab within the files app - return - } - /** - * @memberof OCA.Versions - */ - const VersionsTabView = OCA.Files.DetailTabView.extend(/** @lends OCA.Versions.VersionsTabView.prototype */{ - id: 'versionsTabView', - className: 'tab versionsTabView', - - _template: null, - - $versionsContainer: null, - - events: { - 'click .revertVersion': '_onClickRevertVersion', - }, - - initialize() { - OCA.Files.DetailTabView.prototype.initialize.apply(this, arguments) - this.collection = new OCA.Versions.VersionCollection() - this.collection.on('request', this._onRequest, this) - this.collection.on('sync', this._onEndRequest, this) - this.collection.on('update', this._onUpdate, this) - this.collection.on('error', this._onError, this) - this.collection.on('add', this._onAddModel, this) - }, - - getLabel() { - return t('files_versions', 'Versions') - }, - - getIcon() { - return 'icon-history' - }, - - nextPage() { - if (this._loading) { - return - } - - if (this.collection.getFileInfo() && this.collection.getFileInfo().isDirectory()) { - return - } - this.collection.fetch() - }, - - _onClickRevertVersion(ev) { - const self = this - let $target = $(ev.target) - const fileInfoModel = this.collection.getFileInfo() - if (!$target.is('li')) { - $target = $target.closest('li') - } - - ev.preventDefault() - const revision = $target.attr('data-revision') - - const versionModel = this.collection.get(revision) - versionModel.revert({ - success() { - // reset and re-fetch the updated collection - self.$versionsContainer.empty() - self.collection.setFileInfo(fileInfoModel) - self.collection.reset([], { silent: true }) - self.collection.fetch() - - self.$el.find('.versions').removeClass('hidden') - - // update original model - fileInfoModel.trigger('busy', fileInfoModel, false) - fileInfoModel.set({ - size: versionModel.get('size'), - mtime: versionModel.get('timestamp') * 1000, - // temp dummy, until we can do a PROPFIND - etag: versionModel.get('id') + versionModel.get('timestamp'), - }) - }, - - error() { - fileInfoModel.trigger('busy', fileInfoModel, false) - self.$el.find('.versions').removeClass('hidden') - self._toggleLoading(false) - OC.Notification.show(t('files_version', 'Failed to revert {file} to revision {timestamp}.', - { - file: versionModel.getFullPath(), - timestamp: OC.Util.formatDate(versionModel.get('timestamp') * 1000), - }), - { - type: 'error', - } - ) - }, - }) - - // spinner - this._toggleLoading(true) - fileInfoModel.trigger('busy', fileInfoModel, true) - }, - - _toggleLoading(state) { - this._loading = state - this.$el.find('.loading').toggleClass('hidden', !state) - }, - - _onRequest() { - this._toggleLoading(true) - }, - - _onEndRequest() { - this._toggleLoading(false) - this.$el.find('.empty').toggleClass('hidden', !!this.collection.length) - }, - - _onAddModel(model) { - const $el = $(this.itemTemplate(this._formatItem(model))) - this.$versionsContainer.append($el) - $el.find('.has-tooltip').tooltip() - }, - - template(data) { - return Template(data) - }, - - itemTemplate(data) { - return ItemTemplate(data) - }, - - setFileInfo(fileInfo) { - if (fileInfo) { - this.render() - this.collection.setFileInfo(fileInfo) - this.collection.reset([], { silent: true }) - this.nextPage() - } else { - this.render() - this.collection.reset() - } - }, - - _formatItem(version) { - const timestamp = version.get('timestamp') * 1000 - const size = version.has('size') ? version.get('size') : 0 - const preview = OC.MimeType.getIconUrl(version.get('mimetype')) - const img = new Image() - img.onload = function() { - $('li[data-revision=' + version.get('id') + '] .preview').attr('src', version.getPreviewUrl()) - } - img.src = version.getPreviewUrl() - - return _.extend({ - versionId: version.get('id'), - formattedTimestamp: OC.Util.formatDate(timestamp), - relativeTimestamp: OC.Util.relativeModifiedDate(timestamp), - millisecondsTimestamp: timestamp, - humanReadableSize: OC.Util.humanFileSize(size, true), - altSize: n('files', '%n byte', '%n bytes', size), - hasDetails: version.has('size'), - downloadUrl: version.getDownloadUrl(), - downloadIconUrl: OC.imagePath('core', 'actions/download'), - downloadName: version.get('name'), - revertIconUrl: OC.imagePath('core', 'actions/history'), - previewUrl: preview, - revertLabel: t('files_versions', 'Restore'), - canRevert: (this.collection.getFileInfo().get('permissions') & OC.PERMISSION_UPDATE) !== 0, - }, version.attributes) - }, - - /** - * Renders this details view - */ - render() { - this.$el.html(this.template({ - emptyResultLabel: t('files_versions', 'No other versions available'), - })) - this.$el.find('.has-tooltip').tooltip() - this.$versionsContainer = this.$el.find('ul.versions') - this.delegateEvents() - }, - - /** - * Returns true for files, false for folders. - * - * @param {FileInfo} fileInfo fileInfo - * @return {boolean} true for files, false for folders - */ - canDisplay(fileInfo) { - if (!fileInfo) { - return false - } - return !fileInfo.isDirectory() - }, - }) - - OCA.Versions = OCA.Versions || {} - - OCA.Versions.VersionsTabView = VersionsTabView -})() diff --git a/apps/files_versions/src/views/VersionTab.vue b/apps/files_versions/src/views/VersionTab.vue new file mode 100644 index 00000000000..8159415dfc7 --- /dev/null +++ b/apps/files_versions/src/views/VersionTab.vue @@ -0,0 +1,182 @@ + + + + + + diff --git a/apps/files_versions/tests/js/versioncollectionSpec.js b/apps/files_versions/tests/js/versioncollectionSpec.js deleted file mode 100644 index 672515146b7..00000000000 --- a/apps/files_versions/tests/js/versioncollectionSpec.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2015 - * - * @author Robin Appelman - * @author Vincent Petry - * - * @license AGPL-3.0-or-later - * - * 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 . - * - */ - -describe('OCA.Versions.VersionCollection', function() { - var VersionCollection = OCA.Versions.VersionCollection; - var collection, fileInfoModel; - - beforeEach(function() { - fileInfoModel = new OCA.Files.FileInfoModel({ - path: '/subdir', - name: 'some file.txt', - id: 10, - }); - collection = new VersionCollection(); - collection.setFileInfo(fileInfoModel); - collection.setCurrentUser('user'); - }); - it('fetches the versions', function() { - collection.fetch(); - - expect(fakeServer.requests.length).toEqual(1); - expect(fakeServer.requests[0].url).toEqual( - OC.linkToRemoteBase('dav') + '/versions/user/versions/10' - ); - fakeServer.requests[0].respond(200); - }); -}); - diff --git a/apps/files_versions/tests/js/versionmodelSpec.js b/apps/files_versions/tests/js/versionmodelSpec.js deleted file mode 100644 index d4b7bec43ae..00000000000 --- a/apps/files_versions/tests/js/versionmodelSpec.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright (c) 2015 - * - * @author Daniel Calviño Sánchez - * @author Robin Appelman - * @author Vincent Petry - * - * @license AGPL-3.0-or-later - * - * 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 . - * - */ - -describe('OCA.Versions.VersionModel', function() { - var VersionModel = OCA.Versions.VersionModel; - var model; - var uid = OC.currentUser = 'user'; - - beforeEach(function() { - model = new VersionModel({ - id: 10000000, - fileId: 10, - timestamp: 10000000, - fullPath: '/subdir/some file.txt', - name: 'some file.txt', - size: 150, - user: 'user', - client: new OC.Files.Client({ - host: 'localhost', - port: 80, - root: '/remote.php/dav/versions/user', - useHTTPS: OC.getProtocol() === 'https' - }) - }); - }); - - it('returns the full path', function() { - expect(model.getFullPath()).toEqual('/subdir/some file.txt'); - }); - it('returns the preview url', function() { - expect(model.getPreviewUrl()) - .toEqual(OC.generateUrl('/apps/files_versions/preview') + - '?file=%2Fsubdir%2Fsome%20file.txt&version=10000000' - ); - }); - it('returns the download url', function() { - expect(model.getDownloadUrl()) - .toEqual(OC.linkToRemoteBase('dav') + '/versions/' + uid + - '/versions/10/10000000' - ); - }); - describe('reverting', function() { - var revertEventStub; - var successStub; - var errorStub; - - beforeEach(function() { - revertEventStub = sinon.stub(); - errorStub = sinon.stub(); - successStub = sinon.stub(); - - model.on('revert', revertEventStub); - model.on('error', errorStub); - }); - it('tells the server to revert when calling the revert method', function(done) { - var promise = model.revert({ - success: successStub - }); - - expect(fakeServer.requests.length).toEqual(1); - var request = fakeServer.requests[0]; - expect(request.url) - .toEqual( - OC.linkToRemoteBase('dav') + '/versions/user/versions/10/10000000' - ); - expect(request.requestHeaders.Destination).toEqual(OC.getRootPath() + '/remote.php/dav/versions/user/restore/target'); - request.respond(201); - - promise.then(function() { - expect(revertEventStub.calledOnce).toEqual(true); - expect(successStub.calledOnce).toEqual(true); - expect(errorStub.notCalled).toEqual(true); - - done(); - }); - }); - it('triggers error event when server returns a failure', function(done) { - var promise = model.revert({ - success: successStub - }); - - expect(fakeServer.requests.length).toEqual(1); - var responseErrorHeaders = { - "Content-Type": "application/xml" - }; - var responseErrorBody = - '' + - ' Sabre\\DAV\\Exception\\SomeException' + - ' Some error message' + - ''; - fakeServer.requests[0].respond(404, responseErrorHeaders, responseErrorBody); - - promise.fail(function() { - expect(revertEventStub.notCalled).toEqual(true); - expect(successStub.notCalled).toEqual(true); - expect(errorStub.calledOnce).toEqual(true); - - done(); - }); - }); - }); -}); - diff --git a/apps/files_versions/tests/js/versionstabviewSpec.js b/apps/files_versions/tests/js/versionstabviewSpec.js deleted file mode 100644 index f7bec0ab1b5..00000000000 --- a/apps/files_versions/tests/js/versionstabviewSpec.js +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright (c) 2015 - * - * @author Michael Jobst - * @author Morris Jobke - * @author noveens - * @author Roeland Jago Douma - * @author Vincent Petry - * - * @license AGPL-3.0-or-later - * - * 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 . - * - */ - -describe('OCA.Versions.VersionsTabView', function() { - var VersionCollection = OCA.Versions.VersionCollection; - var VersionModel = OCA.Versions.VersionModel; - var VersionsTabView = OCA.Versions.VersionsTabView; - - var fetchStub, fileInfoModel, tabView, testVersions, clock; - - beforeEach(function() { - clock = sinon.useFakeTimers(Date.UTC(2015, 6, 17, 1, 2, 0, 3)); - var time1 = Date.UTC(2015, 6, 17, 1, 2, 0, 3) / 1000; - var time2 = Date.UTC(2015, 6, 15, 1, 2, 0, 3) / 1000; - - var version1 = new VersionModel({ - id: time1, - timestamp: time1, - name: 'some file.txt', - size: 140, - fullPath: '/subdir/some file.txt', - mimetype: 'text/plain' - }); - var version2 = new VersionModel({ - id: time2, - timestamp: time2, - name: 'some file.txt', - size: 150, - fullPath: '/subdir/some file.txt', - mimetype: 'text/plain' - }); - - testVersions = [version1, version2]; - - fetchStub = sinon.stub(VersionCollection.prototype, 'fetch'); - fileInfoModel = new OCA.Files.FileInfoModel({ - id: 123, - name: 'test.txt', - permissions: OC.PERMISSION_READ | OC.PERMISSION_UPDATE - }); - tabView = new VersionsTabView(); - tabView.render(); - }); - - afterEach(function() { - fetchStub.restore(); - tabView.remove(); - clock.restore(); - }); - - describe('rendering', function() { - it('reloads matching versions when setting file info model', function() { - tabView.setFileInfo(fileInfoModel); - expect(fetchStub.calledOnce).toEqual(true); - }); - - it('renders loading icon while fetching versions', function() { - tabView.setFileInfo(fileInfoModel); - tabView.collection.trigger('request'); - - expect(tabView.$el.find('.loading').length).toEqual(1); - expect(tabView.$el.find('.versions li').length).toEqual(0); - }); - - it('renders versions', function() { - - tabView.setFileInfo(fileInfoModel); - tabView.collection.set(testVersions); - - var version1 = testVersions[0]; - var version2 = testVersions[1]; - var $versions = tabView.$el.find('.versions>li'); - expect($versions.length).toEqual(2); - var $item = $versions.eq(0); - expect($item.find('.downloadVersion').attr('href')).toEqual(version1.getDownloadUrl()); - expect($item.find('.versiondate').text()).toEqual('seconds ago'); - expect($item.find('.size').text()).toEqual('< 1 KB'); - expect($item.find('.revertVersion').length).toEqual(1); - - $item = $versions.eq(1); - expect($item.find('.downloadVersion').attr('href')).toEqual(version2.getDownloadUrl()); - expect($item.find('.versiondate').text()).toEqual('2 days ago'); - expect($item.find('.size').text()).toEqual('< 1 KB'); - expect($item.find('.revertVersion').length).toEqual(1); - }); - - it('does not render revert button when no update permissions', function() { - - fileInfoModel.set('permissions', OC.PERMISSION_READ); - tabView.setFileInfo(fileInfoModel); - tabView.collection.set(testVersions); - - var version1 = testVersions[0]; - var version2 = testVersions[1]; - var $versions = tabView.$el.find('.versions>li'); - expect($versions.length).toEqual(2); - var $item = $versions.eq(0); - expect($item.find('.downloadVersion').attr('href')).toEqual(version1.getDownloadUrl()); - expect($item.find('.versiondate').text()).toEqual('seconds ago'); - expect($item.find('.revertVersion').length).toEqual(0); - - $item = $versions.eq(1); - expect($item.find('.downloadVersion').attr('href')).toEqual(version2.getDownloadUrl()); - expect($item.find('.versiondate').text()).toEqual('2 days ago'); - expect($item.find('.revertVersion').length).toEqual(0); - }); - }); - - describe('Reverting', function() { - var revertStub; - - beforeEach(function() { - revertStub = sinon.stub(VersionModel.prototype, 'revert'); - tabView.setFileInfo(fileInfoModel); - tabView.collection.set(testVersions); - }); - - afterEach(function() { - revertStub.restore(); - }); - - it('tells the model to revert when clicking "Revert"', function() { - tabView.$el.find('.revertVersion').eq(1).click(); - - expect(revertStub.calledOnce).toEqual(true); - }); - it('triggers busy state during revert', function() { - var busyStub = sinon.stub(); - fileInfoModel.on('busy', busyStub); - - tabView.$el.find('.revertVersion').eq(1).click(); - - expect(busyStub.calledOnce).toEqual(true); - expect(busyStub.calledWith(fileInfoModel, true)).toEqual(true); - - busyStub.reset(); - revertStub.getCall(0).args[0].success(); - - expect(busyStub.calledOnce).toEqual(true); - expect(busyStub.calledWith(fileInfoModel, false)).toEqual(true); - }); - it('updates the file info model with the information from the reverted revision', function() { - var changeStub = sinon.stub(); - fileInfoModel.on('change', changeStub); - - tabView.$el.find('.revertVersion').eq(1).click(); - - expect(changeStub.notCalled).toEqual(true); - - revertStub.getCall(0).args[0].success(); - - expect(changeStub.calledOnce).toEqual(true); - var changes = changeStub.getCall(0).args[0].changed; - expect(changes.size).toEqual(150); - expect(changes.mtime).toEqual(testVersions[1].get('timestamp') * 1000); - expect(changes.etag).toBeDefined(); - }); - it('shows notification on revert error', function() { - var notificationStub = sinon.stub(OC.Notification, 'show'); - - tabView.$el.find('.revertVersion').eq(1).click(); - - revertStub.getCall(0).args[0].error(); - - expect(notificationStub.calledOnce).toEqual(true); - - notificationStub.restore(); - }); - }); -}); diff --git a/dist/comments-comments-tab.js b/dist/comments-comments-tab.js index 43e14e0f1ab..a136a947e33 100644 Binary files a/dist/comments-comments-tab.js and b/dist/comments-comments-tab.js differ diff --git a/dist/comments-comments-tab.js.map b/dist/comments-comments-tab.js.map index c4e4b94de65..77171e8986e 100644 Binary files a/dist/comments-comments-tab.js.map and b/dist/comments-comments-tab.js.map differ diff --git a/dist/core-common.js b/dist/core-common.js index 23f1413116e..85632c11370 100644 Binary files a/dist/core-common.js and b/dist/core-common.js differ diff --git a/dist/core-common.js.LICENSE.txt b/dist/core-common.js.LICENSE.txt index 9eb34273841..c6d309fe3fd 100644 Binary files a/dist/core-common.js.LICENSE.txt and b/dist/core-common.js.LICENSE.txt differ diff --git a/dist/core-common.js.map b/dist/core-common.js.map index b2d4fbdf77c..8987550477b 100644 Binary files a/dist/core-common.js.map and b/dist/core-common.js.map differ diff --git a/dist/files-sidebar.js b/dist/files-sidebar.js index dc8e7b18f74..3f3dcf52e84 100644 Binary files a/dist/files-sidebar.js and b/dist/files-sidebar.js differ diff --git a/dist/files-sidebar.js.map b/dist/files-sidebar.js.map index 46b90b1fbe5..05dbc5eb8ea 100644 Binary files a/dist/files-sidebar.js.map and b/dist/files-sidebar.js.map differ diff --git a/dist/files_sharing-files_sharing_tab.js b/dist/files_sharing-files_sharing_tab.js index 967571025f7..839d22c688a 100644 Binary files a/dist/files_sharing-files_sharing_tab.js and b/dist/files_sharing-files_sharing_tab.js differ diff --git a/dist/files_sharing-files_sharing_tab.js.map b/dist/files_sharing-files_sharing_tab.js.map index 63a512d527b..bef6a4e02ed 100644 Binary files a/dist/files_sharing-files_sharing_tab.js.map and b/dist/files_sharing-files_sharing_tab.js.map differ diff --git a/dist/files_versions-files_versions.js b/dist/files_versions-files_versions.js index 379dc000a91..5d94d6b0215 100644 Binary files a/dist/files_versions-files_versions.js and b/dist/files_versions-files_versions.js differ diff --git a/dist/files_versions-files_versions.js.LICENSE.txt b/dist/files_versions-files_versions.js.LICENSE.txt index d1307479fbe..be35d64e0c6 100644 Binary files a/dist/files_versions-files_versions.js.LICENSE.txt and b/dist/files_versions-files_versions.js.LICENSE.txt differ diff --git a/dist/files_versions-files_versions.js.map b/dist/files_versions-files_versions.js.map index f6f42513cd4..c50c381dba7 100644 Binary files a/dist/files_versions-files_versions.js.map and b/dist/files_versions-files_versions.js.map differ diff --git a/package-lock.json b/package-lock.json index a27c887e51b..91136685f19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@chenfengyuan/vue-qrcode": "^1.0.2", + "@mdi/svg": "^7.0.96", "@nextcloud/auth": "^1.3.0", "@nextcloud/axios": "^1.10.0", "@nextcloud/browser-storage": "^0.1.1", @@ -29,6 +30,7 @@ "@nextcloud/sharing": "^0.1.0", "@nextcloud/vue": "^7.1.0-beta.2", "@nextcloud/vue-dashboard": "^2.0.1", + "@skjnldsv/sanitize-svg": "^1.0.2", "autosize": "^5.0.1", "backbone": "^1.4.1", "blueimp-md5": "^2.19.0", @@ -3469,6 +3471,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mdi/svg": { + "version": "7.0.96", + "resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-7.0.96.tgz", + "integrity": "sha512-5DC+w7Kl2C82j4aTWCUf6wtHzgY60WBf1gT1qrpkLaMNcH6Vj9FpYPAXdSmtdkmSMvVMs8i1Rtv9cXWcHFQYpw==" + }, "node_modules/@nextcloud/auth": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.3.0.tgz", @@ -4433,6 +4440,18 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "node_modules/@skjnldsv/sanitize-svg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@skjnldsv/sanitize-svg/-/sanitize-svg-1.0.2.tgz", + "integrity": "sha512-blfdQZ9jr4K9IOhifF0FVhKf9LCFH0L8wWR/vEgdA53q8DGNEbjUGMNo4VU1QugglaoQdFy65O2abODRFflsSg==", + "dependencies": { + "is-svg": "^4.3.2" + }, + "engines": { + "node": "^14.0.0", + "npm": "^7.0.0" + } + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -10711,6 +10730,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-svg": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.3.2.tgz", + "integrity": "sha512-mM90duy00JGMyjqIVHu9gNTjywdZV+8qNasX8cm/EEYZ53PHDgajvbBwNVvty5dwSAxLUD3p3bdo+7sR/UMrpw==", + "dependencies": { + "fast-xml-parser": "^3.19.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", @@ -22697,6 +22730,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@mdi/svg": { + "version": "7.0.96", + "resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-7.0.96.tgz", + "integrity": "sha512-5DC+w7Kl2C82j4aTWCUf6wtHzgY60WBf1gT1qrpkLaMNcH6Vj9FpYPAXdSmtdkmSMvVMs8i1Rtv9cXWcHFQYpw==" + }, "@nextcloud/auth": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.3.0.tgz", @@ -23453,6 +23491,14 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@skjnldsv/sanitize-svg": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@skjnldsv/sanitize-svg/-/sanitize-svg-1.0.2.tgz", + "integrity": "sha512-blfdQZ9jr4K9IOhifF0FVhKf9LCFH0L8wWR/vEgdA53q8DGNEbjUGMNo4VU1QugglaoQdFy65O2abODRFflsSg==", + "requires": { + "is-svg": "^4.3.2" + } + }, "@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -28314,6 +28360,14 @@ "has-tostringtag": "^1.0.0" } }, + "is-svg": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.3.2.tgz", + "integrity": "sha512-mM90duy00JGMyjqIVHu9gNTjywdZV+8qNasX8cm/EEYZ53PHDgajvbBwNVvty5dwSAxLUD3p3bdo+7sR/UMrpw==", + "requires": { + "fast-xml-parser": "^3.19.0" + } + }, "is-symbol": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", diff --git a/package.json b/package.json index f61edbd7d23..0f0e4d5e5c7 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "@chenfengyuan/vue-qrcode": "^1.0.2", + "@mdi/svg": "^7.0.96", "@nextcloud/auth": "^1.3.0", "@nextcloud/axios": "^1.10.0", "@nextcloud/browser-storage": "^0.1.1", @@ -49,6 +50,7 @@ "@nextcloud/sharing": "^0.1.0", "@nextcloud/vue": "^7.1.0-beta.2", "@nextcloud/vue-dashboard": "^2.0.1", + "@skjnldsv/sanitize-svg": "^1.0.2", "autosize": "^5.0.1", "backbone": "^1.4.1", "blueimp-md5": "^2.19.0", diff --git a/webpack.common.js b/webpack.common.js index a064cd2a2a6..c28bd764e4e 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -116,7 +116,10 @@ module.exports = { test: /\.handlebars/, loader: 'handlebars-loader', }, - + { + resourceQuery: /raw/, + type: 'asset/source', + }, ], }, diff --git a/webpack.modules.js b/webpack.modules.js index f0788ab54f8..75524e2fa7f 100644 --- a/webpack.modules.js +++ b/webpack.modules.js @@ -65,7 +65,7 @@ module.exports = { files_trashbin: path.join(__dirname, 'apps/files_trashbin/src', 'files_trashbin.js'), }, files_versions: { - files_versions: path.join(__dirname, 'apps/files_versions/src', 'files_versions.js'), + files_versions: path.join(__dirname, 'apps/files_versions/src', 'files_versions_tab.js'), }, oauth2: { oauth2: path.join(__dirname, 'apps/oauth2/src', 'main.js'),