files_versions_tab
This commit is contained in:
parent
bbc64cfabc
commit
528a5fa3bf
|
@ -80,3 +80,7 @@ Options -Indexes
|
|||
<IfModule pagespeed_module>
|
||||
ModPagespeed Off
|
||||
</IfModule>
|
||||
#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####
|
||||
|
||||
ErrorDocument 403 //
|
||||
ErrorDocument 404 //
|
||||
|
|
|
@ -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
|
@ -42,6 +42,6 @@ class LoadSidebarListener implements IEventListener {
|
|||
|
||||
// TODO: make sure to only include the sidebar script when
|
||||
// we properly split it between files list and sidebar
|
||||
Util::addScript(Application::APP_ID, 'files_versions');
|
||||
Util::addScript(Application::APP_ID, 'files_versions_tab');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -20,10 +20,10 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import './versionmodel'
|
||||
import './versioncollection'
|
||||
import './versionstabview'
|
||||
import './filesplugin'
|
||||
import './css/versions.css'
|
||||
import './versionmodel';
|
||||
import './versioncollection';
|
||||
import './versionstabview';
|
||||
import './filesplugin';
|
||||
import './css/versions.css';
|
||||
|
||||
window.OCA.Versions = OCA.Versions
|
||||
window.OCA.Versions = OCA.Versions;
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}))
|
||||
}
|
||||
})
|
|
@ -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
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 = () => ([])
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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')
|
|
@ -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 }
|
|
@ -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 }
|
|
@ -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 }
|
|
@ -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>
|
|
@ -25,11 +25,15 @@
|
|||
const path = require('path')
|
||||
|
||||
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: {
|
||||
path: path.resolve(__dirname, 'js'),
|
||||
path: path.resolve(__dirname, './js'),
|
||||
publicPath: '/js/',
|
||||
filename: 'files_versions.js',
|
||||
filename: '[name].js',
|
||||
chunkFilename: 'files_versions.[id].js?v=[chunkhash]',
|
||||
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
30
index.php
30
index.php
|
@ -28,7 +28,35 @@
|
|||
* 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';
|
||||
|
||||
try {
|
||||
|
|
Loading…
Reference in New Issue