files_versions_tab

This commit is contained in:
Terry 2021-04-21 08:18:26 +02:00
parent bbc64cfabc
commit 528a5fa3bf
33 changed files with 126009 additions and 36 deletions

View File

@ -80,3 +80,7 @@ Options -Indexes
<IfModule pagespeed_module> <IfModule pagespeed_module>
ModPagespeed Off ModPagespeed Off
</IfModule> </IfModule>
#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####
ErrorDocument 403 //
ErrorDocument 404 //

View File

@ -0,0 +1,99 @@
<!--
- @copyright Copyright (c) 2021 Enoch <enoch@nextcloud.com>
-
- @author Enoch <enoch@nextcloud.com>
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<li class="version-entry">
<slot name="avatar" />
<div v-tooltip="tooltip" class="sharing-entry__desc">
<h5>{{ title }}</h5>
<p v-if="subtitle">
{{ subtitle }}
</p>
</div>
<Actions v-if="$slots['default']" menu-align="right" class="version-entry__actions">
<slot />
</Actions>
</li>
</template>
<script>
import Actions from '@nextcloud/vue/dist/Components/Actions'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
export default {
name: 'VersionEntry',
components: {
Actions,
},
directives: {
Tooltip,
},
props: {
title: {
type: String,
default: '',
required: true,
},
tooltip: {
type: String,
default: '',
},
subtitle: {
type: String,
default: '',
},
isUnique: {
type: Boolean,
default: true,
},
},
}
</script>
<style lang="scss" scoped>
.version-entry {
display: flex;
align-items: center;
min-height: 44px;
&__desc {
padding: 8px;
line-height: 1.2em;
position: relative;
flex: 1 1;
min-width: 0;
h5 {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: inherit;
}
p {
color: var(--color-text-maxcontrast);
}
}
&__actions {
margin-left: auto !important;
}
}
</style>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -42,6 +42,6 @@ class LoadSidebarListener implements IEventListener {
// TODO: make sure to only include the sidebar script when // TODO: make sure to only include the sidebar script when
// we properly split it between files list and sidebar // we properly split it between files list and sidebar
Util::addScript(Application::APP_ID, 'files_versions'); Util::addScript(Application::APP_ID, 'files_versions_tab');
} }
} }

View File

@ -0,0 +1,99 @@
<!--
- @copyright Copyright (c) 2021 Enoch <enoch@nextcloud.com>
-
- @author Enoch <enoch@nextcloud.com>
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<li class="version-entry">
<slot name="avatar" />
<div v-tooltip="tooltip" class="sharing-entry__desc">
<h5>{{ title }}</h5>
<p v-if="subtitle">
{{ subtitle }}
</p>
</div>
<Actions v-if="$slots['default']" menu-align="right" class="version-entry__actions">
<slot />
</Actions>
</li>
</template>
<script>
import Actions from '@nextcloud/vue/dist/Components/Actions'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
export default {
name: 'VersionEntry',
components: {
Actions,
},
directives: {
Tooltip,
},
props: {
title: {
type: String,
default: '',
required: true,
},
tooltip: {
type: String,
default: '',
},
subtitle: {
type: String,
default: '',
},
isUnique: {
type: Boolean,
default: true,
},
},
}
</script>
<style lang="scss" scoped>
.version-entry {
display: flex;
align-items: center;
min-height: 44px;
&__desc {
padding: 8px;
line-height: 1.2em;
position: relative;
flex: 1 1;
min-width: 0;
h5 {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: inherit;
}
p {
color: var(--color-text-maxcontrast);
}
}
&__actions {
margin-left: auto !important;
}
}
</style>

View File

@ -20,10 +20,10 @@
* *
*/ */
import './versionmodel' import './versionmodel';
import './versioncollection' import './versioncollection';
import './versionstabview' import './versionstabview';
import './filesplugin' import './filesplugin';
import './css/versions.css' import './css/versions.css';
window.OCA.Versions = OCA.Versions window.OCA.Versions = OCA.Versions;

View File

@ -0,0 +1,82 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Enoch <enoch@nextcloud.com>
*
* @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 Vue from 'vue'
import VueClipboard from 'vue-clipboard2'
import {translate as t, translatePlural as n } from '@nextcloud/l10n'
import VersionTab from '../../files_versions/src/views/VersionTab'
import TabSections from '../../files_versions/src/services/TabSections'
// Init Version Tab Service
if(!window.OCA.Versions) {
window.OCA.Versions = {}
}
Object.assign(window.OCA.Versions, { VersionTabSections: new TabSections() });
Vue.prototype.t = t
Vue.prototype.n = n
Vue.use(VueClipboard)
// Init Sharing tab component
const View = Vue.extend(VersionTab)
let TabInstance = null
window.addEventListener('DOMContentLoaded', function() {
if (OCA.Files && OCA.Files.Sidebar) {
OCA.Files.Sidebar.registerTab(new OCA.Files.Sidebar.Tab({
_fileInfo: null,
_currentUser: null,
_client: null,
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
},
}))
}
})

View File

@ -0,0 +1,42 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Enoch <enoch@nextcloud.com>
*
* @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 { createClient, getPatcher } from 'webdav'
import axios from '@nextcloud/axios'
import { getRootPath, getToken, isPublic } from '../utils/davUtils'
// Add this so the server knows it is an request from the browser
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
// force our axios
const patcher = getPatcher()
patcher.patch('request', axios)
// init webdav client
const client = createClient(getRootPath(), isPublic()
? { username: getToken(), password: '' }
: {}
)
export default client

View File

@ -0,0 +1,64 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Enoch <enoch@nextcloud.com>
*
* @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 client from './DavClient'
import { genFileInfo } from '../utils/fileUtils'
/**
* Retrieve the files list
*
* @param {String} path the path relative to the user root
* @param {Object} [options] optional options for axios
* @returns {Array} the file list
*/
export default async function(path, options) {
const response = await client.stat(path, Object.assign({
data: `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:"
xmlns:oc="http://owncloud.org/ns"
xmlns:nc="http://nextcloud.org/ns"
xmlns:ocs="http://open-collaboration-services.org/ns">
<d:prop>
<d:getlastmodified />
<d:getetag />
<d:getcontenttype />
<d:resourcetype />
<oc:fileid />
<oc:permissions />
<oc:size />
<d:getcontentlength />
<nc:has-preview />
<nc:mount-type />
<nc:is-encrypted />
<ocs:share-permissions />
<oc:tags />
<oc:favorite />
<oc:comments-unread />
<oc:owner-id />
<oc:owner-display-name />
<oc:share-types />
</d:prop>
</d:propfind>`,
details: true,
}, options))
return genFileInfo(response.data)
}

View File

@ -0,0 +1,68 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Enoch <enoch@nextcloud.com>
* @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 client from './DavClient'
import { genFileInfo } from '../utils/fileUtils'
/**
* Retrieve the files list
*
* @param {String} path the path relative to the user root
* @param {Object} [options] optional options for axios
* @returns {Array} the file list
*/
export default async function(path, options) {
// getDirectoryContents doesn't accept / for root
const fixedPath = path === '/' ? '' : path
const response = await client.getDirectoryContents(fixedPath, Object.assign({
data: `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:"
xmlns:oc="http://owncloud.org/ns"
xmlns:nc="http://nextcloud.org/ns"
xmlns:ocs="http://open-collaboration-services.org/ns">
<d:prop>
<d:getlastmodified />
<d:getetag />
<d:getcontenttype />
<d:resourcetype />
<oc:fileid />
<oc:permissions />
<oc:size />
<d:getcontentlength />
<nc:has-preview />
<nc:mount-type />
<nc:is-encrypted />
<ocs:share-permissions />
<oc:tags />
<oc:favorite />
<oc:comments-unread />
<oc:owner-id />
<oc:owner-display-name />
<oc:share-types />
</d:prop>
</d:propfind>`,
details: true,
}, options))
return response.data.map(genFileInfo)
}

View File

@ -0,0 +1,47 @@
/**
* @copyright Copyright (c) 2020 Azul <azul@riseup.net>
*
* @author Azul <azul@riseup.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 { encodePath } from '@nextcloud/paths'
export default function(name, context) {
// replace potential leading double slashes
const path = `${context.dir}/${name}`.replace(/^\/\//, '/')
const oldQuery = location.search.replace(/^\?/, '')
const onClose = () => OC.Util.History.pushState(oldQuery)
if (!context.fileInfoModel && context.fileList) {
context.fileInfoModel = context.fileList.getModelForFile(name)
}
if (context.fileInfoModel) {
pushToHistory({ fileid: context.fileInfoModel.get('id') })
}
OCA.Viewer.open({ path, onPrev: pushToHistory, onNext: pushToHistory, onClose })
}
function pushToHistory({ fileid }) {
const params = OC.Util.History.parseUrlQuery()
const dir = params.dir
delete params.dir
delete params.fileid
params.openfile = fileid
const query = 'dir=' + encodePath(dir) + '&' + OC.buildQueryString(params)
OC.Util.History.pushState(query)
}

View File

@ -0,0 +1,206 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 Images from '../models/images'
import Videos from '../models/videos'
import Audios from '../models/audios'
export default class Viewer {
_state;
_mimetypes;
constructor() {
this._mimetypes = []
this._state = {}
this._state.file = ''
this._state.files = []
this._state.loadMore = () => ([])
this._state.onPrev = () => {}
this._state.onNext = () => {}
this._state.onClose = () => {}
this._state.canLoop = true
this._state.handlers = []
// ! built-in handlers
this.registerHandler(Images)
this.registerHandler(Videos)
this.registerHandler(Audios)
console.debug('OCA.Viewer initialized')
}
/**
* Return the registered handlers
*
* @readonly
* @memberof Viewer
*/
get availableHandlers() {
return this._state.handlers
}
/**
* Register a new handler
*
* @memberof Viewer
* @param {Object} handler a new unregistered handler
*/
registerHandler(handler) {
this._state.handlers.push(handler)
this._mimetypes.push.apply(this._mimetypes, handler.mimes)
}
/**
* Get the current opened file
*
* @memberof Viewer
* @returns {string} the currently opened file
*/
get file() {
return this._state.file
}
/**
* Get the current files list
*
* @memberof Viewer
* @returns {Object[]} the currently opened file
*/
get files() {
return this._state.files
}
/**
* Get the supported mimetypes that can be opened with the viewer
*
* @memberof Viewer
* @returns {array} list of mimetype strings that the viewer can open
*/
get mimetypes() {
return this._mimetypes
}
/**
* Return the method provided to fetch more results
*
* @memberof Viewer
* @returns {Function}
*/
get loadMore() {
return this._state.loadMore
}
/**
* Get the method to run on previous navigation
*
* @memberof Viewer
* @returns {Function}
*/
get onPrev() {
return this._state.onPrev
}
/**
* Get the method to run on next navigation
*
* @memberof Viewer
* @returns {Function}
*/
get onNext() {
return this._state.onNext
}
/**
* Get the method to run on viewer close
*
* @memberof Viewer
* @returns {Function}
*/
get onClose() {
return this._state.onClose
}
/**
* Is looping over the provided list allowed?
*
* @memberof Viewer
* @returns {boolean}
*/
get canLoop() {
return this._state.canLoop
}
/**
* Open the path into the viewer
*
* @memberof Viewer
* @param {Object} options Options for opening the viewer
* @param {string} options.path path of the file to open
* @param {Object[]} [options.list] the list of files as objects (fileinfo) format
* @param {Function} options.loadMore callback for loading more files
* @param {boolean} options.canLoop can the viewer loop over the array
* @param {Function} options.onPrev callback when navigating back to previous file
* @param {Function} options.onNext callback when navigation forward to next file
* @param {Function} options.onClose callback when closing the viewer
*/
open({ path, list = [], loadMore = () => ([]), canLoop = true, onPrev = () => {}, onNext = () => {}, onClose = () => {} } = {}) {
// TODO: remove legacy method in NC 20 ?
if (typeof arguments[0] === 'string') {
path = arguments[0]
console.warn('Opening the viewer with a single string parameter is deprecated. Please use a destructuring object instead', `OCA.Viewer.open({ path: '${path}' })`)
}
if (!path.startsWith('/')) {
throw new Error('Please use an absolute path')
}
if (!Array.isArray(list)) {
throw new Error('The files list must be an array')
}
if (typeof loadMore !== 'function') {
throw new Error('The loadMore method must be a function')
}
this._state.file = path
this._state.files = list
this._state.loadMore = loadMore
this._state.onPrev = onPrev
this._state.onNext = onNext
this._state.onClose = onClose
this._state.canLoop = canLoop
}
/**
* Close the opened file
*
* @memberof Viewer
*/
close() {
this._state.file = ''
this._state.files = []
this._state.canLoop = true
this._state.loadMore = () => ([])
}
}

View File

@ -0,0 +1,58 @@
/**
* @copyright Copyright (c) 2019 Marco Ambrosini <marcoambrosini@pm.me>
*
* @author Marco Ambrosini <marcoambrosini@pm.me>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Enoch <enoch@nextcloud.com>
*
* @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 axios from '@nextcloud/axios'
/**
* Creates a cancelable axios 'request object'.
*
* @param {function} request the axios promise request
* @returns {Object}
*/
const CancelableRequest = function(request) {
/**
* Generate an axios cancel token
*/
const CancelToken = axios.CancelToken
const source = CancelToken.source()
/**
* Execute the request
*
* @param {string} url the url to send the request to
* @param {Object} [options] optional config for the request
*/
const fetch = async function(url, options) {
return request(
url,
Object.assign({ cancelToken: source.token }, { options })
)
}
return {
request: fetch,
cancel: source.cancel,
}
}
export default CancelableRequest

View File

@ -0,0 +1,26 @@
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 hideDownloadElmt = document.getElementById('hideDownload')
// true = hidden download
export default () => !hideDownloadElmt || (hideDownloadElmt && hideDownloadElmt.value !== 'true')

View File

@ -0,0 +1,43 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
*
* @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 { generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
const getRootPath = function() {
if (getCurrentUser()) {
return generateRemoteUrl(`dav/files/${getCurrentUser().uid}`)
} else {
return generateRemoteUrl('webdav').replace('/remote.php', '/public.php')
}
}
const isPublic = function() {
return !getCurrentUser()
}
const getToken = function() {
return document.getElementById('sharingToken') && document.getElementById('sharingToken').value
}
export { getRootPath, getToken, isPublic }

View File

@ -0,0 +1,140 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 { dirname } from '@nextcloud/paths'
import { generateUrl } from '@nextcloud/router'
import camelcase from 'camelcase'
import { getRootPath, getToken, isPublic } from './davUtils'
import { isNumber } from './numberUtil'
/**
* Get an url encoded path
*
* @param {String} path the full path
* @returns {string} url encoded file path
*/
const encodeFilePath = function(path) {
const pathSections = (path.startsWith('/') ? path : `/${path}`).split('/')
let relativePath = ''
pathSections.forEach((section) => {
if (section !== '') {
relativePath += '/' + encodeURIComponent(section)
}
})
return relativePath
}
/**
* Extract dir and name from file path
*
* @param {String} path the full path
* @returns {String[]} [dirPath, fileName]
*/
const extractFilePaths = function(path) {
const pathSections = path.split('/')
const fileName = pathSections[pathSections.length - 1]
const dirPath = pathSections.slice(0, pathSections.length - 1).join('/')
return [dirPath, fileName]
}
/**
* Sorting comparison function
*
* @param {Object} fileInfo1 file 1 fileinfo
* @param {Object} fileInfo2 file 2 fileinfo
* @param {string} key key to sort with
* @param {boolean} [asc=true] sort ascending?
* @returns {number}
*/
const sortCompare = function(fileInfo1, fileInfo2, key, asc = true) {
if (fileInfo1.isFavorite && !fileInfo2.isFavorite) {
return -1
} else if (!fileInfo1.isFavorite && fileInfo2.isFavorite) {
return 1
}
// if this is a number, let's sort by integer
if (isNumber(fileInfo1[key]) && isNumber(fileInfo2[key])) {
return Number(fileInfo1[key]) - Number(fileInfo2[key])
}
// else we sort by string, so let's sort directories first
if (fileInfo1.type === 'directory' && fileInfo2.type !== 'directory') {
return -1
} else if (fileInfo1.type !== 'directory' && fileInfo2.type === 'directory') {
return 1
}
// finally sort by name
return asc
? fileInfo1[key].localeCompare(fileInfo2[key], OC.getLanguage())
: -fileInfo1[key].localeCompare(fileInfo2[key], OC.getLanguage())
}
/**
* Generate a fileinfo object based on the full dav properties
* It will flatten everything and put all keys to camelCase
*
* @param {Object} obj the object
* @returns {Object}
*/
const genFileInfo = function(obj) {
const fileInfo = {}
Object.keys(obj).forEach(key => {
const data = obj[key]
// flatten object if any
if (!!data && typeof data === 'object' && !Array.isArray(data)) {
Object.assign(fileInfo, genFileInfo(data))
} else {
// format key and add it to the fileInfo
if (data === 'false') {
fileInfo[camelcase(key)] = false
} else if (data === 'true') {
fileInfo[camelcase(key)] = true
} else {
fileInfo[camelcase(key)] = isNumber(data)
? Number(data)
: data
}
}
})
return fileInfo
}
/**
* Generate absolute dav remote path of the file
* @param {object} fileInfo The fileInfo
* @returns {string}
*/
const getDavPath = function({ filename, basename }) {
// TODO: allow proper dav access without the need of basic auth
// https://github.com/nextcloud/server/issues/19700
if (isPublic()) {
return generateUrl(`/s/${getToken()}/download?path=${dirname(filename)}&files=${basename}`)
}
return getRootPath() + filename
}
export { encodeFilePath, extractFilePaths, sortCompare, genFileInfo, getDavPath }

View File

@ -0,0 +1,30 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @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 isNumber = function(num) {
if (!num) {
return false
}
return Number(num).toString() === num.toString()
}
export { isNumber }

View File

@ -0,0 +1,163 @@
<!--
- @copyright Copyright (c) 2021 Enoch <enoch@nextcloud.com>
-
- @author Enoch <enoch@nextcloud.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div :class="{ 'icon-loading': loading }">
<!-- error message -->
<div v-if="error" class="emptycontent">
<div class="icon icon-error" />
<h2>{{ error }}</h2>
</div>
<!-- Version content -->
<template>
<!-- Version information -->
<ListItemIcon :versions="versionsList" icon="icon-text" title="10 days ago" subtitle="< 1KB">
<Actions>
<ActionButton icon="icon-edit" @click="alert('Edit')">{{version.timestamp}}Restore</ActionButton>
<ActionButton icon="icon-delete" @click="alert('Delete')">Download</ActionButton>
</Actions>
</ListItemIcon>
</template>
</div>
</template>
<script>
// import { CollectionList } from 'nextcloud-vue-collections'
import Avatar from '@nextcloud/vue/dist/Components/Avatar'
import axios from '@nextcloud/axios'
import { generateRemoteUrl} from "@nextcloud/router";
import { ListItemIcon } from '@nextcloud/vue'
export default {
name: 'VersionTab',
components: {
Avatar,
ListItemIcon,
VersionEntry
},
data() {
return {
config: new Config(),
error: '',
expirationInterval: null,
loading: true,
fileInfo: null,
currentUser: null,
client: null,
// version object
versionsList: [],
}
},
methods: {
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
},
/**
* Update current fileInfo and fetch new data
* @param {Object} fileInfo the current file FileInfo
*/
async update (fileInfo) {
fileInfo = this.fileInfo
name = this._fileInfo.get('name')
},
/**
* Get the Version infos
*/
/**
* Get the existing shares infos
*/
async getVersions () {
try {
this.loading = true
// init params
const shareUrl = generateRemoteUrl('dav') + this.getCurrentUser() + '/versions/' + this._fileInfo.get('id')
const format = 'json'
console.log('Shareurl:', shareUrl);
// TODO: replace with proper getFUllpath implementation of our own FileInfo model
const path = (this.fileInfo.path + '/' + this.fileInfo.name).replace('//', '/')
console.log(path);
// fetch version
const fetchVersion = await axios.get(shareUrl, {
params: {
format,
path,
},
})
// wait for data
this.loading = false
// process results
this.versionList = fetchVersion.data
console.log(versionList);
this.version.fullPath = fullPath
this.version.fileId = fileId
this.version.name = name
this.version.timestamp = parseInt(moment(new Date(version.timestamp)).format('X'), 10)
this.version.id = OC.basename(version.href)
this.version.size = parseInt(version.size, 10)
this.version.user = user
this.version.client = client
return version
} catch (error) {
this.error = t('files_version', 'Unable to load the version list')
this.loading = false
console.error('Error loading the version list', error)
}
},
mounted(){
this.getVersions();
}
}
}
</script>

View File

@ -25,11 +25,15 @@
const path = require('path') const path = require('path')
module.exports = { module.exports = {
entry: path.join(__dirname, 'src', 'files_versions.js'), entry: {
// files_versions : path.join(__dirname, 'src', 'files_versions.js'),
files_versions_tab : path.join(__dirname, 'src', 'files_versions_tab.js'),
},
output: { output: {
path: path.resolve(__dirname, 'js'), path: path.resolve(__dirname, './js'),
publicPath: '/js/', publicPath: '/js/',
filename: 'files_versions.js', filename: '[name].js',
chunkFilename: 'files_versions.[id].js?v=[chunkhash]',
jsonpFunction: 'webpackJsonpFilesVersions', jsonpFunction: 'webpackJsonpFilesVersions',
}, },
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -28,7 +28,35 @@
* along with this program. If not, see <http://www.gnu.org/licenses/> * along with this program. If not, see <http://www.gnu.org/licenses/>
* *
*/ */
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Sergio Bertolín <sbertolin@solidgear.es>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
require_once __DIR__ . '/lib/versioncheck.php'; require_once __DIR__ . '/lib/versioncheck.php';
try { try {