Comply to eslint
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
7fb6512351
commit
b9bc2417e7
3
Makefile
3
Makefile
|
@ -23,6 +23,9 @@ watch-js:
|
||||||
lint-fix:
|
lint-fix:
|
||||||
npm run lint:fix
|
npm run lint:fix
|
||||||
|
|
||||||
|
lint-fix-watch:
|
||||||
|
npm run lint:fix-watch
|
||||||
|
|
||||||
# Cleaning
|
# Cleaning
|
||||||
clean:
|
clean:
|
||||||
rm -rf apps/accessibility/js/
|
rm -rf apps/accessibility/js/
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es6: true
|
|
||||||
},
|
|
||||||
extends: 'eslint:recommended',
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: 'module'
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
indent: ['error', 'tab'],
|
|
||||||
'linebreak-style': ['error', 'unix'],
|
|
||||||
quotes: ['error', 'single'],
|
|
||||||
semi: ['error', 'always']
|
|
||||||
}
|
|
||||||
};
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,60 +1,56 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="accessibility" class="section">
|
<div id="accessibility" class="section">
|
||||||
<h2>{{t('accessibility', 'Accessibility')}}</h2>
|
<h2>{{ t('accessibility', 'Accessibility') }}</h2>
|
||||||
<p v-html="description" />
|
<p v-html="description" />
|
||||||
<p v-html="descriptionDetail" />
|
<p v-html="descriptionDetail" />
|
||||||
|
|
||||||
<div class="preview-list">
|
<div class="preview-list">
|
||||||
<preview :preview="highcontrast"
|
<ItemPreview :key="highcontrast.id"
|
||||||
:key="highcontrast.id" :selected="selected.highcontrast"
|
:preview="highcontrast"
|
||||||
v-on:select="selectHighContrast"></preview>
|
:selected="selected.highcontrast"
|
||||||
<preview v-for="preview in themes" :preview="preview"
|
@select="selectHighContrast" />
|
||||||
:key="preview.id" :selected="selected.theme"
|
<ItemPreview v-for="preview in themes"
|
||||||
v-on:select="selectTheme"></preview>
|
:key="preview.id"
|
||||||
<preview v-for="preview in fonts" :preview="preview"
|
:preview="preview"
|
||||||
:key="preview.id" :selected="selected.font"
|
:selected="selected.theme"
|
||||||
v-on:select="selectFont"></preview>
|
@select="selectTheme" />
|
||||||
|
<ItemPreview v-for="preview in fonts"
|
||||||
|
:key="preview.id"
|
||||||
|
:preview="preview"
|
||||||
|
:selected="selected.font"
|
||||||
|
@select="selectFont" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import preview from './components/itemPreview';
|
import ItemPreview from './components/ItemPreview'
|
||||||
import axios from 'nextcloud-axios';
|
import axios from 'nextcloud-axios'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Accessibility',
|
name: 'Accessibility',
|
||||||
components: { preview },
|
components: { ItemPreview },
|
||||||
beforeMount() {
|
|
||||||
// importing server data into the app
|
|
||||||
const serverDataElmt = document.getElementById('serverData');
|
|
||||||
if (serverDataElmt !== null) {
|
|
||||||
this.serverData = JSON.parse(
|
|
||||||
document.getElementById('serverData').dataset.server
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
serverData: []
|
serverData: []
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
themes() {
|
themes() {
|
||||||
return this.serverData.themes;
|
return this.serverData.themes
|
||||||
},
|
},
|
||||||
highcontrast() {
|
highcontrast() {
|
||||||
return this.serverData.highcontrast;
|
return this.serverData.highcontrast
|
||||||
},
|
},
|
||||||
fonts() {
|
fonts() {
|
||||||
return this.serverData.fonts;
|
return this.serverData.fonts
|
||||||
},
|
},
|
||||||
selected() {
|
selected() {
|
||||||
return {
|
return {
|
||||||
theme: this.serverData.selected.theme,
|
theme: this.serverData.selected.theme,
|
||||||
highcontrast: this.serverData.selected.highcontrast,
|
highcontrast: this.serverData.selected.highcontrast,
|
||||||
font: this.serverData.selected.font
|
font: this.serverData.selected.font
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
description() {
|
description() {
|
||||||
// using the `t` replace method escape html, we have to do it manually :/
|
// using the `t` replace method escape html, we have to do it manually :/
|
||||||
|
@ -66,7 +62,7 @@ export default {
|
||||||
We aim to be compliant with the {guidelines} 2.1 on AA level,
|
We aim to be compliant with the {guidelines} 2.1 on AA level,
|
||||||
with the high contrast theme even on AAA level.`
|
with the high contrast theme even on AAA level.`
|
||||||
)
|
)
|
||||||
.replace('{guidelines}', this.guidelinesLink)
|
.replace('{guidelines}', this.guidelinesLink)
|
||||||
},
|
},
|
||||||
guidelinesLink() {
|
guidelinesLink() {
|
||||||
return `<a target="_blank" href="https://www.w3.org/WAI/standards-guidelines/wcag/" rel="noreferrer nofollow">${t('accessibility', 'Web Content Accessibility Guidelines')}</a>`
|
return `<a target="_blank" href="https://www.w3.org/WAI/standards-guidelines/wcag/" rel="noreferrer nofollow">${t('accessibility', 'Web Content Accessibility Guidelines')}</a>`
|
||||||
|
@ -77,8 +73,8 @@ export default {
|
||||||
`If you find any issues, don’t hesitate to report them on {issuetracker}.
|
`If you find any issues, don’t hesitate to report them on {issuetracker}.
|
||||||
And if you want to get involved, come join {designteam}!`
|
And if you want to get involved, come join {designteam}!`
|
||||||
)
|
)
|
||||||
.replace('{issuetracker}', this.issuetrackerLink)
|
.replace('{issuetracker}', this.issuetrackerLink)
|
||||||
.replace('{designteam}', this.designteamLink)
|
.replace('{designteam}', this.designteamLink)
|
||||||
},
|
},
|
||||||
issuetrackerLink() {
|
issuetrackerLink() {
|
||||||
return `<a target="_blank" href="https://github.com/nextcloud/server/issues/" rel="noreferrer nofollow">${t('accessibility', 'our issue tracker')}</a>`
|
return `<a target="_blank" href="https://github.com/nextcloud/server/issues/" rel="noreferrer nofollow">${t('accessibility', 'our issue tracker')}</a>`
|
||||||
|
@ -87,19 +83,28 @@ export default {
|
||||||
return `<a target="_blank" href="https://nextcloud.com/design" rel="noreferrer nofollow">${t('accessibility', 'our design team')}</a>`
|
return `<a target="_blank" href="https://nextcloud.com/design" rel="noreferrer nofollow">${t('accessibility', 'our design team')}</a>`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeMount() {
|
||||||
|
// importing server data into the app
|
||||||
|
const serverDataElmt = document.getElementById('serverData')
|
||||||
|
if (serverDataElmt !== null) {
|
||||||
|
this.serverData = JSON.parse(
|
||||||
|
document.getElementById('serverData').dataset.server
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectHighContrast(id) {
|
selectHighContrast(id) {
|
||||||
this.selectItem('highcontrast', id);
|
this.selectItem('highcontrast', id)
|
||||||
},
|
},
|
||||||
selectTheme(id, idSelectedBefore) {
|
selectTheme(id, idSelectedBefore) {
|
||||||
this.selectItem('theme', id);
|
this.selectItem('theme', id)
|
||||||
document.body.classList.remove(idSelectedBefore);
|
document.body.classList.remove(idSelectedBefore)
|
||||||
if (id) {
|
if (id) {
|
||||||
document.body.classList.add(id);
|
document.body.classList.add(id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectFont(id) {
|
selectFont(id) {
|
||||||
this.selectItem('font', id);
|
this.selectItem('font', id)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -110,43 +115,40 @@ export default {
|
||||||
* @param {string} id the data of the change
|
* @param {string} id the data of the change
|
||||||
*/
|
*/
|
||||||
selectItem(type, id) {
|
selectItem(type, id) {
|
||||||
axios.post(
|
axios.post(OC.linkToOCS('apps/accessibility/api/v1/config', 2) + type, { value: id })
|
||||||
OC.linkToOCS('apps/accessibility/api/v1/config', 2) + type,
|
|
||||||
{ value: id }
|
|
||||||
)
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.serverData.selected[type] = id;
|
this.serverData.selected[type] = id
|
||||||
|
|
||||||
// Remove old link
|
// Remove old link
|
||||||
let link = document.querySelector('link[rel=stylesheet][href*=accessibility][href*=user-]');
|
let link = document.querySelector('link[rel=stylesheet][href*=accessibility][href*=user-]')
|
||||||
if (!link) {
|
if (!link) {
|
||||||
// insert new css
|
// insert new css
|
||||||
let link = document.createElement('link');
|
let link = document.createElement('link')
|
||||||
link.rel = 'stylesheet';
|
link.rel = 'stylesheet'
|
||||||
link.href = OC.generateUrl('/apps/accessibility/css/user-style.css') + '?v=' + new Date().getTime();
|
link.href = OC.generateUrl('/apps/accessibility/css/user-style.css') + '?v=' + new Date().getTime()
|
||||||
document.head.appendChild(link);
|
document.head.appendChild(link)
|
||||||
} else {
|
} else {
|
||||||
// compare arrays
|
// compare arrays
|
||||||
if (
|
if (
|
||||||
JSON.stringify(Object.values(this.selected)) ===
|
JSON.stringify(Object.values(this.selected))
|
||||||
JSON.stringify([false, false])
|
=== JSON.stringify([false, false])
|
||||||
) {
|
) {
|
||||||
// if nothing is selected, blindly remove the css
|
// if nothing is selected, blindly remove the css
|
||||||
link.remove();
|
link.remove()
|
||||||
} else {
|
} else {
|
||||||
// force update
|
// force update
|
||||||
link.href =
|
link.href
|
||||||
link.href.split('?')[0] +
|
= link.href.split('?')[0]
|
||||||
'?v=' +
|
+ '?v='
|
||||||
new Date().getTime();
|
+ new Date().getTime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.log(err, err.response);
|
console.error(err, err.response)
|
||||||
OC.Notification.showTemporary(t('accessibility', err.response.data.ocs.meta.message + '. Unable to apply the setting.'));
|
OC.Notification.showTemporary(t('accessibility', err.response.data.ocs.meta.message + '. Unable to apply the setting.'))
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<div :class="{preview: true}">
|
||||||
|
<div class="preview-image" :style="{backgroundImage: 'url(' + preview.img + ')'}" />
|
||||||
|
<div class="preview-description">
|
||||||
|
<h3>{{ preview.title }}</h3>
|
||||||
|
<p>{{ preview.text }}</p>
|
||||||
|
<input :id="'accessibility-' + preview.id"
|
||||||
|
v-model="checked"
|
||||||
|
type="checkbox"
|
||||||
|
class="checkbox">
|
||||||
|
<label :for="'accessibility-' + preview.id">{{ t('accessibility', 'Enable') }} {{ preview.title.toLowerCase() }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ItemPreview',
|
||||||
|
props: {
|
||||||
|
preview: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
checked: {
|
||||||
|
get() {
|
||||||
|
return this.selected === this.preview.id
|
||||||
|
},
|
||||||
|
set(checked) {
|
||||||
|
this.$emit('select', checked ? this.preview.id : false, this.selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,28 +0,0 @@
|
||||||
<template>
|
|
||||||
<div :class="{preview: true}">
|
|
||||||
<div class="preview-image" :style="{backgroundImage: 'url(' + preview.img + ')'}"></div>
|
|
||||||
<div class="preview-description">
|
|
||||||
<h3>{{preview.title}}</h3>
|
|
||||||
<p>{{preview.text}}</p>
|
|
||||||
<input type="checkbox" class="checkbox" :id="'accessibility-' + preview.id" v-model="checked" />
|
|
||||||
<label :for="'accessibility-' + preview.id">{{t('accessibility', 'Enable')}} {{preview.title.toLowerCase()}}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'itemPreview',
|
|
||||||
props: ['preview', 'selected'],
|
|
||||||
computed: {
|
|
||||||
checked: {
|
|
||||||
get() {
|
|
||||||
return this.selected === this.preview.id;
|
|
||||||
},
|
|
||||||
set(checked) {
|
|
||||||
this.$emit('select', checked ? this.preview.id : false, this.selected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
|
@ -1,12 +1,12 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue'
|
||||||
import App from './App.vue';
|
import App from './Accessibility.vue'
|
||||||
|
|
||||||
/* global t */
|
/* global t */
|
||||||
// bind to window
|
// bind to window
|
||||||
Vue.prototype.OC = OC;
|
Vue.prototype.OC = OC
|
||||||
Vue.prototype.t = t;
|
Vue.prototype.t = t
|
||||||
|
|
||||||
new Vue({
|
export default new Vue({
|
||||||
el: '#accessibility',
|
el: '#accessibility',
|
||||||
render: h => h(App)
|
render: h => h(App)
|
||||||
});
|
})
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @author Joas Schilling <coding@schilljs.com>
|
* @author Joas Schilling <coding@schilljs.com>
|
||||||
* Copyright (c) 2016
|
* Copyright (c) 2016
|
||||||
*
|
*
|
||||||
|
@ -18,18 +18,18 @@
|
||||||
* @param {jQuery} $el jQuery handle for this activity
|
* @param {jQuery} $el jQuery handle for this activity
|
||||||
* @param {string} view The view that displayes this activity
|
* @param {string} view The view that displayes this activity
|
||||||
*/
|
*/
|
||||||
prepareModelForDisplay: function (model, $el, view) {
|
prepareModelForDisplay: function(model, $el, view) {
|
||||||
if (model.get('app') !== 'comments' || model.get('type') !== 'comments') {
|
if (model.get('app') !== 'comments' || model.get('type') !== 'comments') {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view === 'ActivityTabView') {
|
if (view === 'ActivityTabView') {
|
||||||
$el.addClass('comment');
|
$el.addClass('comment')
|
||||||
if (model.get('message') && this._isLong(model.get('message'))) {
|
if (model.get('message') && this._isLong(model.get('message'))) {
|
||||||
$el.addClass('collapsed');
|
$el.addClass('collapsed')
|
||||||
var $overlay = $('<div>').addClass('message-overlay');
|
var $overlay = $('<div>').addClass('message-overlay')
|
||||||
$el.find('.activitymessage').after($overlay);
|
$el.find('.activitymessage').after($overlay)
|
||||||
$el.on('click', this._onClickCollapsedComment);
|
$el.on('click', this._onClickCollapsedComment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -38,22 +38,21 @@
|
||||||
* Copy of CommentsTabView._onClickComment()
|
* Copy of CommentsTabView._onClickComment()
|
||||||
*/
|
*/
|
||||||
_onClickCollapsedComment: function(ev) {
|
_onClickCollapsedComment: function(ev) {
|
||||||
var $row = $(ev.target);
|
var $row = $(ev.target)
|
||||||
if (!$row.is('.comment')) {
|
if (!$row.is('.comment')) {
|
||||||
$row = $row.closest('.comment');
|
$row = $row.closest('.comment')
|
||||||
}
|
}
|
||||||
$row.removeClass('collapsed');
|
$row.removeClass('collapsed')
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copy of CommentsTabView._isLong()
|
* Copy of CommentsTabView._isLong()
|
||||||
*/
|
*/
|
||||||
_isLong: function(message) {
|
_isLong: function(message) {
|
||||||
return message.length > 250 || (message.match(/\n/g) || []).length > 1;
|
return message.length > 250 || (message.match(/\n/g) || []).length > 1
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
})()
|
||||||
|
|
||||||
})();
|
OC.Plugins.register('OCA.Activity.RenderingPlugins', OCA.Comments.ActivityTabViewPlugin)
|
||||||
|
|
||||||
OC.Plugins.register('OCA.Activity.RenderingPlugins', OCA.Comments.ActivityTabViewPlugin);
|
|
||||||
|
|
|
@ -13,8 +13,7 @@
|
||||||
/**
|
/**
|
||||||
* @namespace
|
* @namespace
|
||||||
*/
|
*/
|
||||||
OCA.Comments = {};
|
OCA.Comments = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})()
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2016
|
* Copyright (c) 2016
|
||||||
*
|
*
|
||||||
|
@ -20,148 +21,147 @@
|
||||||
var CommentCollection = OC.Backbone.Collection.extend(
|
var CommentCollection = OC.Backbone.Collection.extend(
|
||||||
/** @lends OCA.Comments.CommentCollection.prototype */ {
|
/** @lends OCA.Comments.CommentCollection.prototype */ {
|
||||||
|
|
||||||
sync: OC.Backbone.davSync,
|
sync: OC.Backbone.davSync,
|
||||||
|
|
||||||
model: OCA.Comments.CommentModel,
|
model: OCA.Comments.CommentModel,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object type
|
* Object type
|
||||||
*
|
*
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
_objectType: 'files',
|
_objectType: 'files',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object id
|
* Object id
|
||||||
*
|
*
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
_objectId: null,
|
_objectId: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if there are no more page results left to fetch
|
* True if there are no more page results left to fetch
|
||||||
*
|
*
|
||||||
* @type bool
|
* @type bool
|
||||||
*/
|
*/
|
||||||
_endReached: false,
|
_endReached: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of comments to fetch per page
|
* Number of comments to fetch per page
|
||||||
*
|
*
|
||||||
* @type int
|
* @type int
|
||||||
*/
|
*/
|
||||||
_limit : 20,
|
_limit: 20,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the collection
|
* Initializes the collection
|
||||||
*
|
*
|
||||||
* @param {string} [options.objectType] object type
|
* @param {string} [options.objectType] object type
|
||||||
* @param {string} [options.objectId] object id
|
* @param {string} [options.objectId] object id
|
||||||
*/
|
*/
|
||||||
initialize: function(models, options) {
|
initialize: function(models, options) {
|
||||||
options = options || {};
|
options = options || {}
|
||||||
if (options.objectType) {
|
if (options.objectType) {
|
||||||
this._objectType = options.objectType;
|
this._objectType = options.objectType
|
||||||
}
|
}
|
||||||
if (options.objectId) {
|
if (options.objectId) {
|
||||||
this._objectId = options.objectId;
|
this._objectId = options.objectId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
url: function() {
|
url: function() {
|
||||||
return OC.linkToRemote('dav') + '/comments/' +
|
return OC.linkToRemote('dav') + '/comments/'
|
||||||
encodeURIComponent(this._objectType) + '/' +
|
+ encodeURIComponent(this._objectType) + '/'
|
||||||
encodeURIComponent(this._objectId) + '/';
|
+ encodeURIComponent(this._objectId) + '/'
|
||||||
},
|
},
|
||||||
|
|
||||||
setObjectId: function(objectId) {
|
setObjectId: function(objectId) {
|
||||||
this._objectId = objectId;
|
this._objectId = objectId
|
||||||
},
|
},
|
||||||
|
|
||||||
hasMoreResults: function() {
|
hasMoreResults: function() {
|
||||||
return !this._endReached;
|
return !this._endReached
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: function() {
|
reset: function() {
|
||||||
this._endReached = false;
|
this._endReached = false
|
||||||
this._summaryModel = null;
|
this._summaryModel = null
|
||||||
return OC.Backbone.Collection.prototype.reset.apply(this, arguments);
|
return OC.Backbone.Collection.prototype.reset.apply(this, arguments)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the next set of results
|
* Fetch the next set of results
|
||||||
*/
|
*/
|
||||||
fetchNext: function(options) {
|
fetchNext: function(options) {
|
||||||
var self = this;
|
var self = this
|
||||||
if (!this.hasMoreResults()) {
|
if (!this.hasMoreResults()) {
|
||||||
return null;
|
return null
|
||||||
}
|
|
||||||
|
|
||||||
var body = '<?xml version="1.0" encoding="utf-8" ?>\n' +
|
|
||||||
'<oc:filter-comments xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">\n' +
|
|
||||||
// load one more so we know there is more
|
|
||||||
' <oc:limit>' + (this._limit + 1) + '</oc:limit>\n' +
|
|
||||||
' <oc:offset>' + this.length + '</oc:offset>\n' +
|
|
||||||
'</oc:filter-comments>\n';
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
var success = options.success;
|
|
||||||
options = _.extend({
|
|
||||||
remove: false,
|
|
||||||
parse: true,
|
|
||||||
data: body,
|
|
||||||
davProperties: CommentCollection.prototype.model.prototype.davProperties,
|
|
||||||
success: function(resp) {
|
|
||||||
if (resp.length <= self._limit) {
|
|
||||||
// no new entries, end reached
|
|
||||||
self._endReached = true;
|
|
||||||
} else {
|
|
||||||
// remove last entry, for next page load
|
|
||||||
resp = _.initial(resp);
|
|
||||||
}
|
|
||||||
if (!self.set(resp, options)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (success) {
|
|
||||||
success.apply(null, arguments);
|
|
||||||
}
|
|
||||||
self.trigger('sync', 'REPORT', self, options);
|
|
||||||
}
|
}
|
||||||
}, options);
|
|
||||||
|
|
||||||
return this.sync('REPORT', this, options);
|
var body = '<?xml version="1.0" encoding="utf-8" ?>\n'
|
||||||
},
|
+ '<oc:filter-comments xmlns:D="DAV:" xmlns:oc="http://owncloud.org/ns">\n'
|
||||||
|
// load one more so we know there is more
|
||||||
|
+ ' <oc:limit>' + (this._limit + 1) + '</oc:limit>\n'
|
||||||
|
+ ' <oc:offset>' + this.length + '</oc:offset>\n'
|
||||||
|
+ '</oc:filter-comments>\n'
|
||||||
|
|
||||||
/**
|
options = options || {}
|
||||||
|
var success = options.success
|
||||||
|
options = _.extend({
|
||||||
|
remove: false,
|
||||||
|
parse: true,
|
||||||
|
data: body,
|
||||||
|
davProperties: CommentCollection.prototype.model.prototype.davProperties,
|
||||||
|
success: function(resp) {
|
||||||
|
if (resp.length <= self._limit) {
|
||||||
|
// no new entries, end reached
|
||||||
|
self._endReached = true
|
||||||
|
} else {
|
||||||
|
// remove last entry, for next page load
|
||||||
|
resp = _.initial(resp)
|
||||||
|
}
|
||||||
|
if (!self.set(resp, options)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
success.apply(null, arguments)
|
||||||
|
}
|
||||||
|
self.trigger('sync', 'REPORT', self, options)
|
||||||
|
}
|
||||||
|
}, options)
|
||||||
|
|
||||||
|
return this.sync('REPORT', this, options)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
* Returns the matching summary model
|
* Returns the matching summary model
|
||||||
*
|
*
|
||||||
* @return {OCA.Comments.CommentSummaryModel} summary model
|
* @returns {OCA.Comments.CommentSummaryModel} summary model
|
||||||
*/
|
*/
|
||||||
getSummaryModel: function() {
|
getSummaryModel: function() {
|
||||||
if (!this._summaryModel) {
|
if (!this._summaryModel) {
|
||||||
this._summaryModel = new OCA.Comments.CommentSummaryModel({
|
this._summaryModel = new OCA.Comments.CommentSummaryModel({
|
||||||
id: this._objectId,
|
id: this._objectId,
|
||||||
objectType: this._objectType
|
objectType: this._objectType
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
return this._summaryModel;
|
return this._summaryModel
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the read marker for this comment thread
|
* Updates the read marker for this comment thread
|
||||||
*
|
*
|
||||||
* @param {Date} [date] optional date, defaults to now
|
* @param {Date} [date] optional date, defaults to now
|
||||||
* @param {Object} [options] backbone options
|
* @param {Object} [options] backbone options
|
||||||
*/
|
*/
|
||||||
updateReadMarker: function(date, options) {
|
updateReadMarker: function(date, options) {
|
||||||
options = options || {};
|
options = options || {}
|
||||||
|
|
||||||
return this.getSummaryModel().save({
|
return this.getSummaryModel().save({
|
||||||
readMarker: (date || new Date()).toUTCString()
|
readMarker: (date || new Date()).toUTCString()
|
||||||
}, options);
|
}, options)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
OCA.Comments.CommentCollection = CommentCollection;
|
|
||||||
})(OC, OCA);
|
|
||||||
|
|
||||||
|
OCA.Comments.CommentCollection = CommentCollection
|
||||||
|
})(OC, OCA)
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
_.extend(OC.Files.Client, {
|
_.extend(OC.Files.Client, {
|
||||||
PROPERTY_FILEID: '{' + OC.Files.Client.NS_OWNCLOUD + '}id',
|
PROPERTY_FILEID: '{' + OC.Files.Client.NS_OWNCLOUD + '}id',
|
||||||
PROPERTY_MESSAGE: '{' + OC.Files.Client.NS_OWNCLOUD + '}message',
|
PROPERTY_MESSAGE: '{' + OC.Files.Client.NS_OWNCLOUD + '}message',
|
||||||
PROPERTY_ACTORTYPE: '{' + OC.Files.Client.NS_OWNCLOUD + '}actorType',
|
PROPERTY_ACTORTYPE: '{' + OC.Files.Client.NS_OWNCLOUD + '}actorType',
|
||||||
PROPERTY_ACTORID: '{' + OC.Files.Client.NS_OWNCLOUD + '}actorId',
|
PROPERTY_ACTORID: '{' + OC.Files.Client.NS_OWNCLOUD + '}actorId',
|
||||||
PROPERTY_ISUNREAD: '{' + OC.Files.Client.NS_OWNCLOUD + '}isUnread',
|
PROPERTY_ISUNREAD: '{' + OC.Files.Client.NS_OWNCLOUD + '}isUnread',
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
PROPERTY_ACTORDISPLAYNAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}actorDisplayName',
|
PROPERTY_ACTORDISPLAYNAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}actorDisplayName',
|
||||||
PROPERTY_CREATIONDATETIME: '{' + OC.Files.Client.NS_OWNCLOUD + '}creationDateTime',
|
PROPERTY_CREATIONDATETIME: '{' + OC.Files.Client.NS_OWNCLOUD + '}creationDateTime',
|
||||||
PROPERTY_MENTIONS: '{' + OC.Files.Client.NS_OWNCLOUD + '}mentions'
|
PROPERTY_MENTIONS: '{' + OC.Files.Client.NS_OWNCLOUD + '}mentions'
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class OCA.Comments.CommentModel
|
* @class OCA.Comments.CommentModel
|
||||||
|
@ -32,62 +32,62 @@
|
||||||
*/
|
*/
|
||||||
var CommentModel = OC.Backbone.Model.extend(
|
var CommentModel = OC.Backbone.Model.extend(
|
||||||
/** @lends OCA.Comments.CommentModel.prototype */ {
|
/** @lends OCA.Comments.CommentModel.prototype */ {
|
||||||
sync: OC.Backbone.davSync,
|
sync: OC.Backbone.davSync,
|
||||||
|
|
||||||
defaults: {
|
defaults: {
|
||||||
actorType: 'users',
|
actorType: 'users',
|
||||||
objectType: 'files'
|
objectType: 'files'
|
||||||
},
|
},
|
||||||
|
|
||||||
davProperties: {
|
davProperties: {
|
||||||
'id': OC.Files.Client.PROPERTY_FILEID,
|
'id': OC.Files.Client.PROPERTY_FILEID,
|
||||||
'message': OC.Files.Client.PROPERTY_MESSAGE,
|
'message': OC.Files.Client.PROPERTY_MESSAGE,
|
||||||
'actorType': OC.Files.Client.PROPERTY_ACTORTYPE,
|
'actorType': OC.Files.Client.PROPERTY_ACTORTYPE,
|
||||||
'actorId': OC.Files.Client.PROPERTY_ACTORID,
|
'actorId': OC.Files.Client.PROPERTY_ACTORID,
|
||||||
'actorDisplayName': OC.Files.Client.PROPERTY_ACTORDISPLAYNAME,
|
'actorDisplayName': OC.Files.Client.PROPERTY_ACTORDISPLAYNAME,
|
||||||
'creationDateTime': OC.Files.Client.PROPERTY_CREATIONDATETIME,
|
'creationDateTime': OC.Files.Client.PROPERTY_CREATIONDATETIME,
|
||||||
'objectType': OC.Files.Client.PROPERTY_OBJECTTYPE,
|
'objectType': OC.Files.Client.PROPERTY_OBJECTTYPE,
|
||||||
'objectId': OC.Files.Client.PROPERTY_OBJECTID,
|
'objectId': OC.Files.Client.PROPERTY_OBJECTID,
|
||||||
'isUnread': OC.Files.Client.PROPERTY_ISUNREAD,
|
'isUnread': OC.Files.Client.PROPERTY_ISUNREAD,
|
||||||
'mentions': OC.Files.Client.PROPERTY_MENTIONS
|
'mentions': OC.Files.Client.PROPERTY_MENTIONS
|
||||||
},
|
},
|
||||||
|
|
||||||
parse: function(data) {
|
parse: function(data) {
|
||||||
return {
|
return {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
message: data.message,
|
message: data.message,
|
||||||
actorType: data.actorType,
|
actorType: data.actorType,
|
||||||
actorId: data.actorId,
|
actorId: data.actorId,
|
||||||
actorDisplayName: data.actorDisplayName,
|
actorDisplayName: data.actorDisplayName,
|
||||||
creationDateTime: data.creationDateTime,
|
creationDateTime: data.creationDateTime,
|
||||||
objectType: data.objectType,
|
objectType: data.objectType,
|
||||||
objectId: data.objectId,
|
objectId: data.objectId,
|
||||||
isUnread: (data.isUnread === 'true'),
|
isUnread: (data.isUnread === 'true'),
|
||||||
mentions: this._parseMentions(data.mentions)
|
mentions: this._parseMentions(data.mentions)
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
_parseMentions: function(mentions) {
|
|
||||||
if(_.isUndefined(mentions)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
var result = {};
|
|
||||||
for(var i in mentions) {
|
|
||||||
var mention = mentions[i];
|
|
||||||
if(_.isUndefined(mention.localName) || mention.localName !== 'mention') {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
result[i] = {};
|
},
|
||||||
for (var child = mention.firstChild; child; child = child.nextSibling) {
|
|
||||||
if(_.isUndefined(child.localName) || !child.localName.startsWith('mention')) {
|
_parseMentions: function(mentions) {
|
||||||
continue;
|
if (_.isUndefined(mentions)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
var result = {}
|
||||||
|
for (var i in mentions) {
|
||||||
|
var mention = mentions[i]
|
||||||
|
if (_.isUndefined(mention.localName) || mention.localName !== 'mention') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[i] = {}
|
||||||
|
for (var child = mention.firstChild; child; child = child.nextSibling) {
|
||||||
|
if (_.isUndefined(child.localName) || !child.localName.startsWith('mention')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[i][child.localName] = child.textContent
|
||||||
}
|
}
|
||||||
result[i][child.localName] = child.textContent;
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
return result;
|
})
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
OCA.Comments.CommentModel = CommentModel;
|
OCA.Comments.CommentModel = CommentModel
|
||||||
})(OC, OCA);
|
})(OC, OCA)
|
||||||
|
|
|
@ -15,4 +15,4 @@ import './vendor/At.js/dist/js/jquery.atwho.min'
|
||||||
import './style/autocomplete.scss'
|
import './style/autocomplete.scss'
|
||||||
import './style/comments.scss'
|
import './style/comments.scss'
|
||||||
|
|
||||||
window.OCA.Comments = OCA.Comments;
|
window.OCA.Comments = OCA.Comments
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global Handlebars */
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,7 +22,7 @@
|
||||||
_scopes: [
|
_scopes: [
|
||||||
{
|
{
|
||||||
name: 'edit',
|
name: 'edit',
|
||||||
displayName: t('comments', 'Edit comment'),
|
displayName: t('comments', 'Edit comment'),
|
||||||
iconClass: 'icon-rename'
|
iconClass: 'icon-rename'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -45,14 +44,14 @@
|
||||||
* @param {Object} event event object
|
* @param {Object} event event object
|
||||||
*/
|
*/
|
||||||
_onClickAction: function(event) {
|
_onClickAction: function(event) {
|
||||||
var $target = $(event.currentTarget);
|
var $target = $(event.currentTarget)
|
||||||
if (!$target.hasClass('menuitem')) {
|
if (!$target.hasClass('menuitem')) {
|
||||||
$target = $target.closest('.menuitem');
|
$target = $target.closest('.menuitem')
|
||||||
}
|
}
|
||||||
|
|
||||||
OC.hideMenus();
|
OC.hideMenus()
|
||||||
|
|
||||||
this.trigger('select:menu-item-clicked', event, $target.data('action'));
|
this.trigger('select:menu-item-clicked', event, $target.data('action'))
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,49 +60,49 @@
|
||||||
render: function() {
|
render: function() {
|
||||||
this.$el.html(OCA.Comments.Templates['commentsmodifymenu']({
|
this.$el.html(OCA.Comments.Templates['commentsmodifymenu']({
|
||||||
items: this._scopes
|
items: this._scopes
|
||||||
}));
|
}))
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the menu
|
* Displays the menu
|
||||||
|
* @param {Event} context the click event
|
||||||
*/
|
*/
|
||||||
show: function(context) {
|
show: function(context) {
|
||||||
this._context = context;
|
this._context = context
|
||||||
|
|
||||||
for(var i in this._scopes) {
|
for (var i in this._scopes) {
|
||||||
this._scopes[i].active = false;
|
this._scopes[i].active = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var $el = $(context.target)
|
||||||
var $el = $(context.target);
|
var offsetIcon = $el.offset()
|
||||||
var offsetIcon = $el.offset();
|
var offsetContainer = $el.closest('.authorRow').offset()
|
||||||
var offsetContainer = $el.closest('.authorRow').offset();
|
|
||||||
|
|
||||||
// adding some extra top offset to push the menu below the button.
|
// adding some extra top offset to push the menu below the button.
|
||||||
var position = {
|
var position = {
|
||||||
top: offsetIcon.top - offsetContainer.top + 48,
|
top: offsetIcon.top - offsetContainer.top + 48,
|
||||||
left: '',
|
left: '',
|
||||||
right: ''
|
right: ''
|
||||||
};
|
}
|
||||||
|
|
||||||
position.left = offsetIcon.left - offsetContainer.left;
|
position.left = offsetIcon.left - offsetContainer.left
|
||||||
|
|
||||||
if (position.left > 200) {
|
if (position.left > 200) {
|
||||||
// we need to position the menu to the right.
|
// we need to position the menu to the right.
|
||||||
position.left = '';
|
position.left = ''
|
||||||
position.right = this.$el.closest('.comment').find('.date').width();
|
position.right = this.$el.closest('.comment').find('.date').width()
|
||||||
this.$el.removeClass('menu-left').addClass('menu-right');
|
this.$el.removeClass('menu-left').addClass('menu-right')
|
||||||
} else {
|
} else {
|
||||||
this.$el.removeClass('menu-right').addClass('menu-left');
|
this.$el.removeClass('menu-right').addClass('menu-left')
|
||||||
}
|
}
|
||||||
this.$el.css(position);
|
this.$el.css(position)
|
||||||
this.render();
|
this.render()
|
||||||
this.$el.removeClass('hidden');
|
this.$el.removeClass('hidden')
|
||||||
|
|
||||||
OC.showMenu(null, this.$el);
|
OC.showMenu(null, this.$el)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
OCA.Comments = OCA.Comments || {};
|
OCA.Comments = OCA.Comments || {}
|
||||||
OCA.Comments.CommentsModifyMenu = CommentsModifyMenu;
|
OCA.Comments.CommentsModifyMenu = CommentsModifyMenu
|
||||||
})(OC, OCA);
|
})(OC, OCA)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
_.extend(OC.Files.Client, {
|
_.extend(OC.Files.Client, {
|
||||||
PROPERTY_READMARKER: '{' + OC.Files.Client.NS_OWNCLOUD + '}readMarker'
|
PROPERTY_READMARKER: '{' + OC.Files.Client.NS_OWNCLOUD + '}readMarker'
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class OCA.Comments.CommentSummaryModel
|
* @class OCA.Comments.CommentSummaryModel
|
||||||
|
@ -24,45 +24,47 @@
|
||||||
*/
|
*/
|
||||||
var CommentSummaryModel = OC.Backbone.Model.extend(
|
var CommentSummaryModel = OC.Backbone.Model.extend(
|
||||||
/** @lends OCA.Comments.CommentSummaryModel.prototype */ {
|
/** @lends OCA.Comments.CommentSummaryModel.prototype */ {
|
||||||
sync: OC.Backbone.davSync,
|
sync: OC.Backbone.davSync,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object type
|
* Object type
|
||||||
*
|
*
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
_objectType: 'files',
|
_objectType: 'files',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object id
|
* Object id
|
||||||
*
|
*
|
||||||
* @type string
|
* @type string
|
||||||
*/
|
*/
|
||||||
_objectId: null,
|
_objectId: null,
|
||||||
|
|
||||||
davProperties: {
|
davProperties: {
|
||||||
'readMarker': OC.Files.Client.PROPERTY_READMARKER
|
'readMarker': OC.Files.Client.PROPERTY_READMARKER
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the summary model
|
* Initializes the summary model
|
||||||
*
|
*
|
||||||
* @param {string} [options.objectType] object type
|
* @param {any} [attrs] ignored
|
||||||
* @param {string} [options.objectId] object id
|
* @param {Object} [options] destructuring object
|
||||||
*/
|
* @param {string} [options.objectType] object type
|
||||||
initialize: function(attrs, options) {
|
* @param {string} [options.objectId] object id
|
||||||
options = options || {};
|
*/
|
||||||
if (options.objectType) {
|
initialize: function(attrs, options) {
|
||||||
this._objectType = options.objectType;
|
options = options || {}
|
||||||
|
if (options.objectType) {
|
||||||
|
this._objectType = options.objectType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
url: function() {
|
||||||
|
return OC.linkToRemote('dav') + '/comments/'
|
||||||
|
+ encodeURIComponent(this._objectType) + '/'
|
||||||
|
+ encodeURIComponent(this.id) + '/'
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
|
|
||||||
url: function() {
|
OCA.Comments.CommentSummaryModel = CommentSummaryModel
|
||||||
return OC.linkToRemote('dav') + '/comments/' +
|
})(OC, OCA)
|
||||||
encodeURIComponent(this._objectType) + '/' +
|
|
||||||
encodeURIComponent(this.id) + '/';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
OCA.Comments.CommentSummaryModel = CommentSummaryModel;
|
|
||||||
})(OC, OCA);
|
|
||||||
|
|
|
@ -8,20 +8,18 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global Handlebars */
|
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
_.extend(OC.Files.Client, {
|
_.extend(OC.Files.Client, {
|
||||||
PROPERTY_COMMENTS_UNREAD: '{' + OC.Files.Client.NS_OWNCLOUD + '}comments-unread'
|
PROPERTY_COMMENTS_UNREAD: '{' + OC.Files.Client.NS_OWNCLOUD + '}comments-unread'
|
||||||
});
|
})
|
||||||
|
|
||||||
OCA.Comments = _.extend({}, OCA.Comments);
|
OCA.Comments = _.extend({}, OCA.Comments)
|
||||||
if (!OCA.Comments) {
|
if (!OCA.Comments) {
|
||||||
/**
|
/**
|
||||||
* @namespace
|
* @namespace
|
||||||
*/
|
*/
|
||||||
OCA.Comments = {};
|
OCA.Comments = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,43 +36,43 @@
|
||||||
count: count,
|
count: count,
|
||||||
countMessage: n('comments', '%n unread comment', '%n unread comments', count),
|
countMessage: n('comments', '%n unread comment', '%n unread comments', count),
|
||||||
iconUrl: OC.imagePath('core', 'actions/comment')
|
iconUrl: OC.imagePath('core', 'actions/comment')
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
attach: function(fileList) {
|
attach: function(fileList) {
|
||||||
var self = this;
|
var self = this
|
||||||
if (this.ignoreLists.indexOf(fileList.id) >= 0) {
|
if (this.ignoreLists.indexOf(fileList.id) >= 0) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileList.registerTabView(new OCA.Comments.CommentsTabView('commentsTabView'));
|
fileList.registerTabView(new OCA.Comments.CommentsTabView('commentsTabView'))
|
||||||
|
|
||||||
var oldGetWebdavProperties = fileList._getWebdavProperties;
|
var oldGetWebdavProperties = fileList._getWebdavProperties
|
||||||
fileList._getWebdavProperties = function() {
|
fileList._getWebdavProperties = function() {
|
||||||
var props = oldGetWebdavProperties.apply(this, arguments);
|
var props = oldGetWebdavProperties.apply(this, arguments)
|
||||||
props.push(OC.Files.Client.PROPERTY_COMMENTS_UNREAD);
|
props.push(OC.Files.Client.PROPERTY_COMMENTS_UNREAD)
|
||||||
return props;
|
return props
|
||||||
};
|
}
|
||||||
|
|
||||||
fileList.filesClient.addFileInfoParser(function(response) {
|
fileList.filesClient.addFileInfoParser(function(response) {
|
||||||
var data = {};
|
var data = {}
|
||||||
var props = response.propStat[0].properties;
|
var props = response.propStat[0].properties
|
||||||
var commentsUnread = props[OC.Files.Client.PROPERTY_COMMENTS_UNREAD];
|
var commentsUnread = props[OC.Files.Client.PROPERTY_COMMENTS_UNREAD]
|
||||||
if (!_.isUndefined(commentsUnread) && commentsUnread !== '') {
|
if (!_.isUndefined(commentsUnread) && commentsUnread !== '') {
|
||||||
data.commentsUnread = parseInt(commentsUnread, 10);
|
data.commentsUnread = parseInt(commentsUnread, 10)
|
||||||
}
|
}
|
||||||
return data;
|
return data
|
||||||
});
|
})
|
||||||
|
|
||||||
fileList.$el.addClass('has-comments');
|
fileList.$el.addClass('has-comments')
|
||||||
var oldCreateRow = fileList._createRow;
|
var oldCreateRow = fileList._createRow
|
||||||
fileList._createRow = function(fileData) {
|
fileList._createRow = function(fileData) {
|
||||||
var $tr = oldCreateRow.apply(this, arguments);
|
var $tr = oldCreateRow.apply(this, arguments)
|
||||||
if (fileData.commentsUnread) {
|
if (fileData.commentsUnread) {
|
||||||
$tr.attr('data-comments-unread', fileData.commentsUnread);
|
$tr.attr('data-comments-unread', fileData.commentsUnread)
|
||||||
}
|
}
|
||||||
return $tr;
|
return $tr
|
||||||
};
|
}
|
||||||
|
|
||||||
// register "comment" action for reading comments
|
// register "comment" action for reading comments
|
||||||
fileList.fileActions.registerAction({
|
fileList.fileActions.registerAction({
|
||||||
|
@ -94,35 +92,35 @@
|
||||||
permissions: OC.PERMISSION_READ,
|
permissions: OC.PERMISSION_READ,
|
||||||
type: OCA.Files.FileActions.TYPE_INLINE,
|
type: OCA.Files.FileActions.TYPE_INLINE,
|
||||||
render: function(actionSpec, isDefault, context) {
|
render: function(actionSpec, isDefault, context) {
|
||||||
var $file = context.$file;
|
var $file = context.$file
|
||||||
var unreadComments = $file.data('comments-unread');
|
var unreadComments = $file.data('comments-unread')
|
||||||
if (unreadComments) {
|
if (unreadComments) {
|
||||||
var $actionLink = $(self._formatCommentCount(unreadComments));
|
var $actionLink = $(self._formatCommentCount(unreadComments))
|
||||||
context.$file.find('a.name>span.fileactions').append($actionLink);
|
context.$file.find('a.name>span.fileactions').append($actionLink)
|
||||||
return $actionLink;
|
return $actionLink
|
||||||
}
|
}
|
||||||
return '';
|
return ''
|
||||||
},
|
},
|
||||||
actionHandler: function(fileName, context) {
|
actionHandler: function(fileName, context) {
|
||||||
context.$file.find('.action-comment').tooltip('hide');
|
context.$file.find('.action-comment').tooltip('hide')
|
||||||
// open sidebar in comments section
|
// open sidebar in comments section
|
||||||
context.fileList.showDetailsView(fileName, 'commentsTabView');
|
context.fileList.showDetailsView(fileName, 'commentsTabView')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// add attribute to "elementToFile"
|
// add attribute to "elementToFile"
|
||||||
var oldElementToFile = fileList.elementToFile;
|
var oldElementToFile = fileList.elementToFile
|
||||||
fileList.elementToFile = function($el) {
|
fileList.elementToFile = function($el) {
|
||||||
var fileInfo = oldElementToFile.apply(this, arguments);
|
var fileInfo = oldElementToFile.apply(this, arguments)
|
||||||
var commentsUnread = $el.data('comments-unread');
|
var commentsUnread = $el.data('comments-unread')
|
||||||
if (commentsUnread) {
|
if (commentsUnread) {
|
||||||
fileInfo.commentsUnread = commentsUnread;
|
fileInfo.commentsUnread = commentsUnread
|
||||||
}
|
}
|
||||||
return fileInfo;
|
return fileInfo
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
})();
|
})()
|
||||||
|
|
||||||
OC.Plugins.register('OCA.Files.FileList', OCA.Comments.FilesPlugin);
|
OC.Plugins.register('OCA.Files.FileList', OCA.Comments.FilesPlugin)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014
|
* Copyright (c) 2014
|
||||||
*
|
*
|
||||||
|
@ -8,15 +9,15 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
(function(OC, OCA, $) {
|
(function(OC, OCA, $) {
|
||||||
"use strict";
|
'use strict'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new FileActions instance
|
* Construct a new FileActions instance
|
||||||
* @constructs Files
|
* @constructs Files
|
||||||
*/
|
*/
|
||||||
var Comment = function() {
|
var Comment = function() {
|
||||||
this.initialize();
|
this.initialize()
|
||||||
};
|
}
|
||||||
|
|
||||||
Comment.prototype = {
|
Comment.prototype = {
|
||||||
|
|
||||||
|
@ -27,25 +28,25 @@
|
||||||
*/
|
*/
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
|
|
||||||
var self = this;
|
var self = this
|
||||||
|
|
||||||
this.fileAppLoaded = function() {
|
this.fileAppLoaded = function() {
|
||||||
return !!OCA.Files && !!OCA.Files.App;
|
return !!OCA.Files && !!OCA.Files.App
|
||||||
};
|
}
|
||||||
function inFileList($row, result) {
|
function inFileList($row, result) {
|
||||||
return false;
|
return false
|
||||||
|
|
||||||
if (! self.fileAppLoaded()) {
|
if (!self.fileAppLoaded()) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
var dir = self.fileList.getCurrentDirectory().replace(/\/+$/,'');
|
var dir = self.fileList.getCurrentDirectory().replace(/\/+$/, '')
|
||||||
var resultDir = OC.dirname(result.path);
|
var resultDir = OC.dirname(result.path)
|
||||||
return dir === resultDir && self.fileList.inList(result.name);
|
return dir === resultDir && self.fileList.inList(result.name)
|
||||||
}
|
}
|
||||||
function hideNoFilterResults() {
|
function hideNoFilterResults() {
|
||||||
var $nofilterresults = $('.nofilterresults');
|
var $nofilterresults = $('.nofilterresults')
|
||||||
if ( ! $nofilterresults.hasClass('hidden') ) {
|
if (!$nofilterresults.hasClass('hidden')) {
|
||||||
$nofilterresults.addClass('hidden');
|
$nofilterresults.addClass('hidden')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,73 +65,73 @@
|
||||||
*/
|
*/
|
||||||
this.renderCommentResult = function($row, result) {
|
this.renderCommentResult = function($row, result) {
|
||||||
if (inFileList($row, result)) {
|
if (inFileList($row, result)) {
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
hideNoFilterResults();
|
hideNoFilterResults()
|
||||||
/*render preview icon, show path beneath filename,
|
/* render preview icon, show path beneath filename,
|
||||||
show size and last modified date on the right */
|
show size and last modified date on the right */
|
||||||
this.updateLegacyMimetype(result);
|
this.updateLegacyMimetype(result)
|
||||||
|
|
||||||
var $pathDiv = $('<div>').addClass('path').text(result.path);
|
var $pathDiv = $('<div>').addClass('path').text(result.path)
|
||||||
|
|
||||||
var $avatar = $('<div>');
|
var $avatar = $('<div>')
|
||||||
$avatar.addClass('avatar')
|
$avatar.addClass('avatar')
|
||||||
.css('display', 'inline-block')
|
.css('display', 'inline-block')
|
||||||
.css('vertical-align', 'middle')
|
.css('vertical-align', 'middle')
|
||||||
.css('margin', '0 5px 2px 3px');
|
.css('margin', '0 5px 2px 3px')
|
||||||
|
|
||||||
if (result.authorName) {
|
if (result.authorName) {
|
||||||
$avatar.avatar(result.authorId, 21, undefined, false, undefined, result.authorName);
|
$avatar.avatar(result.authorId, 21, undefined, false, undefined, result.authorName)
|
||||||
} else {
|
} else {
|
||||||
$avatar.avatar(result.authorId, 21);
|
$avatar.avatar(result.authorId, 21)
|
||||||
}
|
}
|
||||||
|
|
||||||
$row.find('td.info div.name').after($pathDiv).text(result.comment).prepend($('<span>').addClass('path').css('margin-right', '5px').text(result.authorName)).prepend($avatar);
|
$row.find('td.info div.name').after($pathDiv).text(result.comment).prepend($('<span>').addClass('path').css('margin-right', '5px').text(result.authorName)).prepend($avatar)
|
||||||
$row.find('td.result a').attr('href', result.link);
|
$row.find('td.result a').attr('href', result.link)
|
||||||
|
|
||||||
$row.find('td.icon')
|
$row.find('td.icon')
|
||||||
.css('background-image', 'url(' + OC.imagePath('core', 'actions/comment') + ')')
|
.css('background-image', 'url(' + OC.imagePath('core', 'actions/comment') + ')')
|
||||||
.css('opacity', '.4');
|
.css('opacity', '.4')
|
||||||
var dir = OC.dirname(result.path);
|
var dir = OC.dirname(result.path)
|
||||||
// "result.path" does not include a leading "/", so "OC.dirname"
|
// "result.path" does not include a leading "/", so "OC.dirname"
|
||||||
// returns the path itself for files or folders in the root.
|
// returns the path itself for files or folders in the root.
|
||||||
if (dir === result.path) {
|
if (dir === result.path) {
|
||||||
dir = '/';
|
dir = '/'
|
||||||
}
|
}
|
||||||
$row.find('td.info a').attr('href',
|
$row.find('td.info a').attr('href',
|
||||||
OC.generateUrl('/apps/files/?dir={dir}&scrollto={scrollto}', {dir: dir, scrollto: result.fileName})
|
OC.generateUrl('/apps/files/?dir={dir}&scrollto={scrollto}', { dir: dir, scrollto: result.fileName })
|
||||||
);
|
)
|
||||||
|
|
||||||
return $row;
|
return $row
|
||||||
};
|
}
|
||||||
|
|
||||||
this.handleCommentClick = function($row, result, event) {
|
this.handleCommentClick = function($row, result, event) {
|
||||||
if (self.fileAppLoaded() && self.fileList.id === 'files') {
|
if (self.fileAppLoaded() && self.fileList.id === 'files') {
|
||||||
self.fileList.changeDirectory(OC.dirname(result.path));
|
self.fileList.changeDirectory(OC.dirname(result.path))
|
||||||
self.fileList.scrollTo(result.name);
|
self.fileList.scrollTo(result.name)
|
||||||
return false;
|
return false
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
this.updateLegacyMimetype = function (result) {
|
this.updateLegacyMimetype = function(result) {
|
||||||
// backward compatibility:
|
// backward compatibility:
|
||||||
if (!result.mime && result.mime_type) {
|
if (!result.mime && result.mime_type) {
|
||||||
result.mime = result.mime_type;
|
result.mime = result.mime_type
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
this.setFileList = function (fileList) {
|
this.setFileList = function(fileList) {
|
||||||
this.fileList = fileList;
|
this.fileList = fileList
|
||||||
};
|
}
|
||||||
|
|
||||||
OC.Plugins.register('OCA.Search.Core', this);
|
OC.Plugins.register('OCA.Search.Core', this)
|
||||||
},
|
},
|
||||||
attach: function(search) {
|
attach: function(search) {
|
||||||
search.setRenderer('comment', this.renderCommentResult.bind(this));
|
search.setRenderer('comment', this.renderCommentResult.bind(this))
|
||||||
search.setHandler('comment', this.handleCommentClick.bind(this));
|
search.setHandler('comment', this.handleCommentClick.bind(this))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
OCA.Search.comment = new Comment();
|
OCA.Search.comment = new Comment()
|
||||||
})(OC, OCA, $);
|
})(OC, OCA, $)
|
||||||
|
|
|
@ -727,11 +727,11 @@ OC.Uploader.prototype = _.extend({
|
||||||
*
|
*
|
||||||
* @param {array} selection of files to upload
|
* @param {array} selection of files to upload
|
||||||
* @param {object} callbacks - object with several callback methods
|
* @param {object} callbacks - object with several callback methods
|
||||||
* @param {function} callbacks.onNoConflicts
|
* @param {Function} callbacks.onNoConflicts
|
||||||
* @param {function} callbacks.onSkipConflicts
|
* @param {Function} callbacks.onSkipConflicts
|
||||||
* @param {function} callbacks.onReplaceConflicts
|
* @param {Function} callbacks.onReplaceConflicts
|
||||||
* @param {function} callbacks.onChooseConflicts
|
* @param {Function} callbacks.onChooseConflicts
|
||||||
* @param {function} callbacks.onCancel
|
* @param {Function} callbacks.onCancel
|
||||||
*/
|
*/
|
||||||
checkExistingFiles: function (selection, callbacks) {
|
checkExistingFiles: function (selection, callbacks) {
|
||||||
var fileList = this.fileList;
|
var fileList = this.fileList;
|
||||||
|
|
|
@ -336,7 +336,7 @@
|
||||||
* - JS periodically checks for this cookie and then knows when the download has started to call the callback
|
* - JS periodically checks for this cookie and then knows when the download has started to call the callback
|
||||||
*
|
*
|
||||||
* @param {string} url download URL
|
* @param {string} url download URL
|
||||||
* @param {function} callback function to call once the download has started
|
* @param {Function} callback function to call once the download has started
|
||||||
*/
|
*/
|
||||||
handleDownload: function(url, callback) {
|
handleDownload: function(url, callback) {
|
||||||
var randomToken = Math.random().toString(36).substring(2),
|
var randomToken = Math.random().toString(36).substring(2),
|
||||||
|
|
|
@ -125,7 +125,7 @@ OCA.Files_External.StatusManager = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to get external mount point list from the files_external API
|
* Function to get external mount point list from the files_external API
|
||||||
* @param {function} afterCallback function to be executed
|
* @param {Function} afterCallback function to be executed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
getMountPointList: function (afterCallback) {
|
getMountPointList: function (afterCallback) {
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es6: true
|
|
||||||
},
|
|
||||||
globals: {
|
|
||||||
t: true,
|
|
||||||
n: true,
|
|
||||||
OC: true,
|
|
||||||
OCA: true
|
|
||||||
},
|
|
||||||
extends: 'eslint:recommended',
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: 'module'
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
indent: ['error', 'tab'],
|
|
||||||
'linebreak-style': ['error', 'unix'],
|
|
||||||
quotes: ['error', 'single'],
|
|
||||||
semi: ['error', 'always']
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -12,7 +12,7 @@ if (!OCA.Sharing) {
|
||||||
/**
|
/**
|
||||||
* @namespace OCA.Sharing
|
* @namespace OCA.Sharing
|
||||||
*/
|
*/
|
||||||
OCA.Sharing = {};
|
OCA.Sharing = {}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @namespace
|
* @namespace
|
||||||
|
@ -25,7 +25,7 @@ OCA.Sharing.App = {
|
||||||
|
|
||||||
initSharingIn: function($el) {
|
initSharingIn: function($el) {
|
||||||
if (this._inFileList) {
|
if (this._inFileList) {
|
||||||
return this._inFileList;
|
return this._inFileList
|
||||||
}
|
}
|
||||||
|
|
||||||
this._inFileList = new OCA.Sharing.FileList(
|
this._inFileList = new OCA.Sharing.FileList(
|
||||||
|
@ -40,19 +40,19 @@ OCA.Sharing.App = {
|
||||||
// if handling the event with the file list already created.
|
// if handling the event with the file list already created.
|
||||||
shown: true
|
shown: true
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
this._extendFileList(this._inFileList);
|
this._extendFileList(this._inFileList)
|
||||||
this._inFileList.appName = t('files_sharing', 'Shared with you');
|
this._inFileList.appName = t('files_sharing', 'Shared with you')
|
||||||
this._inFileList.$el.find('#emptycontent').html('<div class="icon-shared"></div>' +
|
this._inFileList.$el.find('#emptycontent').html('<div class="icon-shared"></div>'
|
||||||
'<h2>' + t('files_sharing', 'Nothing shared with you yet') + '</h2>' +
|
+ '<h2>' + t('files_sharing', 'Nothing shared with you yet') + '</h2>'
|
||||||
'<p>' + t('files_sharing', 'Files and folders others share with you will show up here') + '</p>');
|
+ '<p>' + t('files_sharing', 'Files and folders others share with you will show up here') + '</p>')
|
||||||
return this._inFileList;
|
return this._inFileList
|
||||||
},
|
},
|
||||||
|
|
||||||
initSharingOut: function($el) {
|
initSharingOut: function($el) {
|
||||||
if (this._outFileList) {
|
if (this._outFileList) {
|
||||||
return this._outFileList;
|
return this._outFileList
|
||||||
}
|
}
|
||||||
this._outFileList = new OCA.Sharing.FileList(
|
this._outFileList = new OCA.Sharing.FileList(
|
||||||
$el,
|
$el,
|
||||||
|
@ -66,19 +66,19 @@ OCA.Sharing.App = {
|
||||||
// if handling the event with the file list already created.
|
// if handling the event with the file list already created.
|
||||||
shown: true
|
shown: true
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
this._extendFileList(this._outFileList);
|
this._extendFileList(this._outFileList)
|
||||||
this._outFileList.appName = t('files_sharing', 'Shared with others');
|
this._outFileList.appName = t('files_sharing', 'Shared with others')
|
||||||
this._outFileList.$el.find('#emptycontent').html('<div class="icon-shared"></div>' +
|
this._outFileList.$el.find('#emptycontent').html('<div class="icon-shared"></div>'
|
||||||
'<h2>' + t('files_sharing', 'Nothing shared yet') + '</h2>' +
|
+ '<h2>' + t('files_sharing', 'Nothing shared yet') + '</h2>'
|
||||||
'<p>' + t('files_sharing', 'Files and folders you share will show up here') + '</p>');
|
+ '<p>' + t('files_sharing', 'Files and folders you share will show up here') + '</p>')
|
||||||
return this._outFileList;
|
return this._outFileList
|
||||||
},
|
},
|
||||||
|
|
||||||
initSharingLinks: function($el) {
|
initSharingLinks: function($el) {
|
||||||
if (this._linkFileList) {
|
if (this._linkFileList) {
|
||||||
return this._linkFileList;
|
return this._linkFileList
|
||||||
}
|
}
|
||||||
this._linkFileList = new OCA.Sharing.FileList(
|
this._linkFileList = new OCA.Sharing.FileList(
|
||||||
$el,
|
$el,
|
||||||
|
@ -92,19 +92,19 @@ OCA.Sharing.App = {
|
||||||
// if handling the event with the file list already created.
|
// if handling the event with the file list already created.
|
||||||
shown: true
|
shown: true
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
this._extendFileList(this._linkFileList);
|
this._extendFileList(this._linkFileList)
|
||||||
this._linkFileList.appName = t('files_sharing', 'Shared by link');
|
this._linkFileList.appName = t('files_sharing', 'Shared by link')
|
||||||
this._linkFileList.$el.find('#emptycontent').html('<div class="icon-public"></div>' +
|
this._linkFileList.$el.find('#emptycontent').html('<div class="icon-public"></div>'
|
||||||
'<h2>' + t('files_sharing', 'No shared links') + '</h2>' +
|
+ '<h2>' + t('files_sharing', 'No shared links') + '</h2>'
|
||||||
'<p>' + t('files_sharing', 'Files and folders you share by link will show up here') + '</p>');
|
+ '<p>' + t('files_sharing', 'Files and folders you share by link will show up here') + '</p>')
|
||||||
return this._linkFileList;
|
return this._linkFileList
|
||||||
},
|
},
|
||||||
|
|
||||||
initSharingDeleted: function($el) {
|
initSharingDeleted: function($el) {
|
||||||
if (this._deletedFileList) {
|
if (this._deletedFileList) {
|
||||||
return this._deletedFileList;
|
return this._deletedFileList
|
||||||
}
|
}
|
||||||
this._deletedFileList = new OCA.Sharing.FileList(
|
this._deletedFileList = new OCA.Sharing.FileList(
|
||||||
$el,
|
$el,
|
||||||
|
@ -119,19 +119,19 @@ OCA.Sharing.App = {
|
||||||
// if handling the event with the file list already created.
|
// if handling the event with the file list already created.
|
||||||
shown: true
|
shown: true
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
this._extendFileList(this._deletedFileList);
|
this._extendFileList(this._deletedFileList)
|
||||||
this._deletedFileList.appName = t('files_sharing', 'Deleted shares');
|
this._deletedFileList.appName = t('files_sharing', 'Deleted shares')
|
||||||
this._deletedFileList.$el.find('#emptycontent').html('<div class="icon-share"></div>' +
|
this._deletedFileList.$el.find('#emptycontent').html('<div class="icon-share"></div>'
|
||||||
'<h2>' + t('files_sharing', 'No deleted shares') + '</h2>' +
|
+ '<h2>' + t('files_sharing', 'No deleted shares') + '</h2>'
|
||||||
'<p>' + t('files_sharing', 'Shares you deleted will show up here') + '</p>');
|
+ '<p>' + t('files_sharing', 'Shares you deleted will show up here') + '</p>')
|
||||||
return this._deletedFileList;
|
return this._deletedFileList
|
||||||
},
|
},
|
||||||
|
|
||||||
initShareingOverview: function($el) {
|
initShareingOverview: function($el) {
|
||||||
if (this._overviewFileList) {
|
if (this._overviewFileList) {
|
||||||
return this._overviewFileList;
|
return this._overviewFileList
|
||||||
}
|
}
|
||||||
this._overviewFileList = new OCA.Sharing.FileList(
|
this._overviewFileList = new OCA.Sharing.FileList(
|
||||||
$el,
|
$el,
|
||||||
|
@ -144,43 +144,43 @@ OCA.Sharing.App = {
|
||||||
// if handling the event with the file list already created.
|
// if handling the event with the file list already created.
|
||||||
shown: true
|
shown: true
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
this._extendFileList(this._overviewFileList);
|
this._extendFileList(this._overviewFileList)
|
||||||
this._overviewFileList.appName = t('files_sharing', 'Shares');
|
this._overviewFileList.appName = t('files_sharing', 'Shares')
|
||||||
this._overviewFileList.$el.find('#emptycontent').html('<div class="icon-share"></div>' +
|
this._overviewFileList.$el.find('#emptycontent').html('<div class="icon-share"></div>'
|
||||||
'<h2>' + t('files_sharing', 'No shares') + '</h2>' +
|
+ '<h2>' + t('files_sharing', 'No shares') + '</h2>'
|
||||||
'<p>' + t('files_sharing', 'Shares will show up here') + '</p>');
|
+ '<p>' + t('files_sharing', 'Shares will show up here') + '</p>')
|
||||||
return this._overviewFileList;
|
return this._overviewFileList
|
||||||
},
|
},
|
||||||
|
|
||||||
removeSharingIn: function() {
|
removeSharingIn: function() {
|
||||||
if (this._inFileList) {
|
if (this._inFileList) {
|
||||||
this._inFileList.$fileList.empty();
|
this._inFileList.$fileList.empty()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeSharingOut: function() {
|
removeSharingOut: function() {
|
||||||
if (this._outFileList) {
|
if (this._outFileList) {
|
||||||
this._outFileList.$fileList.empty();
|
this._outFileList.$fileList.empty()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeSharingLinks: function() {
|
removeSharingLinks: function() {
|
||||||
if (this._linkFileList) {
|
if (this._linkFileList) {
|
||||||
this._linkFileList.$fileList.empty();
|
this._linkFileList.$fileList.empty()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeSharingDeleted: function() {
|
removeSharingDeleted: function() {
|
||||||
if (this._deletedFileList) {
|
if (this._deletedFileList) {
|
||||||
this._deletedFileList.$fileList.empty();
|
this._deletedFileList.$fileList.empty()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeSharingOverview: function() {
|
removeSharingOverview: function() {
|
||||||
if (this._overviewFileList) {
|
if (this._overviewFileList) {
|
||||||
this._overviewFileList.$fileList.empty();
|
this._overviewFileList.$fileList.empty()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -188,46 +188,46 @@ OCA.Sharing.App = {
|
||||||
* Destroy the app
|
* Destroy the app
|
||||||
*/
|
*/
|
||||||
destroy: function() {
|
destroy: function() {
|
||||||
OCA.Files.fileActions.off('setDefault.app-sharing', this._onActionsUpdated);
|
OCA.Files.fileActions.off('setDefault.app-sharing', this._onActionsUpdated)
|
||||||
OCA.Files.fileActions.off('registerAction.app-sharing', this._onActionsUpdated);
|
OCA.Files.fileActions.off('registerAction.app-sharing', this._onActionsUpdated)
|
||||||
this.removeSharingIn();
|
this.removeSharingIn()
|
||||||
this.removeSharingOut();
|
this.removeSharingOut()
|
||||||
this.removeSharingLinks();
|
this.removeSharingLinks()
|
||||||
this._inFileList = null;
|
this._inFileList = null
|
||||||
this._outFileList = null;
|
this._outFileList = null
|
||||||
this._linkFileList = null;
|
this._linkFileList = null
|
||||||
this._overviewFileList = null;
|
this._overviewFileList = null
|
||||||
delete this._globalActionsInitialized;
|
delete this._globalActionsInitialized
|
||||||
},
|
},
|
||||||
|
|
||||||
_createFileActions: function() {
|
_createFileActions: function() {
|
||||||
// inherit file actions from the files app
|
// inherit file actions from the files app
|
||||||
var fileActions = new OCA.Files.FileActions();
|
var fileActions = new OCA.Files.FileActions()
|
||||||
// note: not merging the legacy actions because legacy apps are not
|
// note: not merging the legacy actions because legacy apps are not
|
||||||
// compatible with the sharing overview and need to be adapted first
|
// compatible with the sharing overview and need to be adapted first
|
||||||
fileActions.registerDefaultActions();
|
fileActions.registerDefaultActions()
|
||||||
fileActions.merge(OCA.Files.fileActions);
|
fileActions.merge(OCA.Files.fileActions)
|
||||||
|
|
||||||
if (!this._globalActionsInitialized) {
|
if (!this._globalActionsInitialized) {
|
||||||
// in case actions are registered later
|
// in case actions are registered later
|
||||||
this._onActionsUpdated = _.bind(this._onActionsUpdated, this);
|
this._onActionsUpdated = _.bind(this._onActionsUpdated, this)
|
||||||
OCA.Files.fileActions.on('setDefault.app-sharing', this._onActionsUpdated);
|
OCA.Files.fileActions.on('setDefault.app-sharing', this._onActionsUpdated)
|
||||||
OCA.Files.fileActions.on('registerAction.app-sharing', this._onActionsUpdated);
|
OCA.Files.fileActions.on('registerAction.app-sharing', this._onActionsUpdated)
|
||||||
this._globalActionsInitialized = true;
|
this._globalActionsInitialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// when the user clicks on a folder, redirect to the corresponding
|
// when the user clicks on a folder, redirect to the corresponding
|
||||||
// folder in the files app instead of opening it directly
|
// folder in the files app instead of opening it directly
|
||||||
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
|
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename, context) {
|
||||||
OCA.Files.App.setActiveView('files', {silent: true});
|
OCA.Files.App.setActiveView('files', { silent: true })
|
||||||
OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true);
|
OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true)
|
||||||
});
|
})
|
||||||
fileActions.setDefault('dir', 'Open');
|
fileActions.setDefault('dir', 'Open')
|
||||||
return fileActions;
|
return fileActions
|
||||||
},
|
},
|
||||||
|
|
||||||
_restoreShareAction: function() {
|
_restoreShareAction: function() {
|
||||||
var fileActions = new OCA.Files.FileActions();
|
var fileActions = new OCA.Files.FileActions()
|
||||||
fileActions.registerAction({
|
fileActions.registerAction({
|
||||||
name: 'Restore',
|
name: 'Restore',
|
||||||
displayName: '',
|
displayName: '',
|
||||||
|
@ -237,70 +237,70 @@ OCA.Sharing.App = {
|
||||||
iconClass: 'icon-history',
|
iconClass: 'icon-history',
|
||||||
type: OCA.Files.FileActions.TYPE_INLINE,
|
type: OCA.Files.FileActions.TYPE_INLINE,
|
||||||
actionHandler: function(fileName, context) {
|
actionHandler: function(fileName, context) {
|
||||||
var shareId = context.$file.data('shareId');
|
var shareId = context.$file.data('shareId')
|
||||||
$.post(OC.linkToOCS('apps/files_sharing/api/v1/deletedshares', 2) + shareId)
|
$.post(OC.linkToOCS('apps/files_sharing/api/v1/deletedshares', 2) + shareId)
|
||||||
.success(function(result) {
|
.success(function(result) {
|
||||||
context.fileList.remove(context.fileInfoModel.attributes.name);
|
context.fileList.remove(context.fileInfoModel.attributes.name)
|
||||||
}).fail(function() {
|
}).fail(function() {
|
||||||
OC.Notification.showTemporary(t('files_sharing', 'Something happened. Unable to restore the share.'));
|
OC.Notification.showTemporary(t('files_sharing', 'Something happened. Unable to restore the share.'))
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
return fileActions;
|
return fileActions
|
||||||
},
|
},
|
||||||
|
|
||||||
_onActionsUpdated: function(ev) {
|
_onActionsUpdated: function(ev) {
|
||||||
_.each([this._inFileList, this._outFileList, this._linkFileList], function(list) {
|
_.each([this._inFileList, this._outFileList, this._linkFileList], function(list) {
|
||||||
if (!list) {
|
if (!list) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.action) {
|
if (ev.action) {
|
||||||
list.fileActions.registerAction(ev.action);
|
list.fileActions.registerAction(ev.action)
|
||||||
} else if (ev.defaultAction) {
|
} else if (ev.defaultAction) {
|
||||||
list.fileActions.setDefault(
|
list.fileActions.setDefault(
|
||||||
ev.defaultAction.mime,
|
ev.defaultAction.mime,
|
||||||
ev.defaultAction.name
|
ev.defaultAction.name
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
_extendFileList: function(fileList) {
|
_extendFileList: function(fileList) {
|
||||||
// remove size column from summary
|
// remove size column from summary
|
||||||
fileList.fileSummary.$el.find('.filesize').remove();
|
fileList.fileSummary.$el.find('.filesize').remove()
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#app-content-sharingin').on('show', function(e) {
|
$('#app-content-sharingin').on('show', function(e) {
|
||||||
OCA.Sharing.App.initSharingIn($(e.target));
|
OCA.Sharing.App.initSharingIn($(e.target))
|
||||||
});
|
})
|
||||||
$('#app-content-sharingin').on('hide', function() {
|
$('#app-content-sharingin').on('hide', function() {
|
||||||
OCA.Sharing.App.removeSharingIn();
|
OCA.Sharing.App.removeSharingIn()
|
||||||
});
|
})
|
||||||
$('#app-content-sharingout').on('show', function(e) {
|
$('#app-content-sharingout').on('show', function(e) {
|
||||||
OCA.Sharing.App.initSharingOut($(e.target));
|
OCA.Sharing.App.initSharingOut($(e.target))
|
||||||
});
|
})
|
||||||
$('#app-content-sharingout').on('hide', function() {
|
$('#app-content-sharingout').on('hide', function() {
|
||||||
OCA.Sharing.App.removeSharingOut();
|
OCA.Sharing.App.removeSharingOut()
|
||||||
});
|
})
|
||||||
$('#app-content-sharinglinks').on('show', function(e) {
|
$('#app-content-sharinglinks').on('show', function(e) {
|
||||||
OCA.Sharing.App.initSharingLinks($(e.target));
|
OCA.Sharing.App.initSharingLinks($(e.target))
|
||||||
});
|
})
|
||||||
$('#app-content-sharinglinks').on('hide', function() {
|
$('#app-content-sharinglinks').on('hide', function() {
|
||||||
OCA.Sharing.App.removeSharingLinks();
|
OCA.Sharing.App.removeSharingLinks()
|
||||||
});
|
})
|
||||||
$('#app-content-deletedshares').on('show', function(e) {
|
$('#app-content-deletedshares').on('show', function(e) {
|
||||||
OCA.Sharing.App.initSharingDeleted($(e.target));
|
OCA.Sharing.App.initSharingDeleted($(e.target))
|
||||||
});
|
})
|
||||||
$('#app-content-deletedshares').on('hide', function() {
|
$('#app-content-deletedshares').on('hide', function() {
|
||||||
OCA.Sharing.App.removeSharingDeleted();
|
OCA.Sharing.App.removeSharingDeleted()
|
||||||
});
|
})
|
||||||
$('#app-content-shareoverview').on('show', function(e) {
|
$('#app-content-shareoverview').on('show', function(e) {
|
||||||
OCA.Sharing.App.initShareingOverview($(e.target));
|
OCA.Sharing.App.initShareingOverview($(e.target))
|
||||||
});
|
})
|
||||||
$('#app-content-shareoverview').on('hide', function() {
|
$('#app-content-shareoverview').on('hide', function() {
|
||||||
OCA.Sharing.App.removeSharingOverview();
|
OCA.Sharing.App.removeSharingOverview()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,2 +1,2 @@
|
||||||
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/js/",t(t.s=0)}([function(e,n,r){r.p=OC.linkTo("files_sharing","js/dist/"),r.nc=btoa(OC.requestToken),window.OCP.Collaboration.registerType("file",{action:function(){return new Promise((function(e,n){OC.dialogs.filepicker(t("files_sharing","Link to a file"),(function(t){OC.Files.getClient().getFileInfo(t).then((function(n,t){e(t.id)})).fail((function(){n()}))}),!1,null,!1,OC.dialogs.FILEPICKER_TYPE_CHOOSE,"",{allowDirectoryChooser:!0})}))},typeString:t("files_sharing","Link to a file"),typeIconClass:"icon-files-dark"})}]);
|
!function(e){var n={};function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:r})},t.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.t=function(e,n){if(1&n&&(e=t(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var o in e)t.d(r,o,function(n){return e[n]}.bind(null,o));return r},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="/js/",t(t.s=0)}([function(e,n,r){r.p=OC.linkTo("files_sharing","js/dist/"),r.nc=btoa(OC.requestToken),window.OCP.Collaboration.registerType("file",{action:function(){return new Promise((function(e,n){OC.dialogs.filepicker(t("files_sharing","Link to a file"),(function(t){OC.Files.getClient().getFileInfo(t).then((function(n,t){e(t.id)})).fail((function(){n(new Error("Cannot get fileinfo"))}))}),!1,null,!1,OC.dialogs.FILEPICKER_TYPE_CHOOSE,"",{allowDirectoryChooser:!0})}))},typeString:t("files_sharing","Link to a file"),typeIconClass:"icon-files-dark"})}]);
|
||||||
//# sourceMappingURL=collaboration.js.map
|
//# sourceMappingURL=collaboration.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
||||||
(window.webpackJsonpFilesSharing=window.webpackJsonpFilesSharing||[]).push([[4],{14:function(e,o,i){"use strict";i.r(o);var n=i(16),l=i(18),r=i(28),u=i(31),s=i.n(u),a={name:"CollaborationView",computed:{fileId:function(){return this.$root.model&&this.$root.model.id?""+this.$root.model.id:null},filename:function(){return this.$root.model&&this.$root.model.name?""+this.$root.model.name:""}},components:{CollectionList:i(32).a}},d=i(56),c=Object(d.a)(a,(function(){var t=this.$createElement,e=this._self._c||t;return this.fileId?e("collection-list",{attrs:{type:"file",id:this.fileId,name:this.filename}}):this._e()}),[],!1,null,null,null).exports;i.d(o,"Vue",(function(){return n.default})),i.d(o,"View",(function(){return c})),
|
(window.webpackJsonpFilesSharing=window.webpackJsonpFilesSharing||[]).push([[4],{14:function(e,o,i){"use strict";i.r(o);var n=i(16),l=i(18),r=i(28),u=i(31),s=i.n(u),a={name:"CollaborationView",components:{CollectionList:i(32).a},computed:{fileId:function(){return this.$root.model&&this.$root.model.id?""+this.$root.model.id:null},filename:function(){return this.$root.model&&this.$root.model.name?""+this.$root.model.name:""}}},d=i(56),c=Object(d.a)(a,(function(){var t=this.$createElement,e=this._self._c||t;return this.fileId?e("CollectionList",{attrs:{id:this.fileId,type:"file",name:this.filename}}):this._e()}),[],!1,null,null,null).exports;i.d(o,"Vue",(function(){return n.default})),i.d(o,"View",(function(){return c})),
|
||||||
/*
|
/**
|
||||||
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||||
*
|
*
|
||||||
* @author Julius Härtl <jus@bitgrid.net>
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
@ -20,5 +20,5 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
n.default.prototype.t=t,n.default.component("PopoverMenu",r.PopoverMenu),n.default.directive("ClickOutside",s.a),r.Tooltip.options.defaultHtml=!1,n.default.directive("Tooltip",r.Tooltip),n.default.use(l.a)}}]);
|
n.default.prototype.t=t,r.Tooltip.options.defaultHtml=!1,n.default.component("PopoverMenu",r.PopoverMenu),n.default.directive("ClickOutside",s.a),n.default.directive("Tooltip",r.Tooltip),n.default.use(l.a)}}]);
|
||||||
//# sourceMappingURL=files_sharing.4.js.map?v=07d5a7a4994f0ef93170
|
//# sourceMappingURL=files_sharing.4.js.map?v=4e4a795c94e467758967
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
|
* Copyright (c) 2014 Vincent Petry <pvince81@owncloud.com>
|
||||||
*
|
*
|
||||||
|
@ -25,428 +26,423 @@
|
||||||
* @param {boolean} [options.linksOnly] true to return only link shares
|
* @param {boolean} [options.linksOnly] true to return only link shares
|
||||||
*/
|
*/
|
||||||
var FileList = function($el, options) {
|
var FileList = function($el, options) {
|
||||||
this.initialize($el, options);
|
this.initialize($el, options)
|
||||||
};
|
}
|
||||||
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
|
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
|
||||||
/** @lends OCA.Sharing.FileList.prototype */ {
|
/** @lends OCA.Sharing.FileList.prototype */ {
|
||||||
appName: 'Shares',
|
appName: 'Shares',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the list shows the files shared with the user (true) or
|
* Whether the list shows the files shared with the user (true) or
|
||||||
* the files that the user shared with others (false).
|
* the files that the user shared with others (false).
|
||||||
*/
|
*/
|
||||||
_sharedWithUser: false,
|
_sharedWithUser: false,
|
||||||
_linksOnly: false,
|
_linksOnly: false,
|
||||||
_showDeleted: false,
|
_showDeleted: false,
|
||||||
_clientSideSort: true,
|
_clientSideSort: true,
|
||||||
_allowSelection: false,
|
_allowSelection: false,
|
||||||
_isOverview: false,
|
_isOverview: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
initialize: function($el, options) {
|
initialize: function($el, options) {
|
||||||
OCA.Files.FileList.prototype.initialize.apply(this, arguments);
|
OCA.Files.FileList.prototype.initialize.apply(this, arguments)
|
||||||
if (this.initialized) {
|
if (this.initialized) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: consolidate both options
|
// TODO: consolidate both options
|
||||||
if (options && options.sharedWithUser) {
|
if (options && options.sharedWithUser) {
|
||||||
this._sharedWithUser = true;
|
this._sharedWithUser = true
|
||||||
}
|
}
|
||||||
if (options && options.linksOnly) {
|
if (options && options.linksOnly) {
|
||||||
this._linksOnly = true;
|
this._linksOnly = true
|
||||||
}
|
}
|
||||||
if (options && options.showDeleted) {
|
if (options && options.showDeleted) {
|
||||||
this._showDeleted = true;
|
this._showDeleted = true
|
||||||
}
|
}
|
||||||
if (options && options.isOverview) {
|
if (options && options.isOverview) {
|
||||||
this._isOverview = true;
|
this._isOverview = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderRow: function() {
|
_renderRow: function() {
|
||||||
// HACK: needed to call the overridden _renderRow
|
// HACK: needed to call the overridden _renderRow
|
||||||
// this is because at the time this class is created
|
// this is because at the time this class is created
|
||||||
// the overriding hasn't been done yet...
|
// the overriding hasn't been done yet...
|
||||||
return OCA.Files.FileList.prototype._renderRow.apply(this, arguments);
|
return OCA.Files.FileList.prototype._renderRow.apply(this, arguments)
|
||||||
},
|
},
|
||||||
|
|
||||||
_createRow: function(fileData) {
|
_createRow: function(fileData) {
|
||||||
// TODO: hook earlier and render the whole row here
|
// TODO: hook earlier and render the whole row here
|
||||||
var $tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments);
|
var $tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments)
|
||||||
$tr.find('.filesize').remove();
|
$tr.find('.filesize').remove()
|
||||||
$tr.find('td.date').before($tr.children('td:first'));
|
$tr.find('td.date').before($tr.children('td:first'))
|
||||||
$tr.find('td.filename input:checkbox').remove();
|
$tr.find('td.filename input:checkbox').remove()
|
||||||
$tr.attr('data-share-id', _.pluck(fileData.shares, 'id').join(','));
|
$tr.attr('data-share-id', _.pluck(fileData.shares, 'id').join(','))
|
||||||
if (this._sharedWithUser) {
|
if (this._sharedWithUser) {
|
||||||
$tr.attr('data-share-owner', fileData.shareOwner);
|
$tr.attr('data-share-owner', fileData.shareOwner)
|
||||||
$tr.attr('data-mounttype', 'shared-root');
|
$tr.attr('data-mounttype', 'shared-root')
|
||||||
var permission = parseInt($tr.attr('data-permissions')) | OC.PERMISSION_DELETE;
|
var permission = parseInt($tr.attr('data-permissions')) | OC.PERMISSION_DELETE
|
||||||
$tr.attr('data-permissions', permission);
|
$tr.attr('data-permissions', permission)
|
||||||
}
|
|
||||||
if (this._showDeleted) {
|
|
||||||
var permission = fileData.permissions;
|
|
||||||
$tr.attr('data-share-permissions', permission);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add row with expiration date for link only shares - influenced by _createRow of filelist
|
|
||||||
if (this._linksOnly) {
|
|
||||||
var expirationTimestamp = 0;
|
|
||||||
if(fileData.shares && fileData.shares[0].expiration !== null) {
|
|
||||||
expirationTimestamp = moment(fileData.shares[0].expiration).valueOf();
|
|
||||||
}
|
}
|
||||||
$tr.attr('data-expiration', expirationTimestamp);
|
if (this._showDeleted) {
|
||||||
|
var permission = fileData.permissions
|
||||||
// date column (1000 milliseconds to seconds, 60 seconds, 60 minutes, 24 hours)
|
$tr.attr('data-share-permissions', permission)
|
||||||
// difference in days multiplied by 5 - brightest shade for expiry dates in more than 32 days (160/5)
|
|
||||||
var modifiedColor = Math.round((expirationTimestamp - (new Date()).getTime()) / 1000 / 60 / 60 / 24 * 5);
|
|
||||||
// ensure that the brightest color is still readable
|
|
||||||
if (modifiedColor >= 160) {
|
|
||||||
modifiedColor = 160;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var formatted;
|
// add row with expiration date for link only shares - influenced by _createRow of filelist
|
||||||
var text;
|
if (this._linksOnly) {
|
||||||
if (expirationTimestamp > 0) {
|
var expirationTimestamp = 0
|
||||||
formatted = OC.Util.formatDate(expirationTimestamp);
|
if (fileData.shares && fileData.shares[0].expiration !== null) {
|
||||||
text = OC.Util.relativeModifiedDate(expirationTimestamp);
|
expirationTimestamp = moment(fileData.shares[0].expiration).valueOf()
|
||||||
} else {
|
}
|
||||||
formatted = t('files_sharing', 'No expiration date set');
|
$tr.attr('data-expiration', expirationTimestamp)
|
||||||
text = '';
|
|
||||||
modifiedColor = 160;
|
// date column (1000 milliseconds to seconds, 60 seconds, 60 minutes, 24 hours)
|
||||||
}
|
// difference in days multiplied by 5 - brightest shade for expiry dates in more than 32 days (160/5)
|
||||||
td = $('<td></td>').attr({"class": "date"});
|
var modifiedColor = Math.round((expirationTimestamp - (new Date()).getTime()) / 1000 / 60 / 60 / 24 * 5)
|
||||||
td.append($('<span></span>').attr({
|
// ensure that the brightest color is still readable
|
||||||
"class": "modified",
|
if (modifiedColor >= 160) {
|
||||||
"title": formatted,
|
modifiedColor = 160
|
||||||
"style": 'color:rgb(' + modifiedColor + ',' + modifiedColor + ',' + modifiedColor + ')'
|
}
|
||||||
|
|
||||||
|
var formatted
|
||||||
|
var text
|
||||||
|
if (expirationTimestamp > 0) {
|
||||||
|
formatted = OC.Util.formatDate(expirationTimestamp)
|
||||||
|
text = OC.Util.relativeModifiedDate(expirationTimestamp)
|
||||||
|
} else {
|
||||||
|
formatted = t('files_sharing', 'No expiration date set')
|
||||||
|
text = ''
|
||||||
|
modifiedColor = 160
|
||||||
|
}
|
||||||
|
td = $('<td></td>').attr({ 'class': 'date' })
|
||||||
|
td.append($('<span></span>').attr({
|
||||||
|
'class': 'modified',
|
||||||
|
'title': formatted,
|
||||||
|
'style': 'color:rgb(' + modifiedColor + ',' + modifiedColor + ',' + modifiedColor + ')'
|
||||||
}).text(text)
|
}).text(text)
|
||||||
.tooltip({placement: 'top'})
|
.tooltip({ placement: 'top' })
|
||||||
);
|
)
|
||||||
|
|
||||||
$tr.append(td);
|
$tr.append(td)
|
||||||
}
|
}
|
||||||
return $tr;
|
return $tr
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set whether the list should contain outgoing shares
|
* Set whether the list should contain outgoing shares
|
||||||
* or incoming shares.
|
* or incoming shares.
|
||||||
*
|
*
|
||||||
* @param state true for incoming shares, false otherwise
|
* @param state true for incoming shares, false otherwise
|
||||||
*/
|
*/
|
||||||
setSharedWithUser: function(state) {
|
setSharedWithUser: function(state) {
|
||||||
this._sharedWithUser = !!state;
|
this._sharedWithUser = !!state
|
||||||
},
|
},
|
||||||
|
|
||||||
updateEmptyContent: function() {
|
updateEmptyContent: function() {
|
||||||
var dir = this.getCurrentDirectory();
|
var dir = this.getCurrentDirectory()
|
||||||
if (dir === '/') {
|
if (dir === '/') {
|
||||||
// root has special permissions
|
// root has special permissions
|
||||||
this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty);
|
this.$el.find('#emptycontent').toggleClass('hidden', !this.isEmpty)
|
||||||
this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty);
|
this.$el.find('#filestable thead th').toggleClass('hidden', this.isEmpty)
|
||||||
|
|
||||||
// hide expiration date header for non link only shares
|
// hide expiration date header for non link only shares
|
||||||
if (!this._linksOnly) {
|
if (!this._linksOnly) {
|
||||||
this.$el.find('th.column-expiration').addClass('hidden');
|
this.$el.find('th.column-expiration').addClass('hidden')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
else {
|
|
||||||
OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getDirectoryPermissions: function() {
|
getDirectoryPermissions: function() {
|
||||||
return OC.PERMISSION_READ | OC.PERMISSION_DELETE;
|
return OC.PERMISSION_READ | OC.PERMISSION_DELETE
|
||||||
},
|
},
|
||||||
|
|
||||||
updateStorageStatistics: function() {
|
updateStorageStatistics: function() {
|
||||||
// no op because it doesn't have
|
// no op because it doesn't have
|
||||||
// storage info like free space / used space
|
// storage info like free space / used space
|
||||||
},
|
},
|
||||||
|
|
||||||
updateRow: function($tr, fileInfo, options) {
|
updateRow: function($tr, fileInfo, options) {
|
||||||
// no-op, suppress re-rendering
|
// no-op, suppress re-rendering
|
||||||
return $tr;
|
return $tr
|
||||||
},
|
},
|
||||||
|
|
||||||
reload: function() {
|
reload: function() {
|
||||||
this.showMask();
|
this.showMask()
|
||||||
if (this._reloadCall) {
|
if (this._reloadCall) {
|
||||||
this._reloadCall.abort();
|
this._reloadCall.abort()
|
||||||
}
|
|
||||||
|
|
||||||
// there is only root
|
|
||||||
this._setCurrentDir('/', false);
|
|
||||||
|
|
||||||
var promises = [];
|
|
||||||
|
|
||||||
var deletedShares = {
|
|
||||||
url: OC.linkToOCS('apps/files_sharing/api/v1', 2) + 'deletedshares',
|
|
||||||
/* jshint camelcase: false */
|
|
||||||
data: {
|
|
||||||
format: 'json',
|
|
||||||
include_tags: true
|
|
||||||
},
|
|
||||||
type: 'GET',
|
|
||||||
beforeSend: function (xhr) {
|
|
||||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var shares = {
|
|
||||||
url: OC.linkToOCS('apps/files_sharing/api/v1') + 'shares',
|
|
||||||
/* jshint camelcase: false */
|
|
||||||
data: {
|
|
||||||
format: 'json',
|
|
||||||
shared_with_me: this._sharedWithUser !== false,
|
|
||||||
include_tags: true
|
|
||||||
},
|
|
||||||
type: 'GET',
|
|
||||||
beforeSend: function (xhr) {
|
|
||||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var remoteShares = {
|
|
||||||
url: OC.linkToOCS('apps/files_sharing/api/v1') + 'remote_shares',
|
|
||||||
/* jshint camelcase: false */
|
|
||||||
data: {
|
|
||||||
format: 'json',
|
|
||||||
include_tags: true
|
|
||||||
},
|
|
||||||
type: 'GET',
|
|
||||||
beforeSend: function (xhr) {
|
|
||||||
xhr.setRequestHeader('OCS-APIREQUEST', 'true');
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the proper ajax requests to the list and run them
|
|
||||||
// and make sure we have 2 promises
|
|
||||||
if (this._showDeleted) {
|
|
||||||
promises.push($.ajax(deletedShares));
|
|
||||||
} else {
|
|
||||||
promises.push($.ajax(shares));
|
|
||||||
|
|
||||||
if (this._sharedWithUser !== false || this._isOverview) {
|
|
||||||
promises.push($.ajax(remoteShares));
|
|
||||||
}
|
}
|
||||||
if (this._isOverview) {
|
|
||||||
shares.data.shared_with_me = !shares.data.shared_with_me;
|
// there is only root
|
||||||
promises.push($.ajax(shares));
|
this._setCurrentDir('/', false)
|
||||||
|
|
||||||
|
var promises = []
|
||||||
|
|
||||||
|
var deletedShares = {
|
||||||
|
url: OC.linkToOCS('apps/files_sharing/api/v1', 2) + 'deletedshares',
|
||||||
|
/* jshint camelcase: false */
|
||||||
|
data: {
|
||||||
|
format: 'json',
|
||||||
|
include_tags: true
|
||||||
|
},
|
||||||
|
type: 'GET',
|
||||||
|
beforeSend: function(xhr) {
|
||||||
|
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this._reloadCall = $.when.apply($, promises);
|
var shares = {
|
||||||
var callBack = this.reloadCallback.bind(this);
|
url: OC.linkToOCS('apps/files_sharing/api/v1') + 'shares',
|
||||||
return this._reloadCall.then(callBack, callBack);
|
/* jshint camelcase: false */
|
||||||
},
|
data: {
|
||||||
|
format: 'json',
|
||||||
|
shared_with_me: this._sharedWithUser !== false,
|
||||||
|
include_tags: true
|
||||||
|
},
|
||||||
|
type: 'GET',
|
||||||
|
beforeSend: function(xhr) {
|
||||||
|
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reloadCallback: function(shares, remoteShares, additionalShares) {
|
var remoteShares = {
|
||||||
delete this._reloadCall;
|
url: OC.linkToOCS('apps/files_sharing/api/v1') + 'remote_shares',
|
||||||
this.hideMask();
|
/* jshint camelcase: false */
|
||||||
|
data: {
|
||||||
|
format: 'json',
|
||||||
|
include_tags: true
|
||||||
|
},
|
||||||
|
type: 'GET',
|
||||||
|
beforeSend: function(xhr) {
|
||||||
|
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.$el.find('#headerSharedWith').text(
|
// Add the proper ajax requests to the list and run them
|
||||||
t('files_sharing', this._sharedWithUser ? 'Shared by' : 'Shared with')
|
// and make sure we have 2 promises
|
||||||
);
|
if (this._showDeleted) {
|
||||||
|
promises.push($.ajax(deletedShares))
|
||||||
|
} else {
|
||||||
|
promises.push($.ajax(shares))
|
||||||
|
|
||||||
var files = [];
|
if (this._sharedWithUser !== false || this._isOverview) {
|
||||||
|
promises.push($.ajax(remoteShares))
|
||||||
|
}
|
||||||
|
if (this._isOverview) {
|
||||||
|
shares.data.shared_with_me = !shares.data.shared_with_me
|
||||||
|
promises.push($.ajax(shares))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// make sure to use the same format
|
this._reloadCall = $.when.apply($, promises)
|
||||||
if (shares[0] && shares[0].ocs) {
|
var callBack = this.reloadCallback.bind(this)
|
||||||
shares = shares[0];
|
return this._reloadCall.then(callBack, callBack)
|
||||||
}
|
},
|
||||||
if (remoteShares && remoteShares[0] && remoteShares[0].ocs) {
|
|
||||||
remoteShares = remoteShares[0];
|
|
||||||
}
|
|
||||||
if (additionalShares && additionalShares[0] && additionalShares[0].ocs) {
|
|
||||||
additionalShares = additionalShares[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shares.ocs && shares.ocs.data) {
|
reloadCallback: function(shares, remoteShares, additionalShares) {
|
||||||
files = files.concat(this._makeFilesFromShares(shares.ocs.data, this._sharedWithUser));
|
delete this._reloadCall
|
||||||
}
|
this.hideMask()
|
||||||
|
|
||||||
if (remoteShares && remoteShares.ocs && remoteShares.ocs.data) {
|
this.$el.find('#headerSharedWith').text(
|
||||||
files = files.concat(this._makeFilesFromRemoteShares(remoteShares.ocs.data));
|
t('files_sharing', this._sharedWithUser ? 'Shared by' : 'Shared with')
|
||||||
}
|
)
|
||||||
|
|
||||||
if (additionalShares && additionalShares.ocs && additionalShares.ocs.data) {
|
var files = []
|
||||||
files = files.concat(this._makeFilesFromShares(additionalShares.ocs.data, !this._sharedWithUser));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// make sure to use the same format
|
||||||
|
if (shares[0] && shares[0].ocs) {
|
||||||
|
shares = shares[0]
|
||||||
|
}
|
||||||
|
if (remoteShares && remoteShares[0] && remoteShares[0].ocs) {
|
||||||
|
remoteShares = remoteShares[0]
|
||||||
|
}
|
||||||
|
if (additionalShares && additionalShares[0] && additionalShares[0].ocs) {
|
||||||
|
additionalShares = additionalShares[0]
|
||||||
|
}
|
||||||
|
|
||||||
this.setFiles(files);
|
if (shares.ocs && shares.ocs.data) {
|
||||||
return true;
|
files = files.concat(this._makeFilesFromShares(shares.ocs.data, this._sharedWithUser))
|
||||||
},
|
}
|
||||||
|
|
||||||
_makeFilesFromRemoteShares: function(data) {
|
if (remoteShares && remoteShares.ocs && remoteShares.ocs.data) {
|
||||||
var files = data;
|
files = files.concat(this._makeFilesFromRemoteShares(remoteShares.ocs.data))
|
||||||
|
}
|
||||||
|
|
||||||
files = _.chain(files)
|
if (additionalShares && additionalShares.ocs && additionalShares.ocs.data) {
|
||||||
|
files = files.concat(this._makeFilesFromShares(additionalShares.ocs.data, !this._sharedWithUser))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setFiles(files)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
_makeFilesFromRemoteShares: function(data) {
|
||||||
|
var files = data
|
||||||
|
|
||||||
|
files = _.chain(files)
|
||||||
// convert share data to file data
|
// convert share data to file data
|
||||||
.map(function(share) {
|
.map(function(share) {
|
||||||
var file = {
|
var file = {
|
||||||
shareOwner: share.owner + '@' + share.remote.replace(/.*?:\/\//g, ""),
|
shareOwner: share.owner + '@' + share.remote.replace(/.*?:\/\//g, ''),
|
||||||
name: OC.basename(share.mountpoint),
|
name: OC.basename(share.mountpoint),
|
||||||
mtime: share.mtime * 1000,
|
mtime: share.mtime * 1000,
|
||||||
mimetype: share.mimetype,
|
mimetype: share.mimetype,
|
||||||
type: share.type,
|
type: share.type,
|
||||||
id: share.file_id,
|
id: share.file_id,
|
||||||
path: OC.dirname(share.mountpoint),
|
path: OC.dirname(share.mountpoint),
|
||||||
permissions: share.permissions,
|
permissions: share.permissions,
|
||||||
tags: share.tags || []
|
tags: share.tags || []
|
||||||
};
|
}
|
||||||
|
|
||||||
file.shares = [{
|
file.shares = [{
|
||||||
id: share.id,
|
id: share.id,
|
||||||
type: OC.Share.SHARE_TYPE_REMOTE
|
type: OC.Share.SHARE_TYPE_REMOTE
|
||||||
}];
|
}]
|
||||||
return file;
|
return file
|
||||||
})
|
})
|
||||||
.value();
|
.value()
|
||||||
return files;
|
return files
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the OCS API share response data to a file info
|
* Converts the OCS API share response data to a file info
|
||||||
* list
|
* list
|
||||||
* @param {Array} data OCS API share array
|
* @param {Array} data OCS API share array
|
||||||
* @param {bool} sharedWithUser
|
* @param {bool} sharedWithUser
|
||||||
* @return {Array.<OCA.Sharing.SharedFileInfo>} array of shared file info
|
* @returns {Array.<OCA.Sharing.SharedFileInfo>} array of shared file info
|
||||||
*/
|
*/
|
||||||
_makeFilesFromShares: function(data, sharedWithUser) {
|
_makeFilesFromShares: function(data, sharedWithUser) {
|
||||||
/* jshint camelcase: false */
|
/* jshint camelcase: false */
|
||||||
var files = data;
|
var files = data
|
||||||
|
|
||||||
if (this._linksOnly) {
|
if (this._linksOnly) {
|
||||||
files = _.filter(data, function(share) {
|
files = _.filter(data, function(share) {
|
||||||
return share.share_type === OC.Share.SHARE_TYPE_LINK;
|
return share.share_type === OC.Share.SHARE_TYPE_LINK
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// OCS API uses non-camelcased names
|
// OCS API uses non-camelcased names
|
||||||
files = _.chain(files)
|
files = _.chain(files)
|
||||||
// convert share data to file data
|
// convert share data to file data
|
||||||
.map(function(share) {
|
.map(function(share) {
|
||||||
// TODO: use OC.Files.FileInfo
|
// TODO: use OC.Files.FileInfo
|
||||||
var file = {
|
var file = {
|
||||||
id: share.file_source,
|
id: share.file_source,
|
||||||
icon: OC.MimeType.getIconUrl(share.mimetype),
|
icon: OC.MimeType.getIconUrl(share.mimetype),
|
||||||
mimetype: share.mimetype,
|
mimetype: share.mimetype,
|
||||||
tags: share.tags || []
|
tags: share.tags || []
|
||||||
};
|
|
||||||
if (share.item_type === 'folder') {
|
|
||||||
file.type = 'dir';
|
|
||||||
file.mimetype = 'httpd/unix-directory';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
file.type = 'file';
|
|
||||||
}
|
|
||||||
file.share = {
|
|
||||||
id: share.id,
|
|
||||||
type: share.share_type,
|
|
||||||
target: share.share_with,
|
|
||||||
stime: share.stime * 1000,
|
|
||||||
expiration: share.expiration,
|
|
||||||
};
|
|
||||||
if (sharedWithUser) {
|
|
||||||
file.shareOwner = share.displayname_owner;
|
|
||||||
file.shareOwnerId = share.uid_owner;
|
|
||||||
file.name = OC.basename(share.file_target);
|
|
||||||
file.path = OC.dirname(share.file_target);
|
|
||||||
file.permissions = share.permissions;
|
|
||||||
if (file.path) {
|
|
||||||
file.extraData = share.file_target;
|
|
||||||
}
|
}
|
||||||
}
|
if (share.item_type === 'folder') {
|
||||||
else {
|
file.type = 'dir'
|
||||||
if (share.share_type !== OC.Share.SHARE_TYPE_LINK) {
|
file.mimetype = 'httpd/unix-directory'
|
||||||
file.share.targetDisplayName = share.share_with_displayname;
|
} else {
|
||||||
file.share.targetShareWithId = share.share_with;
|
file.type = 'file'
|
||||||
}
|
}
|
||||||
file.name = OC.basename(share.path);
|
file.share = {
|
||||||
file.path = OC.dirname(share.path);
|
id: share.id,
|
||||||
file.permissions = OC.PERMISSION_ALL;
|
type: share.share_type,
|
||||||
if (file.path) {
|
target: share.share_with,
|
||||||
file.extraData = share.path;
|
stime: share.stime * 1000,
|
||||||
|
expiration: share.expiration
|
||||||
}
|
}
|
||||||
}
|
if (sharedWithUser) {
|
||||||
return file;
|
file.shareOwner = share.displayname_owner
|
||||||
})
|
file.shareOwnerId = share.uid_owner
|
||||||
|
file.name = OC.basename(share.file_target)
|
||||||
|
file.path = OC.dirname(share.file_target)
|
||||||
|
file.permissions = share.permissions
|
||||||
|
if (file.path) {
|
||||||
|
file.extraData = share.file_target
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (share.share_type !== OC.Share.SHARE_TYPE_LINK) {
|
||||||
|
file.share.targetDisplayName = share.share_with_displayname
|
||||||
|
file.share.targetShareWithId = share.share_with
|
||||||
|
}
|
||||||
|
file.name = OC.basename(share.path)
|
||||||
|
file.path = OC.dirname(share.path)
|
||||||
|
file.permissions = OC.PERMISSION_ALL
|
||||||
|
if (file.path) {
|
||||||
|
file.extraData = share.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
})
|
||||||
// Group all files and have a "shares" array with
|
// Group all files and have a "shares" array with
|
||||||
// the share info for each file.
|
// the share info for each file.
|
||||||
//
|
//
|
||||||
// This uses a hash memo to cumulate share information
|
// This uses a hash memo to cumulate share information
|
||||||
// inside the same file object (by file id).
|
// inside the same file object (by file id).
|
||||||
.reduce(function(memo, file) {
|
.reduce(function(memo, file) {
|
||||||
var data = memo[file.id];
|
var data = memo[file.id]
|
||||||
var recipient = file.share.targetDisplayName;
|
var recipient = file.share.targetDisplayName
|
||||||
var recipientId = file.share.targetShareWithId;
|
var recipientId = file.share.targetShareWithId
|
||||||
if (!data) {
|
if (!data) {
|
||||||
data = memo[file.id] = file;
|
data = memo[file.id] = file
|
||||||
data.shares = [file.share];
|
data.shares = [file.share]
|
||||||
// using a hash to make them unique,
|
// using a hash to make them unique,
|
||||||
// this is only a list to be displayed
|
// this is only a list to be displayed
|
||||||
data.recipients = {};
|
data.recipients = {}
|
||||||
data.recipientData = {};
|
data.recipientData = {}
|
||||||
// share types
|
// share types
|
||||||
data.shareTypes = {};
|
data.shareTypes = {}
|
||||||
// counter is cheaper than calling _.keys().length
|
// counter is cheaper than calling _.keys().length
|
||||||
data.recipientsCount = 0;
|
data.recipientsCount = 0
|
||||||
data.mtime = file.share.stime;
|
data.mtime = file.share.stime
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// always take the most recent stime
|
// always take the most recent stime
|
||||||
if (file.share.stime > data.mtime) {
|
if (file.share.stime > data.mtime) {
|
||||||
data.mtime = file.share.stime;
|
data.mtime = file.share.stime
|
||||||
|
}
|
||||||
|
data.shares.push(file.share)
|
||||||
}
|
}
|
||||||
data.shares.push(file.share);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipient) {
|
if (recipient) {
|
||||||
// limit counterparts for output
|
// limit counterparts for output
|
||||||
if (data.recipientsCount < 4) {
|
if (data.recipientsCount < 4) {
|
||||||
// only store the first ones, they will be the only ones
|
// only store the first ones, they will be the only ones
|
||||||
// displayed
|
// displayed
|
||||||
data.recipients[recipient] = true;
|
data.recipients[recipient] = true
|
||||||
data.recipientData[data.recipientsCount] = {
|
data.recipientData[data.recipientsCount] = {
|
||||||
'shareWith': recipientId,
|
'shareWith': recipientId,
|
||||||
'shareWithDisplayName': recipient
|
'shareWithDisplayName': recipient
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
data.recipientsCount++
|
||||||
}
|
}
|
||||||
data.recipientsCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.shareTypes[file.share.type] = true;
|
data.shareTypes[file.share.type] = true
|
||||||
|
|
||||||
delete file.share;
|
delete file.share
|
||||||
return memo;
|
return memo
|
||||||
}, {})
|
}, {})
|
||||||
// Retrieve only the values of the returned hash
|
// Retrieve only the values of the returned hash
|
||||||
.values()
|
.values()
|
||||||
// Clean up
|
// Clean up
|
||||||
.each(function(data) {
|
.each(function(data) {
|
||||||
// convert the recipients map to a flat
|
// convert the recipients map to a flat
|
||||||
// array of sorted names
|
// array of sorted names
|
||||||
data.mountType = 'shared';
|
data.mountType = 'shared'
|
||||||
delete data.recipientsCount;
|
delete data.recipientsCount
|
||||||
if (sharedWithUser) {
|
if (sharedWithUser) {
|
||||||
// only for outgoing shares
|
// only for outgoing shares
|
||||||
delete data.shareTypes;
|
delete data.shareTypes
|
||||||
} else {
|
} else {
|
||||||
data.shareTypes = _.keys(data.shareTypes);
|
data.shareTypes = _.keys(data.shareTypes)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Finish the chain by getting the result
|
// Finish the chain by getting the result
|
||||||
.value();
|
.value()
|
||||||
|
|
||||||
// Sort by expected sort comparator
|
// Sort by expected sort comparator
|
||||||
return files.sort(this._sortComparator);
|
return files.sort(this._sortComparator)
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share info attributes.
|
* Share info attributes.
|
||||||
|
@ -486,5 +482,5 @@
|
||||||
* passing to HTML data attributes with jQuery)
|
* passing to HTML data attributes with jQuery)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
OCA.Sharing.FileList = FileList;
|
OCA.Sharing.FileList = FileList
|
||||||
})();
|
})()
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
__webpack_public_path__ = OC.linkTo('files_sharing', 'js/dist/');
|
|
||||||
__webpack_nonce__ = btoa(OC.requestToken);
|
|
||||||
|
|
||||||
import './share'
|
import './share'
|
||||||
import './sharetabview'
|
import './sharetabview'
|
||||||
import './sharebreadcrumbview'
|
import './sharebreadcrumbview'
|
||||||
|
@ -10,4 +7,9 @@ import './style/sharebreadcrumb.scss'
|
||||||
|
|
||||||
import './collaborationresourceshandler.js'
|
import './collaborationresourceshandler.js'
|
||||||
|
|
||||||
window.OCA.Sharing = OCA.Sharing;
|
// eslint-disable-next-line camelcase
|
||||||
|
__webpack_public_path__ = OC.linkTo('files_sharing', 'js/dist/')
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
__webpack_nonce__ = btoa(OC.requestToken)
|
||||||
|
|
||||||
|
window.OCA.Sharing = OCA.Sharing
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
|
||||||
*
|
*
|
||||||
* @author Julius Härtl <jus@bitgrid.net>
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
@ -20,21 +20,23 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex'
|
||||||
import { Tooltip, PopoverMenu } from 'nextcloud-vue';
|
import { Tooltip, PopoverMenu } from 'nextcloud-vue'
|
||||||
import ClickOutside from 'vue-click-outside';
|
import ClickOutside from 'vue-click-outside'
|
||||||
|
|
||||||
Vue.prototype.t = t;
|
import View from './views/CollaborationView'
|
||||||
Vue.component('PopoverMenu', PopoverMenu);
|
|
||||||
Vue.directive('ClickOutside', ClickOutside);
|
Vue.prototype.t = t
|
||||||
Tooltip.options.defaultHtml = false
|
Tooltip.options.defaultHtml = false
|
||||||
Vue.directive('Tooltip', Tooltip);
|
|
||||||
Vue.use(Vuex);
|
|
||||||
|
|
||||||
import View from './views/CollaborationView';
|
// eslint-disable-next-line vue/match-component-file-name
|
||||||
|
Vue.component('PopoverMenu', PopoverMenu)
|
||||||
|
Vue.directive('ClickOutside', ClickOutside)
|
||||||
|
Vue.directive('Tooltip', Tooltip)
|
||||||
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Vue,
|
Vue,
|
||||||
View
|
View
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
__webpack_public_path__ = OC.linkTo('files_sharing', 'js/dist/');
|
// eslint-disable-next-line camelcase
|
||||||
__webpack_nonce__ = btoa(OC.requestToken);
|
__webpack_public_path__ = OC.linkTo('files_sharing', 'js/dist/')
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
__webpack_nonce__ = btoa(OC.requestToken)
|
||||||
|
|
||||||
window.OCP.Collaboration.registerType('file', {
|
window.OCP.Collaboration.registerType('file', {
|
||||||
action: () => {
|
action: () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
OC.dialogs.filepicker(t('files_sharing', 'Link to a file'), function (f) {
|
OC.dialogs.filepicker(t('files_sharing', 'Link to a file'), function(f) {
|
||||||
const client = OC.Files.getClient();
|
const client = OC.Files.getClient()
|
||||||
client.getFileInfo(f).then((status, fileInfo) => {
|
client.getFileInfo(f).then((status, fileInfo) => {
|
||||||
resolve(fileInfo.id);
|
resolve(fileInfo.id)
|
||||||
}).fail(() => {
|
}).fail(() => {
|
||||||
reject();
|
reject(new Error('Cannot get fileinfo'))
|
||||||
});
|
})
|
||||||
}, false, null, false, OC.dialogs.FILEPICKER_TYPE_CHOOSE, '', { allowDirectoryChooser: true });
|
}, false, null, false, OC.dialogs.FILEPICKER_TYPE_CHOOSE, '', { allowDirectoryChooser: true })
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
typeString: t('files_sharing', 'Link to a file'),
|
typeString: t('files_sharing', 'Link to a file'),
|
||||||
typeIconClass: 'icon-files-dark'
|
typeIconClass: 'icon-files-dark'
|
||||||
});
|
})
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
__webpack_nonce__ = btoa(OC.requestToken);
|
import '../js/app'
|
||||||
__webpack_public_path__ = OC.linkTo('files_sharing', 'js/dist/');
|
import '../js/sharedfilelist'
|
||||||
|
|
||||||
import '../js/app';
|
// eslint-disable-next-line camelcase
|
||||||
import '../js/sharedfilelist';
|
__webpack_nonce__ = btoa(OC.requestToken)
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
__webpack_public_path__ = OC.linkTo('files_sharing', 'js/dist/')
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014
|
* Copyright (c) 2014
|
||||||
*
|
*
|
||||||
|
@ -14,10 +15,10 @@
|
||||||
PROPERTY_SHARE_TYPES: '{' + OC.Files.Client.NS_OWNCLOUD + '}share-types',
|
PROPERTY_SHARE_TYPES: '{' + OC.Files.Client.NS_OWNCLOUD + '}share-types',
|
||||||
PROPERTY_OWNER_ID: '{' + OC.Files.Client.NS_OWNCLOUD + '}owner-id',
|
PROPERTY_OWNER_ID: '{' + OC.Files.Client.NS_OWNCLOUD + '}owner-id',
|
||||||
PROPERTY_OWNER_DISPLAY_NAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}owner-display-name'
|
PROPERTY_OWNER_DISPLAY_NAME: '{' + OC.Files.Client.NS_OWNCLOUD + '}owner-display-name'
|
||||||
});
|
})
|
||||||
|
|
||||||
if (!OCA.Sharing) {
|
if (!OCA.Sharing) {
|
||||||
OCA.Sharing = {};
|
OCA.Sharing = {}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @namespace
|
* @namespace
|
||||||
|
@ -34,131 +35,130 @@
|
||||||
attach: function(fileList) {
|
attach: function(fileList) {
|
||||||
// core sharing is disabled/not loaded
|
// core sharing is disabled/not loaded
|
||||||
if (!OC.Share) {
|
if (!OC.Share) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
if (fileList.id === 'trashbin' || fileList.id === 'files.public') {
|
if (fileList.id === 'trashbin' || fileList.id === 'files.public') {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
var fileActions = fileList.fileActions;
|
var fileActions = fileList.fileActions
|
||||||
var oldCreateRow = fileList._createRow;
|
var oldCreateRow = fileList._createRow
|
||||||
fileList._createRow = function(fileData) {
|
fileList._createRow = function(fileData) {
|
||||||
|
|
||||||
var tr = oldCreateRow.apply(this, arguments);
|
var tr = oldCreateRow.apply(this, arguments)
|
||||||
var sharePermissions = OCA.Sharing.Util.getSharePermissions(fileData);
|
var sharePermissions = OCA.Sharing.Util.getSharePermissions(fileData)
|
||||||
|
|
||||||
if (fileData.permissions === 0) {
|
if (fileData.permissions === 0) {
|
||||||
// no permission, disabling sidebar
|
// no permission, disabling sidebar
|
||||||
delete fileActions.actions.all.Comment;
|
delete fileActions.actions.all.Comment
|
||||||
delete fileActions.actions.all.Details;
|
delete fileActions.actions.all.Details
|
||||||
delete fileActions.actions.all.Goto;
|
delete fileActions.actions.all.Goto
|
||||||
}
|
}
|
||||||
tr.attr('data-share-permissions', sharePermissions);
|
tr.attr('data-share-permissions', sharePermissions)
|
||||||
if (fileData.shareOwner) {
|
if (fileData.shareOwner) {
|
||||||
tr.attr('data-share-owner', fileData.shareOwner);
|
tr.attr('data-share-owner', fileData.shareOwner)
|
||||||
tr.attr('data-share-owner-id', fileData.shareOwnerId);
|
tr.attr('data-share-owner-id', fileData.shareOwnerId)
|
||||||
// user should always be able to rename a mount point
|
// user should always be able to rename a mount point
|
||||||
if (fileData.mountType === 'shared-root') {
|
if (fileData.mountType === 'shared-root') {
|
||||||
tr.attr('data-permissions', fileData.permissions | OC.PERMISSION_UPDATE);
|
tr.attr('data-permissions', fileData.permissions | OC.PERMISSION_UPDATE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fileData.recipientData && !_.isEmpty(fileData.recipientData)) {
|
if (fileData.recipientData && !_.isEmpty(fileData.recipientData)) {
|
||||||
tr.attr('data-share-recipient-data', JSON.stringify(fileData.recipientData));
|
tr.attr('data-share-recipient-data', JSON.stringify(fileData.recipientData))
|
||||||
}
|
}
|
||||||
if (fileData.shareTypes) {
|
if (fileData.shareTypes) {
|
||||||
tr.attr('data-share-types', fileData.shareTypes.join(','));
|
tr.attr('data-share-types', fileData.shareTypes.join(','))
|
||||||
}
|
}
|
||||||
return tr;
|
return tr
|
||||||
};
|
}
|
||||||
|
|
||||||
var oldElementToFile = fileList.elementToFile;
|
var oldElementToFile = fileList.elementToFile
|
||||||
fileList.elementToFile = function($el) {
|
fileList.elementToFile = function($el) {
|
||||||
var fileInfo = oldElementToFile.apply(this, arguments);
|
var fileInfo = oldElementToFile.apply(this, arguments)
|
||||||
fileInfo.sharePermissions = $el.attr('data-share-permissions') || undefined;
|
fileInfo.sharePermissions = $el.attr('data-share-permissions') || undefined
|
||||||
fileInfo.shareOwner = $el.attr('data-share-owner') || undefined;
|
fileInfo.shareOwner = $el.attr('data-share-owner') || undefined
|
||||||
fileInfo.shareOwnerId = $el.attr('data-share-owner-id') || undefined;
|
fileInfo.shareOwnerId = $el.attr('data-share-owner-id') || undefined
|
||||||
|
|
||||||
if( $el.attr('data-share-types')){
|
if ($el.attr('data-share-types')) {
|
||||||
fileInfo.shareTypes = $el.attr('data-share-types').split(',');
|
fileInfo.shareTypes = $el.attr('data-share-types').split(',')
|
||||||
}
|
}
|
||||||
|
|
||||||
if( $el.attr('data-expiration')){
|
if ($el.attr('data-expiration')) {
|
||||||
var expirationTimestamp = parseInt($el.attr('data-expiration'));
|
var expirationTimestamp = parseInt($el.attr('data-expiration'))
|
||||||
fileInfo.shares = [];
|
fileInfo.shares = []
|
||||||
fileInfo.shares.push({expiration: expirationTimestamp});
|
fileInfo.shares.push({ expiration: expirationTimestamp })
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileInfo;
|
return fileInfo
|
||||||
};
|
}
|
||||||
|
|
||||||
var oldGetWebdavProperties = fileList._getWebdavProperties;
|
var oldGetWebdavProperties = fileList._getWebdavProperties
|
||||||
fileList._getWebdavProperties = function() {
|
fileList._getWebdavProperties = function() {
|
||||||
var props = oldGetWebdavProperties.apply(this, arguments);
|
var props = oldGetWebdavProperties.apply(this, arguments)
|
||||||
props.push(OC.Files.Client.PROPERTY_OWNER_ID);
|
props.push(OC.Files.Client.PROPERTY_OWNER_ID)
|
||||||
props.push(OC.Files.Client.PROPERTY_OWNER_DISPLAY_NAME);
|
props.push(OC.Files.Client.PROPERTY_OWNER_DISPLAY_NAME)
|
||||||
props.push(OC.Files.Client.PROPERTY_SHARE_TYPES);
|
props.push(OC.Files.Client.PROPERTY_SHARE_TYPES)
|
||||||
return props;
|
return props
|
||||||
};
|
}
|
||||||
|
|
||||||
fileList.filesClient.addFileInfoParser(function(response) {
|
fileList.filesClient.addFileInfoParser(function(response) {
|
||||||
var data = {};
|
var data = {}
|
||||||
var props = response.propStat[0].properties;
|
var props = response.propStat[0].properties
|
||||||
var permissionsProp = props[OC.Files.Client.PROPERTY_PERMISSIONS];
|
var permissionsProp = props[OC.Files.Client.PROPERTY_PERMISSIONS]
|
||||||
|
|
||||||
if (permissionsProp && permissionsProp.indexOf('S') >= 0) {
|
if (permissionsProp && permissionsProp.indexOf('S') >= 0) {
|
||||||
data.shareOwner = props[OC.Files.Client.PROPERTY_OWNER_DISPLAY_NAME];
|
data.shareOwner = props[OC.Files.Client.PROPERTY_OWNER_DISPLAY_NAME]
|
||||||
data.shareOwnerId = props[OC.Files.Client.PROPERTY_OWNER_ID];
|
data.shareOwnerId = props[OC.Files.Client.PROPERTY_OWNER_ID]
|
||||||
}
|
}
|
||||||
|
|
||||||
var shareTypesProp = props[OC.Files.Client.PROPERTY_SHARE_TYPES];
|
var shareTypesProp = props[OC.Files.Client.PROPERTY_SHARE_TYPES]
|
||||||
if (shareTypesProp) {
|
if (shareTypesProp) {
|
||||||
data.shareTypes = _.chain(shareTypesProp).filter(function(xmlvalue) {
|
data.shareTypes = _.chain(shareTypesProp).filter(function(xmlvalue) {
|
||||||
return (xmlvalue.namespaceURI === OC.Files.Client.NS_OWNCLOUD && xmlvalue.nodeName.split(':')[1] === 'share-type');
|
return (xmlvalue.namespaceURI === OC.Files.Client.NS_OWNCLOUD && xmlvalue.nodeName.split(':')[1] === 'share-type')
|
||||||
}).map(function(xmlvalue) {
|
}).map(function(xmlvalue) {
|
||||||
return parseInt(xmlvalue.textContent || xmlvalue.text, 10);
|
return parseInt(xmlvalue.textContent || xmlvalue.text, 10)
|
||||||
}).value();
|
}).value()
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data
|
||||||
});
|
})
|
||||||
|
|
||||||
// use delegate to catch the case with multiple file lists
|
// use delegate to catch the case with multiple file lists
|
||||||
fileList.$el.on('fileActionsReady', function(ev){
|
fileList.$el.on('fileActionsReady', function(ev) {
|
||||||
var $files = ev.$files;
|
var $files = ev.$files
|
||||||
|
|
||||||
_.each($files, function(file) {
|
_.each($files, function(file) {
|
||||||
var $tr = $(file);
|
var $tr = $(file)
|
||||||
var shareTypes = $tr.attr('data-share-types') || '';
|
var shareTypes = $tr.attr('data-share-types') || ''
|
||||||
var shareOwner = $tr.attr('data-share-owner');
|
var shareOwner = $tr.attr('data-share-owner')
|
||||||
if (shareTypes || shareOwner) {
|
if (shareTypes || shareOwner) {
|
||||||
var hasLink = false;
|
var hasLink = false
|
||||||
var hasShares = false;
|
var hasShares = false
|
||||||
_.each(shareTypes.split(',') || [], function(shareType) {
|
_.each(shareTypes.split(',') || [], function(shareType) {
|
||||||
shareType = parseInt(shareType, 10);
|
shareType = parseInt(shareType, 10)
|
||||||
if (shareType === OC.Share.SHARE_TYPE_LINK) {
|
if (shareType === OC.Share.SHARE_TYPE_LINK) {
|
||||||
hasLink = true;
|
hasLink = true
|
||||||
} else if (shareType === OC.Share.SHARE_TYPE_EMAIL) {
|
} else if (shareType === OC.Share.SHARE_TYPE_EMAIL) {
|
||||||
hasLink = true;
|
hasLink = true
|
||||||
} else if (shareType === OC.Share.SHARE_TYPE_USER) {
|
} else if (shareType === OC.Share.SHARE_TYPE_USER) {
|
||||||
hasShares = true;
|
hasShares = true
|
||||||
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
|
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
|
||||||
hasShares = true;
|
hasShares = true
|
||||||
} else if (shareType === OC.Share.SHARE_TYPE_REMOTE) {
|
} else if (shareType === OC.Share.SHARE_TYPE_REMOTE) {
|
||||||
hasShares = true;
|
hasShares = true
|
||||||
} else if (shareType === OC.Share.SHARE_TYPE_CIRCLE) {
|
} else if (shareType === OC.Share.SHARE_TYPE_CIRCLE) {
|
||||||
hasShares = true;
|
hasShares = true
|
||||||
} else if (shareType === OC.Share.SHARE_TYPE_ROOM) {
|
} else if (shareType === OC.Share.SHARE_TYPE_ROOM) {
|
||||||
hasShares = true;
|
hasShares = true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
OCA.Sharing.Util._updateFileActionIcon($tr, hasShares, hasLink);
|
OCA.Sharing.Util._updateFileActionIcon($tr, hasShares, hasLink)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
||||||
fileList.$el.on('changeDirectory', function() {
|
fileList.$el.on('changeDirectory', function() {
|
||||||
OCA.Sharing.sharesLoaded = false;
|
OCA.Sharing.sharesLoaded = false
|
||||||
});
|
})
|
||||||
|
|
||||||
fileActions.registerAction({
|
fileActions.registerAction({
|
||||||
name: 'Share',
|
name: 'Share',
|
||||||
|
@ -193,40 +193,40 @@
|
||||||
type: OCA.Files.FileActions.TYPE_INLINE,
|
type: OCA.Files.FileActions.TYPE_INLINE,
|
||||||
actionHandler: function(fileName, context) {
|
actionHandler: function(fileName, context) {
|
||||||
// do not open sidebar if permission is set and equal to 0
|
// do not open sidebar if permission is set and equal to 0
|
||||||
var permissions = parseInt(context.$file.data('share-permissions'), 10);
|
var permissions = parseInt(context.$file.data('share-permissions'), 10)
|
||||||
if (isNaN(permissions) || permissions > 0) {
|
if (isNaN(permissions) || permissions > 0) {
|
||||||
fileList.showDetailsView(fileName, 'shareTabView');
|
fileList.showDetailsView(fileName, 'shareTabView')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: function(actionSpec, isDefault, context) {
|
render: function(actionSpec, isDefault, context) {
|
||||||
var permissions = parseInt(context.$file.data('permissions'), 10);
|
var permissions = parseInt(context.$file.data('permissions'), 10)
|
||||||
// if no share permissions but share owner exists, still show the link
|
// if no share permissions but share owner exists, still show the link
|
||||||
if ((permissions & OC.PERMISSION_SHARE) !== 0 || context.$file.attr('data-share-owner')) {
|
if ((permissions & OC.PERMISSION_SHARE) !== 0 || context.$file.attr('data-share-owner')) {
|
||||||
return fileActions._defaultRenderAction.call(fileActions, actionSpec, isDefault, context);
|
return fileActions._defaultRenderAction.call(fileActions, actionSpec, isDefault, context)
|
||||||
}
|
}
|
||||||
// don't render anything
|
// don't render anything
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
var shareTab = new OCA.Sharing.ShareTabView('shareTabView', {order: -20});
|
var shareTab = new OCA.Sharing.ShareTabView('shareTabView', { order: -20 })
|
||||||
// detect changes and change the matching list entry
|
// detect changes and change the matching list entry
|
||||||
shareTab.on('sharesChanged', function(shareModel) {
|
shareTab.on('sharesChanged', function(shareModel) {
|
||||||
var fileInfoModel = shareModel.fileInfoModel;
|
var fileInfoModel = shareModel.fileInfoModel
|
||||||
var $tr = fileList.findFileEl(fileInfoModel.get('name'));
|
var $tr = fileList.findFileEl(fileInfoModel.get('name'))
|
||||||
|
|
||||||
// We count email shares as link share
|
// We count email shares as link share
|
||||||
var hasLinkShares = shareModel.hasLinkShares();
|
var hasLinkShares = shareModel.hasLinkShares()
|
||||||
shareModel.get('shares').forEach(function (share) {
|
shareModel.get('shares').forEach(function(share) {
|
||||||
if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) {
|
if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) {
|
||||||
hasLinkShares = true;
|
hasLinkShares = true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
OCA.Sharing.Util._updateFileListDataAttributes(fileList, $tr, shareModel);
|
OCA.Sharing.Util._updateFileListDataAttributes(fileList, $tr, shareModel)
|
||||||
if (!OCA.Sharing.Util._updateFileActionIcon($tr, shareModel.hasUserShares(), hasLinkShares)) {
|
if (!OCA.Sharing.Util._updateFileActionIcon($tr, shareModel.hasUserShares(), hasLinkShares)) {
|
||||||
// remove icon, if applicable
|
// remove icon, if applicable
|
||||||
OC.Share.markFileAsShared($tr, false, false);
|
OC.Share.markFileAsShared($tr, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this is too convoluted. We need to get rid of the above updates
|
// FIXME: this is too convoluted. We need to get rid of the above updates
|
||||||
|
@ -237,12 +237,12 @@
|
||||||
// we need to modify the model
|
// we need to modify the model
|
||||||
// (FIXME: yes, this is hacky)
|
// (FIXME: yes, this is hacky)
|
||||||
icon: $tr.attr('data-icon')
|
icon: $tr.attr('data-icon')
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
fileList.registerTabView(shareTab);
|
fileList.registerTabView(shareTab)
|
||||||
|
|
||||||
var breadCrumbSharingDetailView = new OCA.Sharing.ShareBreadCrumbView({shareTab: shareTab});
|
var breadCrumbSharingDetailView = new OCA.Sharing.ShareBreadCrumbView({ shareTab: shareTab })
|
||||||
fileList.registerBreadCrumbDetailView(breadCrumbSharingDetailView);
|
fileList.registerBreadCrumbDetailView(breadCrumbSharingDetailView)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -252,18 +252,17 @@
|
||||||
// files app current cannot show recipients on load, so we don't update the
|
// files app current cannot show recipients on load, so we don't update the
|
||||||
// icon when changed for consistency
|
// icon when changed for consistency
|
||||||
if (fileList.id === 'files') {
|
if (fileList.id === 'files') {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
var recipients = _.pluck(shareModel.get('shares'), 'share_with_displayname');
|
var recipients = _.pluck(shareModel.get('shares'), 'share_with_displayname')
|
||||||
// note: we only update the data attribute because updateIcon()
|
// note: we only update the data attribute because updateIcon()
|
||||||
if (recipients.length) {
|
if (recipients.length) {
|
||||||
var recipientData = _.mapObject(shareModel.get('shares'), function (share) {
|
var recipientData = _.mapObject(shareModel.get('shares'), function(share) {
|
||||||
return {shareWith: share.share_with, shareWithDisplayName: share.share_with_displayname};
|
return { shareWith: share.share_with, shareWithDisplayName: share.share_with_displayname }
|
||||||
});
|
})
|
||||||
$tr.attr('data-share-recipient-data', JSON.stringify(recipientData));
|
$tr.attr('data-share-recipient-data', JSON.stringify(recipientData))
|
||||||
}
|
} else {
|
||||||
else {
|
$tr.removeAttr('data-share-recipient-data')
|
||||||
$tr.removeAttr('data-share-recipient-data');
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -274,16 +273,16 @@
|
||||||
* @param {boolean} hasUserShares true if a user share exists
|
* @param {boolean} hasUserShares true if a user share exists
|
||||||
* @param {boolean} hasLinkShares true if a link share exists
|
* @param {boolean} hasLinkShares true if a link share exists
|
||||||
*
|
*
|
||||||
* @return {boolean} true if the icon was set, false otherwise
|
* @returns {boolean} true if the icon was set, false otherwise
|
||||||
*/
|
*/
|
||||||
_updateFileActionIcon: function($tr, hasUserShares, hasLinkShares) {
|
_updateFileActionIcon: function($tr, hasUserShares, hasLinkShares) {
|
||||||
// if the statuses are loaded already, use them for the icon
|
// if the statuses are loaded already, use them for the icon
|
||||||
// (needed when scrolling to the next page)
|
// (needed when scrolling to the next page)
|
||||||
if (hasUserShares || hasLinkShares || $tr.attr('data-share-recipient-data') || $tr.attr('data-share-owner')) {
|
if (hasUserShares || hasLinkShares || $tr.attr('data-share-recipient-data') || $tr.attr('data-share-owner')) {
|
||||||
OC.Share.markFileAsShared($tr, true, hasLinkShares);
|
OC.Share.markFileAsShared($tr, true, hasLinkShares)
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -291,9 +290,9 @@
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
getSharePermissions: function(fileData) {
|
getSharePermissions: function(fileData) {
|
||||||
return fileData.sharePermissions;
|
return fileData.sharePermissions
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
})();
|
})()
|
||||||
|
|
||||||
OC.Plugins.register('OCA.Files.FileList', OCA.Sharing.Util);
|
OC.Plugins.register('OCA.Files.FileList', OCA.Sharing.Util)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
/* global Handlebars, OC */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright 2016 Christoph Wurst <christoph@winzerhof-wurst.at>
|
* @copyright 2016 Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||||
*
|
*
|
||||||
|
@ -23,7 +21,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict'
|
||||||
|
|
||||||
var BreadCrumbView = OC.Backbone.View.extend({
|
var BreadCrumbView = OC.Backbone.View.extend({
|
||||||
tagName: 'span',
|
tagName: 'span',
|
||||||
|
@ -36,68 +34,68 @@
|
||||||
_shareTab: undefined,
|
_shareTab: undefined,
|
||||||
|
|
||||||
initialize: function(options) {
|
initialize: function(options) {
|
||||||
this._shareTab = options.shareTab;
|
this._shareTab = options.shareTab
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
this._dirInfo = data.dirInfo || null;
|
this._dirInfo = data.dirInfo || null
|
||||||
|
|
||||||
if (this._dirInfo !== null && (this._dirInfo.path !== '/' || this._dirInfo.name !== '')) {
|
if (this._dirInfo !== null && (this._dirInfo.path !== '/' || this._dirInfo.name !== '')) {
|
||||||
var isShared = data.dirInfo && data.dirInfo.shareTypes && data.dirInfo.shareTypes.length > 0;
|
var isShared = data.dirInfo && data.dirInfo.shareTypes && data.dirInfo.shareTypes.length > 0
|
||||||
this.$el.removeClass('shared icon-public icon-shared');
|
this.$el.removeClass('shared icon-public icon-shared')
|
||||||
if (isShared) {
|
if (isShared) {
|
||||||
this.$el.addClass('shared');
|
this.$el.addClass('shared')
|
||||||
if (data.dirInfo.shareTypes.indexOf(OC.Share.SHARE_TYPE_LINK) !== -1) {
|
if (data.dirInfo.shareTypes.indexOf(OC.Share.SHARE_TYPE_LINK) !== -1) {
|
||||||
this.$el.addClass('icon-public');
|
this.$el.addClass('icon-public')
|
||||||
} else {
|
} else {
|
||||||
this.$el.addClass('icon-shared');
|
this.$el.addClass('icon-shared')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.$el.addClass('icon-shared');
|
this.$el.addClass('icon-shared')
|
||||||
}
|
}
|
||||||
this.$el.show();
|
this.$el.show()
|
||||||
this.delegateEvents();
|
this.delegateEvents()
|
||||||
} else {
|
} else {
|
||||||
this.$el.removeClass('shared icon-public icon-shared');
|
this.$el.removeClass('shared icon-public icon-shared')
|
||||||
this.$el.hide();
|
this.$el.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this
|
||||||
},
|
},
|
||||||
_onClick: function(e) {
|
_onClick: function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
|
|
||||||
var fileInfoModel = new OCA.Files.FileInfoModel(this._dirInfo);
|
var fileInfoModel = new OCA.Files.FileInfoModel(this._dirInfo)
|
||||||
var self = this;
|
var self = this
|
||||||
fileInfoModel.on('change', function() {
|
fileInfoModel.on('change', function() {
|
||||||
self.render({
|
self.render({
|
||||||
dirInfo: self._dirInfo
|
dirInfo: self._dirInfo
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
this._shareTab.on('sharesChanged', function(shareModel) {
|
this._shareTab.on('sharesChanged', function(shareModel) {
|
||||||
var shareTypes = [];
|
var shareTypes = []
|
||||||
var shares = shareModel.getSharesWithCurrentItem();
|
var shares = shareModel.getSharesWithCurrentItem()
|
||||||
|
|
||||||
for(var i = 0; i < shares.length; i++) {
|
for (var i = 0; i < shares.length; i++) {
|
||||||
if (shareTypes.indexOf(shares[i].share_type) === -1) {
|
if (shareTypes.indexOf(shares[i].share_type) === -1) {
|
||||||
shareTypes.push(shares[i].share_type);
|
shareTypes.push(shares[i].share_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shareModel.hasLinkShares()) {
|
if (shareModel.hasLinkShares()) {
|
||||||
shareTypes.push(OC.Share.SHARE_TYPE_LINK);
|
shareTypes.push(OC.Share.SHARE_TYPE_LINK)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since the dirInfo isn't updated we need to do this dark hackery
|
// Since the dirInfo isn't updated we need to do this dark hackery
|
||||||
self._dirInfo.shareTypes = shareTypes;
|
self._dirInfo.shareTypes = shareTypes
|
||||||
|
|
||||||
self.render({
|
self.render({
|
||||||
dirInfo: self._dirInfo
|
dirInfo: self._dirInfo
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
OCA.Files.App.fileList.showDetailsView(fileInfoModel, 'shareTabView');
|
OCA.Files.App.fileList.showDetailsView(fileInfoModel, 'shareTabView')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
OCA.Sharing.ShareBreadCrumbView = BreadCrumbView;
|
OCA.Sharing.ShareBreadCrumbView = BreadCrumbView
|
||||||
})();
|
})()
|
||||||
|
|
|
@ -11,77 +11,77 @@
|
||||||
/* @global Handlebars */
|
/* @global Handlebars */
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var TEMPLATE =
|
var TEMPLATE
|
||||||
'<div>' +
|
= '<div>'
|
||||||
'<div class="dialogContainer"></div>' +
|
+ '<div class="dialogContainer"></div>'
|
||||||
'<div id="collaborationResources"></div>' +
|
+ '<div id="collaborationResources"></div>'
|
||||||
'</div>';
|
+ '</div>'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @memberof OCA.Sharing
|
* @memberof OCA.Sharing
|
||||||
*/
|
*/
|
||||||
var ShareTabView = OCA.Files.DetailTabView.extend(
|
var ShareTabView = OCA.Files.DetailTabView.extend(
|
||||||
/** @lends OCA.Sharing.ShareTabView.prototype */ {
|
/** @lends OCA.Sharing.ShareTabView.prototype */ {
|
||||||
id: 'shareTabView',
|
id: 'shareTabView',
|
||||||
className: 'tab shareTabView',
|
className: 'tab shareTabView',
|
||||||
|
|
||||||
initialize: function(name, options) {
|
initialize: function(name, options) {
|
||||||
OCA.Files.DetailTabView.prototype.initialize.call(this, name, options);
|
OCA.Files.DetailTabView.prototype.initialize.call(this, name, options)
|
||||||
OC.Plugins.attach('OCA.Sharing.ShareTabView', this);
|
OC.Plugins.attach('OCA.Sharing.ShareTabView', this)
|
||||||
},
|
},
|
||||||
|
|
||||||
template: function(params) {
|
template: function(params) {
|
||||||
return TEMPLATE;
|
return TEMPLATE
|
||||||
},
|
},
|
||||||
|
|
||||||
getLabel: function() {
|
getLabel: function() {
|
||||||
return t('files_sharing', 'Sharing');
|
return t('files_sharing', 'Sharing')
|
||||||
},
|
},
|
||||||
|
|
||||||
getIcon: function() {
|
getIcon: function() {
|
||||||
return 'icon-shared';
|
return 'icon-shared'
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders this details view
|
* Renders this details view
|
||||||
*/
|
*/
|
||||||
render: function() {
|
render: function() {
|
||||||
var self = this;
|
var self = this
|
||||||
if (this._dialog) {
|
if (this._dialog) {
|
||||||
// remove/destroy older instance
|
// remove/destroy older instance
|
||||||
this._dialog.model.off();
|
this._dialog.model.off()
|
||||||
this._dialog.remove();
|
this._dialog.remove()
|
||||||
this._dialog = null;
|
this._dialog = null
|
||||||
}
|
|
||||||
|
|
||||||
if (this.model) {
|
|
||||||
this.$el.html(this.template());
|
|
||||||
|
|
||||||
if (_.isUndefined(this.model.get('sharePermissions'))) {
|
|
||||||
this.model.set('sharePermissions', OCA.Sharing.Util.getSharePermissions(this.model.attributes));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: the model should read these directly off the passed fileInfoModel
|
if (this.model) {
|
||||||
var attributes = {
|
this.$el.html(this.template())
|
||||||
itemType: this.model.isDirectory() ? 'folder' : 'file',
|
|
||||||
itemSource: this.model.get('id'),
|
if (_.isUndefined(this.model.get('sharePermissions'))) {
|
||||||
possiblePermissions: this.model.get('sharePermissions')
|
this.model.set('sharePermissions', OCA.Sharing.Util.getSharePermissions(this.model.attributes))
|
||||||
};
|
}
|
||||||
var configModel = new OC.Share.ShareConfigModel();
|
|
||||||
var shareModel = new OC.Share.ShareItemModel(attributes, {
|
// TODO: the model should read these directly off the passed fileInfoModel
|
||||||
configModel: configModel,
|
var attributes = {
|
||||||
fileInfoModel: this.model
|
itemType: this.model.isDirectory() ? 'folder' : 'file',
|
||||||
});
|
itemSource: this.model.get('id'),
|
||||||
this._dialog = new OC.Share.ShareDialogView({
|
possiblePermissions: this.model.get('sharePermissions')
|
||||||
configModel: configModel,
|
}
|
||||||
model: shareModel
|
var configModel = new OC.Share.ShareConfigModel()
|
||||||
});
|
var shareModel = new OC.Share.ShareItemModel(attributes, {
|
||||||
this.$el.find('.dialogContainer').append(this._dialog.$el);
|
configModel: configModel,
|
||||||
this._dialog.render();
|
fileInfoModel: this.model
|
||||||
this._dialog.model.fetch();
|
})
|
||||||
this._dialog.model.on('change', function() {
|
this._dialog = new OC.Share.ShareDialogView({
|
||||||
self.trigger('sharesChanged', shareModel);
|
configModel: configModel,
|
||||||
});
|
model: shareModel
|
||||||
|
})
|
||||||
|
this.$el.find('.dialogContainer').append(this._dialog.$el)
|
||||||
|
this._dialog.render()
|
||||||
|
this._dialog.model.fetch()
|
||||||
|
this._dialog.model.on('change', function() {
|
||||||
|
self.trigger('sharesChanged', shareModel)
|
||||||
|
})
|
||||||
|
|
||||||
import('./collaborationresources').then((Resources) => {
|
import('./collaborationresources').then((Resources) => {
|
||||||
var vm = new Resources.Vue({
|
var vm = new Resources.Vue({
|
||||||
|
@ -89,20 +89,19 @@
|
||||||
render: h => h(Resources.View),
|
render: h => h(Resources.View),
|
||||||
data: {
|
data: {
|
||||||
model: this.model.toJSON()
|
model: this.model.toJSON()
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
this.model.on('change', () => { vm.data = this.model.toJSON() })
|
this.model.on('change', () => { vm.data = this.model.toJSON() })
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.$el.empty();
|
this.$el.empty()
|
||||||
// TODO: render placeholder text?
|
// TODO: render placeholder text?
|
||||||
|
}
|
||||||
|
this.trigger('rendered')
|
||||||
}
|
}
|
||||||
this.trigger('rendered');
|
})
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
OCA.Sharing.ShareTabView = ShareTabView;
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
OCA.Sharing.ShareTabView = ShareTabView
|
||||||
|
})()
|
||||||
|
|
|
@ -21,7 +21,10 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<collection-list v-if="fileId" type="file" :id="fileId" :name="filename"></collection-list>
|
<CollectionList v-if="fileId"
|
||||||
|
:id="fileId"
|
||||||
|
type="file"
|
||||||
|
:name="filename" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -29,22 +32,22 @@ import { CollectionList } from 'nextcloud-vue-collections'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'CollaborationView',
|
name: 'CollaborationView',
|
||||||
|
components: {
|
||||||
|
CollectionList
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
fileId() {
|
fileId() {
|
||||||
if (this.$root.model && this.$root.model.id) {
|
if (this.$root.model && this.$root.model.id) {
|
||||||
return '' + this.$root.model.id;
|
return '' + this.$root.model.id
|
||||||
}
|
}
|
||||||
return null;
|
return null
|
||||||
},
|
},
|
||||||
filename() {
|
filename() {
|
||||||
if (this.$root.model && this.$root.model.name) {
|
if (this.$root.model && this.$root.model.name) {
|
||||||
return '' + this.$root.model.name;
|
return '' + this.$root.model.name
|
||||||
}
|
}
|
||||||
return '';
|
return ''
|
||||||
}
|
}
|
||||||
},
|
|
||||||
components: {
|
|
||||||
CollectionList
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* Copyright (c) 2014
|
* Copyright (c) 2014
|
||||||
*
|
*
|
||||||
* This file is licensed under the Affero General Public License version 3
|
* This file is licensed under the Affero General Public License version 3
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
/**
|
/**
|
||||||
* @namespace OCA.Trashbin
|
* @namespace OCA.Trashbin
|
||||||
*/
|
*/
|
||||||
OCA.Trashbin = {};
|
OCA.Trashbin = {}
|
||||||
/**
|
/**
|
||||||
* @namespace OCA.Trashbin.App
|
* @namespace OCA.Trashbin.App
|
||||||
*/
|
*/
|
||||||
|
@ -20,19 +20,19 @@ OCA.Trashbin.App = {
|
||||||
/** @type {OC.Files.Client} */
|
/** @type {OC.Files.Client} */
|
||||||
client: null,
|
client: null,
|
||||||
|
|
||||||
initialize: function ($el) {
|
initialize: function($el) {
|
||||||
if (this._initialized) {
|
if (this._initialized) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
this._initialized = true;
|
this._initialized = true
|
||||||
|
|
||||||
this.client = new OC.Files.Client({
|
this.client = new OC.Files.Client({
|
||||||
host: OC.getHost(),
|
host: OC.getHost(),
|
||||||
port: OC.getPort(),
|
port: OC.getPort(),
|
||||||
root: OC.linkToRemoteBase('dav') + '/trashbin/' + OC.getCurrentUser().uid,
|
root: OC.linkToRemoteBase('dav') + '/trashbin/' + OC.getCurrentUser().uid,
|
||||||
useHTTPS: OC.getProtocol() === 'https'
|
useHTTPS: OC.getProtocol() === 'https'
|
||||||
});
|
})
|
||||||
var urlParams = OC.Util.History.parseUrlQuery();
|
var urlParams = OC.Util.History.parseUrlQuery()
|
||||||
this.fileList = new OCA.Trashbin.FileList(
|
this.fileList = new OCA.Trashbin.FileList(
|
||||||
$('#app-content-trashbin'), {
|
$('#app-content-trashbin'), {
|
||||||
fileActions: this._createFileActions(),
|
fileActions: this._createFileActions(),
|
||||||
|
@ -43,12 +43,12 @@ OCA.Trashbin.App = {
|
||||||
{
|
{
|
||||||
name: 'restore',
|
name: 'restore',
|
||||||
displayName: t('files_trashbin', 'Restore'),
|
displayName: t('files_trashbin', 'Restore'),
|
||||||
iconClass: 'icon-history',
|
iconClass: 'icon-history'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'delete',
|
name: 'delete',
|
||||||
displayName: t('files_trashbin', 'Delete permanently'),
|
displayName: t('files_trashbin', 'Delete permanently'),
|
||||||
iconClass: 'icon-delete',
|
iconClass: 'icon-delete'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
client: this.client,
|
client: this.client,
|
||||||
|
@ -57,18 +57,18 @@ OCA.Trashbin.App = {
|
||||||
// if handling the event with the file list already created.
|
// if handling the event with the file list already created.
|
||||||
shown: true
|
shown: true
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
_createFileActions: function () {
|
_createFileActions: function() {
|
||||||
var client = this.client;
|
var client = this.client
|
||||||
var fileActions = new OCA.Files.FileActions();
|
var fileActions = new OCA.Files.FileActions()
|
||||||
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
|
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function(filename, context) {
|
||||||
var dir = context.fileList.getCurrentDirectory();
|
var dir = context.fileList.getCurrentDirectory()
|
||||||
context.fileList.changeDirectory(OC.joinPaths(dir, filename));
|
context.fileList.changeDirectory(OC.joinPaths(dir, filename))
|
||||||
});
|
})
|
||||||
|
|
||||||
fileActions.setDefault('dir', 'Open');
|
fileActions.setDefault('dir', 'Open')
|
||||||
|
|
||||||
fileActions.registerAction({
|
fileActions.registerAction({
|
||||||
name: 'Restore',
|
name: 'Restore',
|
||||||
|
@ -77,21 +77,21 @@ OCA.Trashbin.App = {
|
||||||
mime: 'all',
|
mime: 'all',
|
||||||
permissions: OC.PERMISSION_READ,
|
permissions: OC.PERMISSION_READ,
|
||||||
iconClass: 'icon-history',
|
iconClass: 'icon-history',
|
||||||
actionHandler: function (filename, context) {
|
actionHandler: function(filename, context) {
|
||||||
var fileList = context.fileList;
|
var fileList = context.fileList
|
||||||
var tr = fileList.findFileEl(filename);
|
var tr = fileList.findFileEl(filename)
|
||||||
fileList.showFileBusyState(tr, true);
|
fileList.showFileBusyState(tr, true)
|
||||||
var dir = context.fileList.getCurrentDirectory();
|
var dir = context.fileList.getCurrentDirectory()
|
||||||
client.move(OC.joinPaths('trash', dir, filename), OC.joinPaths('restore', filename), true)
|
client.move(OC.joinPaths('trash', dir, filename), OC.joinPaths('restore', filename), true)
|
||||||
.then(
|
.then(
|
||||||
fileList._removeCallback.bind(fileList, [filename]),
|
fileList._removeCallback.bind(fileList, [filename]),
|
||||||
function () {
|
function() {
|
||||||
fileList.showFileBusyState(tr, false);
|
fileList.showFileBusyState(tr, false)
|
||||||
OC.Notification.show(t('files_trashbin', 'Error while restoring file from trashbin'));
|
OC.Notification.show(t('files_trashbin', 'Error while restoring file from trashbin'))
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
fileActions.registerAction({
|
fileActions.registerAction({
|
||||||
name: 'Delete',
|
name: 'Delete',
|
||||||
|
@ -99,39 +99,38 @@ OCA.Trashbin.App = {
|
||||||
mime: 'all',
|
mime: 'all',
|
||||||
permissions: OC.PERMISSION_READ,
|
permissions: OC.PERMISSION_READ,
|
||||||
iconClass: 'icon-delete',
|
iconClass: 'icon-delete',
|
||||||
render: function (actionSpec, isDefault, context) {
|
render: function(actionSpec, isDefault, context) {
|
||||||
var $actionLink = fileActions._makeActionLink(actionSpec, context);
|
var $actionLink = fileActions._makeActionLink(actionSpec, context)
|
||||||
$actionLink.attr('original-title', t('files_trashbin', 'Delete permanently'));
|
$actionLink.attr('original-title', t('files_trashbin', 'Delete permanently'))
|
||||||
$actionLink.children('img').attr('alt', t('files_trashbin', 'Delete permanently'));
|
$actionLink.children('img').attr('alt', t('files_trashbin', 'Delete permanently'))
|
||||||
context.$file.find('td:last').append($actionLink);
|
context.$file.find('td:last').append($actionLink)
|
||||||
return $actionLink;
|
return $actionLink
|
||||||
},
|
},
|
||||||
actionHandler: function (filename, context) {
|
actionHandler: function(filename, context) {
|
||||||
var fileList = context.fileList;
|
var fileList = context.fileList
|
||||||
$('.tipsy').remove();
|
$('.tipsy').remove()
|
||||||
var tr = fileList.findFileEl(filename);
|
var tr = fileList.findFileEl(filename)
|
||||||
fileList.showFileBusyState(tr, true);
|
fileList.showFileBusyState(tr, true)
|
||||||
var dir = context.fileList.getCurrentDirectory();
|
var dir = context.fileList.getCurrentDirectory()
|
||||||
client.remove(OC.joinPaths('trash', dir, filename))
|
client.remove(OC.joinPaths('trash', dir, filename))
|
||||||
.then(
|
.then(
|
||||||
fileList._removeCallback.bind(fileList, [filename]),
|
fileList._removeCallback.bind(fileList, [filename]),
|
||||||
function () {
|
function() {
|
||||||
fileList.showFileBusyState(tr, false);
|
fileList.showFileBusyState(tr, false)
|
||||||
OC.Notification.show(t('files_trashbin', 'Error while removing file from trashbin'));
|
OC.Notification.show(t('files_trashbin', 'Error while removing file from trashbin'))
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
return fileActions;
|
return fileActions
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function() {
|
||||||
$('#app-content-trashbin').one('show', function () {
|
$('#app-content-trashbin').one('show', function() {
|
||||||
var App = OCA.Trashbin.App;
|
var App = OCA.Trashbin.App
|
||||||
App.initialize($('#app-content-trashbin'));
|
App.initialize($('#app-content-trashbin'))
|
||||||
// force breadcrumb init
|
// force breadcrumb init
|
||||||
// App.fileList.changeDirectory(App.fileList.getCurrentDirectory(), false, true);
|
// App.fileList.changeDirectory(App.fileList.getCurrentDirectory(), false, true);
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable */
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2014
|
* Copyright (c) 2014
|
||||||
*
|
*
|
||||||
|
@ -8,25 +9,25 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
(function() {
|
(function() {
|
||||||
var DELETED_REGEXP = new RegExp(/^(.+)\.d[0-9]+$/);
|
var DELETED_REGEXP = new RegExp(/^(.+)\.d[0-9]+$/)
|
||||||
var FILENAME_PROP = '{http://nextcloud.org/ns}trashbin-filename';
|
var FILENAME_PROP = '{http://nextcloud.org/ns}trashbin-filename'
|
||||||
var DELETION_TIME_PROP = '{http://nextcloud.org/ns}trashbin-deletion-time';
|
var DELETION_TIME_PROP = '{http://nextcloud.org/ns}trashbin-deletion-time'
|
||||||
var TRASHBIN_ORIGINAL_LOCATION = '{http://nextcloud.org/ns}trashbin-original-location';
|
var TRASHBIN_ORIGINAL_LOCATION = '{http://nextcloud.org/ns}trashbin-original-location'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a file name in the format filename.d12345 to the real file name.
|
* Convert a file name in the format filename.d12345 to the real file name.
|
||||||
* This will use basename.
|
* This will use basename.
|
||||||
* The name will not be changed if it has no ".d12345" suffix.
|
* The name will not be changed if it has no ".d12345" suffix.
|
||||||
* @param {String} name file name
|
* @param {String} name file name
|
||||||
* @return {String} converted file name
|
* @returns {String} converted file name
|
||||||
*/
|
*/
|
||||||
function getDeletedFileName(name) {
|
function getDeletedFileName(name) {
|
||||||
name = OC.basename(name);
|
name = OC.basename(name)
|
||||||
var match = DELETED_REGEXP.exec(name);
|
var match = DELETED_REGEXP.exec(name)
|
||||||
if (match && match.length > 1) {
|
if (match && match.length > 1) {
|
||||||
name = match[1];
|
name = match[1]
|
||||||
}
|
}
|
||||||
return name;
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,283 +40,282 @@
|
||||||
* @param [options] map of options
|
* @param [options] map of options
|
||||||
*/
|
*/
|
||||||
var FileList = function($el, options) {
|
var FileList = function($el, options) {
|
||||||
this.client = options.client;
|
this.client = options.client
|
||||||
this.initialize($el, options);
|
this.initialize($el, options)
|
||||||
};
|
}
|
||||||
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
|
FileList.prototype = _.extend({}, OCA.Files.FileList.prototype,
|
||||||
/** @lends OCA.Trashbin.FileList.prototype */ {
|
/** @lends OCA.Trashbin.FileList.prototype */ {
|
||||||
id: 'trashbin',
|
id: 'trashbin',
|
||||||
appName: t('files_trashbin', 'Deleted files'),
|
appName: t('files_trashbin', 'Deleted files'),
|
||||||
/** @type {OC.Files.Client} */
|
/** @type {OC.Files.Client} */
|
||||||
client: null,
|
client: null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
this.client.addFileInfoParser(function(response, data) {
|
this.client.addFileInfoParser(function(response, data) {
|
||||||
var props = response.propStat[0].properties;
|
var props = response.propStat[0].properties
|
||||||
var path = props[TRASHBIN_ORIGINAL_LOCATION];
|
var path = props[TRASHBIN_ORIGINAL_LOCATION]
|
||||||
return {
|
return {
|
||||||
displayName: props[FILENAME_PROP],
|
displayName: props[FILENAME_PROP],
|
||||||
mtime: parseInt(props[DELETION_TIME_PROP], 10) * 1000,
|
mtime: parseInt(props[DELETION_TIME_PROP], 10) * 1000,
|
||||||
hasPreview: true,
|
hasPreview: true,
|
||||||
path: path,
|
path: path,
|
||||||
extraData: path
|
extraData: path
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments);
|
var result = OCA.Files.FileList.prototype.initialize.apply(this, arguments)
|
||||||
this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this));
|
this.$el.find('.undelete').click('click', _.bind(this._onClickRestoreSelected, this))
|
||||||
|
|
||||||
this.setSort('mtime', 'desc');
|
this.setSort('mtime', 'desc')
|
||||||
/**
|
/**
|
||||||
* Override crumb making to add "Deleted Files" entry
|
* Override crumb making to add "Deleted Files" entry
|
||||||
* and convert files with ".d" extensions to a more
|
* and convert files with ".d" extensions to a more
|
||||||
* user friendly name.
|
* user friendly name.
|
||||||
*/
|
*/
|
||||||
this.breadcrumb._makeCrumbs = function() {
|
this.breadcrumb._makeCrumbs = function() {
|
||||||
var parts = OCA.Files.BreadCrumb.prototype._makeCrumbs.apply(this, arguments);
|
var parts = OCA.Files.BreadCrumb.prototype._makeCrumbs.apply(this, arguments)
|
||||||
for (var i = 1; i < parts.length; i++) {
|
for (var i = 1; i < parts.length; i++) {
|
||||||
parts[i].name = getDeletedFileName(parts[i].name);
|
parts[i].name = getDeletedFileName(parts[i].name)
|
||||||
|
}
|
||||||
|
return parts
|
||||||
}
|
}
|
||||||
return parts;
|
|
||||||
};
|
|
||||||
|
|
||||||
OC.Plugins.attach('OCA.Trashbin.FileList', this);
|
OC.Plugins.attach('OCA.Trashbin.FileList', this)
|
||||||
return result;
|
return result
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override to only return read permissions
|
* Override to only return read permissions
|
||||||
*/
|
*/
|
||||||
getDirectoryPermissions: function() {
|
getDirectoryPermissions: function() {
|
||||||
return OC.PERMISSION_READ | OC.PERMISSION_DELETE;
|
return OC.PERMISSION_READ | OC.PERMISSION_DELETE
|
||||||
},
|
},
|
||||||
|
|
||||||
_setCurrentDir: function(targetDir) {
|
_setCurrentDir: function(targetDir) {
|
||||||
OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments);
|
OCA.Files.FileList.prototype._setCurrentDir.apply(this, arguments)
|
||||||
|
|
||||||
var baseDir = OC.basename(targetDir);
|
var baseDir = OC.basename(targetDir)
|
||||||
if (baseDir !== '') {
|
if (baseDir !== '') {
|
||||||
this.setPageTitle(getDeletedFileName(baseDir));
|
this.setPageTitle(getDeletedFileName(baseDir))
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_createRow: function() {
|
|
||||||
// FIXME: MEGAHACK until we find a better solution
|
|
||||||
var tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments);
|
|
||||||
tr.find('td.filesize').remove();
|
|
||||||
return tr;
|
|
||||||
},
|
|
||||||
|
|
||||||
getAjaxUrl: function(action, params) {
|
|
||||||
var q = '';
|
|
||||||
if (params) {
|
|
||||||
q = '?' + OC.buildQueryString(params);
|
|
||||||
}
|
|
||||||
return OC.filePath('files_trashbin', 'ajax', action + '.php') + q;
|
|
||||||
},
|
|
||||||
|
|
||||||
setupUploadEvents: function() {
|
|
||||||
// override and do nothing
|
|
||||||
},
|
|
||||||
|
|
||||||
linkTo: function(dir){
|
|
||||||
return OC.linkTo('files', 'index.php')+"?view=trashbin&dir="+ encodeURIComponent(dir).replace(/%2F/g, '/');
|
|
||||||
},
|
|
||||||
|
|
||||||
elementToFile: function($el) {
|
|
||||||
var fileInfo = OCA.Files.FileList.prototype.elementToFile($el);
|
|
||||||
if (this.getCurrentDirectory() === '/') {
|
|
||||||
fileInfo.displayName = getDeletedFileName(fileInfo.name);
|
|
||||||
}
|
|
||||||
// no size available
|
|
||||||
delete fileInfo.size;
|
|
||||||
return fileInfo;
|
|
||||||
},
|
|
||||||
|
|
||||||
updateEmptyContent: function(){
|
|
||||||
var exists = this.$fileList.find('tr:first').exists();
|
|
||||||
this.$el.find('#emptycontent').toggleClass('hidden', exists);
|
|
||||||
this.$el.find('#filestable th').toggleClass('hidden', !exists);
|
|
||||||
},
|
|
||||||
|
|
||||||
_removeCallback: function(files) {
|
|
||||||
var $el;
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
|
||||||
$el = this.remove(OC.basename(files[i]), {updateSummary: false});
|
|
||||||
this.fileSummary.remove({type: $el.attr('data-type'), size: $el.attr('data-size')});
|
|
||||||
}
|
|
||||||
this.fileSummary.update();
|
|
||||||
this.updateEmptyContent();
|
|
||||||
},
|
|
||||||
|
|
||||||
_onClickRestoreSelected: function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
var self = this;
|
|
||||||
var files = _.pluck(this.getSelectedFiles(), 'name');
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
|
||||||
var tr = this.findFileEl(files[i]);
|
|
||||||
this.showFileBusyState(tr, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fileMultiSelectMenu.toggleLoading('restore', true);
|
|
||||||
var restorePromises = files.map(function(file) {
|
|
||||||
return self.client.move(OC.joinPaths('trash', self.getCurrentDirectory(), file), OC.joinPaths('restore', file), true)
|
|
||||||
.then(
|
|
||||||
function() {
|
|
||||||
self._removeCallback([file]);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return Promise.all(restorePromises).then(
|
|
||||||
function() {
|
|
||||||
self.fileMultiSelectMenu.toggleLoading('restore', false);
|
|
||||||
},
|
|
||||||
function() {
|
|
||||||
OC.Notification.show(t('files_trashbin', 'Error while restoring files from trashbin'));
|
|
||||||
}
|
}
|
||||||
);
|
},
|
||||||
},
|
|
||||||
|
|
||||||
_onClickDeleteSelected: function(event) {
|
_createRow: function() {
|
||||||
event.preventDefault();
|
// FIXME: MEGAHACK until we find a better solution
|
||||||
var self = this;
|
var tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments)
|
||||||
var allFiles = this.$el.find('.select-all').is(':checked');
|
tr.find('td.filesize').remove()
|
||||||
var files = _.pluck(this.getSelectedFiles(), 'name');
|
return tr
|
||||||
for (var i = 0; i < files.length; i++) {
|
},
|
||||||
var tr = this.findFileEl(files[i]);
|
|
||||||
this.showFileBusyState(tr, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allFiles) {
|
getAjaxUrl: function(action, params) {
|
||||||
return this.client.remove(OC.joinPaths('trash', this.getCurrentDirectory()))
|
var q = ''
|
||||||
.then(
|
if (params) {
|
||||||
function() {
|
q = '?' + OC.buildQueryString(params)
|
||||||
self.hideMask();
|
}
|
||||||
self.setFiles([]);
|
return OC.filePath('files_trashbin', 'ajax', action + '.php') + q
|
||||||
},
|
},
|
||||||
function() {
|
|
||||||
OC.Notification.show(t('files_trashbin', 'Error while emptying trashbin'));
|
setupUploadEvents: function() {
|
||||||
}
|
// override and do nothing
|
||||||
);
|
},
|
||||||
} else {
|
|
||||||
this.fileMultiSelectMenu.toggleLoading('delete', true);
|
linkTo: function(dir) {
|
||||||
var deletePromises = files.map(function(file) {
|
return OC.linkTo('files', 'index.php') + '?view=trashbin&dir=' + encodeURIComponent(dir).replace(/%2F/g, '/')
|
||||||
return self.client.remove(OC.joinPaths('trash', self.getCurrentDirectory(), file))
|
},
|
||||||
|
|
||||||
|
elementToFile: function($el) {
|
||||||
|
var fileInfo = OCA.Files.FileList.prototype.elementToFile($el)
|
||||||
|
if (this.getCurrentDirectory() === '/') {
|
||||||
|
fileInfo.displayName = getDeletedFileName(fileInfo.name)
|
||||||
|
}
|
||||||
|
// no size available
|
||||||
|
delete fileInfo.size
|
||||||
|
return fileInfo
|
||||||
|
},
|
||||||
|
|
||||||
|
updateEmptyContent: function() {
|
||||||
|
var exists = this.$fileList.find('tr:first').exists()
|
||||||
|
this.$el.find('#emptycontent').toggleClass('hidden', exists)
|
||||||
|
this.$el.find('#filestable th').toggleClass('hidden', !exists)
|
||||||
|
},
|
||||||
|
|
||||||
|
_removeCallback: function(files) {
|
||||||
|
var $el
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
$el = this.remove(OC.basename(files[i]), { updateSummary: false })
|
||||||
|
this.fileSummary.remove({ type: $el.attr('data-type'), size: $el.attr('data-size') })
|
||||||
|
}
|
||||||
|
this.fileSummary.update()
|
||||||
|
this.updateEmptyContent()
|
||||||
|
},
|
||||||
|
|
||||||
|
_onClickRestoreSelected: function(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
var self = this
|
||||||
|
var files = _.pluck(this.getSelectedFiles(), 'name')
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
var tr = this.findFileEl(files[i])
|
||||||
|
this.showFileBusyState(tr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fileMultiSelectMenu.toggleLoading('restore', true)
|
||||||
|
var restorePromises = files.map(function(file) {
|
||||||
|
return self.client.move(OC.joinPaths('trash', self.getCurrentDirectory(), file), OC.joinPaths('restore', file), true)
|
||||||
.then(
|
.then(
|
||||||
function() {
|
function() {
|
||||||
self._removeCallback([file]);
|
self._removeCallback([file])
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
return Promise.all(deletePromises).then(
|
return Promise.all(restorePromises).then(
|
||||||
function() {
|
function() {
|
||||||
self.fileMultiSelectMenu.toggleLoading('delete', false);
|
self.fileMultiSelectMenu.toggleLoading('restore', false)
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
OC.Notification.show(t('files_trashbin', 'Error while removing files from trashbin'));
|
OC.Notification.show(t('files_trashbin', 'Error while restoring files from trashbin'))
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
}
|
},
|
||||||
},
|
|
||||||
|
|
||||||
_onClickFile: function(event) {
|
_onClickDeleteSelected: function(event) {
|
||||||
var mime = $(this).parent().parent().data('mime');
|
event.preventDefault()
|
||||||
if (mime !== 'httpd/unix-directory') {
|
var self = this
|
||||||
event.preventDefault();
|
var allFiles = this.$el.find('.select-all').is(':checked')
|
||||||
}
|
var files = _.pluck(this.getSelectedFiles(), 'name')
|
||||||
return OCA.Files.FileList.prototype._onClickFile.apply(this, arguments);
|
for (var i = 0; i < files.length; i++) {
|
||||||
},
|
var tr = this.findFileEl(files[i])
|
||||||
|
this.showFileBusyState(tr, true)
|
||||||
|
}
|
||||||
|
|
||||||
generatePreviewUrl: function(urlSpec) {
|
if (allFiles) {
|
||||||
return OC.generateUrl('/apps/files_trashbin/preview?') + $.param(urlSpec);
|
return this.client.remove(OC.joinPaths('trash', this.getCurrentDirectory()))
|
||||||
},
|
.then(
|
||||||
|
function() {
|
||||||
|
self.hideMask()
|
||||||
|
self.setFiles([])
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
OC.Notification.show(t('files_trashbin', 'Error while emptying trashbin'))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.fileMultiSelectMenu.toggleLoading('delete', true)
|
||||||
|
var deletePromises = files.map(function(file) {
|
||||||
|
return self.client.remove(OC.joinPaths('trash', self.getCurrentDirectory(), file))
|
||||||
|
.then(
|
||||||
|
function() {
|
||||||
|
self._removeCallback([file])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return Promise.all(deletePromises).then(
|
||||||
|
function() {
|
||||||
|
self.fileMultiSelectMenu.toggleLoading('delete', false)
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
OC.Notification.show(t('files_trashbin', 'Error while removing files from trashbin'))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getDownloadUrl: function() {
|
_onClickFile: function(event) {
|
||||||
|
var mime = $(this).parent().parent().data('mime')
|
||||||
|
if (mime !== 'httpd/unix-directory') {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
return OCA.Files.FileList.prototype._onClickFile.apply(this, arguments)
|
||||||
|
},
|
||||||
|
|
||||||
|
generatePreviewUrl: function(urlSpec) {
|
||||||
|
return OC.generateUrl('/apps/files_trashbin/preview?') + $.param(urlSpec)
|
||||||
|
},
|
||||||
|
|
||||||
|
getDownloadUrl: function() {
|
||||||
// no downloads
|
// no downloads
|
||||||
return '#';
|
return '#'
|
||||||
},
|
},
|
||||||
|
|
||||||
updateStorageStatistics: function() {
|
updateStorageStatistics: function() {
|
||||||
// no op because the trashbin doesn't have
|
// no op because the trashbin doesn't have
|
||||||
// storage info like free space / used space
|
// storage info like free space / used space
|
||||||
},
|
},
|
||||||
|
|
||||||
isSelectedDeletable: function() {
|
isSelectedDeletable: function() {
|
||||||
return true;
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns list of webdav properties to request
|
* Returns list of webdav properties to request
|
||||||
*/
|
*/
|
||||||
_getWebdavProperties: function() {
|
_getWebdavProperties: function() {
|
||||||
return [FILENAME_PROP, DELETION_TIME_PROP, TRASHBIN_ORIGINAL_LOCATION].concat(this.filesClient.getPropfindProperties());
|
return [FILENAME_PROP, DELETION_TIME_PROP, TRASHBIN_ORIGINAL_LOCATION].concat(this.filesClient.getPropfindProperties())
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reloads the file list using ajax call
|
* Reloads the file list using ajax call
|
||||||
*
|
*
|
||||||
* @return ajax call object
|
* @returns ajax call object
|
||||||
*/
|
*/
|
||||||
reload: function() {
|
reload: function() {
|
||||||
this._selectedFiles = {};
|
this._selectedFiles = {}
|
||||||
this._selectionSummary.clear();
|
this._selectionSummary.clear()
|
||||||
this.$el.find('.select-all').prop('checked', false);
|
this.$el.find('.select-all').prop('checked', false)
|
||||||
this.showMask();
|
this.showMask()
|
||||||
if (this._reloadCall) {
|
if (this._reloadCall) {
|
||||||
this._reloadCall.abort();
|
this._reloadCall.abort()
|
||||||
}
|
|
||||||
this._reloadCall = this.client.getFolderContents(
|
|
||||||
'trash/' + this.getCurrentDirectory(), {
|
|
||||||
includeParent: false,
|
|
||||||
properties: this._getWebdavProperties()
|
|
||||||
}
|
}
|
||||||
);
|
this._reloadCall = this.client.getFolderContents(
|
||||||
var callBack = this.reloadCallback.bind(this);
|
'trash/' + this.getCurrentDirectory(), {
|
||||||
return this._reloadCall.then(callBack, callBack);
|
includeParent: false,
|
||||||
},
|
properties: this._getWebdavProperties()
|
||||||
reloadCallback: function(status, result) {
|
}
|
||||||
delete this._reloadCall;
|
)
|
||||||
this.hideMask();
|
var callBack = this.reloadCallback.bind(this)
|
||||||
|
return this._reloadCall.then(callBack, callBack)
|
||||||
|
},
|
||||||
|
reloadCallback: function(status, result) {
|
||||||
|
delete this._reloadCall
|
||||||
|
this.hideMask()
|
||||||
|
|
||||||
if (status === 401) {
|
if (status === 401) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Firewall Blocked request?
|
// Firewall Blocked request?
|
||||||
if (status === 403) {
|
if (status === 403) {
|
||||||
// Go home
|
// Go home
|
||||||
this.changeDirectory('/');
|
this.changeDirectory('/')
|
||||||
OC.Notification.show(t('files', 'This operation is forbidden'));
|
OC.Notification.show(t('files', 'This operation is forbidden'))
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Did share service die or something else fail?
|
// Did share service die or something else fail?
|
||||||
if (status === 500) {
|
if (status === 500) {
|
||||||
// Go home
|
// Go home
|
||||||
this.changeDirectory('/');
|
this.changeDirectory('/')
|
||||||
OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'));
|
OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'))
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 404) {
|
if (status === 404) {
|
||||||
// go back home
|
// go back home
|
||||||
this.changeDirectory('/');
|
this.changeDirectory('/')
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
// aborted ?
|
// aborted ?
|
||||||
if (status === 0){
|
if (status === 0) {
|
||||||
return true;
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setFiles(result)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setFiles(result);
|
})
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
OCA.Trashbin.FileList = FileList;
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
OCA.Trashbin.FileList = FileList
|
||||||
|
})()
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -9,7 +9,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
OCA.Versions = OCA.Versions || {};
|
OCA.Versions = OCA.Versions || {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @namespace
|
* @namespace
|
||||||
|
@ -22,13 +22,12 @@
|
||||||
*/
|
*/
|
||||||
attach: function(fileList) {
|
attach: function(fileList) {
|
||||||
if (fileList.id === 'trashbin' || fileList.id === 'files.public') {
|
if (fileList.id === 'trashbin' || fileList.id === 'files.public') {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileList.registerTabView(new OCA.Versions.VersionsTabView('versionsTabView', {order: -10}));
|
fileList.registerTabView(new OCA.Versions.VersionsTabView('versionsTabView', { order: -10 }))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
})();
|
})()
|
||||||
|
|
||||||
OC.Plugins.register('OCA.Files.FileList', OCA.Versions.Util);
|
|
||||||
|
|
||||||
|
OC.Plugins.register('OCA.Files.FileList', OCA.Versions.Util)
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
(function () {
|
(function() {
|
||||||
/**
|
/**
|
||||||
* @memberof OCA.Versions
|
* @memberof OCA.Versions
|
||||||
*/
|
*/
|
||||||
|
@ -25,24 +25,24 @@
|
||||||
|
|
||||||
_client: null,
|
_client: null,
|
||||||
|
|
||||||
setFileInfo: function (fileInfo) {
|
setFileInfo: function(fileInfo) {
|
||||||
this._fileInfo = fileInfo;
|
this._fileInfo = fileInfo
|
||||||
},
|
},
|
||||||
|
|
||||||
getFileInfo: function () {
|
getFileInfo: function() {
|
||||||
return this._fileInfo;
|
return this._fileInfo
|
||||||
},
|
},
|
||||||
|
|
||||||
setCurrentUser: function(user) {
|
setCurrentUser: function(user) {
|
||||||
this._currentUser = user;
|
this._currentUser = user
|
||||||
},
|
},
|
||||||
|
|
||||||
getCurrentUser: function() {
|
getCurrentUser: function() {
|
||||||
return this._currentUser || OC.getCurrentUser().uid;
|
return this._currentUser || OC.getCurrentUser().uid
|
||||||
},
|
},
|
||||||
|
|
||||||
setClient: function(client) {
|
setClient: function(client) {
|
||||||
this._client = client;
|
this._client = client
|
||||||
},
|
},
|
||||||
|
|
||||||
getClient: function() {
|
getClient: function() {
|
||||||
|
@ -50,35 +50,34 @@
|
||||||
host: OC.getHost(),
|
host: OC.getHost(),
|
||||||
root: OC.linkToRemoteBase('dav') + '/versions/' + this.getCurrentUser(),
|
root: OC.linkToRemoteBase('dav') + '/versions/' + this.getCurrentUser(),
|
||||||
useHTTPS: OC.getProtocol() === 'https'
|
useHTTPS: OC.getProtocol() === 'https'
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
url: function () {
|
url: function() {
|
||||||
return OC.linkToRemoteBase('dav') + '/versions/' + this.getCurrentUser() + '/versions/' + this._fileInfo.get('id');
|
return OC.linkToRemoteBase('dav') + '/versions/' + this.getCurrentUser() + '/versions/' + this._fileInfo.get('id')
|
||||||
},
|
},
|
||||||
|
|
||||||
parse: function(result) {
|
parse: function(result) {
|
||||||
var fullPath = this._fileInfo.getFullPath();
|
var fullPath = this._fileInfo.getFullPath()
|
||||||
var fileId = this._fileInfo.get('id');
|
var fileId = this._fileInfo.get('id')
|
||||||
var name = this._fileInfo.get('name');
|
var name = this._fileInfo.get('name')
|
||||||
var user = this.getCurrentUser();
|
var user = this.getCurrentUser()
|
||||||
var client = this.getClient();
|
var client = this.getClient()
|
||||||
return _.map(result, function(version) {
|
return _.map(result, function(version) {
|
||||||
version.fullPath = fullPath;
|
version.fullPath = fullPath
|
||||||
version.fileId = fileId;
|
version.fileId = fileId
|
||||||
version.name = name;
|
version.name = name
|
||||||
version.timestamp = parseInt(moment(new Date(version.timestamp)).format('X'), 10);
|
version.timestamp = parseInt(moment(new Date(version.timestamp)).format('X'), 10)
|
||||||
version.id = OC.basename(version.href);
|
version.id = OC.basename(version.href)
|
||||||
version.size = parseInt(version.size, 10);
|
version.size = parseInt(version.size, 10)
|
||||||
version.user = user;
|
version.user = user
|
||||||
version.client = client;
|
version.client = client
|
||||||
return version;
|
return version
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
OCA.Versions = OCA.Versions || {};
|
OCA.Versions = OCA.Versions || {}
|
||||||
|
|
||||||
OCA.Versions.VersionCollection = VersionCollection;
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
OCA.Versions.VersionCollection = VersionCollection
|
||||||
|
})()
|
||||||
|
|
|
@ -8,9 +8,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* global moment */
|
(function() {
|
||||||
|
|
||||||
(function () {
|
|
||||||
/**
|
/**
|
||||||
* @memberof OCA.Versions
|
* @memberof OCA.Versions
|
||||||
*/
|
*/
|
||||||
|
@ -20,53 +18,55 @@
|
||||||
davProperties: {
|
davProperties: {
|
||||||
'size': '{DAV:}getcontentlength',
|
'size': '{DAV:}getcontentlength',
|
||||||
'mimetype': '{DAV:}getcontenttype',
|
'mimetype': '{DAV:}getcontenttype',
|
||||||
'timestamp': '{DAV:}getlastmodified',
|
'timestamp': '{DAV:}getlastmodified'
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores the original file to this revision
|
* Restores the original file to this revision
|
||||||
|
*
|
||||||
|
* @param {Object} [options] options
|
||||||
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
revert: function (options) {
|
revert: function(options) {
|
||||||
options = options ? _.clone(options) : {};
|
options = options ? _.clone(options) : {}
|
||||||
var model = this;
|
var model = this
|
||||||
|
|
||||||
var client = this.get('client');
|
var client = this.get('client')
|
||||||
|
|
||||||
return client.move('/versions/' + this.get('fileId') + '/' + this.get('id'), '/restore/target', true)
|
return client.move('/versions/' + this.get('fileId') + '/' + this.get('id'), '/restore/target', true)
|
||||||
.done(function () {
|
.done(function() {
|
||||||
if (options.success) {
|
if (options.success) {
|
||||||
options.success.call(options.context, model, {}, options);
|
options.success.call(options.context, model, {}, options)
|
||||||
}
|
}
|
||||||
model.trigger('revert', model, options);
|
model.trigger('revert', model, options)
|
||||||
})
|
})
|
||||||
.fail(function () {
|
.fail(function() {
|
||||||
if (options.error) {
|
if (options.error) {
|
||||||
options.error.call(options.context, model, {}, options);
|
options.error.call(options.context, model, {}, options)
|
||||||
}
|
}
|
||||||
model.trigger('error', model, {}, options);
|
model.trigger('error', model, {}, options)
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getFullPath: function () {
|
getFullPath: function() {
|
||||||
return this.get('fullPath');
|
return this.get('fullPath')
|
||||||
},
|
},
|
||||||
|
|
||||||
getPreviewUrl: function () {
|
getPreviewUrl: function() {
|
||||||
var url = OC.generateUrl('/apps/files_versions/preview');
|
var url = OC.generateUrl('/apps/files_versions/preview')
|
||||||
var params = {
|
var params = {
|
||||||
file: this.get('fullPath'),
|
file: this.get('fullPath'),
|
||||||
version: this.get('id')
|
version: this.get('id')
|
||||||
};
|
}
|
||||||
return url + '?' + OC.buildQueryString(params);
|
return url + '?' + OC.buildQueryString(params)
|
||||||
},
|
},
|
||||||
|
|
||||||
getDownloadUrl: function () {
|
getDownloadUrl: function() {
|
||||||
return OC.linkToRemoteBase('dav') + '/versions/' + this.get('user') + '/versions/' + this.get('fileId') + '/' + this.get('id');
|
return OC.linkToRemoteBase('dav') + '/versions/' + this.get('user') + '/versions/' + this.get('fileId') + '/' + this.get('id')
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
OCA.Versions = OCA.Versions || {};
|
OCA.Versions = OCA.Versions || {}
|
||||||
|
|
||||||
OCA.Versions.VersionModel = VersionModel;
|
|
||||||
})();
|
|
||||||
|
|
||||||
|
OCA.Versions.VersionModel = VersionModel
|
||||||
|
})()
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ItemTemplate from './templates/item.handlebars';
|
import ItemTemplate from './templates/item.handlebars'
|
||||||
import Template from './templates/template.handlebars';
|
import Template from './templates/template.handlebars';
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
@ -28,137 +28,137 @@ import Template from './templates/template.handlebars';
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
OCA.Files.DetailTabView.prototype.initialize.apply(this, arguments);
|
OCA.Files.DetailTabView.prototype.initialize.apply(this, arguments)
|
||||||
this.collection = new OCA.Versions.VersionCollection();
|
this.collection = new OCA.Versions.VersionCollection()
|
||||||
this.collection.on('request', this._onRequest, this);
|
this.collection.on('request', this._onRequest, this)
|
||||||
this.collection.on('sync', this._onEndRequest, this);
|
this.collection.on('sync', this._onEndRequest, this)
|
||||||
this.collection.on('update', this._onUpdate, this);
|
this.collection.on('update', this._onUpdate, this)
|
||||||
this.collection.on('error', this._onError, this);
|
this.collection.on('error', this._onError, this)
|
||||||
this.collection.on('add', this._onAddModel, this);
|
this.collection.on('add', this._onAddModel, this)
|
||||||
},
|
},
|
||||||
|
|
||||||
getLabel: function() {
|
getLabel: function() {
|
||||||
return t('files_versions', 'Versions');
|
return t('files_versions', 'Versions')
|
||||||
},
|
},
|
||||||
|
|
||||||
getIcon: function() {
|
getIcon: function() {
|
||||||
return 'icon-history';
|
return 'icon-history'
|
||||||
},
|
},
|
||||||
|
|
||||||
nextPage: function() {
|
nextPage: function() {
|
||||||
if (this._loading) {
|
if (this._loading) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.collection.getFileInfo() && this.collection.getFileInfo().isDirectory()) {
|
if (this.collection.getFileInfo() && this.collection.getFileInfo().isDirectory()) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
this.collection.fetch();
|
this.collection.fetch()
|
||||||
},
|
},
|
||||||
|
|
||||||
_onClickRevertVersion: function(ev) {
|
_onClickRevertVersion: function(ev) {
|
||||||
var self = this;
|
var self = this
|
||||||
var $target = $(ev.target);
|
var $target = $(ev.target)
|
||||||
var fileInfoModel = this.collection.getFileInfo();
|
var fileInfoModel = this.collection.getFileInfo()
|
||||||
var revision;
|
var revision
|
||||||
if (!$target.is('li')) {
|
if (!$target.is('li')) {
|
||||||
$target = $target.closest('li');
|
$target = $target.closest('li')
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.preventDefault();
|
ev.preventDefault()
|
||||||
revision = $target.attr('data-revision');
|
revision = $target.attr('data-revision')
|
||||||
|
|
||||||
var versionModel = this.collection.get(revision);
|
var versionModel = this.collection.get(revision)
|
||||||
versionModel.revert({
|
versionModel.revert({
|
||||||
success: function() {
|
success: function() {
|
||||||
// reset and re-fetch the updated collection
|
// reset and re-fetch the updated collection
|
||||||
self.$versionsContainer.empty();
|
self.$versionsContainer.empty()
|
||||||
self.collection.setFileInfo(fileInfoModel);
|
self.collection.setFileInfo(fileInfoModel)
|
||||||
self.collection.reset([], {silent: true});
|
self.collection.reset([], { silent: true })
|
||||||
self.collection.fetch();
|
self.collection.fetch()
|
||||||
|
|
||||||
self.$el.find('.versions').removeClass('hidden');
|
self.$el.find('.versions').removeClass('hidden')
|
||||||
|
|
||||||
// update original model
|
// update original model
|
||||||
fileInfoModel.trigger('busy', fileInfoModel, false);
|
fileInfoModel.trigger('busy', fileInfoModel, false)
|
||||||
fileInfoModel.set({
|
fileInfoModel.set({
|
||||||
size: versionModel.get('size'),
|
size: versionModel.get('size'),
|
||||||
mtime: versionModel.get('timestamp') * 1000,
|
mtime: versionModel.get('timestamp') * 1000,
|
||||||
// temp dummy, until we can do a PROPFIND
|
// temp dummy, until we can do a PROPFIND
|
||||||
etag: versionModel.get('id') + versionModel.get('timestamp')
|
etag: versionModel.get('id') + versionModel.get('timestamp')
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
error: function() {
|
error: function() {
|
||||||
fileInfoModel.trigger('busy', fileInfoModel, false);
|
fileInfoModel.trigger('busy', fileInfoModel, false)
|
||||||
self.$el.find('.versions').removeClass('hidden');
|
self.$el.find('.versions').removeClass('hidden')
|
||||||
self._toggleLoading(false);
|
self._toggleLoading(false)
|
||||||
OC.Notification.show(t('files_version', 'Failed to revert {file} to revision {timestamp}.',
|
OC.Notification.show(t('files_version', 'Failed to revert {file} to revision {timestamp}.',
|
||||||
{
|
{
|
||||||
file: versionModel.getFullPath(),
|
file: versionModel.getFullPath(),
|
||||||
timestamp: OC.Util.formatDate(versionModel.get('timestamp') * 1000)
|
timestamp: OC.Util.formatDate(versionModel.get('timestamp') * 1000)
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
type: 'error'
|
type: 'error'
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// spinner
|
// spinner
|
||||||
this._toggleLoading(true);
|
this._toggleLoading(true)
|
||||||
fileInfoModel.trigger('busy', fileInfoModel, true);
|
fileInfoModel.trigger('busy', fileInfoModel, true)
|
||||||
},
|
},
|
||||||
|
|
||||||
_toggleLoading: function(state) {
|
_toggleLoading: function(state) {
|
||||||
this._loading = state;
|
this._loading = state
|
||||||
this.$el.find('.loading').toggleClass('hidden', !state);
|
this.$el.find('.loading').toggleClass('hidden', !state)
|
||||||
},
|
},
|
||||||
|
|
||||||
_onRequest: function() {
|
_onRequest: function() {
|
||||||
this._toggleLoading(true);
|
this._toggleLoading(true)
|
||||||
},
|
},
|
||||||
|
|
||||||
_onEndRequest: function() {
|
_onEndRequest: function() {
|
||||||
this._toggleLoading(false);
|
this._toggleLoading(false)
|
||||||
this.$el.find('.empty').toggleClass('hidden', !!this.collection.length);
|
this.$el.find('.empty').toggleClass('hidden', !!this.collection.length)
|
||||||
},
|
},
|
||||||
|
|
||||||
_onAddModel: function(model) {
|
_onAddModel: function(model) {
|
||||||
var $el = $(this.itemTemplate(this._formatItem(model)));
|
var $el = $(this.itemTemplate(this._formatItem(model)))
|
||||||
this.$versionsContainer.append($el);
|
this.$versionsContainer.append($el)
|
||||||
$el.find('.has-tooltip').tooltip();
|
$el.find('.has-tooltip').tooltip()
|
||||||
},
|
},
|
||||||
|
|
||||||
template: function(data) {
|
template: function(data) {
|
||||||
return Template(data);
|
return Template(data)
|
||||||
},
|
},
|
||||||
|
|
||||||
itemTemplate: function(data) {
|
itemTemplate: function(data) {
|
||||||
return ItemTemplate(data);
|
return ItemTemplate(data)
|
||||||
},
|
},
|
||||||
|
|
||||||
setFileInfo: function(fileInfo) {
|
setFileInfo: function(fileInfo) {
|
||||||
if (fileInfo) {
|
if (fileInfo) {
|
||||||
this.render();
|
this.render()
|
||||||
this.collection.setFileInfo(fileInfo);
|
this.collection.setFileInfo(fileInfo)
|
||||||
this.collection.reset([], {silent: true});
|
this.collection.reset([], { silent: true })
|
||||||
this.nextPage();
|
this.nextPage()
|
||||||
} else {
|
} else {
|
||||||
this.render();
|
this.render()
|
||||||
this.collection.reset();
|
this.collection.reset()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_formatItem: function(version) {
|
_formatItem: function(version) {
|
||||||
var timestamp = version.get('timestamp') * 1000;
|
var timestamp = version.get('timestamp') * 1000
|
||||||
var size = version.has('size') ? version.get('size') : 0;
|
var size = version.has('size') ? version.get('size') : 0
|
||||||
var preview = OC.MimeType.getIconUrl(version.get('mimetype'));
|
var preview = OC.MimeType.getIconUrl(version.get('mimetype'))
|
||||||
var img = new Image();
|
var img = new Image()
|
||||||
img.onload = function () {
|
img.onload = function() {
|
||||||
$('li[data-revision=' + version.get('id') + '] .preview').attr('src', version.getPreviewUrl());
|
$('li[data-revision=' + version.get('id') + '] .preview').attr('src', version.getPreviewUrl())
|
||||||
};
|
}
|
||||||
img.src = version.getPreviewUrl();
|
img.src = version.getPreviewUrl()
|
||||||
|
|
||||||
return _.extend({
|
return _.extend({
|
||||||
versionId: version.get('id'),
|
versionId: version.get('id'),
|
||||||
|
@ -175,7 +175,7 @@ import Template from './templates/template.handlebars';
|
||||||
previewUrl: preview,
|
previewUrl: preview,
|
||||||
revertLabel: t('files_versions', 'Restore'),
|
revertLabel: t('files_versions', 'Restore'),
|
||||||
canRevert: (this.collection.getFileInfo().get('permissions') & OC.PERMISSION_UPDATE) !== 0
|
canRevert: (this.collection.getFileInfo().get('permissions') & OC.PERMISSION_UPDATE) !== 0
|
||||||
}, version.attributes);
|
}, version.attributes)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -183,27 +183,27 @@ import Template from './templates/template.handlebars';
|
||||||
*/
|
*/
|
||||||
render: function() {
|
render: function() {
|
||||||
this.$el.html(this.template({
|
this.$el.html(this.template({
|
||||||
emptyResultLabel: t('files_versions', 'No other versions available'),
|
emptyResultLabel: t('files_versions', 'No other versions available')
|
||||||
}));
|
}))
|
||||||
this.$el.find('.has-tooltip').tooltip();
|
this.$el.find('.has-tooltip').tooltip()
|
||||||
this.$versionsContainer = this.$el.find('ul.versions');
|
this.$versionsContainer = this.$el.find('ul.versions')
|
||||||
this.delegateEvents();
|
this.delegateEvents()
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true for files, false for folders.
|
* Returns true for files, false for folders.
|
||||||
*
|
* @param {FileInfo} fileInfo fileInfo
|
||||||
* @return {bool} true for files, false for folders
|
* @returns {bool} true for files, false for folders
|
||||||
*/
|
*/
|
||||||
canDisplay: function(fileInfo) {
|
canDisplay: function(fileInfo) {
|
||||||
if (!fileInfo) {
|
if (!fileInfo) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
return !fileInfo.isDirectory();
|
return !fileInfo.isDirectory()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
OCA.Versions = OCA.Versions || {};
|
OCA.Versions = OCA.Versions || {}
|
||||||
|
|
||||||
OCA.Versions.VersionsTabView = VersionsTabView;
|
OCA.Versions.VersionsTabView = VersionsTabView
|
||||||
})();
|
})()
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -22,52 +22,71 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="oauth2" class="section">
|
<div id="oauth2" class="section">
|
||||||
<h2>{{ t('oauth2', 'OAuth 2.0 clients') }}</h2>
|
<h2>{{ t('oauth2', 'OAuth 2.0 clients') }}</h2>
|
||||||
<p class="settings-hint">{{ t('oauth2', 'OAuth 2.0 allows external services to request access to {instanceName}.', { instanceName: OC.theme.name}) }}</p>
|
<p class="settings-hint">
|
||||||
<table class="grid" v-if="clients.length > 0">
|
{{ t('oauth2', 'OAuth 2.0 allows external services to request access to {instanceName}.', { instanceName: OC.theme.name}) }}
|
||||||
|
</p>
|
||||||
|
<table v-if="clients.length > 0" class="grid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th id="headerName" scope="col">{{ t('oauth2', 'Name') }}</th>
|
<th id="headerName" scope="col">
|
||||||
<th id="headerRedirectUri" scope="col">{{ t('oauth2', 'Redirection URI') }}</th>
|
{{ t('oauth2', 'Name') }}
|
||||||
<th id="headerClientIdentifier" scope="col">{{ t('oauth2', 'Client Identifier') }}</th>
|
</th>
|
||||||
<th id="headerSecret" scope="col">{{ t('oauth2', 'Secret') }}</th>
|
<th id="headerRedirectUri" scope="col">
|
||||||
<th id="headerRemove"> </th>
|
{{ t('oauth2', 'Redirection URI') }}
|
||||||
|
</th>
|
||||||
|
<th id="headerClientIdentifier" scope="col">
|
||||||
|
{{ t('oauth2', 'Client Identifier') }}
|
||||||
|
</th>
|
||||||
|
<th id="headerSecret" scope="col">
|
||||||
|
{{ t('oauth2', 'Secret') }}
|
||||||
|
</th>
|
||||||
|
<th id="headerRemove">
|
||||||
|
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<OAuthItem v-for="client in clients"
|
<OAuthItem v-for="client in clients"
|
||||||
:key="client.id"
|
:key="client.id"
|
||||||
:client="client"
|
:client="client"
|
||||||
@delete="deleteClient"
|
@delete="deleteClient" />
|
||||||
/>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<br/>
|
<br>
|
||||||
<h3>{{ t('oauth2', 'Add client') }}</h3>
|
<h3>{{ t('oauth2', 'Add client') }}</h3>
|
||||||
<span v-if="newClient.error" class="msg error">{{newClient.errorMsg}}</span>
|
<span v-if="newClient.error" class="msg error">{{ newClient.errorMsg }}</span>
|
||||||
<form @submit.prevent="addClient">
|
<form @submit.prevent="addClient">
|
||||||
<input type="text" id="name" name="name" :placeholder="t('oauth2', 'Name')" v-model="newClient.name">
|
<input id="name"
|
||||||
<input type="url" id="redirectUri" name="redirectUri" :placeholder="t('oauth2', 'Redirection URI')" v-model="newClient.redirectUri">
|
v-model="newClient.name"
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
:placeholder="t('oauth2', 'Name')">
|
||||||
|
<input id="redirectUri"
|
||||||
|
v-model="newClient.redirectUri"
|
||||||
|
type="url"
|
||||||
|
name="redirectUri"
|
||||||
|
:placeholder="t('oauth2', 'Redirection URI')">
|
||||||
<input type="submit" class="button" :value="t('oauth2', 'Add')">
|
<input type="submit" class="button" :value="t('oauth2', 'Add')">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Axios from 'nextcloud-axios'
|
import axios from 'nextcloud-axios'
|
||||||
import OAuthItem from './components/OAuthItem';
|
import OAuthItem from './components/OAuthItem'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
OAuthItem
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
clients: {
|
clients: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
|
||||||
OAuthItem
|
|
||||||
},
|
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
newClient: {
|
newClient: {
|
||||||
|
@ -76,34 +95,34 @@ export default {
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
error: false
|
error: false
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
deleteClient(id) {
|
deleteClient(id) {
|
||||||
Axios.delete(OC.generateUrl('apps/oauth2/clients/{id}', {id: id}))
|
axios.delete(OC.generateUrl('apps/oauth2/clients/{id}', { id: id }))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
this.clients = this.clients.filter(client => client.id !== id);
|
this.clients = this.clients.filter(client => client.id !== id)
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
addClient() {
|
addClient() {
|
||||||
this.newClient.error = false;
|
this.newClient.error = false
|
||||||
|
|
||||||
Axios.post(
|
axios.post(
|
||||||
OC.generateUrl('apps/oauth2/clients'),
|
OC.generateUrl('apps/oauth2/clients'),
|
||||||
{
|
{
|
||||||
name: this.newClient.name,
|
name: this.newClient.name,
|
||||||
redirectUri: this.newClient.redirectUri
|
redirectUri: this.newClient.redirectUri
|
||||||
}
|
}
|
||||||
).then(response => {
|
).then(response => {
|
||||||
this.clients.push(response.data);
|
this.clients.push(response.data)
|
||||||
|
|
||||||
this.newClient.name = '';
|
this.newClient.name = ''
|
||||||
this.newClient.redirectUri = '';
|
this.newClient.redirectUri = ''
|
||||||
}).catch(reason => {
|
}).catch(reason => {
|
||||||
this.newClient.error = true;
|
this.newClient.error = true
|
||||||
this.newClient.errorMsg = reason.response.data.message;
|
this.newClient.errorMsg = reason.response.data.message
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -21,11 +21,13 @@
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{name}}</td>
|
<td>{{ name }}</td>
|
||||||
<td>{{redirectUri}}</td>
|
<td>{{ redirectUri }}</td>
|
||||||
<td><code>{{clientId}}</code></td>
|
<td><code>{{ clientId }}</code></td>
|
||||||
<td><code>{{renderedSecret}}</code><a class='icon-toggle has-tooltip' :title="t('oauth2', 'Show client secret')" @click="toggleSecret"></a></td>
|
<td><code>{{ renderedSecret }}</code><a class="icon-toggle has-tooltip" :title="t('oauth2', 'Show client secret')" @click="toggleSecret" /></td>
|
||||||
<td class="action-column"><span><a class="icon-delete has-tooltip" :title="t('oauth2', 'Delete')" @click="$emit('delete', id)"></a></span></td>
|
<td class="action-column">
|
||||||
|
<span><a class="icon-delete has-tooltip" :title="t('oauth2', 'Delete')" @click="$emit('delete', id)" /></span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -39,27 +41,27 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: function() {
|
data: function() {
|
||||||
return {
|
return {
|
||||||
id: this.client.id,
|
id: this.client.id,
|
||||||
name: this.client.name,
|
name: this.client.name,
|
||||||
redirectUri: this.client.redirectUri,
|
redirectUri: this.client.redirectUri,
|
||||||
clientId: this.client.clientId,
|
clientId: this.client.clientId,
|
||||||
clientSecret: this.client.clientSecret,
|
clientSecret: this.client.clientSecret,
|
||||||
renderSecret: false,
|
renderSecret: false
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
renderedSecret: function() {
|
renderedSecret: function() {
|
||||||
if (this.renderSecret) {
|
if (this.renderSecret) {
|
||||||
return this.clientSecret;
|
return this.clientSecret
|
||||||
} else {
|
} else {
|
||||||
return '****';
|
return '****'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleSecret() {
|
toggleSecret() {
|
||||||
this.renderSecret = !this.renderSecret;
|
this.renderSecret = !this.renderSecret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,18 +20,19 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue'
|
||||||
import App from './App.vue';
|
import App from './App.vue'
|
||||||
import { loadState } from 'nextcloud-initial-state'
|
import { loadState } from 'nextcloud-initial-state'
|
||||||
|
|
||||||
Vue.prototype.t = t;
|
Vue.prototype.t = t
|
||||||
Vue.prototype.OC = OC;
|
Vue.prototype.OC = OC
|
||||||
|
|
||||||
const clients = loadState('oauth2', 'clients');
|
const clients = loadState('oauth2', 'clients')
|
||||||
|
|
||||||
const View = Vue.extend(App)
|
const View = Vue.extend(App)
|
||||||
new View({
|
const oauth = new View({
|
||||||
propsData: {
|
propsData: {
|
||||||
clients
|
clients
|
||||||
}
|
}
|
||||||
}).$mount('#oauth2');
|
})
|
||||||
|
oauth.$mount('#oauth2')
|
||||||
|
|
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
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
|
@ -21,7 +21,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<router-view></router-view>
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -29,9 +29,9 @@ export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
beforeMount: function() {
|
beforeMount: function() {
|
||||||
// importing server data into the store
|
// importing server data into the store
|
||||||
const serverDataElmt = document.getElementById('serverData');
|
const serverDataElmt = document.getElementById('serverData')
|
||||||
if (serverDataElmt !== null) {
|
if (serverDataElmt !== null) {
|
||||||
this.$store.commit('setServerData', JSON.parse(document.getElementById('serverData').dataset.server));
|
this.$store.commit('setServerData', JSON.parse(document.getElementById('serverData').dataset.server))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
{{ t('settings', 'Two-factor authentication can be enforced for all users and specific groups. If they do not have a two-factor provider configured, they will be unable to log into the system.') }}
|
{{ t('settings', 'Two-factor authentication can be enforced for all users and specific groups. If they do not have a two-factor provider configured, they will be unable to log into the system.') }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="loading">
|
<p v-if="loading">
|
||||||
<span class="icon-loading-small two-factor-loading"></span>
|
<span class="icon-loading-small two-factor-loading" />
|
||||||
<span>{{ t('settings', 'Enforce two-factor authentication') }}</span>
|
<span>{{ t('settings', 'Enforce two-factor authentication') }}</span>
|
||||||
</p>
|
</p>
|
||||||
<p v-else>
|
<p v-else>
|
||||||
<input type="checkbox"
|
<input id="two-factor-enforced"
|
||||||
id="two-factor-enforced"
|
v-model="enforced"
|
||||||
class="checkbox"
|
type="checkbox"
|
||||||
v-model="enforced">
|
class="checkbox">
|
||||||
<label for="two-factor-enforced">{{ t('settings', 'Enforce two-factor authentication') }}</label>
|
<label for="two-factor-enforced">{{ t('settings', 'Enforce two-factor authentication') }}</label>
|
||||||
</p>
|
</p>
|
||||||
<template v-if="enforced">
|
<template v-if="enforced">
|
||||||
|
@ -22,32 +22,30 @@
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<Multiselect v-model="enforcedGroups"
|
<Multiselect v-model="enforcedGroups"
|
||||||
:options="groups"
|
:options="groups"
|
||||||
:placeholder="t('settings', 'Enforced groups')"
|
:placeholder="t('settings', 'Enforced groups')"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
@search-change="searchGroup"
|
:loading="loadingGroups"
|
||||||
:loading="loadingGroups"
|
:show-no-options="false"
|
||||||
:show-no-options="false"
|
:close-on-select="false"
|
||||||
:close-on-select="false">
|
@search-change="searchGroup" />
|
||||||
</Multiselect>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{ t('settings', 'Two-factor authentication is not enforced for members of the following groups.') }}
|
{{ t('settings', 'Two-factor authentication is not enforced for members of the following groups.') }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<Multiselect v-model="excludedGroups"
|
<Multiselect v-model="excludedGroups"
|
||||||
:options="groups"
|
:options="groups"
|
||||||
:placeholder="t('settings', 'Excluded groups')"
|
:placeholder="t('settings', 'Excluded groups')"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
@search-change="searchGroup"
|
:loading="loadingGroups"
|
||||||
:loading="loadingGroups"
|
:show-no-options="false"
|
||||||
:show-no-options="false"
|
:close-on-select="false"
|
||||||
:close-on-select="false">
|
@search-change="searchGroup" />
|
||||||
</Multiselect>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<em>
|
<em>
|
||||||
|
@ -57,10 +55,10 @@
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
<p>
|
<p>
|
||||||
<button class="button primary"
|
<button v-if="dirty"
|
||||||
v-if="dirty"
|
class="button primary"
|
||||||
v-on:click="saveChanges"
|
:disabled="loading"
|
||||||
:disabled="loading">
|
@click="saveChanges">
|
||||||
{{ t('settings', 'Save changes') }}
|
{{ t('settings', 'Save changes') }}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
@ -68,94 +66,93 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Axios from 'nextcloud-axios'
|
import axios from 'nextcloud-axios'
|
||||||
import { mapState } from 'vuex'
|
import { Multiselect } from 'nextcloud-vue'
|
||||||
import {Multiselect} from 'nextcloud-vue'
|
import _ from 'lodash'
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AdminTwoFactor",
|
name: 'AdminTwoFactor',
|
||||||
components: {
|
components: {
|
||||||
Multiselect
|
Multiselect
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
dirty: false,
|
dirty: false,
|
||||||
groups: [],
|
groups: [],
|
||||||
loadingGroups: false,
|
loadingGroups: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
enforced: {
|
||||||
|
get: function() {
|
||||||
|
return this.$store.state.enforced
|
||||||
|
},
|
||||||
|
set: function(val) {
|
||||||
|
this.dirty = true
|
||||||
|
this.$store.commit('setEnforced', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
enforcedGroups: {
|
||||||
enforced: {
|
get: function() {
|
||||||
get: function () {
|
return this.$store.state.enforcedGroups
|
||||||
return this.$store.state.enforced
|
|
||||||
},
|
|
||||||
set: function (val) {
|
|
||||||
this.dirty = true
|
|
||||||
this.$store.commit('setEnforced', val)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enforcedGroups: {
|
|
||||||
get: function () {
|
|
||||||
return this.$store.state.enforcedGroups
|
|
||||||
},
|
|
||||||
set: function (val) {
|
|
||||||
this.dirty = true
|
|
||||||
this.$store.commit('setEnforcedGroups', val)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
excludedGroups: {
|
|
||||||
get: function () {
|
|
||||||
return this.$store.state.excludedGroups
|
|
||||||
},
|
|
||||||
set: function (val) {
|
|
||||||
this.dirty = true
|
|
||||||
this.$store.commit('setExcludedGroups', val)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
set: function(val) {
|
||||||
|
this.dirty = true
|
||||||
|
this.$store.commit('setEnforcedGroups', val)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
excludedGroups: {
|
||||||
// Groups are loaded dynamically, but the assigned ones *should*
|
get: function() {
|
||||||
// be valid groups, so let's add them as initial state
|
return this.$store.state.excludedGroups
|
||||||
this.groups = _.sortedUniq(_.uniq(this.enforcedGroups.concat(this.excludedGroups)))
|
},
|
||||||
|
set: function(val) {
|
||||||
// Populate the groups with a first set so the dropdown is not empty
|
this.dirty = true
|
||||||
// when opening the page the first time
|
this.$store.commit('setExcludedGroups', val)
|
||||||
this.searchGroup('')
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
searchGroup: _.debounce(function (query) {
|
|
||||||
this.loadingGroups = true
|
|
||||||
Axios.get(OC.linkToOCS(`cloud/groups?offset=0&search=${encodeURIComponent(query)}&limit=20`, 2))
|
|
||||||
.then(res => res.data.ocs)
|
|
||||||
.then(ocs => ocs.data.groups)
|
|
||||||
.then(groups => this.groups = _.sortedUniq(_.uniq(this.groups.concat(groups))))
|
|
||||||
.catch(err => console.error('could not search groups', err))
|
|
||||||
.then(() => this.loadingGroups = false)
|
|
||||||
}, 500),
|
|
||||||
|
|
||||||
saveChanges () {
|
|
||||||
this.loading = true
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
enforced: this.enforced,
|
|
||||||
enforcedGroups: this.enforcedGroups,
|
|
||||||
excludedGroups: this.excludedGroups,
|
|
||||||
}
|
|
||||||
Axios.put(OC.generateUrl('/settings/api/admin/twofactorauth'), data)
|
|
||||||
.then(resp => resp.data)
|
|
||||||
.then(state => {
|
|
||||||
this.state = state
|
|
||||||
this.dirty = false
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error('could not save changes', err)
|
|
||||||
})
|
|
||||||
.then(() => this.loading = false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// Groups are loaded dynamically, but the assigned ones *should*
|
||||||
|
// be valid groups, so let's add them as initial state
|
||||||
|
this.groups = _.sortedUniq(_.uniq(this.enforcedGroups.concat(this.excludedGroups)))
|
||||||
|
|
||||||
|
// Populate the groups with a first set so the dropdown is not empty
|
||||||
|
// when opening the page the first time
|
||||||
|
this.searchGroup('')
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
searchGroup: _.debounce(function(query) {
|
||||||
|
this.loadingGroups = true
|
||||||
|
axios.get(OC.linkToOCS(`cloud/groups?offset=0&search=${encodeURIComponent(query)}&limit=20`, 2))
|
||||||
|
.then(res => res.data.ocs)
|
||||||
|
.then(ocs => ocs.data.groups)
|
||||||
|
.then(groups => { this.groups = _.sortedUniq(_.uniq(this.groups.concat(groups))) })
|
||||||
|
.catch(err => console.error('could not search groups', err))
|
||||||
|
.then(() => { this.loadingGroups = false })
|
||||||
|
}, 500),
|
||||||
|
|
||||||
|
saveChanges() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
enforced: this.enforced,
|
||||||
|
enforcedGroups: this.enforcedGroups,
|
||||||
|
excludedGroups: this.excludedGroups
|
||||||
|
}
|
||||||
|
axios.put(OC.generateUrl('/settings/api/admin/twofactorauth'), data)
|
||||||
|
.then(resp => resp.data)
|
||||||
|
.then(state => {
|
||||||
|
this.state = state
|
||||||
|
this.dirty = false
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('could not save changes', err)
|
||||||
|
})
|
||||||
|
.then(() => { this.loading = false })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -0,0 +1,326 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @license GNU AGPL version 3 or any later version
|
||||||
|
-
|
||||||
|
- This program is free software: you can redistribute it and/or modify
|
||||||
|
- it under the terms of the GNU Affero General Public License as
|
||||||
|
- published by the Free Software Foundation, either version 3 of the
|
||||||
|
- License, or (at your option) any later version.
|
||||||
|
-
|
||||||
|
- This program is distributed in the hope that it will be useful,
|
||||||
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
- GNU Affero General Public License for more details.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the GNU Affero General Public License
|
||||||
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="app-details-view" style="padding: 20px;">
|
||||||
|
<h2>
|
||||||
|
<div v-if="!app.preview" class="icon-settings-dark" />
|
||||||
|
<svg v-if="app.previewAsIcon && app.preview"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 32 32">
|
||||||
|
<defs><filter :id="filterId"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter></defs>
|
||||||
|
<image x="0"
|
||||||
|
y="0"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
preserveAspectRatio="xMinYMin meet"
|
||||||
|
:filter="filterUrl"
|
||||||
|
:xlink:href="app.preview"
|
||||||
|
class="app-icon" />
|
||||||
|
</svg>
|
||||||
|
{{ app.name }}
|
||||||
|
</h2>
|
||||||
|
<img v-if="app.screenshot" :src="app.screenshot" width="100%">
|
||||||
|
<div v-if="app.level === 300 || app.level === 200 || hasRating" class="app-level">
|
||||||
|
<span v-if="app.level === 300"
|
||||||
|
v-tooltip.auto="t('settings', 'This app is supported via your current Nextcloud subscription.')"
|
||||||
|
class="supported icon-checkmark-color">
|
||||||
|
{{ t('settings', 'Supported') }}</span>
|
||||||
|
<span v-if="app.level === 200"
|
||||||
|
v-tooltip.auto="t('settings', 'Official apps are developed by and within the community. They offer central functionality and are ready for production use.')"
|
||||||
|
class="official icon-checkmark">
|
||||||
|
{{ t('settings', 'Official') }}</span>
|
||||||
|
<AppScore v-if="hasRating" :score="app.appstoreData.ratingOverall" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="author" class="app-author">
|
||||||
|
{{ t('settings', 'by') }}
|
||||||
|
<span v-for="(a, index) in author" :key="index">
|
||||||
|
<a v-if="a['@attributes'] && a['@attributes']['homepage']" :href="a['@attributes']['homepage']">{{ a['@value'] }}</a><span v-else-if="a['@value']">{{ a['@value'] }}</span><span v-else>{{ a }}</span><span v-if="index+1 < author.length">, </span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="licence" class="app-licence">
|
||||||
|
{{ licence }}
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="actions-buttons">
|
||||||
|
<input v-if="app.update"
|
||||||
|
class="update primary"
|
||||||
|
type="button"
|
||||||
|
:value="t('settings', 'Update to {version}', {version: app.update})"
|
||||||
|
:disabled="installing || loading(app.id)"
|
||||||
|
@click="update(app.id)">
|
||||||
|
<input v-if="app.canUnInstall"
|
||||||
|
class="uninstall"
|
||||||
|
type="button"
|
||||||
|
:value="t('settings', 'Remove')"
|
||||||
|
:disabled="installing || loading(app.id)"
|
||||||
|
@click="remove(app.id)">
|
||||||
|
<input v-if="app.active"
|
||||||
|
class="enable"
|
||||||
|
type="button"
|
||||||
|
:value="t('settings','Disable')"
|
||||||
|
:disabled="installing || loading(app.id)"
|
||||||
|
@click="disable(app.id)">
|
||||||
|
<input v-if="!app.active && (app.canInstall || app.isCompatible)"
|
||||||
|
v-tooltip.auto="enableButtonTooltip"
|
||||||
|
class="enable primary"
|
||||||
|
type="button"
|
||||||
|
:value="enableButtonText"
|
||||||
|
:disabled="!app.canInstall || installing || loading(app.id)"
|
||||||
|
@click="enable(app.id)">
|
||||||
|
<input v-else-if="!app.active"
|
||||||
|
v-tooltip.auto="forceEnableButtonTooltip"
|
||||||
|
class="enable force"
|
||||||
|
type="button"
|
||||||
|
:value="forceEnableButtonText"
|
||||||
|
:disabled="installing || loading(app.id)"
|
||||||
|
@click="forceEnable(app.id)">
|
||||||
|
</div>
|
||||||
|
<div class="app-groups">
|
||||||
|
<div v-if="app.active && canLimitToGroups(app)" class="groups-enable">
|
||||||
|
<input :id="prefix('groups_enable', app.id)"
|
||||||
|
v-model="groupCheckedAppsData"
|
||||||
|
type="checkbox"
|
||||||
|
:value="app.id"
|
||||||
|
class="groups-enable__checkbox checkbox"
|
||||||
|
@change="setGroupLimit">
|
||||||
|
<label :for="prefix('groups_enable', app.id)">{{ t('settings', 'Limit to groups') }}</label>
|
||||||
|
<input type="hidden"
|
||||||
|
class="group_select"
|
||||||
|
:title="t('settings', 'All')"
|
||||||
|
value="">
|
||||||
|
<Multiselect v-if="isLimitedToGroups(app)"
|
||||||
|
:options="groups"
|
||||||
|
:value="appGroups"
|
||||||
|
:options-limit="5"
|
||||||
|
:placeholder="t('settings', 'Limit app usage to groups')"
|
||||||
|
label="name"
|
||||||
|
track-by="id"
|
||||||
|
class="multiselect-vue"
|
||||||
|
:multiple="true"
|
||||||
|
:close-on-select="false"
|
||||||
|
:tag-width="60"
|
||||||
|
@select="addGroupLimitation"
|
||||||
|
@remove="removeGroupLimitation"
|
||||||
|
@search-change="asyncFindGroup">
|
||||||
|
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||||
|
</Multiselect>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="app-dependencies">
|
||||||
|
<li v-if="app.missingMinOwnCloudVersion">
|
||||||
|
{{ t('settings', 'This app has no minimum Nextcloud version assigned. This will be an error in the future.') }}
|
||||||
|
</li>
|
||||||
|
<li v-if="app.missingMaxOwnCloudVersion">
|
||||||
|
{{ t('settings', 'This app has no maximum Nextcloud version assigned. This will be an error in the future.') }}
|
||||||
|
</li>
|
||||||
|
<li v-if="!app.canInstall">
|
||||||
|
{{ t('settings', 'This app cannot be installed because the following dependencies are not fulfilled:') }}
|
||||||
|
<ul class="missing-dependencies">
|
||||||
|
<li v-for="(dep, index) in app.missingDependencies" :key="index">
|
||||||
|
{{ dep }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p class="documentation">
|
||||||
|
<a v-if="!app.internal"
|
||||||
|
class="appslink"
|
||||||
|
:href="appstoreUrl"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener">{{ t('settings', 'View in store') }} ↗</a>
|
||||||
|
|
||||||
|
<a v-if="app.website"
|
||||||
|
class="appslink"
|
||||||
|
:href="app.website"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener">{{ t('settings', 'Visit website') }} ↗</a>
|
||||||
|
<a v-if="app.bugs"
|
||||||
|
class="appslink"
|
||||||
|
:href="app.bugs"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener">{{ t('settings', 'Report a bug') }} ↗</a>
|
||||||
|
|
||||||
|
<a v-if="app.documentation && app.documentation.user"
|
||||||
|
class="appslink"
|
||||||
|
:href="app.documentation.user"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener">{{ t('settings', 'User documentation') }} ↗</a>
|
||||||
|
<a v-if="app.documentation && app.documentation.admin"
|
||||||
|
class="appslink"
|
||||||
|
:href="app.documentation.admin"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener">{{ t('settings', 'Admin documentation') }} ↗</a>
|
||||||
|
<a v-if="app.documentation && app.documentation.developer"
|
||||||
|
class="appslink"
|
||||||
|
:href="app.documentation.developer"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener">{{ t('settings', 'Developer documentation') }} ↗</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="app-description" v-html="renderMarkdown" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Multiselect } from 'nextcloud-vue'
|
||||||
|
import marked from 'marked'
|
||||||
|
import dompurify from 'dompurify'
|
||||||
|
|
||||||
|
import AppScore from './AppList/AppScore'
|
||||||
|
import AppManagement from './AppManagement'
|
||||||
|
import PrefixMixin from './PrefixMixin'
|
||||||
|
import SvgFilterMixin from './SvgFilterMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AppDetails',
|
||||||
|
components: {
|
||||||
|
Multiselect,
|
||||||
|
AppScore
|
||||||
|
},
|
||||||
|
mixins: [AppManagement, PrefixMixin, SvgFilterMixin],
|
||||||
|
props: ['category', 'app'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
groupCheckedAppsData: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
appstoreUrl() {
|
||||||
|
return `https://apps.nextcloud.com/apps/${this.app.id}`
|
||||||
|
},
|
||||||
|
licence() {
|
||||||
|
if (this.app.licence) {
|
||||||
|
return t('settings', '{license}-licensed', { license: ('' + this.app.licence).toUpperCase() })
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
hasRating() {
|
||||||
|
return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5
|
||||||
|
},
|
||||||
|
author() {
|
||||||
|
if (typeof this.app.author === 'string') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'@value': this.app.author
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (this.app.author['@value']) {
|
||||||
|
return [this.app.author]
|
||||||
|
}
|
||||||
|
return this.app.author
|
||||||
|
},
|
||||||
|
appGroups() {
|
||||||
|
return this.app.groups.map(group => { return { id: group, name: group } })
|
||||||
|
},
|
||||||
|
groups() {
|
||||||
|
return this.$store.getters.getGroups
|
||||||
|
.filter(group => group.id !== 'disabled')
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
},
|
||||||
|
renderMarkdown() {
|
||||||
|
var renderer = new marked.Renderer()
|
||||||
|
renderer.link = function(href, title, text) {
|
||||||
|
try {
|
||||||
|
var prot = decodeURIComponent(unescape(href))
|
||||||
|
.replace(/[^\w:]/g, '')
|
||||||
|
.toLowerCase()
|
||||||
|
} catch (e) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prot.indexOf('http:') !== 0 && prot.indexOf('https:') !== 0) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
var out = '<a href="' + href + '" rel="noreferrer noopener"'
|
||||||
|
if (title) {
|
||||||
|
out += ' title="' + title + '"'
|
||||||
|
}
|
||||||
|
out += '>' + text + '</a>'
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
renderer.image = function(href, title, text) {
|
||||||
|
if (text) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
renderer.blockquote = function(quote) {
|
||||||
|
return quote
|
||||||
|
}
|
||||||
|
return dompurify.sanitize(
|
||||||
|
marked(this.app.description.trim(), {
|
||||||
|
renderer: renderer,
|
||||||
|
gfm: false,
|
||||||
|
highlight: false,
|
||||||
|
tables: false,
|
||||||
|
breaks: false,
|
||||||
|
pedantic: false,
|
||||||
|
sanitize: true,
|
||||||
|
smartLists: true,
|
||||||
|
smartypants: false
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
SAFE_FOR_JQUERY: true,
|
||||||
|
ALLOWED_TAGS: [
|
||||||
|
'strong',
|
||||||
|
'p',
|
||||||
|
'a',
|
||||||
|
'ul',
|
||||||
|
'ol',
|
||||||
|
'li',
|
||||||
|
'em',
|
||||||
|
'del',
|
||||||
|
'blockquote'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.app.groups.length > 0) {
|
||||||
|
this.groupCheckedAppsData = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.force {
|
||||||
|
background: var(--color-main-background);
|
||||||
|
border-color: var(--color-error);
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
.force:hover,
|
||||||
|
.force:active {
|
||||||
|
background: var(--color-error);
|
||||||
|
border-color: var(--color-error) !important;
|
||||||
|
color: var(--color-main-background);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,201 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @license GNU AGPL version 3 or any later version
|
||||||
|
-
|
||||||
|
- This program is free software: you can redistribute it and/or modify
|
||||||
|
- it under the terms of the GNU Affero General Public License as
|
||||||
|
- published by the Free Software Foundation, either version 3 of the
|
||||||
|
- License, or (at your option) any later version.
|
||||||
|
-
|
||||||
|
- This program is distributed in the hope that it will be useful,
|
||||||
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
- GNU Affero General Public License for more details.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the GNU Affero General Public License
|
||||||
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="app-content-inner">
|
||||||
|
<div id="apps-list" class="apps-list" :class="{installed: (useBundleView || useListView), store: useAppStoreView}">
|
||||||
|
<template v-if="useListView">
|
||||||
|
<transition-group name="app-list" tag="div" class="apps-list-container">
|
||||||
|
<AppItem v-for="app in apps"
|
||||||
|
:key="app.id"
|
||||||
|
:app="app"
|
||||||
|
:category="category" />
|
||||||
|
</transition-group>
|
||||||
|
</template>
|
||||||
|
<transition-group v-if="useBundleView"
|
||||||
|
name="app-list"
|
||||||
|
tag="div"
|
||||||
|
class="apps-list-container">
|
||||||
|
<template v-for="bundle in bundles">
|
||||||
|
<div :key="bundle.id" class="apps-header">
|
||||||
|
<div class="app-image" />
|
||||||
|
<h2>{{ bundle.name }} <input type="button" :value="bundleToggleText(bundle.id)" @click="toggleBundle(bundle.id)"></h2>
|
||||||
|
<div class="app-version" />
|
||||||
|
<div class="app-level" />
|
||||||
|
<div class="app-groups" />
|
||||||
|
<div class="actions">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AppItem v-for="app in bundleApps(bundle.id)"
|
||||||
|
:key="bundle.id + app.id"
|
||||||
|
:app="app"
|
||||||
|
:category="category" />
|
||||||
|
</template>
|
||||||
|
</transition-group>
|
||||||
|
<template v-if="useAppStoreView">
|
||||||
|
<AppItem v-for="app in apps"
|
||||||
|
:key="app.id"
|
||||||
|
:app="app"
|
||||||
|
:category="category"
|
||||||
|
:list-view="false" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="apps-list-search" class="apps-list installed">
|
||||||
|
<div class="apps-list-container">
|
||||||
|
<template v-if="search !== '' && searchApps.length > 0">
|
||||||
|
<div class="section">
|
||||||
|
<div />
|
||||||
|
<td colspan="5">
|
||||||
|
<h2>{{ t('settings', 'Results from other categories') }}</h2>
|
||||||
|
</td>
|
||||||
|
</div>
|
||||||
|
<AppItem v-for="app in searchApps"
|
||||||
|
:key="app.id"
|
||||||
|
:app="app"
|
||||||
|
:category="category"
|
||||||
|
:list-view="true" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="search !== '' && !loading && searchApps.length === 0 && apps.length === 0" id="apps-list-empty" class="emptycontent emptycontent-search">
|
||||||
|
<div id="app-list-empty-icon" class="icon-settings-dark" />
|
||||||
|
<h2>{{ t('settings', 'No apps found for your version') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="searchresults" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AppItem from './AppList/AppItem'
|
||||||
|
import PrefixMixin from './PrefixMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AppList',
|
||||||
|
components: {
|
||||||
|
AppItem
|
||||||
|
},
|
||||||
|
mixins: [PrefixMixin],
|
||||||
|
props: ['category', 'app', 'search'],
|
||||||
|
computed: {
|
||||||
|
loading() {
|
||||||
|
return this.$store.getters.loading('list')
|
||||||
|
},
|
||||||
|
apps() {
|
||||||
|
let apps = this.$store.getters.getAllApps
|
||||||
|
.filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
|
||||||
|
.sort(function(a, b) {
|
||||||
|
const sortStringA = '' + (a.active ? 0 : 1) + (a.update ? 0 : 1) + a.name
|
||||||
|
const sortStringB = '' + (b.active ? 0 : 1) + (b.update ? 0 : 1) + b.name
|
||||||
|
return OC.Util.naturalSortCompare(sortStringA, sortStringB)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.category === 'installed') {
|
||||||
|
return apps.filter(app => app.installed)
|
||||||
|
}
|
||||||
|
if (this.category === 'enabled') {
|
||||||
|
return apps.filter(app => app.active && app.installed)
|
||||||
|
}
|
||||||
|
if (this.category === 'disabled') {
|
||||||
|
return apps.filter(app => !app.active && app.installed)
|
||||||
|
}
|
||||||
|
if (this.category === 'app-bundles') {
|
||||||
|
return apps.filter(app => app.bundles)
|
||||||
|
}
|
||||||
|
if (this.category === 'updates') {
|
||||||
|
return apps.filter(app => app.update)
|
||||||
|
}
|
||||||
|
// filter app store categories
|
||||||
|
return apps.filter(app => {
|
||||||
|
return app.appstore && app.category !== undefined
|
||||||
|
&& (app.category === this.category || app.category.indexOf(this.category) > -1)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
bundles() {
|
||||||
|
return this.$store.getters.getServerData.bundles.filter(bundle => this.bundleApps(bundle.id).length > 0)
|
||||||
|
},
|
||||||
|
bundleApps() {
|
||||||
|
return function(bundle) {
|
||||||
|
return this.$store.getters.getAllApps
|
||||||
|
.filter(app => app.bundleId === bundle)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
searchApps() {
|
||||||
|
if (this.search === '') {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return this.$store.getters.getAllApps
|
||||||
|
.filter(app => {
|
||||||
|
if (app.name.toLowerCase().search(this.search.toLowerCase()) !== -1) {
|
||||||
|
return (!this.apps.find(_app => _app.id === app.id))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
useAppStoreView() {
|
||||||
|
return !this.useListView && !this.useBundleView
|
||||||
|
},
|
||||||
|
useListView() {
|
||||||
|
return (this.category === 'installed' || this.category === 'enabled' || this.category === 'disabled' || this.category === 'updates')
|
||||||
|
},
|
||||||
|
useBundleView() {
|
||||||
|
return (this.category === 'app-bundles')
|
||||||
|
},
|
||||||
|
allBundlesEnabled() {
|
||||||
|
let self = this
|
||||||
|
return function(id) {
|
||||||
|
return self.bundleApps(id).filter(app => !app.active).length === 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bundleToggleText() {
|
||||||
|
let self = this
|
||||||
|
return function(id) {
|
||||||
|
if (self.allBundlesEnabled(id)) {
|
||||||
|
return t('settings', 'Disable all')
|
||||||
|
}
|
||||||
|
return t('settings', 'Enable all')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleBundle(id) {
|
||||||
|
if (this.allBundlesEnabled(id)) {
|
||||||
|
return this.disableBundle(id)
|
||||||
|
}
|
||||||
|
return this.enableBundle(id)
|
||||||
|
},
|
||||||
|
enableBundle(id) {
|
||||||
|
let apps = this.bundleApps(id).map(app => app.id)
|
||||||
|
this.$store.dispatch('enableApp', { appId: apps, groups: [] })
|
||||||
|
.catch((error) => { console.error(error); OC.Notification.show(error) })
|
||||||
|
},
|
||||||
|
disableBundle(id) {
|
||||||
|
let apps = this.bundleApps(id).map(app => app.id)
|
||||||
|
this.$store.dispatch('disableApp', { appId: apps, groups: [] })
|
||||||
|
.catch((error) => { OC.Notification.show(error) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,179 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @license GNU AGPL version 3 or any later version
|
||||||
|
-
|
||||||
|
- This program is free software: you can redistribute it and/or modify
|
||||||
|
- it under the terms of the GNU Affero General Public License as
|
||||||
|
- published by the Free Software Foundation, either version 3 of the
|
||||||
|
- License, or (at your option) any later version.
|
||||||
|
-
|
||||||
|
- This program is distributed in the hope that it will be useful,
|
||||||
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
- GNU Affero General Public License for more details.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the GNU Affero General Public License
|
||||||
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="section" :class="{ selected: isSelected }" @click="showAppDetails">
|
||||||
|
<div class="app-image app-image-icon" @click="showAppDetails">
|
||||||
|
<div v-if="(listView && !app.preview) || (!listView && !app.screenshot)" class="icon-settings-dark" />
|
||||||
|
|
||||||
|
<svg v-if="listView && app.preview"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 32 32">
|
||||||
|
<defs><filter :id="filterId"><feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0" /></filter></defs>
|
||||||
|
<image x="0"
|
||||||
|
y="0"
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
preserveAspectRatio="xMinYMin meet"
|
||||||
|
:filter="filterUrl"
|
||||||
|
:xlink:href="app.preview"
|
||||||
|
class="app-icon" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<img v-if="!listView && app.screenshot" :src="app.screenshot" width="100%">
|
||||||
|
</div>
|
||||||
|
<div class="app-name" @click="showAppDetails">
|
||||||
|
{{ app.name }}
|
||||||
|
</div>
|
||||||
|
<div v-if="!listView" class="app-summary">
|
||||||
|
{{ app.summary }}
|
||||||
|
</div>
|
||||||
|
<div v-if="listView" class="app-version">
|
||||||
|
<span v-if="app.version">{{ app.version }}</span>
|
||||||
|
<span v-else-if="app.appstoreData.releases[0].version">{{ app.appstoreData.releases[0].version }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="app-level">
|
||||||
|
<span v-if="app.level === 300"
|
||||||
|
v-tooltip.auto="t('settings', 'This app is supported via your current Nextcloud subscription.')"
|
||||||
|
class="supported icon-checkmark-color">
|
||||||
|
{{ t('settings', 'Supported') }}</span>
|
||||||
|
<span v-if="app.level === 200"
|
||||||
|
v-tooltip.auto="t('settings', 'Official apps are developed by and within the community. They offer central functionality and are ready for production use.')"
|
||||||
|
class="official icon-checkmark">
|
||||||
|
{{ t('settings', 'Official') }}</span>
|
||||||
|
<AppScore v-if="hasRating && !listView" :score="app.score" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<div v-if="app.error" class="warning">
|
||||||
|
{{ app.error }}
|
||||||
|
</div>
|
||||||
|
<div v-if="loading(app.id)" class="icon icon-loading-small" />
|
||||||
|
<input v-if="app.update"
|
||||||
|
class="update primary"
|
||||||
|
type="button"
|
||||||
|
:value="t('settings', 'Update to {update}', {update:app.update})"
|
||||||
|
:disabled="installing || loading(app.id)"
|
||||||
|
@click.stop="update(app.id)">
|
||||||
|
<input v-if="app.canUnInstall"
|
||||||
|
class="uninstall"
|
||||||
|
type="button"
|
||||||
|
:value="t('settings', 'Remove')"
|
||||||
|
:disabled="installing || loading(app.id)"
|
||||||
|
@click.stop="remove(app.id)">
|
||||||
|
<input v-if="app.active"
|
||||||
|
class="enable"
|
||||||
|
type="button"
|
||||||
|
:value="t('settings','Disable')"
|
||||||
|
:disabled="installing || loading(app.id)"
|
||||||
|
@click.stop="disable(app.id)">
|
||||||
|
<input v-if="!app.active && (app.canInstall || app.isCompatible)"
|
||||||
|
v-tooltip.auto="enableButtonTooltip"
|
||||||
|
class="enable"
|
||||||
|
type="button"
|
||||||
|
:value="enableButtonText"
|
||||||
|
:disabled="!app.canInstall || installing || loading(app.id)"
|
||||||
|
@click.stop="enable(app.id)">
|
||||||
|
<input v-else-if="!app.active"
|
||||||
|
v-tooltip.auto="forceEnableButtonTooltip"
|
||||||
|
class="enable force"
|
||||||
|
type="button"
|
||||||
|
:value="forceEnableButtonText"
|
||||||
|
:disabled="installing || loading(app.id)"
|
||||||
|
@click.stop="forceEnable(app.id)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AppScore from './AppScore'
|
||||||
|
import AppManagement from '../AppManagement'
|
||||||
|
import SvgFilterMixin from '../SvgFilterMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AppItem',
|
||||||
|
components: {
|
||||||
|
AppScore
|
||||||
|
},
|
||||||
|
mixins: [AppManagement, SvgFilterMixin],
|
||||||
|
props: {
|
||||||
|
app: {},
|
||||||
|
category: {},
|
||||||
|
listView: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isSelected: false,
|
||||||
|
scrolled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
hasRating() {
|
||||||
|
return this.app.appstoreData && this.app.appstoreData.ratingNumOverall > 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$route.params.id': function(id) {
|
||||||
|
this.isSelected = (this.app.id === id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.isSelected = (this.app.id === this.$route.params.id)
|
||||||
|
},
|
||||||
|
watchers: {
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showAppDetails(event) {
|
||||||
|
if (event.currentTarget.tagName === 'INPUT' || event.currentTarget.tagName === 'A') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$router.push({
|
||||||
|
name: 'apps-details',
|
||||||
|
params: { category: this.category, id: this.app.id }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
prefix(prefix, content) {
|
||||||
|
return prefix + '_' + content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.force {
|
||||||
|
background: var(--color-main-background);
|
||||||
|
border-color: var(--color-error);
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
.force:hover,
|
||||||
|
.force:active {
|
||||||
|
background: var(--color-error);
|
||||||
|
border-color: var(--color-error) !important;
|
||||||
|
color: var(--color-main-background);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @license GNU AGPL version 3 or any later version
|
||||||
|
-
|
||||||
|
- This program is free software: you can redistribute it and/or modify
|
||||||
|
- it under the terms of the GNU Affero General Public License as
|
||||||
|
- published by the Free Software Foundation, either version 3 of the
|
||||||
|
- License, or (at your option) any later version.
|
||||||
|
-
|
||||||
|
- This program is distributed in the hope that it will be useful,
|
||||||
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
- GNU Affero General Public License for more details.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the GNU Affero General Public License
|
||||||
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<img :src="scoreImage" class="app-score-image">
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AppScore',
|
||||||
|
props: ['score'],
|
||||||
|
computed: {
|
||||||
|
scoreImage() {
|
||||||
|
let score = Math.round(this.score * 10)
|
||||||
|
let imageName = 'rating/s' + score + '.svg'
|
||||||
|
return OC.imagePath('core', imageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,138 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @license GNU AGPL version 3 or any later version
|
||||||
|
-
|
||||||
|
- This program is free software: you can redistribute it and/or modify
|
||||||
|
- it under the terms of the GNU Affero General Public License as
|
||||||
|
- published by the Free Software Foundation, either version 3 of the
|
||||||
|
- License, or (at your option) any later version.
|
||||||
|
-
|
||||||
|
- This program is distributed in the hope that it will be useful,
|
||||||
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
- GNU Affero General Public License for more details.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the GNU Affero General Public License
|
||||||
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
appGroups() {
|
||||||
|
return this.app.groups.map(group => { return { id: group, name: group } })
|
||||||
|
},
|
||||||
|
loading() {
|
||||||
|
let self = this
|
||||||
|
return function(id) {
|
||||||
|
return self.$store.getters.loading(id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
installing() {
|
||||||
|
return this.$store.getters.loading('install')
|
||||||
|
},
|
||||||
|
enableButtonText() {
|
||||||
|
if (this.app.needsDownload) {
|
||||||
|
return t('settings', 'Download and enable')
|
||||||
|
}
|
||||||
|
return t('settings', 'Enable')
|
||||||
|
},
|
||||||
|
forceEnableButtonText() {
|
||||||
|
if (this.app.needsDownload) {
|
||||||
|
return t('settings', 'Enable untested app')
|
||||||
|
}
|
||||||
|
return t('settings', 'Enable untested app')
|
||||||
|
},
|
||||||
|
enableButtonTooltip() {
|
||||||
|
if (this.app.needsDownload) {
|
||||||
|
return t('settings', 'The app will be downloaded from the app store')
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
forceEnableButtonTooltip() {
|
||||||
|
const base = t('settings', 'This app is not marked as compatible with your Nextcloud version. If you continue you will still be able to install the app. Note that the app might not work as expected.')
|
||||||
|
if (this.app.needsDownload) {
|
||||||
|
return base + ' ' + t('settings', 'The app will be downloaded from the app store')
|
||||||
|
}
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.app.groups.length > 0) {
|
||||||
|
this.groupCheckedAppsData = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
asyncFindGroup(query) {
|
||||||
|
return this.$store.dispatch('getGroups', { search: query, limit: 5, offset: 0 })
|
||||||
|
},
|
||||||
|
isLimitedToGroups(app) {
|
||||||
|
if (this.app.groups.length || this.groupCheckedAppsData) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
setGroupLimit: function() {
|
||||||
|
if (!this.groupCheckedAppsData) {
|
||||||
|
this.$store.dispatch('enableApp', { appId: this.app.id, groups: [] })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
canLimitToGroups(app) {
|
||||||
|
if ((app.types && app.types.includes('filesystem'))
|
||||||
|
|| app.types.includes('prelogin')
|
||||||
|
|| app.types.includes('authentication')
|
||||||
|
|| app.types.includes('logging')
|
||||||
|
|| app.types.includes('prevent_group_restriction')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
addGroupLimitation(group) {
|
||||||
|
let groups = this.app.groups.concat([]).concat([group.id])
|
||||||
|
this.$store.dispatch('enableApp', { appId: this.app.id, groups: groups })
|
||||||
|
},
|
||||||
|
removeGroupLimitation(group) {
|
||||||
|
let currentGroups = this.app.groups.concat([])
|
||||||
|
let index = currentGroups.indexOf(group.id)
|
||||||
|
if (index > -1) {
|
||||||
|
currentGroups.splice(index, 1)
|
||||||
|
}
|
||||||
|
this.$store.dispatch('enableApp', { appId: this.app.id, groups: currentGroups })
|
||||||
|
},
|
||||||
|
forceEnable(appId) {
|
||||||
|
this.$store.dispatch('forceEnableApp', { appId: appId, groups: [] })
|
||||||
|
.then((response) => { OC.Settings.Apps.rebuildNavigation() })
|
||||||
|
.catch((error) => { OC.Notification.show(error) })
|
||||||
|
},
|
||||||
|
enable(appId) {
|
||||||
|
this.$store.dispatch('enableApp', { appId: appId, groups: [] })
|
||||||
|
.then((response) => { OC.Settings.Apps.rebuildNavigation() })
|
||||||
|
.catch((error) => { OC.Notification.show(error) })
|
||||||
|
},
|
||||||
|
disable(appId) {
|
||||||
|
this.$store.dispatch('disableApp', { appId: appId })
|
||||||
|
.then((response) => { OC.Settings.Apps.rebuildNavigation() })
|
||||||
|
.catch((error) => { OC.Notification.show(error) })
|
||||||
|
},
|
||||||
|
remove(appId) {
|
||||||
|
this.$store.dispatch('uninstallApp', { appId: appId })
|
||||||
|
.then((response) => { OC.Settings.Apps.rebuildNavigation() })
|
||||||
|
.catch((error) => { OC.Notification.show(error) })
|
||||||
|
},
|
||||||
|
install(appId) {
|
||||||
|
this.$store.dispatch('enableApp', { appId: appId })
|
||||||
|
.then((response) => { OC.Settings.Apps.rebuildNavigation() })
|
||||||
|
.catch((error) => { OC.Notification.show(error) })
|
||||||
|
},
|
||||||
|
update(appId) {
|
||||||
|
this.$store.dispatch('updateApp', { appId: appId })
|
||||||
|
.then((response) => { OC.Settings.Apps.rebuildNavigation() })
|
||||||
|
.catch((error) => { OC.Notification.show(error) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -23,31 +23,29 @@
|
||||||
<tr :data-id="token.id"
|
<tr :data-id="token.id"
|
||||||
:class="wiping">
|
:class="wiping">
|
||||||
<td class="client">
|
<td class="client">
|
||||||
<div :class="iconName.icon"></div>
|
<div :class="iconName.icon" />
|
||||||
</td>
|
</td>
|
||||||
<td class="token-name">
|
<td class="token-name">
|
||||||
<input v-if="token.canRename && renaming"
|
<input v-if="token.canRename && renaming"
|
||||||
type="text"
|
ref="input"
|
||||||
ref="input"
|
v-model="newName"
|
||||||
v-model="newName"
|
type="text"
|
||||||
@keyup.enter="rename"
|
@keyup.enter="rename"
|
||||||
@blur="cancelRename"
|
@blur="cancelRename"
|
||||||
@keyup.esc="cancelRename">
|
@keyup.esc="cancelRename">
|
||||||
<span v-else>{{iconName.name}}</span>
|
<span v-else>{{ iconName.name }}</span>
|
||||||
<span v-if="wiping"
|
<span v-if="wiping" class="wiping-warning">({{ t('settings', 'Marked for remote wipe') }})</span>
|
||||||
class="wiping-warning">({{ t('settings', 'Marked for remote wipe') }})</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="last-activity" v-tooltip="lastActivity">{{lastActivityRelative}}</span>
|
<span v-tooltip="lastActivity" class="last-activity">{{ lastActivityRelative }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="more">
|
<td class="more">
|
||||||
<Actions v-if="!token.current"
|
<Actions v-if="!token.current"
|
||||||
:actions="actions"
|
|
||||||
:open.sync="actionOpen"
|
|
||||||
v-tooltip.auto="{
|
v-tooltip.auto="{
|
||||||
content: t('settings', 'Device settings'),
|
content: t('settings', 'Device settings'),
|
||||||
container: 'body'
|
container: 'body'
|
||||||
}">
|
}"
|
||||||
|
:open.sync="actionOpen">
|
||||||
<ActionCheckbox v-if="token.type === 1"
|
<ActionCheckbox v-if="token.type === 1"
|
||||||
:checked="token.scope.filesystem"
|
:checked="token.scope.filesystem"
|
||||||
@change.stop.prevent="$emit('toggleScope', token, 'filesystem', !token.scope.filesystem)">
|
@change.stop.prevent="$emit('toggleScope', token, 'filesystem', !token.scope.filesystem)">
|
||||||
|
@ -91,7 +89,7 @@ import {
|
||||||
Actions,
|
Actions,
|
||||||
ActionButton,
|
ActionButton,
|
||||||
ActionCheckbox
|
ActionCheckbox
|
||||||
} from 'nextcloud-vue';
|
} from 'nextcloud-vue'
|
||||||
|
|
||||||
const userAgentMap = {
|
const userAgentMap = {
|
||||||
ie: /(?:MSIE|Trident|Trident\/7.0; rv)[ :](\d+)/,
|
ie: /(?:MSIE|Trident|Trident\/7.0; rv)[ :](\d+)/,
|
||||||
|
@ -106,18 +104,18 @@ const userAgentMap = {
|
||||||
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
|
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
|
||||||
androidChrome: /Android.*(?:; (.*) Build\/).*Chrome\/(\d+)[0-9.]+/,
|
androidChrome: /Android.*(?:; (.*) Build\/).*Chrome\/(\d+)[0-9.]+/,
|
||||||
iphone: / *CPU +iPhone +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
|
iphone: / *CPU +iPhone +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
|
||||||
ipad: /\(iPad\; *CPU +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
|
ipad: /\(iPad; *CPU +OS +([0-9]+)_(?:[0-9_])+ +like +Mac +OS +X */,
|
||||||
iosClient: /^Mozilla\/5\.0 \(iOS\) (ownCloud|Nextcloud)\-iOS.*$/,
|
iosClient: /^Mozilla\/5\.0 \(iOS\) (ownCloud|Nextcloud)-iOS.*$/,
|
||||||
androidClient: /^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/,
|
androidClient: /^Mozilla\/5\.0 \(Android\) ownCloud-android.*$/,
|
||||||
iosTalkClient: /^Mozilla\/5\.0 \(iOS\) Nextcloud\-Talk.*$/,
|
iosTalkClient: /^Mozilla\/5\.0 \(iOS\) Nextcloud-Talk.*$/,
|
||||||
androidTalkClient: /^Mozilla\/5\.0 \(Android\) Nextcloud\-Talk.*$/,
|
androidTalkClient: /^Mozilla\/5\.0 \(Android\) Nextcloud-Talk.*$/,
|
||||||
// DAVdroid/1.2 (2016/07/03; dav4android; okhttp3) Android/6.0.1
|
// DAVdroid/1.2 (2016/07/03; dav4android; okhttp3) Android/6.0.1
|
||||||
davDroid: /DAV(droid|x5)\/([0-9.]+)/,
|
davDroid: /DAV(droid|x5)\/([0-9.]+)/,
|
||||||
// Mozilla/5.0 (U; Linux; Maemo; Jolla; Sailfish; like Android 4.3) AppleWebKit/538.1 (KHTML, like Gecko) WebPirate/2.0 like Mobile Safari/538.1 (compatible)
|
// Mozilla/5.0 (U; Linux; Maemo; Jolla; Sailfish; like Android 4.3) AppleWebKit/538.1 (KHTML, like Gecko) WebPirate/2.0 like Mobile Safari/538.1 (compatible)
|
||||||
webPirate: /(Sailfish).*WebPirate\/(\d+)/,
|
webPirate: /(Sailfish).*WebPirate\/(\d+)/,
|
||||||
// Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0
|
// Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0
|
||||||
sailfishBrowser: /(Sailfish).*SailfishBrowser\/(\d+)/
|
sailfishBrowser: /(Sailfish).*SailfishBrowser\/(\d+)/
|
||||||
};
|
}
|
||||||
const nameMap = {
|
const nameMap = {
|
||||||
ie: t('setting', 'Internet Explorer'),
|
ie: t('setting', 'Internet Explorer'),
|
||||||
edge: t('setting', 'Edge'),
|
edge: t('setting', 'Edge'),
|
||||||
|
@ -134,7 +132,7 @@ const nameMap = {
|
||||||
davDroid: 'DAVdroid',
|
davDroid: 'DAVdroid',
|
||||||
webPirate: 'WebPirate',
|
webPirate: 'WebPirate',
|
||||||
sailfishBrowser: 'SailfishBrowser'
|
sailfishBrowser: 'SailfishBrowser'
|
||||||
};
|
}
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
ie: 'icon-desktop',
|
ie: 'icon-desktop',
|
||||||
edge: 'icon-desktop',
|
edge: 'icon-desktop',
|
||||||
|
@ -151,10 +149,10 @@ const iconMap = {
|
||||||
davDroid: 'icon-phone',
|
davDroid: 'icon-phone',
|
||||||
webPirate: 'icon-link',
|
webPirate: 'icon-link',
|
||||||
sailfishBrowser: 'icon-link'
|
sailfishBrowser: 'icon-link'
|
||||||
};
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AuthToken",
|
name: 'AuthToken',
|
||||||
components: {
|
components: {
|
||||||
Actions,
|
Actions,
|
||||||
ActionButton,
|
ActionButton,
|
||||||
|
@ -163,91 +161,93 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
token: {
|
token: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
data() {
|
||||||
lastActivityRelative () {
|
|
||||||
return OC.Util.relativeModifiedDate(this.token.lastActivity * 1000);
|
|
||||||
},
|
|
||||||
lastActivity () {
|
|
||||||
return OC.Util.formatDate(this.token.lastActivity * 1000, 'LLL');
|
|
||||||
},
|
|
||||||
iconName () {
|
|
||||||
// pretty format sync client user agent
|
|
||||||
let matches = this.token.name.match(/Mozilla\/5\.0 \((\w+)\) (?:mirall|csyncoC)\/(\d+\.\d+\.\d+)/);
|
|
||||||
|
|
||||||
let icon = '';
|
|
||||||
if (matches) {
|
|
||||||
this.token.name = t('settings', 'Sync client - {os}', {
|
|
||||||
os: matches[1],
|
|
||||||
version: matches[2]
|
|
||||||
});
|
|
||||||
icon = 'icon-desktop';
|
|
||||||
}
|
|
||||||
|
|
||||||
// preserve title for cases where we format it further
|
|
||||||
const title = this.token.name;
|
|
||||||
let name = this.token.name;
|
|
||||||
for (let client in userAgentMap) {
|
|
||||||
if (matches = title.match(userAgentMap[client])) {
|
|
||||||
if (matches[2] && matches[1]) { // version number and os
|
|
||||||
name = nameMap[client] + ' ' + matches[2] + ' - ' + matches[1];
|
|
||||||
} else if (matches[1]) { // only version number
|
|
||||||
name = nameMap[client] + ' ' + matches[1];
|
|
||||||
} else {
|
|
||||||
name = nameMap[client];
|
|
||||||
}
|
|
||||||
|
|
||||||
icon = iconMap[client];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.token.current) {
|
|
||||||
name = t('settings', 'This session');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
icon,
|
|
||||||
name,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
wiping() {
|
|
||||||
return this.token.type === 2;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
return {
|
||||||
showMore: this.token.canScope || this.token.canDelete,
|
showMore: this.token.canScope || this.token.canDelete,
|
||||||
renaming: false,
|
renaming: false,
|
||||||
newName: '',
|
newName: '',
|
||||||
actionOpen: false,
|
actionOpen: false
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
lastActivityRelative() {
|
||||||
|
return OC.Util.relativeModifiedDate(this.token.lastActivity * 1000)
|
||||||
|
},
|
||||||
|
lastActivity() {
|
||||||
|
return OC.Util.formatDate(this.token.lastActivity * 1000, 'LLL')
|
||||||
|
},
|
||||||
|
iconName() {
|
||||||
|
// pretty format sync client user agent
|
||||||
|
let matches = this.token.name.match(/Mozilla\/5\.0 \((\w+)\) (?:mirall|csyncoC)\/(\d+\.\d+\.\d+)/)
|
||||||
|
|
||||||
|
let icon = ''
|
||||||
|
if (matches) {
|
||||||
|
/* eslint-disable-next-line */
|
||||||
|
this.token.name = t('settings', 'Sync client - {os}', {
|
||||||
|
os: matches[1],
|
||||||
|
version: matches[2]
|
||||||
|
})
|
||||||
|
icon = 'icon-desktop'
|
||||||
|
}
|
||||||
|
|
||||||
|
// preserve title for cases where we format it further
|
||||||
|
const title = this.token.name
|
||||||
|
let name = this.token.name
|
||||||
|
for (let client in userAgentMap) {
|
||||||
|
const matches = title.match(userAgentMap[client])
|
||||||
|
if (matches) {
|
||||||
|
if (matches[2] && matches[1]) { // version number and os
|
||||||
|
name = nameMap[client] + ' ' + matches[2] + ' - ' + matches[1]
|
||||||
|
} else if (matches[1]) { // only version number
|
||||||
|
name = nameMap[client] + ' ' + matches[1]
|
||||||
|
} else {
|
||||||
|
name = nameMap[client]
|
||||||
|
}
|
||||||
|
|
||||||
|
icon = iconMap[client]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.token.current) {
|
||||||
|
name = t('settings', 'This session')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
icon,
|
||||||
|
name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
wiping() {
|
||||||
|
return this.token.type === 2
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
startRename() {
|
startRename() {
|
||||||
// Close action (popover menu)
|
// Close action (popover menu)
|
||||||
this.actionOpen = false;
|
this.actionOpen = false
|
||||||
|
|
||||||
this.newName = this.token.name;
|
this.newName = this.token.name
|
||||||
this.renaming = true;
|
this.renaming = true
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.input.select();
|
this.$refs.input.select()
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
cancelRename() {
|
cancelRename() {
|
||||||
this.renaming = false;
|
this.renaming = false
|
||||||
},
|
},
|
||||||
revoke() {
|
revoke() {
|
||||||
this.actionOpen = false;
|
this.actionOpen = false
|
||||||
this.$emit('delete', this.token)
|
this.$emit('delete', this.token)
|
||||||
},
|
},
|
||||||
rename() {
|
rename() {
|
||||||
this.renaming = false;
|
this.renaming = false
|
||||||
this.$emit('rename', this.token, this.newName);
|
this.$emit('rename', this.token, this.newName)
|
||||||
},
|
},
|
||||||
wipe() {
|
wipe() {
|
||||||
this.actionOpen = false;
|
this.actionOpen = false
|
||||||
this.$emit('wipe', this.token);
|
this.$emit('wipe', this.token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,67 +22,67 @@
|
||||||
<template>
|
<template>
|
||||||
<table id="app-tokens-table">
|
<table id="app-tokens-table">
|
||||||
<thead v-if="tokens.length">
|
<thead v-if="tokens.length">
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th />
|
||||||
<th>{{ t('settings', 'Device') }}</th>
|
<th>{{ t('settings', 'Device') }}</th>
|
||||||
<th>{{ t('settings', 'Last activity') }}</th>
|
<th>{{ t('settings', 'Last activity') }}</th>
|
||||||
<th></th>
|
<th />
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="token-list">
|
<tbody class="token-list">
|
||||||
<AuthToken v-for="token in sortedTokens"
|
<AuthToken v-for="token in sortedTokens"
|
||||||
:key="token.id"
|
:key="token.id"
|
||||||
:token="token"
|
:token="token"
|
||||||
@toggleScope="toggleScope"
|
@toggleScope="toggleScope"
|
||||||
@rename="rename"
|
@rename="rename"
|
||||||
@delete="onDelete"
|
@delete="onDelete"
|
||||||
@wipe="onWipe" />
|
@wipe="onWipe" />
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AuthToken from './AuthToken';
|
import AuthToken from './AuthToken'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AuthTokenList',
|
name: 'AuthTokenList',
|
||||||
components: {
|
components: {
|
||||||
AuthToken
|
AuthToken
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
tokens: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
sortedTokens() {
|
||||||
|
return this.tokens.slice().sort((t1, t2) => {
|
||||||
|
var ts1 = parseInt(t1.lastActivity, 10)
|
||||||
|
var ts2 = parseInt(t2.lastActivity, 10)
|
||||||
|
return ts2 - ts1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleScope(token, scope, value) {
|
||||||
|
// Just pass it on
|
||||||
|
this.$emit('toggleScope', token, scope, value)
|
||||||
},
|
},
|
||||||
props: {
|
rename(token, newName) {
|
||||||
tokens: {
|
// Just pass it on
|
||||||
type: Array,
|
this.$emit('rename', token, newName)
|
||||||
required: true,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
onDelete(token) {
|
||||||
sortedTokens () {
|
// Just pass it on
|
||||||
return this.tokens.sort((t1, t2) => {
|
this.$emit('delete', token)
|
||||||
var ts1 = parseInt(t1.lastActivity, 10);
|
|
||||||
var ts2 = parseInt(t2.lastActivity, 10);
|
|
||||||
return ts2 - ts1;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
onWipe(token) {
|
||||||
toggleScope (token, scope, value) {
|
// Just pass it on
|
||||||
// Just pass it on
|
this.$emit('wipe', token)
|
||||||
this.$emit('toggleScope', token, scope, value);
|
|
||||||
},
|
|
||||||
rename (token, newName) {
|
|
||||||
// Just pass it on
|
|
||||||
this.$emit('rename', token, newName);
|
|
||||||
},
|
|
||||||
onDelete (token) {
|
|
||||||
// Just pass it on
|
|
||||||
this.$emit('delete', token);
|
|
||||||
},
|
|
||||||
onWipe(token) {
|
|
||||||
// Just pass it on
|
|
||||||
this.$emit('wipe', token);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -22,155 +22,159 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="security" class="section">
|
<div id="security" class="section">
|
||||||
<h2>{{ t('settings', 'Devices & sessions') }}</h2>
|
<h2>{{ t('settings', 'Devices & sessions') }}</h2>
|
||||||
<p class="settings-hint hidden-when-empty">{{ t('settings', 'Web, desktop and mobile clients currently logged in to your account.') }}</p>
|
<p class="settings-hint hidden-when-empty">
|
||||||
|
{{ t('settings', 'Web, desktop and mobile clients currently logged in to your account.') }}
|
||||||
|
</p>
|
||||||
<AuthTokenList :tokens="tokens"
|
<AuthTokenList :tokens="tokens"
|
||||||
@toggleScope="toggleTokenScope"
|
@toggleScope="toggleTokenScope"
|
||||||
@rename="rename"
|
@rename="rename"
|
||||||
@delete="deleteToken"
|
@delete="deleteToken"
|
||||||
@wipe="wipeToken" />
|
@wipe="wipeToken" />
|
||||||
<AuthTokenSetupDialogue v-if="canCreateToken" :add="addNewToken" />
|
<AuthTokenSetupDialogue v-if="canCreateToken" :add="addNewToken" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Axios from 'nextcloud-axios';
|
import axios from 'nextcloud-axios'
|
||||||
import confirmPassword from 'nextcloud-password-confirmation';
|
import confirmPassword from 'nextcloud-password-confirmation'
|
||||||
|
|
||||||
import AuthTokenList from './AuthTokenList';
|
import AuthTokenList from './AuthTokenList'
|
||||||
import AuthTokenSetupDialogue from './AuthTokenSetupDialogue';
|
import AuthTokenSetupDialogue from './AuthTokenSetupDialogue'
|
||||||
|
|
||||||
const confirm = () => {
|
const confirm = () => {
|
||||||
return new Promise(res => {
|
return new Promise(resolve => {
|
||||||
OC.dialogs.confirm(
|
OC.dialogs.confirm(
|
||||||
t('core', 'Do you really want to wipe your data from this device?'),
|
t('core', 'Do you really want to wipe your data from this device?'),
|
||||||
t('core', 'Confirm wipe'),
|
t('core', 'Confirm wipe'),
|
||||||
res,
|
resolve,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tap into a promise without losing the value
|
* Tap into a promise without losing the value
|
||||||
*/
|
* @param {Function} cb the callback
|
||||||
const tap = cb => val => {
|
* @returns {any} val the value
|
||||||
cb(val);
|
*/
|
||||||
return val;
|
const tap = cb => val => {
|
||||||
};
|
cb(val)
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AuthTokenSection",
|
name: 'AuthTokenSection',
|
||||||
props: {
|
components: {
|
||||||
tokens: {
|
AuthTokenSetupDialogue,
|
||||||
type: Array,
|
AuthTokenList
|
||||||
required: true,
|
},
|
||||||
},
|
props: {
|
||||||
canCreateToken: {
|
tokens: {
|
||||||
type: Boolean,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
canCreateToken: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
baseUrl: OC.generateUrl('/settings/personal/authtokens')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addNewToken(name) {
|
||||||
|
console.debug('creating a new app token', name)
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name
|
||||||
}
|
}
|
||||||
|
return axios.post(this.baseUrl, data)
|
||||||
|
.then(resp => resp.data)
|
||||||
|
.then(tap(() => console.debug('app token created')))
|
||||||
|
.then(tap(data => this.tokens.push(data.deviceToken)))
|
||||||
|
.catch(err => {
|
||||||
|
console.error.bind('could not create app password', err)
|
||||||
|
OC.Notification.showTemporary(t('core', 'Error while creating device token'))
|
||||||
|
throw err
|
||||||
|
})
|
||||||
},
|
},
|
||||||
components: {
|
toggleTokenScope(token, scope, value) {
|
||||||
AuthTokenSetupDialogue,
|
console.debug('updating app token scope', token.id, scope, value)
|
||||||
AuthTokenList
|
|
||||||
|
const oldVal = token.scope[scope]
|
||||||
|
token.scope[scope] = value
|
||||||
|
|
||||||
|
return this.updateToken(token)
|
||||||
|
.then(tap(() => console.debug('app token scope updated')))
|
||||||
|
.catch(err => {
|
||||||
|
console.error.bind('could not update app token scope', err)
|
||||||
|
OC.Notification.showTemporary(t('core', 'Error while updating device token scope'))
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
token.scope[scope] = oldVal
|
||||||
|
|
||||||
|
throw err
|
||||||
|
})
|
||||||
},
|
},
|
||||||
data() {
|
rename(token, newName) {
|
||||||
return {
|
console.debug('renaming app token', token.id, token.name, newName)
|
||||||
baseUrl: OC.generateUrl('/settings/personal/authtokens'),
|
|
||||||
}
|
const oldName = token.name
|
||||||
|
token.name = newName
|
||||||
|
|
||||||
|
return this.updateToken(token)
|
||||||
|
.then(tap(() => console.debug('app token name updated')))
|
||||||
|
.catch(err => {
|
||||||
|
console.error.bind('could not update app token name', err)
|
||||||
|
OC.Notification.showTemporary(t('core', 'Error while updating device token name'))
|
||||||
|
|
||||||
|
// Restore
|
||||||
|
token.name = oldName
|
||||||
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
updateToken(token) {
|
||||||
addNewToken (name) {
|
return axios.put(this.baseUrl + '/' + token.id, token)
|
||||||
console.debug('creating a new app token', name);
|
.then(resp => resp.data)
|
||||||
|
},
|
||||||
|
deleteToken(token) {
|
||||||
|
console.debug('deleting app token', token)
|
||||||
|
|
||||||
const data = {
|
this.tokens = this.tokens.filter(t => t !== token)
|
||||||
name,
|
|
||||||
};
|
|
||||||
return Axios.post(this.baseUrl, data)
|
|
||||||
.then(resp => resp.data)
|
|
||||||
.then(tap(() => console.debug('app token created')))
|
|
||||||
.then(tap(data => this.tokens.push(data.deviceToken)))
|
|
||||||
.catch(err => {
|
|
||||||
console.error.bind('could not create app password', err);
|
|
||||||
OC.Notification.showTemporary(t('core', 'Error while creating device token'));
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
toggleTokenScope (token, scope, value) {
|
|
||||||
console.debug('updating app token scope', token.id, scope, value);
|
|
||||||
|
|
||||||
const oldVal = token.scope[scope];
|
return axios.delete(this.baseUrl + '/' + token.id)
|
||||||
token.scope[scope] = value;
|
.then(resp => resp.data)
|
||||||
|
.then(tap(() => console.debug('app token deleted')))
|
||||||
|
.catch(err => {
|
||||||
|
console.error.bind('could not delete app token', err)
|
||||||
|
OC.Notification.showTemporary(t('core', 'Error while deleting the token'))
|
||||||
|
|
||||||
return this.updateToken(token)
|
// Restore
|
||||||
.then(tap(() => console.debug('app token scope updated')))
|
this.tokens.push(token)
|
||||||
.catch(err => {
|
})
|
||||||
console.error.bind('could not update app token scope', err);
|
},
|
||||||
OC.Notification.showTemporary(t('core', 'Error while updating device token scope'));
|
async wipeToken(token) {
|
||||||
|
console.debug('wiping app token', token)
|
||||||
|
|
||||||
// Restore
|
try {
|
||||||
token.scope[scope] = oldVal;
|
await confirmPassword()
|
||||||
|
|
||||||
throw err;
|
if (!(await confirm())) {
|
||||||
})
|
console.debug('wipe aborted by user')
|
||||||
},
|
return
|
||||||
rename (token, newName) {
|
|
||||||
console.debug('renaming app token', token.id, token.name, newName);
|
|
||||||
|
|
||||||
const oldName = token.name;
|
|
||||||
token.name = newName;
|
|
||||||
|
|
||||||
return this.updateToken(token)
|
|
||||||
.then(tap(() => console.debug('app token name updated')))
|
|
||||||
.catch(err => {
|
|
||||||
console.error.bind('could not update app token name', err);
|
|
||||||
OC.Notification.showTemporary(t('core', 'Error while updating device token name'));
|
|
||||||
|
|
||||||
// Restore
|
|
||||||
token.name = oldName;
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateToken (token) {
|
|
||||||
return Axios.put(this.baseUrl + '/' + token.id, token)
|
|
||||||
.then(resp => resp.data)
|
|
||||||
},
|
|
||||||
deleteToken (token) {
|
|
||||||
console.debug('deleting app token', token);
|
|
||||||
|
|
||||||
this.tokens = this.tokens.filter(t => t !== token);
|
|
||||||
|
|
||||||
return Axios.delete(this.baseUrl + '/' + token.id)
|
|
||||||
.then(resp => resp.data)
|
|
||||||
.then(tap(() => console.debug('app token deleted')))
|
|
||||||
.catch(err => {
|
|
||||||
console.error.bind('could not delete app token', err);
|
|
||||||
OC.Notification.showTemporary(t('core', 'Error while deleting the token'));
|
|
||||||
|
|
||||||
// Restore
|
|
||||||
this.tokens.push(token);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
async wipeToken(token) {
|
|
||||||
console.debug('wiping app token', token);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await confirmPassword()
|
|
||||||
|
|
||||||
if (!(await confirm())) {
|
|
||||||
console.debug('wipe aborted by user')
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await Axios.post(this.baseUrl + '/wipe/' + token.id)
|
|
||||||
console.debug('app token marked for wipe')
|
|
||||||
|
|
||||||
token.type = 2;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('could not wipe app token', err);
|
|
||||||
OC.Notification.showTemporary(t('core', 'Error while wiping the device with the token'));
|
|
||||||
}
|
}
|
||||||
|
await axios.post(this.baseUrl + '/wipe/' + token.id)
|
||||||
|
console.debug('app token marked for wipe')
|
||||||
|
|
||||||
|
token.type = 2
|
||||||
|
} catch (err) {
|
||||||
|
console.error('could not wipe app token', err)
|
||||||
|
OC.Notification.showTemporary(t('core', 'Error while wiping the device with the token'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -22,13 +22,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!adding">
|
<div v-if="!adding">
|
||||||
<input v-model="deviceName"
|
<input v-model="deviceName"
|
||||||
type="text"
|
type="text"
|
||||||
@keydown.enter="submit"
|
:disabled="loading"
|
||||||
:disabled="loading"
|
:placeholder="t('settings', 'App name')"
|
||||||
:placeholder="t('settings', 'App name')">
|
@keydown.enter="submit">
|
||||||
<button class="button"
|
<button class="button"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
@click="submit">{{ t('settings', 'Create new app password') }}
|
@click="submit">
|
||||||
|
{{ t('settings', 'Create new app password') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
@ -37,142 +38,142 @@
|
||||||
<div class="app-password-row">
|
<div class="app-password-row">
|
||||||
<span class="app-password-label">{{ t('settings', 'Username') }}</span>
|
<span class="app-password-label">{{ t('settings', 'Username') }}</span>
|
||||||
<input :value="loginName"
|
<input :value="loginName"
|
||||||
type="text"
|
type="text"
|
||||||
class="monospaced"
|
class="monospaced"
|
||||||
readonly="readonly"
|
readonly="readonly"
|
||||||
@focus="selectInput"/>
|
@focus="selectInput">
|
||||||
</div>
|
</div>
|
||||||
<div class="app-password-row">
|
<div class="app-password-row">
|
||||||
<span class="app-password-label">{{ t('settings', 'Password') }}</span>
|
<span class="app-password-label">{{ t('settings', 'Password') }}</span>
|
||||||
<input :value="appPassword"
|
<input ref="appPassword"
|
||||||
type="text"
|
:value="appPassword"
|
||||||
class="monospaced"
|
type="text"
|
||||||
ref="appPassword"
|
class="monospaced"
|
||||||
readonly="readonly"
|
readonly="readonly"
|
||||||
@focus="selectInput"/>
|
@focus="selectInput">
|
||||||
<a class="icon icon-clippy"
|
<a ref="clipboardButton"
|
||||||
ref="clipboardButton"
|
v-tooltip="copyTooltipOptions"
|
||||||
v-tooltip="copyTooltipOptions"
|
v-clipboard:copy="appPassword"
|
||||||
@mouseover="hoveringCopyButton = true"
|
v-clipboard:success="onCopyPassword"
|
||||||
@mouseleave="hoveringCopyButton = false"
|
v-clipboard:error="onCopyPasswordFailed"
|
||||||
v-clipboard:copy="appPassword"
|
class="icon icon-clippy"
|
||||||
v-clipboard:success="onCopyPassword"
|
@mouseover="hoveringCopyButton = true"
|
||||||
v-clipboard:error="onCopyPasswordFailed"></a>
|
@mouseleave="hoveringCopyButton = false" />
|
||||||
<button class="button"
|
<button class="button"
|
||||||
@click="reset">
|
@click="reset">
|
||||||
{{ t('settings', 'Done') }}
|
{{ t('settings', 'Done') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="app-password-row">
|
<div class="app-password-row">
|
||||||
<span class="app-password-label"></span>
|
<span class="app-password-label" />
|
||||||
<a v-if="!showQR"
|
<a v-if="!showQR"
|
||||||
@click="showQR = true">
|
@click="showQR = true">
|
||||||
{{ t('settings', 'Show QR code for mobile apps') }}
|
{{ t('settings', 'Show QR code for mobile apps') }}
|
||||||
</a>
|
</a>
|
||||||
<QR v-else
|
<QR v-else
|
||||||
:value="qrUrl"></QR>
|
:value="qrUrl" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import QR from '@chenfengyuan/vue-qrcode';
|
import QR from '@chenfengyuan/vue-qrcode'
|
||||||
import confirmPassword from 'nextcloud-password-confirmation';
|
import confirmPassword from 'nextcloud-password-confirmation'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AuthTokenSetupDialogue',
|
name: 'AuthTokenSetupDialogue',
|
||||||
components: {
|
components: {
|
||||||
QR,
|
QR
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
add: {
|
add: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true,
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
adding: false,
|
||||||
|
loading: false,
|
||||||
|
deviceName: '',
|
||||||
|
appPassword: '',
|
||||||
|
loginName: '',
|
||||||
|
passwordCopied: false,
|
||||||
|
showQR: false,
|
||||||
|
qrUrl: '',
|
||||||
|
hoveringCopyButton: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
copyTooltipOptions() {
|
||||||
|
const base = {
|
||||||
|
hideOnTargetClick: false,
|
||||||
|
trigger: 'manual'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
data () {
|
|
||||||
return {
|
|
||||||
adding: false,
|
|
||||||
loading: false,
|
|
||||||
deviceName: '',
|
|
||||||
appPassword: '',
|
|
||||||
loginName: '',
|
|
||||||
passwordCopied: false,
|
|
||||||
showQR: false,
|
|
||||||
qrUrl: '',
|
|
||||||
hoveringCopyButton: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
copyTooltipOptions() {
|
|
||||||
const base = {
|
|
||||||
hideOnTargetClick: false,
|
|
||||||
trigger: 'manual',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.passwordCopied) {
|
if (this.passwordCopied) {
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
content:t('core', 'Copied!'),
|
content: t('core', 'Copied!'),
|
||||||
show: true,
|
show: true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
content: t('core', 'Copy'),
|
content: t('core', 'Copy'),
|
||||||
show: this.hoveringCopyButton,
|
show: this.hoveringCopyButton
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectInput(e) {
|
||||||
|
e.currentTarget.select()
|
||||||
},
|
},
|
||||||
methods: {
|
submit: function() {
|
||||||
selectInput (e) {
|
confirmPassword()
|
||||||
e.currentTarget.select();
|
.then(() => {
|
||||||
},
|
this.loading = true
|
||||||
submit: function () {
|
return this.add(this.deviceName)
|
||||||
confirmPassword()
|
})
|
||||||
.then(() => {
|
.then(token => {
|
||||||
this.loading = true;
|
this.adding = true
|
||||||
return this.add(this.deviceName)
|
this.loginName = token.loginName
|
||||||
|
this.appPassword = token.token
|
||||||
|
|
||||||
|
const server = window.location.protocol + '//' + window.location.host + OC.getRootPath()
|
||||||
|
this.qrUrl = `nc://login/user:${token.loginName}&password:${token.token}&server:${server}`
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.appPassword.select()
|
||||||
})
|
})
|
||||||
.then(token => {
|
})
|
||||||
this.adding = true;
|
.catch(err => {
|
||||||
this.loginName = token.loginName;
|
console.error('could not create a new app password', err)
|
||||||
this.appPassword = token.token;
|
OC.Notification.showTemporary(t('core', 'Error while creating device token'))
|
||||||
|
|
||||||
const server = window.location.protocol + '//' + window.location.host + OC.getRootPath();
|
this.reset()
|
||||||
this.qrUrl = `nc://login/user:${token.loginName}&password:${token.token}&server:${server}`;
|
})
|
||||||
|
},
|
||||||
this.$nextTick(() => {
|
onCopyPassword() {
|
||||||
this.$refs.appPassword.select();
|
this.passwordCopied = true
|
||||||
});
|
this.$refs.clipboardButton.blur()
|
||||||
})
|
setTimeout(() => { this.passwordCopied = false }, 3000)
|
||||||
.catch(err => {
|
},
|
||||||
console.error('could not create a new app password', err);
|
onCopyPasswordFailed() {
|
||||||
OC.Notification.showTemporary(t('core', 'Error while creating device token'));
|
OC.Notification.showTemporary(t('core', 'Could not copy app password. Please copy it manually.'))
|
||||||
|
},
|
||||||
this.reset();
|
reset() {
|
||||||
});
|
this.adding = false
|
||||||
},
|
this.loading = false
|
||||||
onCopyPassword() {
|
this.showQR = false
|
||||||
this.passwordCopied = true;
|
this.qrUrl = ''
|
||||||
this.$refs.clipboardButton.blur();
|
this.deviceName = ''
|
||||||
setTimeout(() => this.passwordCopied = false, 3000);
|
this.appPassword = ''
|
||||||
},
|
this.loginName = ''
|
||||||
onCopyPasswordFailed() {
|
|
||||||
OC.Notification.showTemporary(t('core', 'Could not copy app password. Please copy it manually.'));
|
|
||||||
},
|
|
||||||
reset () {
|
|
||||||
this.adding = false;
|
|
||||||
this.loading = false;
|
|
||||||
this.showQR = false;
|
|
||||||
this.qrUrl = '';
|
|
||||||
this.deviceName = '';
|
|
||||||
this.appPassword = '';
|
|
||||||
this.loginName = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @license GNU AGPL version 3 or any later version
|
||||||
|
-
|
||||||
|
- This program is free software: you can redistribute it and/or modify
|
||||||
|
- it under the terms of the GNU Affero General Public License as
|
||||||
|
- published by the Free Software Foundation, either version 3 of the
|
||||||
|
- License, or (at your option) any later version.
|
||||||
|
-
|
||||||
|
- This program is distributed in the hope that it will be useful,
|
||||||
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
- GNU Affero General Public License for more details.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the GNU Affero General Public License
|
||||||
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'PrefixMixin',
|
||||||
|
methods: {
|
||||||
|
prefix(prefix, content) {
|
||||||
|
return prefix + '_' + content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
-
|
||||||
|
- @license GNU AGPL version 3 or any later version
|
||||||
|
-
|
||||||
|
- This program is free software: you can redistribute it and/or modify
|
||||||
|
- it under the terms of the GNU Affero General Public License as
|
||||||
|
- published by the Free Software Foundation, either version 3 of the
|
||||||
|
- License, or (at your option) any later version.
|
||||||
|
-
|
||||||
|
- This program is distributed in the hope that it will be useful,
|
||||||
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
- GNU Affero General Public License for more details.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the GNU Affero General Public License
|
||||||
|
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'SvgFilterMixin',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
filterId: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filterUrl() {
|
||||||
|
return `url(#${this.filterId})`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.filterId = 'invertIconApps' + Math.floor((Math.random() * 100)) + new Date().getSeconds() + new Date().getMilliseconds()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,553 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 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/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="app-content" class="user-list-grid" @scroll.passive="onScroll">
|
||||||
|
<div id="grid-header" class="row" :class="{'sticky': scrolled && !showConfig.showNewUserForm}">
|
||||||
|
<div id="headerAvatar" class="avatar" />
|
||||||
|
<div id="headerName" class="name">
|
||||||
|
{{ t('settings', 'Username') }}
|
||||||
|
</div>
|
||||||
|
<div id="headerDisplayName" class="displayName">
|
||||||
|
{{ t('settings', 'Display name') }}
|
||||||
|
</div>
|
||||||
|
<div id="headerPassword" class="password">
|
||||||
|
{{ t('settings', 'Password') }}
|
||||||
|
</div>
|
||||||
|
<div id="headerAddress" class="mailAddress">
|
||||||
|
{{ t('settings', 'Email') }}
|
||||||
|
</div>
|
||||||
|
<div id="headerGroups" class="groups">
|
||||||
|
{{ t('settings', 'Groups') }}
|
||||||
|
</div>
|
||||||
|
<div v-if="subAdminsGroups.length>0 && settings.isAdmin"
|
||||||
|
id="headerSubAdmins"
|
||||||
|
class="subadmins">
|
||||||
|
{{ t('settings', 'Group admin for') }}
|
||||||
|
</div>
|
||||||
|
<div id="headerQuota" class="quota">
|
||||||
|
{{ t('settings', 'Quota') }}
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showLanguages"
|
||||||
|
id="headerLanguages"
|
||||||
|
class="languages">
|
||||||
|
{{ t('settings', 'Language') }}
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showStoragePath"
|
||||||
|
class="headerStorageLocation storageLocation">
|
||||||
|
{{ t('settings', 'Storage location') }}
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showUserBackend"
|
||||||
|
class="headerUserBackend userBackend">
|
||||||
|
{{ t('settings', 'User backend') }}
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showLastLogin"
|
||||||
|
class="headerLastLogin lastLogin">
|
||||||
|
{{ t('settings', 'Last login') }}
|
||||||
|
</div>
|
||||||
|
<div class="userActions" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form v-show="showConfig.showNewUserForm"
|
||||||
|
id="new-user"
|
||||||
|
class="row"
|
||||||
|
:disabled="loading.all"
|
||||||
|
:class="{'sticky': scrolled && showConfig.showNewUserForm}"
|
||||||
|
@submit.prevent="createUser">
|
||||||
|
<div :class="loading.all?'icon-loading-small':'icon-add'" />
|
||||||
|
<div class="name">
|
||||||
|
<input id="newusername"
|
||||||
|
ref="newusername"
|
||||||
|
v-model="newUser.id"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
:placeholder="settings.newUserGenerateUserID
|
||||||
|
? t('settings', 'Will be autogenerated')
|
||||||
|
: t('settings', 'Username')"
|
||||||
|
name="username"
|
||||||
|
autocomplete="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
autocorrect="off"
|
||||||
|
pattern="[a-zA-Z0-9 _\.@\-']+"
|
||||||
|
:disabled="settings.newUserGenerateUserID">
|
||||||
|
</div>
|
||||||
|
<div class="displayName">
|
||||||
|
<input id="newdisplayname"
|
||||||
|
v-model="newUser.displayName"
|
||||||
|
type="text"
|
||||||
|
:placeholder="t('settings', 'Display name')"
|
||||||
|
name="displayname"
|
||||||
|
autocomplete="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
autocorrect="off">
|
||||||
|
</div>
|
||||||
|
<div class="password">
|
||||||
|
<input id="newuserpassword"
|
||||||
|
ref="newuserpassword"
|
||||||
|
v-model="newUser.password"
|
||||||
|
type="password"
|
||||||
|
:required="newUser.mailAddress===''"
|
||||||
|
:placeholder="t('settings', 'Password')"
|
||||||
|
name="password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
autocapitalize="none"
|
||||||
|
autocorrect="off"
|
||||||
|
:minlength="minPasswordLength">
|
||||||
|
</div>
|
||||||
|
<div class="mailAddress">
|
||||||
|
<input id="newemail"
|
||||||
|
v-model="newUser.mailAddress"
|
||||||
|
type="email"
|
||||||
|
:required="newUser.password==='' || settings.newUserRequireEmail"
|
||||||
|
:placeholder="t('settings', 'Email')"
|
||||||
|
name="email"
|
||||||
|
autocomplete="off"
|
||||||
|
autocapitalize="none"
|
||||||
|
autocorrect="off">
|
||||||
|
</div>
|
||||||
|
<div class="groups">
|
||||||
|
<!-- hidden input trick for vanilla html5 form validation -->
|
||||||
|
<input v-if="!settings.isAdmin"
|
||||||
|
id="newgroups"
|
||||||
|
type="text"
|
||||||
|
:value="newUser.groups"
|
||||||
|
tabindex="-1"
|
||||||
|
:required="!settings.isAdmin"
|
||||||
|
:class="{'icon-loading-small': loading.groups}">
|
||||||
|
<Multiselect v-model="newUser.groups"
|
||||||
|
:options="canAddGroups"
|
||||||
|
:disabled="loading.groups||loading.all"
|
||||||
|
tag-placeholder="create"
|
||||||
|
:placeholder="t('settings', 'Add user in group')"
|
||||||
|
label="name"
|
||||||
|
track-by="id"
|
||||||
|
class="multiselect-vue"
|
||||||
|
:multiple="true"
|
||||||
|
:taggable="true"
|
||||||
|
:close-on-select="false"
|
||||||
|
:tag-width="60"
|
||||||
|
@tag="createGroup">
|
||||||
|
<!-- If user is not admin, he is a subadmin.
|
||||||
|
Subadmins can't create users outside their groups
|
||||||
|
Therefore, empty select is forbidden -->
|
||||||
|
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||||
|
</Multiselect>
|
||||||
|
</div>
|
||||||
|
<div v-if="subAdminsGroups.length>0 && settings.isAdmin" class="subadmins">
|
||||||
|
<Multiselect v-model="newUser.subAdminsGroups"
|
||||||
|
:options="subAdminsGroups"
|
||||||
|
:placeholder="t('settings', 'Set user as admin for')"
|
||||||
|
label="name"
|
||||||
|
track-by="id"
|
||||||
|
class="multiselect-vue"
|
||||||
|
:multiple="true"
|
||||||
|
:close-on-select="false"
|
||||||
|
:tag-width="60">
|
||||||
|
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||||
|
</Multiselect>
|
||||||
|
</div>
|
||||||
|
<div class="quota">
|
||||||
|
<Multiselect v-model="newUser.quota"
|
||||||
|
:options="quotaOptions"
|
||||||
|
:placeholder="t('settings', 'Select user quota')"
|
||||||
|
label="label"
|
||||||
|
track-by="id"
|
||||||
|
class="multiselect-vue"
|
||||||
|
:allow-empty="false"
|
||||||
|
:taggable="true"
|
||||||
|
@tag="validateQuota" />
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showLanguages" class="languages">
|
||||||
|
<Multiselect v-model="newUser.language"
|
||||||
|
:options="languages"
|
||||||
|
:placeholder="t('settings', 'Default language')"
|
||||||
|
label="name"
|
||||||
|
track-by="code"
|
||||||
|
class="multiselect-vue"
|
||||||
|
:allow-empty="false"
|
||||||
|
group-values="languages"
|
||||||
|
group-label="label" />
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showStoragePath" class="storageLocation" />
|
||||||
|
<div v-if="showConfig.showUserBackend" class="userBackend" />
|
||||||
|
<div v-if="showConfig.showLastLogin" class="lastLogin" />
|
||||||
|
<div class="userActions">
|
||||||
|
<input id="newsubmit"
|
||||||
|
type="submit"
|
||||||
|
class="button primary icon-checkmark-white has-tooltip"
|
||||||
|
value=""
|
||||||
|
:title="t('settings', 'Add a new user')">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<user-row v-for="(user, key) in filteredUsers"
|
||||||
|
:key="key"
|
||||||
|
:user="user"
|
||||||
|
:settings="settings"
|
||||||
|
:show-config="showConfig"
|
||||||
|
:groups="groups"
|
||||||
|
:sub-admins-groups="subAdminsGroups"
|
||||||
|
:quota-options="quotaOptions"
|
||||||
|
:languages="languages"
|
||||||
|
:external-actions="externalActions" />
|
||||||
|
<InfiniteLoading ref="infiniteLoading" @infinite="infiniteHandler">
|
||||||
|
<div slot="spinner">
|
||||||
|
<div class="users-icon-loading icon-loading" />
|
||||||
|
</div>
|
||||||
|
<div slot="no-more">
|
||||||
|
<div class="users-list-end" />
|
||||||
|
</div>
|
||||||
|
<div slot="no-results">
|
||||||
|
<div id="emptycontent">
|
||||||
|
<div class="icon-contacts-dark" />
|
||||||
|
<h2>{{ t('settings', 'No users in here') }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</InfiniteLoading>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import userRow from './userList/UserRow'
|
||||||
|
import { Multiselect } from 'nextcloud-vue'
|
||||||
|
import InfiniteLoading from 'vue-infinite-loading'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
const unlimitedQuota = {
|
||||||
|
id: 'none',
|
||||||
|
label: t('settings', 'Unlimited')
|
||||||
|
}
|
||||||
|
const defaultQuota = {
|
||||||
|
id: 'default',
|
||||||
|
label: t('settings', 'Default quota')
|
||||||
|
}
|
||||||
|
const newUser = {
|
||||||
|
id: '',
|
||||||
|
displayName: '',
|
||||||
|
password: '',
|
||||||
|
mailAddress: '',
|
||||||
|
groups: [],
|
||||||
|
subAdminsGroups: [],
|
||||||
|
quota: defaultQuota,
|
||||||
|
language: {
|
||||||
|
code: 'en',
|
||||||
|
name: t('settings', 'Default language')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'UserList',
|
||||||
|
components: {
|
||||||
|
userRow,
|
||||||
|
Multiselect,
|
||||||
|
InfiniteLoading
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
users: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
showConfig: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selectedGroup: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
externalActions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
unlimitedQuota,
|
||||||
|
defaultQuota,
|
||||||
|
loading: {
|
||||||
|
all: false,
|
||||||
|
groups: false
|
||||||
|
},
|
||||||
|
scrolled: false,
|
||||||
|
searchQuery: '',
|
||||||
|
newUser: Object.assign({}, newUser)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
settings() {
|
||||||
|
return this.$store.getters.getServerData
|
||||||
|
},
|
||||||
|
filteredUsers() {
|
||||||
|
if (this.selectedGroup === 'disabled') {
|
||||||
|
return this.users.filter(user => user.enabled === false)
|
||||||
|
}
|
||||||
|
if (!this.settings.isAdmin) {
|
||||||
|
// we don't want subadmins to edit themselves
|
||||||
|
return this.users.filter(user => user.enabled !== false && user.id !== OC.getCurrentUser().uid)
|
||||||
|
}
|
||||||
|
return this.users.filter(user => user.enabled !== false)
|
||||||
|
},
|
||||||
|
groups() {
|
||||||
|
// data provided php side + remove the disabled group
|
||||||
|
return this.$store.getters.getGroups
|
||||||
|
.filter(group => group.id !== 'disabled')
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
},
|
||||||
|
canAddGroups() {
|
||||||
|
// disabled if no permission to add new users to group
|
||||||
|
return this.groups.map(group => {
|
||||||
|
// clone object because we don't want
|
||||||
|
// to edit the original groups
|
||||||
|
group = Object.assign({}, group)
|
||||||
|
group.$isDisabled = group.canAdd === false
|
||||||
|
return group
|
||||||
|
})
|
||||||
|
},
|
||||||
|
subAdminsGroups() {
|
||||||
|
// data provided php side
|
||||||
|
return this.$store.getters.getSubadminGroups
|
||||||
|
},
|
||||||
|
quotaOptions() {
|
||||||
|
// convert the preset array into objects
|
||||||
|
let quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({ id: cur, label: cur }), [])
|
||||||
|
// add default presets
|
||||||
|
quotaPreset.unshift(this.unlimitedQuota)
|
||||||
|
quotaPreset.unshift(this.defaultQuota)
|
||||||
|
return quotaPreset
|
||||||
|
},
|
||||||
|
minPasswordLength() {
|
||||||
|
return this.$store.getters.getPasswordPolicyMinLength
|
||||||
|
},
|
||||||
|
usersOffset() {
|
||||||
|
return this.$store.getters.getUsersOffset
|
||||||
|
},
|
||||||
|
usersLimit() {
|
||||||
|
return this.$store.getters.getUsersLimit
|
||||||
|
},
|
||||||
|
usersCount() {
|
||||||
|
return this.users.length
|
||||||
|
},
|
||||||
|
|
||||||
|
/* LANGUAGES */
|
||||||
|
languages() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('settings', 'Common languages'),
|
||||||
|
languages: this.settings.languages.commonlanguages
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings', 'All languages'),
|
||||||
|
languages: this.settings.languages.languages
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// watch url change and group select
|
||||||
|
selectedGroup: function(val, old) {
|
||||||
|
// if selected is the disabled group but it's empty
|
||||||
|
this.redirectIfDisabled()
|
||||||
|
this.$store.commit('resetUsers')
|
||||||
|
this.$refs.infiniteLoading.stateChanger.reset()
|
||||||
|
this.setNewUserDefaultGroup(val)
|
||||||
|
},
|
||||||
|
|
||||||
|
// make sure the infiniteLoading state is changed if we manually
|
||||||
|
// add/remove data from the store
|
||||||
|
usersCount: function(val, old) {
|
||||||
|
// deleting the last user, reset the list
|
||||||
|
if (val === 0 && old === 1) {
|
||||||
|
this.$refs.infiniteLoading.stateChanger.reset()
|
||||||
|
// adding the first user, warn the infiniteLoader that
|
||||||
|
// the list is not empty anymore (we don't fetch the newly
|
||||||
|
// added user as we already have all the info we need)
|
||||||
|
} else if (val === 1 && old === 0) {
|
||||||
|
this.$refs.infiniteLoading.stateChanger.loaded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (!this.settings.canChangePassword) {
|
||||||
|
OC.Notification.showTemporary(t('settings', 'Password change is disabled because the master key is disabled'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset and init new user form
|
||||||
|
*/
|
||||||
|
this.resetForm()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register search
|
||||||
|
*/
|
||||||
|
this.userSearch = new OCA.Search(this.search, this.resetSearch)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If disabled group but empty, redirect
|
||||||
|
*/
|
||||||
|
this.redirectIfDisabled()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onScroll(event) {
|
||||||
|
this.scrolled = event.target.scrollTo > 0
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate quota string to make sure it's a valid human file size
|
||||||
|
*
|
||||||
|
* @param {string} quota Quota in readable format '5 GB'
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
validateQuota(quota) {
|
||||||
|
// only used for new presets sent through @Tag
|
||||||
|
let validQuota = OC.Util.computerFileSize(quota)
|
||||||
|
if (validQuota !== null && validQuota >= 0) {
|
||||||
|
// unify format output
|
||||||
|
quota = OC.Util.humanFileSize(OC.Util.computerFileSize(quota))
|
||||||
|
this.newUser.quota = { id: quota, label: quota }
|
||||||
|
return this.newUser.quota
|
||||||
|
}
|
||||||
|
// Default is unlimited
|
||||||
|
this.newUser.quota = this.quotaOptions[0]
|
||||||
|
return this.quotaOptions[0]
|
||||||
|
},
|
||||||
|
|
||||||
|
infiniteHandler($state) {
|
||||||
|
this.$store.dispatch('getUsers', {
|
||||||
|
offset: this.usersOffset,
|
||||||
|
limit: this.usersLimit,
|
||||||
|
group: this.selectedGroup !== 'disabled' ? this.selectedGroup : '',
|
||||||
|
search: this.searchQuery
|
||||||
|
})
|
||||||
|
.then((response) => { response ? $state.loaded() : $state.complete() })
|
||||||
|
},
|
||||||
|
|
||||||
|
/* SEARCH */
|
||||||
|
search(query) {
|
||||||
|
this.searchQuery = query
|
||||||
|
this.$store.commit('resetUsers')
|
||||||
|
this.$refs.infiniteLoading.stateChanger.reset()
|
||||||
|
},
|
||||||
|
resetSearch() {
|
||||||
|
this.search('')
|
||||||
|
},
|
||||||
|
|
||||||
|
resetForm() {
|
||||||
|
// revert form to original state
|
||||||
|
this.newUser = Object.assign({}, newUser)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init default language from server data. The use of this.settings
|
||||||
|
* requires a computed variable, which break the v-model binding of the form,
|
||||||
|
* this is a much easier solution than getter and setter on a computed var
|
||||||
|
*/
|
||||||
|
if (this.settings.defaultLanguage) {
|
||||||
|
Vue.set(this.newUser.language, 'code', this.settings.defaultLanguage)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In case the user directly loaded the user list within a group
|
||||||
|
* the watch won't be triggered. We need to initialize it.
|
||||||
|
*/
|
||||||
|
this.setNewUserDefaultGroup(this.selectedGroup)
|
||||||
|
|
||||||
|
this.loading.all = false
|
||||||
|
},
|
||||||
|
createUser() {
|
||||||
|
this.loading.all = true
|
||||||
|
this.$store.dispatch('addUser', {
|
||||||
|
userid: this.newUser.id,
|
||||||
|
password: this.newUser.password,
|
||||||
|
displayName: this.newUser.displayName,
|
||||||
|
email: this.newUser.mailAddress,
|
||||||
|
groups: this.newUser.groups.map(group => group.id),
|
||||||
|
subadmin: this.newUser.subAdminsGroups.map(group => group.id),
|
||||||
|
quota: this.newUser.quota.id,
|
||||||
|
language: this.newUser.language.code
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.resetForm()
|
||||||
|
this.$refs.newusername.focus()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.loading.all = false
|
||||||
|
if (error.response && error.response.data && error.response.data.ocs && error.response.data.ocs.meta) {
|
||||||
|
const statuscode = error.response.data.ocs.meta.statuscode
|
||||||
|
if (statuscode === 102) {
|
||||||
|
// wrong username
|
||||||
|
this.$refs.newusername.focus()
|
||||||
|
} else if (statuscode === 107) {
|
||||||
|
// wrong password
|
||||||
|
this.$refs.newuserpassword.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setNewUserDefaultGroup(value) {
|
||||||
|
if (value && value.length > 0) {
|
||||||
|
// setting new user default group to the current selected one
|
||||||
|
let currentGroup = this.groups.find(group => group.id === value)
|
||||||
|
if (currentGroup) {
|
||||||
|
this.newUser.groups = [currentGroup]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fallback, empty selected group
|
||||||
|
this.newUser.groups = []
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new group
|
||||||
|
*
|
||||||
|
* @param {string} gid Group id
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
createGroup(gid) {
|
||||||
|
this.loading.groups = true
|
||||||
|
this.$store.dispatch('addGroup', gid)
|
||||||
|
.then((group) => {
|
||||||
|
this.newUser.groups.push(this.groups.find(group => group.id === gid))
|
||||||
|
this.loading.groups = false
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.loading.groups = false
|
||||||
|
})
|
||||||
|
return this.$store.getters.getGroups[this.groups.length]
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the selected group is the disabled group but the count is 0
|
||||||
|
* redirect to the all users page.
|
||||||
|
* * we only check for 0 because we don't have the count on ldap
|
||||||
|
* * and we therefore set the usercount to -1 in this specific case
|
||||||
|
*/
|
||||||
|
redirectIfDisabled() {
|
||||||
|
const allGroups = this.$store.getters.getGroups
|
||||||
|
if (this.selectedGroup === 'disabled'
|
||||||
|
&& allGroups.findIndex(group => group.id === 'disabled' && group.usercount === 0) > -1) {
|
||||||
|
// disabled group is empty, redirection to all users
|
||||||
|
this.$router.push({ name: 'users' })
|
||||||
|
this.$refs.infiniteLoading.stateChanger.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -25,160 +25,177 @@
|
||||||
<div id="apps-list" class="apps-list" :class="{installed: (useBundleView || useListView), store: useAppStoreView}">
|
<div id="apps-list" class="apps-list" :class="{installed: (useBundleView || useListView), store: useAppStoreView}">
|
||||||
<template v-if="useListView">
|
<template v-if="useListView">
|
||||||
<transition-group name="app-list" tag="div" class="apps-list-container">
|
<transition-group name="app-list" tag="div" class="apps-list-container">
|
||||||
<app-item v-for="app in apps" :key="app.id" :app="app" :category="category" />
|
<AppItem v-for="app in apps"
|
||||||
|
:key="app.id"
|
||||||
|
:app="app"
|
||||||
|
:category="category" />
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</template>
|
</template>
|
||||||
<template v-for="bundle in bundles" v-if="useBundleView && bundleApps(bundle.id).length > 0">
|
<transition-group v-if="useBundleView"
|
||||||
<transition-group name="app-list" tag="div" class="apps-list-container">
|
name="app-list"
|
||||||
|
tag="div"
|
||||||
<div class="apps-header" :key="bundle.id">
|
class="apps-list-container">
|
||||||
<div class="app-image"></div>
|
<template v-for="bundle in bundles">
|
||||||
<h2>{{ bundle.name }} <input type="button" :value="bundleToggleText(bundle.id)" v-on:click="toggleBundle(bundle.id)"></h2>
|
<div :key="bundle.id" class="apps-header">
|
||||||
<div class="app-version"></div>
|
<div class="app-image" />
|
||||||
<div class="app-level"></div>
|
<h2>{{ bundle.name }} <input type="button" :value="bundleToggleText(bundle.id)" @click="toggleBundle(bundle.id)"></h2>
|
||||||
<div class="app-groups"></div>
|
<div class="app-version" />
|
||||||
<div class="actions"> </div>
|
<div class="app-level" />
|
||||||
|
<div class="app-groups" />
|
||||||
|
<div class="actions">
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-item v-for="app in bundleApps(bundle.id)" :key="bundle.id + app.id" :app="app" :category="category"/>
|
<AppItem v-for="app in bundleApps(bundle.id)"
|
||||||
</transition-group>
|
:key="bundle.id + app.id"
|
||||||
</template>
|
:app="app"
|
||||||
|
:category="category" />
|
||||||
|
</template>
|
||||||
|
</transition-group>
|
||||||
<template v-if="useAppStoreView">
|
<template v-if="useAppStoreView">
|
||||||
<app-item v-for="app in apps" :key="app.id" :app="app" :category="category" :list-view="false" />
|
<AppItem v-for="app in apps"
|
||||||
|
:key="app.id"
|
||||||
|
:app="app"
|
||||||
|
:category="category"
|
||||||
|
:list-view="false" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="apps-list-search" class="apps-list installed">
|
<div id="apps-list-search" class="apps-list installed">
|
||||||
<div class="apps-list-container">
|
<div class="apps-list-container">
|
||||||
<template v-if="search !== '' && searchApps.length > 0">
|
<template v-if="search !== '' && searchApps.length > 0">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div></div>
|
<div />
|
||||||
<td colspan="5">
|
<td colspan="5">
|
||||||
<h2>{{ t('settings', 'Results from other categories') }}</h2>
|
<h2>{{ t('settings', 'Results from other categories') }}</h2>
|
||||||
</td>
|
</td>
|
||||||
</div>
|
</div>
|
||||||
<app-item v-for="app in searchApps" :key="app.id" :app="app" :category="category" :list-view="true" />
|
<AppItem v-for="app in searchApps"
|
||||||
|
:key="app.id"
|
||||||
|
:app="app"
|
||||||
|
:category="category"
|
||||||
|
:list-view="true" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="apps-list-empty" class="emptycontent emptycontent-search" v-if="search !== '' && !loading && searchApps.length === 0 && apps.length === 0">
|
<div v-if="search !== '' && !loading && searchApps.length === 0 && apps.length === 0" id="apps-list-empty" class="emptycontent emptycontent-search">
|
||||||
<div id="app-list-empty-icon" class="icon-settings-dark"></div>
|
<div id="app-list-empty-icon" class="icon-settings-dark" />
|
||||||
<h2>{{ t('settings', 'No apps found for your version')}}</h2>
|
<h2>{{ t('settings', 'No apps found for your version') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="searchresults"></div>
|
<div id="searchresults" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import appItem from './appList/appItem';
|
import AppItem from './AppList/AppItem'
|
||||||
import prefix from './prefixMixin';
|
import PrefixMixin from './PrefixMixin'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'appList',
|
name: 'AppList',
|
||||||
mixins: [prefix],
|
|
||||||
props: ['category', 'app', 'search'],
|
|
||||||
components: {
|
components: {
|
||||||
appItem
|
AppItem
|
||||||
},
|
},
|
||||||
|
mixins: [PrefixMixin],
|
||||||
|
props: ['category', 'app', 'search'],
|
||||||
computed: {
|
computed: {
|
||||||
loading() {
|
loading() {
|
||||||
return this.$store.getters.loading('list');
|
return this.$store.getters.loading('list')
|
||||||
},
|
},
|
||||||
apps() {
|
apps() {
|
||||||
let apps = this.$store.getters.getAllApps
|
let apps = this.$store.getters.getAllApps
|
||||||
.filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
|
.filter(app => app.name.toLowerCase().search(this.search.toLowerCase()) !== -1)
|
||||||
.sort(function (a, b) {
|
.sort(function(a, b) {
|
||||||
const sortStringA = '' + (a.active ? 0 : 1) + (a.update ? 0 : 1) + a.name;
|
const sortStringA = '' + (a.active ? 0 : 1) + (a.update ? 0 : 1) + a.name
|
||||||
const sortStringB = '' + (b.active ? 0 : 1) + (b.update ? 0 : 1) + b.name;
|
const sortStringB = '' + (b.active ? 0 : 1) + (b.update ? 0 : 1) + b.name
|
||||||
return OC.Util.naturalSortCompare(sortStringA, sortStringB);
|
return OC.Util.naturalSortCompare(sortStringA, sortStringB)
|
||||||
});
|
})
|
||||||
|
|
||||||
if (this.category === 'installed') {
|
if (this.category === 'installed') {
|
||||||
return apps.filter(app => app.installed);
|
return apps.filter(app => app.installed)
|
||||||
}
|
}
|
||||||
if (this.category === 'enabled') {
|
if (this.category === 'enabled') {
|
||||||
return apps.filter(app => app.active && app.installed);
|
return apps.filter(app => app.active && app.installed)
|
||||||
}
|
}
|
||||||
if (this.category === 'disabled') {
|
if (this.category === 'disabled') {
|
||||||
return apps.filter(app => !app.active && app.installed);
|
return apps.filter(app => !app.active && app.installed)
|
||||||
}
|
}
|
||||||
if (this.category === 'app-bundles') {
|
if (this.category === 'app-bundles') {
|
||||||
return apps.filter(app => app.bundles);
|
return apps.filter(app => app.bundles)
|
||||||
}
|
}
|
||||||
if (this.category === 'updates') {
|
if (this.category === 'updates') {
|
||||||
return apps.filter(app => app.update);
|
return apps.filter(app => app.update)
|
||||||
}
|
}
|
||||||
// filter app store categories
|
// filter app store categories
|
||||||
return apps.filter(app => {
|
return apps.filter(app => {
|
||||||
return app.appstore && app.category !== undefined &&
|
return app.appstore && app.category !== undefined
|
||||||
(app.category === this.category || app.category.indexOf(this.category) > -1);
|
&& (app.category === this.category || app.category.indexOf(this.category) > -1)
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
bundles() {
|
bundles() {
|
||||||
return this.$store.getters.getServerData.bundles;
|
return this.$store.getters.getServerData.bundles.filter(bundle => this.bundleApps(bundle.id).length > 0)
|
||||||
},
|
},
|
||||||
bundleApps() {
|
bundleApps() {
|
||||||
return function(bundle) {
|
return function(bundle) {
|
||||||
return this.$store.getters.getAllApps
|
return this.$store.getters.getAllApps
|
||||||
.filter(app => app.bundleId === bundle);
|
.filter(app => app.bundleId === bundle)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
searchApps() {
|
searchApps() {
|
||||||
if (this.search === '') {
|
if (this.search === '') {
|
||||||
return [];
|
return []
|
||||||
}
|
}
|
||||||
return this.$store.getters.getAllApps
|
return this.$store.getters.getAllApps
|
||||||
.filter(app => {
|
.filter(app => {
|
||||||
if (app.name.toLowerCase().search(this.search.toLowerCase()) !== -1) {
|
if (app.name.toLowerCase().search(this.search.toLowerCase()) !== -1) {
|
||||||
return (!this.apps.find(_app => _app.id === app.id));
|
return (!this.apps.find(_app => _app.id === app.id))
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
useAppStoreView() {
|
useAppStoreView() {
|
||||||
return !this.useListView && !this.useBundleView;
|
return !this.useListView && !this.useBundleView
|
||||||
},
|
},
|
||||||
useListView() {
|
useListView() {
|
||||||
return (this.category === 'installed' || this.category === 'enabled' || this.category === 'disabled' || this.category === 'updates');
|
return (this.category === 'installed' || this.category === 'enabled' || this.category === 'disabled' || this.category === 'updates')
|
||||||
},
|
},
|
||||||
useBundleView() {
|
useBundleView() {
|
||||||
return (this.category === 'app-bundles');
|
return (this.category === 'app-bundles')
|
||||||
},
|
},
|
||||||
allBundlesEnabled() {
|
allBundlesEnabled() {
|
||||||
let self = this;
|
let self = this
|
||||||
return function(id) {
|
return function(id) {
|
||||||
return self.bundleApps(id).filter(app => !app.active).length === 0;
|
return self.bundleApps(id).filter(app => !app.active).length === 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bundleToggleText() {
|
bundleToggleText() {
|
||||||
let self = this;
|
let self = this
|
||||||
return function(id) {
|
return function(id) {
|
||||||
if (self.allBundlesEnabled(id)) {
|
if (self.allBundlesEnabled(id)) {
|
||||||
return t('settings', 'Disable all');
|
return t('settings', 'Disable all')
|
||||||
}
|
}
|
||||||
return t('settings', 'Enable all');
|
return t('settings', 'Enable all')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleBundle(id) {
|
toggleBundle(id) {
|
||||||
if (this.allBundlesEnabled(id)) {
|
if (this.allBundlesEnabled(id)) {
|
||||||
return this.disableBundle(id);
|
return this.disableBundle(id)
|
||||||
}
|
}
|
||||||
return this.enableBundle(id);
|
return this.enableBundle(id)
|
||||||
},
|
},
|
||||||
enableBundle(id) {
|
enableBundle(id) {
|
||||||
let apps = this.bundleApps(id).map(app => app.id);
|
let apps = this.bundleApps(id).map(app => app.id)
|
||||||
this.$store.dispatch('enableApp', { appId: apps, groups: [] })
|
this.$store.dispatch('enableApp', { appId: apps, groups: [] })
|
||||||
.catch((error) => { console.log(error); OC.Notification.show(error)});
|
.catch((error) => { console.error(error); OC.Notification.show(error) })
|
||||||
},
|
},
|
||||||
disableBundle(id) {
|
disableBundle(id) {
|
||||||
let apps = this.bundleApps(id).map(app => app.id);
|
let apps = this.bundleApps(id).map(app => app.id)
|
||||||
this.$store.dispatch('disableApp', { appId: apps, groups: [] })
|
this.$store.dispatch('disableApp', { appId: apps, groups: [] })
|
||||||
.catch((error) => { OC.Notification.show(error)});
|
.catch((error) => { OC.Notification.show(error) })
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -21,18 +21,18 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img :src="scoreImage" class="app-score-image" />
|
<img :src="scoreImage" class="app-score-image">
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'appScore',
|
name: 'AppScore',
|
||||||
props: ['score'],
|
props: ['score'],
|
||||||
computed: {
|
computed: {
|
||||||
scoreImage() {
|
scoreImage() {
|
||||||
let score = Math.round( this.score * 10 );
|
let score = Math.round(this.score * 10)
|
||||||
let imageName = 'rating/s' + score + '.svg';
|
let imageName = 'rating/s' + score + '.svg'
|
||||||
return OC.imagePath('core', imageName);
|
return OC.imagePath('core', imageName)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
</script>
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -21,12 +21,12 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'prefixMixin',
|
name: 'PrefixMixin',
|
||||||
methods: {
|
methods: {
|
||||||
prefix (prefix, content) {
|
prefix(prefix, content) {
|
||||||
return prefix + '_' + content;
|
return prefix + '_' + content
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -21,20 +21,20 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'svgFilterMixin',
|
name: 'SvgFilterMixin',
|
||||||
mounted() {
|
data() {
|
||||||
this.filterId = 'invertIconApps' + Math.floor((Math.random() * 100 )) + new Date().getSeconds() + new Date().getMilliseconds();
|
return {
|
||||||
},
|
filterId: ''
|
||||||
computed: {
|
}
|
||||||
filterUrl () {
|
},
|
||||||
return `url(#${this.filterId})`;
|
computed: {
|
||||||
},
|
filterUrl() {
|
||||||
},
|
return `url(#${this.filterId})`
|
||||||
data() {
|
}
|
||||||
return {
|
},
|
||||||
filterId: '',
|
mounted() {
|
||||||
};
|
this.filterId = 'invertIconApps' + Math.floor((Math.random() * 100)) + new Date().getSeconds() + new Date().getMilliseconds()
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,706 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2018 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/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- Obfuscated user: Logged in user does not have permissions to see all of the data -->
|
||||||
|
<div v-if="Object.keys(user).length ===1" class="row" :data-id="user.id">
|
||||||
|
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
|
||||||
|
<img v-if="!loading.delete && !loading.disable && !loading.wipe"
|
||||||
|
alt=""
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
:src="generateAvatar(user.id, 32)"
|
||||||
|
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'">
|
||||||
|
</div>
|
||||||
|
<div class="name">
|
||||||
|
{{ user.id }}
|
||||||
|
</div>
|
||||||
|
<div class="obfuscated">
|
||||||
|
{{ t('settings','You do not have permissions to see the details of this user') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User full data -->
|
||||||
|
<div v-else
|
||||||
|
class="row"
|
||||||
|
:class="{'disabled': loading.delete || loading.disable}"
|
||||||
|
:data-id="user.id">
|
||||||
|
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
|
||||||
|
<img v-if="!loading.delete && !loading.disable && !loading.wipe"
|
||||||
|
alt=""
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
:src="generateAvatar(user.id, 32)"
|
||||||
|
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'">
|
||||||
|
</div>
|
||||||
|
<!-- dirty hack to ellipsis on two lines -->
|
||||||
|
<div class="name">
|
||||||
|
{{ user.id }}
|
||||||
|
</div>
|
||||||
|
<form class="displayName" :class="{'icon-loading-small': loading.displayName}" @submit.prevent="updateDisplayName">
|
||||||
|
<template v-if="user.backendCapabilities.setDisplayName">
|
||||||
|
<input v-if="user.backendCapabilities.setDisplayName"
|
||||||
|
:id="'displayName'+user.id+rand"
|
||||||
|
ref="displayName"
|
||||||
|
type="text"
|
||||||
|
:disabled="loading.displayName||loading.all"
|
||||||
|
:value="user.displayname"
|
||||||
|
autocomplete="new-password"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
spellcheck="false">
|
||||||
|
<input v-if="user.backendCapabilities.setDisplayName"
|
||||||
|
type="submit"
|
||||||
|
class="icon-confirm"
|
||||||
|
value="">
|
||||||
|
</template>
|
||||||
|
<div v-else v-tooltip.auto="t('settings', 'The backend does not support changing the display name')" class="name">
|
||||||
|
{{ user.displayname }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form v-if="settings.canChangePassword && user.backendCapabilities.setPassword"
|
||||||
|
class="password"
|
||||||
|
:class="{'icon-loading-small': loading.password}"
|
||||||
|
@submit.prevent="updatePassword">
|
||||||
|
<input :id="'password'+user.id+rand"
|
||||||
|
ref="password"
|
||||||
|
type="password"
|
||||||
|
required
|
||||||
|
:disabled="loading.password||loading.all"
|
||||||
|
:minlength="minPasswordLength"
|
||||||
|
value=""
|
||||||
|
:placeholder="t('settings', 'New password')"
|
||||||
|
autocomplete="new-password"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
spellcheck="false">
|
||||||
|
<input type="submit" class="icon-confirm" value="">
|
||||||
|
</form>
|
||||||
|
<div v-else />
|
||||||
|
<form class="mailAddress" :class="{'icon-loading-small': loading.mailAddress}" @submit.prevent="updateEmail">
|
||||||
|
<input :id="'mailAddress'+user.id+rand"
|
||||||
|
ref="mailAddress"
|
||||||
|
type="email"
|
||||||
|
:disabled="loading.mailAddress||loading.all"
|
||||||
|
:value="user.email"
|
||||||
|
autocomplete="new-password"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
spellcheck="false">
|
||||||
|
<input type="submit" class="icon-confirm" value="">
|
||||||
|
</form>
|
||||||
|
<div class="groups" :class="{'icon-loading-small': loading.groups}">
|
||||||
|
<Multiselect :value="userGroups"
|
||||||
|
:options="availableGroups"
|
||||||
|
:disabled="loading.groups||loading.all"
|
||||||
|
tag-placeholder="create"
|
||||||
|
:placeholder="t('settings', 'Add user in group')"
|
||||||
|
label="name"
|
||||||
|
track-by="id"
|
||||||
|
class="multiselect-vue"
|
||||||
|
:limit="2"
|
||||||
|
:multiple="true"
|
||||||
|
:taggable="settings.isAdmin"
|
||||||
|
:close-on-select="false"
|
||||||
|
:tag-width="60"
|
||||||
|
@tag="createGroup"
|
||||||
|
@select="addUserGroup"
|
||||||
|
@remove="removeUserGroup">
|
||||||
|
<span slot="limit" v-tooltip.auto="formatGroupsTitle(userGroups)" class="multiselect__limit">+{{ userGroups.length-2 }}</span>
|
||||||
|
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||||
|
</Multiselect>
|
||||||
|
</div>
|
||||||
|
<div v-if="subAdminsGroups.length>0 && settings.isAdmin" class="subadmins" :class="{'icon-loading-small': loading.subadmins}">
|
||||||
|
<Multiselect :value="userSubAdminsGroups"
|
||||||
|
:options="subAdminsGroups"
|
||||||
|
:disabled="loading.subadmins||loading.all"
|
||||||
|
:placeholder="t('settings', 'Set user as admin for')"
|
||||||
|
label="name"
|
||||||
|
track-by="id"
|
||||||
|
class="multiselect-vue"
|
||||||
|
:limit="2"
|
||||||
|
:multiple="true"
|
||||||
|
:close-on-select="false"
|
||||||
|
:tag-width="60"
|
||||||
|
@select="addUserSubAdmin"
|
||||||
|
@remove="removeUserSubAdmin">
|
||||||
|
<span slot="limit" v-tooltip.auto="formatGroupsTitle(userSubAdminsGroups)" class="multiselect__limit">+{{ userSubAdminsGroups.length-2 }}</span>
|
||||||
|
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||||
|
</Multiselect>
|
||||||
|
</div>
|
||||||
|
<div v-tooltip.auto="usedSpace" class="quota" :class="{'icon-loading-small': loading.quota}">
|
||||||
|
<Multiselect :value="userQuota"
|
||||||
|
:options="quotaOptions"
|
||||||
|
:disabled="loading.quota||loading.all"
|
||||||
|
tag-placeholder="create"
|
||||||
|
:placeholder="t('settings', 'Select user quota')"
|
||||||
|
label="label"
|
||||||
|
track-by="id"
|
||||||
|
class="multiselect-vue"
|
||||||
|
:allow-empty="false"
|
||||||
|
:taggable="true"
|
||||||
|
@tag="validateQuota"
|
||||||
|
@input="setUserQuota" />
|
||||||
|
<progress class="quota-user-progress"
|
||||||
|
:class="{'warn':usedQuota>80}"
|
||||||
|
:value="usedQuota"
|
||||||
|
max="100" />
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showLanguages"
|
||||||
|
class="languages"
|
||||||
|
:class="{'icon-loading-small': loading.languages}">
|
||||||
|
<Multiselect :value="userLanguage"
|
||||||
|
:options="languages"
|
||||||
|
:disabled="loading.languages||loading.all"
|
||||||
|
:placeholder="t('settings', 'No language set')"
|
||||||
|
label="name"
|
||||||
|
track-by="code"
|
||||||
|
class="multiselect-vue"
|
||||||
|
:allow-empty="false"
|
||||||
|
group-values="languages"
|
||||||
|
group-label="label"
|
||||||
|
@input="setUserLanguage" />
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showStoragePath" class="storageLocation">
|
||||||
|
{{ user.storageLocation }}
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showUserBackend" class="userBackend">
|
||||||
|
{{ user.backend }}
|
||||||
|
</div>
|
||||||
|
<div v-if="showConfig.showLastLogin" v-tooltip.auto="user.lastLogin>0 ? OC.Util.formatDate(user.lastLogin) : ''" class="lastLogin">
|
||||||
|
{{ user.lastLogin>0 ? OC.Util.relativeModifiedDate(user.lastLogin) : t('settings','Never') }}
|
||||||
|
</div>
|
||||||
|
<div class="userActions">
|
||||||
|
<div v-if="OC.currentUser !== user.id && user.id !== 'admin' && !loading.all" class="toggleUserActions">
|
||||||
|
<div v-click-outside="hideMenu" class="icon-more" @click="toggleMenu" />
|
||||||
|
<div class="popovermenu" :class="{ 'open': openedMenu }">
|
||||||
|
<PopoverMenu :menu="userActions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="feedback" :style="{opacity: feedbackMessage !== '' ? 1 : 0}">
|
||||||
|
<div class="icon-checkmark" />
|
||||||
|
{{ feedbackMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ClickOutside from 'vue-click-outside'
|
||||||
|
import Vue from 'vue'
|
||||||
|
import VTooltip from 'v-tooltip'
|
||||||
|
import { PopoverMenu, Multiselect } from 'nextcloud-vue'
|
||||||
|
|
||||||
|
Vue.use(VTooltip)
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'UserRow',
|
||||||
|
components: {
|
||||||
|
PopoverMenu,
|
||||||
|
Multiselect
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
ClickOutside
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
user: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
groups: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
subAdminsGroups: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
quotaOptions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
showConfig: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
languages: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
externalActions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
rand: parseInt(Math.random() * 1000),
|
||||||
|
openedMenu: false,
|
||||||
|
feedbackMessage: '',
|
||||||
|
loading: {
|
||||||
|
all: false,
|
||||||
|
displayName: false,
|
||||||
|
password: false,
|
||||||
|
mailAddress: false,
|
||||||
|
groups: false,
|
||||||
|
subadmins: false,
|
||||||
|
quota: false,
|
||||||
|
delete: false,
|
||||||
|
disable: false,
|
||||||
|
languages: false,
|
||||||
|
wipe: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/* USER POPOVERMENU ACTIONS */
|
||||||
|
userActions() {
|
||||||
|
let actions = [
|
||||||
|
{
|
||||||
|
icon: 'icon-delete',
|
||||||
|
text: t('settings', 'Delete user'),
|
||||||
|
action: this.deleteUser
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'icon-delete',
|
||||||
|
text: t('settings', 'Wipe all devices'),
|
||||||
|
action: this.wipeUserDevices
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: this.user.enabled ? 'icon-close' : 'icon-add',
|
||||||
|
text: this.user.enabled ? t('settings', 'Disable user') : t('settings', 'Enable user'),
|
||||||
|
action: this.enableDisableUser
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if (this.user.email !== null && this.user.email !== '') {
|
||||||
|
actions.push({
|
||||||
|
icon: 'icon-mail',
|
||||||
|
text: t('settings', 'Resend welcome email'),
|
||||||
|
action: this.sendWelcomeMail
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return actions.concat(this.externalActions)
|
||||||
|
},
|
||||||
|
|
||||||
|
/* GROUPS MANAGEMENT */
|
||||||
|
userGroups() {
|
||||||
|
let userGroups = this.groups.filter(group => this.user.groups.includes(group.id))
|
||||||
|
return userGroups
|
||||||
|
},
|
||||||
|
userSubAdminsGroups() {
|
||||||
|
let userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id))
|
||||||
|
return userSubAdminsGroups
|
||||||
|
},
|
||||||
|
availableGroups() {
|
||||||
|
return this.groups.map((group) => {
|
||||||
|
// clone object because we don't want
|
||||||
|
// to edit the original groups
|
||||||
|
let groupClone = Object.assign({}, group)
|
||||||
|
|
||||||
|
// two settings here:
|
||||||
|
// 1. user NOT in group but no permission to add
|
||||||
|
// 2. user is in group but no permission to remove
|
||||||
|
groupClone.$isDisabled
|
||||||
|
= (group.canAdd === false
|
||||||
|
&& !this.user.groups.includes(group.id))
|
||||||
|
|| (group.canRemove === false
|
||||||
|
&& this.user.groups.includes(group.id))
|
||||||
|
return groupClone
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/* QUOTA MANAGEMENT */
|
||||||
|
usedSpace() {
|
||||||
|
if (this.user.quota.used) {
|
||||||
|
return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota.used) })
|
||||||
|
}
|
||||||
|
return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) })
|
||||||
|
},
|
||||||
|
usedQuota() {
|
||||||
|
let quota = this.user.quota.quota
|
||||||
|
if (quota > 0) {
|
||||||
|
quota = Math.min(100, Math.round(this.user.quota.used / quota * 100))
|
||||||
|
} else {
|
||||||
|
var usedInGB = this.user.quota.used / (10 * Math.pow(2, 30))
|
||||||
|
// asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
|
||||||
|
quota = 95 * (1 - (1 / (usedInGB + 1)))
|
||||||
|
}
|
||||||
|
return isNaN(quota) ? 0 : quota
|
||||||
|
},
|
||||||
|
// Mapping saved values to objects
|
||||||
|
userQuota() {
|
||||||
|
if (this.user.quota.quota >= 0) {
|
||||||
|
// if value is valid, let's map the quotaOptions or return custom quota
|
||||||
|
let humanQuota = OC.Util.humanFileSize(this.user.quota.quota)
|
||||||
|
let userQuota = this.quotaOptions.find(quota => quota.id === humanQuota)
|
||||||
|
return userQuota || { id: humanQuota, label: humanQuota }
|
||||||
|
} else if (this.user.quota.quota === 'default') {
|
||||||
|
// default quota is replaced by the proper value on load
|
||||||
|
return this.quotaOptions[0]
|
||||||
|
}
|
||||||
|
return this.quotaOptions[1] // unlimited
|
||||||
|
},
|
||||||
|
|
||||||
|
/* PASSWORD POLICY? */
|
||||||
|
minPasswordLength() {
|
||||||
|
return this.$store.getters.getPasswordPolicyMinLength
|
||||||
|
},
|
||||||
|
|
||||||
|
/* LANGUAGE */
|
||||||
|
userLanguage() {
|
||||||
|
let availableLanguages = this.languages[0].languages.concat(this.languages[1].languages)
|
||||||
|
let userLang = availableLanguages.find(lang => lang.code === this.user.language)
|
||||||
|
if (typeof userLang !== 'object' && this.user.language !== '') {
|
||||||
|
return {
|
||||||
|
code: this.user.language,
|
||||||
|
name: this.user.language
|
||||||
|
}
|
||||||
|
} else if (this.user.language === '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return userLang
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// required if popup needs to stay opened after menu click
|
||||||
|
// since we only have disable/delete actions, let's close it directly
|
||||||
|
// this.popupItem = this.$el;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/* MENU HANDLING */
|
||||||
|
toggleMenu() {
|
||||||
|
this.openedMenu = !this.openedMenu
|
||||||
|
},
|
||||||
|
hideMenu() {
|
||||||
|
this.openedMenu = false
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate avatar url
|
||||||
|
*
|
||||||
|
* @param {string} user The user name
|
||||||
|
* @param {int} size Size integer, default 32
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
generateAvatar(user, size = 32) {
|
||||||
|
return OC.generateUrl(
|
||||||
|
'/avatar/{user}/{size}?v={version}',
|
||||||
|
{
|
||||||
|
user: user,
|
||||||
|
size: size,
|
||||||
|
version: oc_userconfig.avatar.version
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format array of groups objects to a string for the popup
|
||||||
|
*
|
||||||
|
* @param {array} groups The groups
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
formatGroupsTitle(groups) {
|
||||||
|
let names = groups.map(group => group.name)
|
||||||
|
return names.slice(2).join(', ')
|
||||||
|
},
|
||||||
|
|
||||||
|
wipeUserDevices() {
|
||||||
|
this.loading.wipe = true
|
||||||
|
this.loading.all = true
|
||||||
|
let userid = this.user.id
|
||||||
|
return this.$store.dispatch('wipeUserDevices', userid)
|
||||||
|
.then(() => {
|
||||||
|
this.loading.wipe = false
|
||||||
|
this.loading.all = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteUser() {
|
||||||
|
this.loading.delete = true
|
||||||
|
this.loading.all = true
|
||||||
|
let userid = this.user.id
|
||||||
|
return this.$store.dispatch('deleteUser', userid)
|
||||||
|
.then(() => {
|
||||||
|
this.loading.delete = false
|
||||||
|
this.loading.all = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
enableDisableUser() {
|
||||||
|
this.loading.delete = true
|
||||||
|
this.loading.all = true
|
||||||
|
let userid = this.user.id
|
||||||
|
let enabled = !this.user.enabled
|
||||||
|
return this.$store.dispatch('enableDisableUser', { userid, enabled })
|
||||||
|
.then(() => {
|
||||||
|
this.loading.delete = false
|
||||||
|
this.loading.all = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user displayName
|
||||||
|
*
|
||||||
|
* @param {string} displayName The display name
|
||||||
|
*/
|
||||||
|
updateDisplayName() {
|
||||||
|
let displayName = this.$refs.displayName.value
|
||||||
|
this.loading.displayName = true
|
||||||
|
this.$store.dispatch('setUserData', {
|
||||||
|
userid: this.user.id,
|
||||||
|
key: 'displayname',
|
||||||
|
value: displayName
|
||||||
|
}).then(() => {
|
||||||
|
this.loading.displayName = false
|
||||||
|
this.$refs.displayName.value = displayName
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user password
|
||||||
|
*
|
||||||
|
* @param {string} password The email adress
|
||||||
|
*/
|
||||||
|
updatePassword() {
|
||||||
|
let password = this.$refs.password.value
|
||||||
|
this.loading.password = true
|
||||||
|
this.$store.dispatch('setUserData', {
|
||||||
|
userid: this.user.id,
|
||||||
|
key: 'password',
|
||||||
|
value: password
|
||||||
|
}).then(() => {
|
||||||
|
this.loading.password = false
|
||||||
|
this.$refs.password.value = '' // empty & show placeholder
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user mailAddress
|
||||||
|
*
|
||||||
|
* @param {string} mailAddress The email adress
|
||||||
|
*/
|
||||||
|
updateEmail() {
|
||||||
|
let mailAddress = this.$refs.mailAddress.value
|
||||||
|
this.loading.mailAddress = true
|
||||||
|
this.$store.dispatch('setUserData', {
|
||||||
|
userid: this.user.id,
|
||||||
|
key: 'email',
|
||||||
|
value: mailAddress
|
||||||
|
}).then(() => {
|
||||||
|
this.loading.mailAddress = false
|
||||||
|
this.$refs.mailAddress.value = mailAddress
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new group and add user to it
|
||||||
|
*
|
||||||
|
* @param {string} gid Group id
|
||||||
|
*/
|
||||||
|
async createGroup(gid) {
|
||||||
|
this.loading = { groups: true, subadmins: true }
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('addGroup', gid)
|
||||||
|
let userid = this.user.id
|
||||||
|
await this.$store.dispatch('addUserGroup', { userid, gid })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
this.loading = { groups: false, subadmins: false }
|
||||||
|
}
|
||||||
|
return this.$store.getters.getGroups[this.groups.length]
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add user to group
|
||||||
|
*
|
||||||
|
* @param {object} group Group object
|
||||||
|
*/
|
||||||
|
async addUserGroup(group) {
|
||||||
|
if (group.canAdd === false) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
this.loading.groups = true
|
||||||
|
let userid = this.user.id
|
||||||
|
let gid = group.id
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('addUserGroup', { userid, gid })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
this.loading.groups = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove user from group
|
||||||
|
*
|
||||||
|
* @param {object} group Group object
|
||||||
|
*/
|
||||||
|
async removeUserGroup(group) {
|
||||||
|
if (group.canRemove === false) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading.groups = true
|
||||||
|
let userid = this.user.id
|
||||||
|
let gid = group.id
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('removeUserGroup', { userid, gid })
|
||||||
|
this.loading.groups = false
|
||||||
|
// remove user from current list if current list is the removed group
|
||||||
|
if (this.$route.params.selectedGroup === gid) {
|
||||||
|
this.$store.commit('deleteUser', userid)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.loading.groups = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add user to group
|
||||||
|
*
|
||||||
|
* @param {object} group Group object
|
||||||
|
*/
|
||||||
|
async addUserSubAdmin(group) {
|
||||||
|
this.loading.subadmins = true
|
||||||
|
let userid = this.user.id
|
||||||
|
let gid = group.id
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('addUserSubAdmin', { userid, gid })
|
||||||
|
this.loading.subadmins = false
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove user from group
|
||||||
|
*
|
||||||
|
* @param {object} group Group object
|
||||||
|
*/
|
||||||
|
async removeUserSubAdmin(group) {
|
||||||
|
this.loading.subadmins = true
|
||||||
|
let userid = this.user.id
|
||||||
|
let gid = group.id
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('removeUserSubAdmin', { userid, gid })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
this.loading.subadmins = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch quota set request
|
||||||
|
*
|
||||||
|
* @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'}
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
async setUserQuota(quota = 'none') {
|
||||||
|
this.loading.quota = true
|
||||||
|
// ensure we only send the preset id
|
||||||
|
quota = quota.id ? quota.id : quota
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('setUserData', {
|
||||||
|
userid: this.user.id,
|
||||||
|
key: 'quota',
|
||||||
|
value: quota
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
this.loading.quota = false
|
||||||
|
}
|
||||||
|
return quota
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate quota string to make sure it's a valid human file size
|
||||||
|
*
|
||||||
|
* @param {string} quota Quota in readable format '5 GB'
|
||||||
|
* @returns {Promise|boolean}
|
||||||
|
*/
|
||||||
|
validateQuota(quota) {
|
||||||
|
// only used for new presets sent through @Tag
|
||||||
|
let validQuota = OC.Util.computerFileSize(quota)
|
||||||
|
if (validQuota !== null && validQuota >= 0) {
|
||||||
|
// unify format output
|
||||||
|
return this.setUserQuota(OC.Util.humanFileSize(OC.Util.computerFileSize(quota)))
|
||||||
|
}
|
||||||
|
// if no valid do not change
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch language set request
|
||||||
|
*
|
||||||
|
* @param {Object} lang language object {code:'en', name:'English'}
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
async setUserLanguage(lang) {
|
||||||
|
this.loading.languages = true
|
||||||
|
// ensure we only send the preset id
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('setUserData', {
|
||||||
|
userid: this.user.id,
|
||||||
|
key: 'language',
|
||||||
|
value: lang.code
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
} finally {
|
||||||
|
this.loading.languages = false
|
||||||
|
}
|
||||||
|
return lang
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch new welcome mail request
|
||||||
|
*/
|
||||||
|
sendWelcomeMail() {
|
||||||
|
this.loading.all = true
|
||||||
|
this.$store.dispatch('sendWelcomeMail', this.user.id)
|
||||||
|
.then(success => {
|
||||||
|
if (success) {
|
||||||
|
// Show feedback to indicate the success
|
||||||
|
this.feedbackMessage = t('setting', 'Welcome mail sent!')
|
||||||
|
setTimeout(() => {
|
||||||
|
this.feedbackMessage = ''
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
this.loading.all = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,574 +0,0 @@
|
||||||
<!--
|
|
||||||
- @copyright Copyright (c) 2018 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/>.
|
|
||||||
-
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- Obfuscated user: Logged in user does not have permissions to see all of the data -->
|
|
||||||
<div class="row" v-if="Object.keys(user).length ===1" :data-id="user.id">
|
|
||||||
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
|
|
||||||
<img alt="" width="32" height="32" :src="generateAvatar(user.id, 32)"
|
|
||||||
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
|
|
||||||
v-if="!loading.delete && !loading.disable && !loading.wipe">
|
|
||||||
</div>
|
|
||||||
<div class="name">{{user.id}}</div>
|
|
||||||
<div class="obfuscated">{{t('settings','You do not have permissions to see the details of this user')}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- User full data -->
|
|
||||||
<div class="row" v-else :class="{'disabled': loading.delete || loading.disable}" :data-id="user.id">
|
|
||||||
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
|
|
||||||
<img alt="" width="32" height="32" :src="generateAvatar(user.id, 32)"
|
|
||||||
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
|
|
||||||
v-if="!loading.delete && !loading.disable && !loading.wipe">
|
|
||||||
</div>
|
|
||||||
<!-- dirty hack to ellipsis on two lines -->
|
|
||||||
<div class="name">{{user.id}}</div>
|
|
||||||
<form class="displayName" :class="{'icon-loading-small': loading.displayName}" v-on:submit.prevent="updateDisplayName">
|
|
||||||
<template v-if="user.backendCapabilities.setDisplayName">
|
|
||||||
<input v-if="user.backendCapabilities.setDisplayName"
|
|
||||||
:id="'displayName'+user.id+rand" type="text"
|
|
||||||
:disabled="loading.displayName||loading.all"
|
|
||||||
:value="user.displayname" ref="displayName"
|
|
||||||
autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
|
|
||||||
<input v-if="user.backendCapabilities.setDisplayName" type="submit" class="icon-confirm" value="" />
|
|
||||||
</template>
|
|
||||||
<div v-else class="name" v-tooltip.auto="t('settings', 'The backend does not support changing the display name')">{{user.displayname}}</div>
|
|
||||||
</form>
|
|
||||||
<form class="password" v-if="settings.canChangePassword && user.backendCapabilities.setPassword" :class="{'icon-loading-small': loading.password}"
|
|
||||||
v-on:submit.prevent="updatePassword">
|
|
||||||
<input :id="'password'+user.id+rand" type="password" required
|
|
||||||
:disabled="loading.password||loading.all" :minlength="minPasswordLength"
|
|
||||||
value="" :placeholder="t('settings', 'New password')" ref="password"
|
|
||||||
autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
|
|
||||||
<input type="submit" class="icon-confirm" value="" />
|
|
||||||
</form>
|
|
||||||
<div v-else></div>
|
|
||||||
<form class="mailAddress" :class="{'icon-loading-small': loading.mailAddress}" v-on:submit.prevent="updateEmail">
|
|
||||||
<input :id="'mailAddress'+user.id+rand" type="email"
|
|
||||||
:disabled="loading.mailAddress||loading.all"
|
|
||||||
:value="user.email" ref="mailAddress"
|
|
||||||
autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
|
|
||||||
<input type="submit" class="icon-confirm" value="" />
|
|
||||||
</form>
|
|
||||||
<div class="groups" :class="{'icon-loading-small': loading.groups}">
|
|
||||||
<multiselect :value="userGroups" :options="availableGroups" :disabled="loading.groups||loading.all"
|
|
||||||
tag-placeholder="create" :placeholder="t('settings', 'Add user in group')"
|
|
||||||
label="name" track-by="id" class="multiselect-vue" :limit="2"
|
|
||||||
:multiple="true" :taggable="settings.isAdmin" :closeOnSelect="false"
|
|
||||||
:tag-width="60"
|
|
||||||
@tag="createGroup" @select="addUserGroup" @remove="removeUserGroup">
|
|
||||||
<span slot="limit" class="multiselect__limit" v-tooltip.auto="formatGroupsTitle(userGroups)">+{{userGroups.length-2}}</span>
|
|
||||||
<span slot="noResult">{{t('settings', 'No results')}}</span>
|
|
||||||
</multiselect>
|
|
||||||
</div>
|
|
||||||
<div class="subadmins" v-if="subAdminsGroups.length>0 && settings.isAdmin" :class="{'icon-loading-small': loading.subadmins}">
|
|
||||||
<multiselect :value="userSubAdminsGroups" :options="subAdminsGroups" :disabled="loading.subadmins||loading.all"
|
|
||||||
:placeholder="t('settings', 'Set user as admin for')"
|
|
||||||
label="name" track-by="id" class="multiselect-vue" :limit="2"
|
|
||||||
:multiple="true" :closeOnSelect="false" :tag-width="60"
|
|
||||||
@select="addUserSubAdmin" @remove="removeUserSubAdmin">
|
|
||||||
<span slot="limit" class="multiselect__limit" v-tooltip.auto="formatGroupsTitle(userSubAdminsGroups)">+{{userSubAdminsGroups.length-2}}</span>
|
|
||||||
<span slot="noResult">{{t('settings', 'No results')}}</span>
|
|
||||||
</multiselect>
|
|
||||||
</div>
|
|
||||||
<div class="quota" :class="{'icon-loading-small': loading.quota}" v-tooltip.auto="usedSpace">
|
|
||||||
<multiselect :value="userQuota" :options="quotaOptions" :disabled="loading.quota||loading.all"
|
|
||||||
tag-placeholder="create" :placeholder="t('settings', 'Select user quota')"
|
|
||||||
label="label" track-by="id" class="multiselect-vue"
|
|
||||||
:allowEmpty="false" :taggable="true"
|
|
||||||
@tag="validateQuota" @input="setUserQuota">
|
|
||||||
</multiselect>
|
|
||||||
<progress class="quota-user-progress" :class="{'warn':usedQuota>80}" :value="usedQuota" max="100"></progress>
|
|
||||||
</div>
|
|
||||||
<div class="languages" :class="{'icon-loading-small': loading.languages}"
|
|
||||||
v-if="showConfig.showLanguages">
|
|
||||||
<multiselect :value="userLanguage" :options="languages" :disabled="loading.languages||loading.all"
|
|
||||||
:placeholder="t('settings', 'No language set')"
|
|
||||||
label="name" track-by="code" class="multiselect-vue"
|
|
||||||
:allowEmpty="false" group-values="languages" group-label="label"
|
|
||||||
@input="setUserLanguage">
|
|
||||||
</multiselect>
|
|
||||||
</div>
|
|
||||||
<div class="storageLocation" v-if="showConfig.showStoragePath">{{user.storageLocation}}</div>
|
|
||||||
<div class="userBackend" v-if="showConfig.showUserBackend">{{user.backend}}</div>
|
|
||||||
<div class="lastLogin" v-if="showConfig.showLastLogin" v-tooltip.auto="user.lastLogin>0 ? OC.Util.formatDate(user.lastLogin) : ''">
|
|
||||||
{{user.lastLogin>0 ? OC.Util.relativeModifiedDate(user.lastLogin) : t('settings','Never')}}
|
|
||||||
</div>
|
|
||||||
<div class="userActions">
|
|
||||||
<div class="toggleUserActions" v-if="OC.currentUser !== user.id && user.id !== 'admin' && !loading.all">
|
|
||||||
<div class="icon-more" v-click-outside="hideMenu" @click="toggleMenu"></div>
|
|
||||||
<div class="popovermenu" :class="{ 'open': openedMenu }">
|
|
||||||
<popover-menu :menu="userActions" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="feedback" :style="{opacity: feedbackMessage !== '' ? 1 : 0}">
|
|
||||||
<div class="icon-checkmark"></div>
|
|
||||||
{{feedbackMessage}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ClickOutside from 'vue-click-outside';
|
|
||||||
import Vue from 'vue'
|
|
||||||
import VTooltip from 'v-tooltip'
|
|
||||||
import { PopoverMenu, Multiselect } from 'nextcloud-vue'
|
|
||||||
|
|
||||||
Vue.use(VTooltip)
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'userRow',
|
|
||||||
props: ['user', 'settings', 'groups', 'subAdminsGroups', 'quotaOptions', 'showConfig', 'languages', 'externalActions'],
|
|
||||||
components: {
|
|
||||||
PopoverMenu,
|
|
||||||
Multiselect
|
|
||||||
},
|
|
||||||
directives: {
|
|
||||||
ClickOutside
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// required if popup needs to stay opened after menu click
|
|
||||||
// since we only have disable/delete actions, let's close it directly
|
|
||||||
// this.popupItem = this.$el;
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
rand: parseInt(Math.random() * 1000),
|
|
||||||
openedMenu: false,
|
|
||||||
feedbackMessage: '',
|
|
||||||
loading: {
|
|
||||||
all: false,
|
|
||||||
displayName: false,
|
|
||||||
password: false,
|
|
||||||
mailAddress: false,
|
|
||||||
groups: false,
|
|
||||||
subadmins: false,
|
|
||||||
quota: false,
|
|
||||||
delete: false,
|
|
||||||
disable: false,
|
|
||||||
languages: false,
|
|
||||||
wipe: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
/* USER POPOVERMENU ACTIONS */
|
|
||||||
userActions() {
|
|
||||||
let actions = [
|
|
||||||
{
|
|
||||||
icon: 'icon-delete',
|
|
||||||
text: t('settings', 'Delete user'),
|
|
||||||
action: this.deleteUser,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'icon-delete',
|
|
||||||
text: t('settings', 'Wipe all devices'),
|
|
||||||
action: this.wipeUserDevices,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: this.user.enabled ? 'icon-close' : 'icon-add',
|
|
||||||
text: this.user.enabled ? t('settings', 'Disable user') : t('settings', 'Enable user'),
|
|
||||||
action: this.enableDisableUser,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
if (this.user.email !== null && this.user.email !== '') {
|
|
||||||
actions.push({
|
|
||||||
icon: 'icon-mail',
|
|
||||||
text: t('settings','Resend welcome email'),
|
|
||||||
action: this.sendWelcomeMail
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return actions.concat(this.externalActions);
|
|
||||||
},
|
|
||||||
|
|
||||||
/* GROUPS MANAGEMENT */
|
|
||||||
userGroups() {
|
|
||||||
let userGroups = this.groups.filter(group => this.user.groups.includes(group.id));
|
|
||||||
return userGroups;
|
|
||||||
},
|
|
||||||
userSubAdminsGroups() {
|
|
||||||
let userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id));
|
|
||||||
return userSubAdminsGroups;
|
|
||||||
},
|
|
||||||
availableGroups() {
|
|
||||||
return this.groups.map((group) => {
|
|
||||||
// clone object because we don't want
|
|
||||||
// to edit the original groups
|
|
||||||
let groupClone = Object.assign({}, group);
|
|
||||||
|
|
||||||
// two settings here:
|
|
||||||
// 1. user NOT in group but no permission to add
|
|
||||||
// 2. user is in group but no permission to remove
|
|
||||||
groupClone.$isDisabled =
|
|
||||||
(group.canAdd === false &&
|
|
||||||
!this.user.groups.includes(group.id)) ||
|
|
||||||
(group.canRemove === false &&
|
|
||||||
this.user.groups.includes(group.id));
|
|
||||||
return groupClone;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/* QUOTA MANAGEMENT */
|
|
||||||
usedSpace() {
|
|
||||||
if (this.user.quota.used) {
|
|
||||||
return t('settings', '{size} used', {size: OC.Util.humanFileSize(this.user.quota.used)});
|
|
||||||
}
|
|
||||||
return t('settings', '{size} used', {size: OC.Util.humanFileSize(0)});
|
|
||||||
},
|
|
||||||
usedQuota() {
|
|
||||||
let quota = this.user.quota.quota;
|
|
||||||
if (quota > 0) {
|
|
||||||
quota = Math.min(100, Math.round(this.user.quota.used / quota * 100));
|
|
||||||
} else {
|
|
||||||
var usedInGB = this.user.quota.used / (10 * Math.pow(2, 30));
|
|
||||||
//asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
|
|
||||||
quota = 95 * (1 - (1 / (usedInGB + 1)));
|
|
||||||
}
|
|
||||||
return isNaN(quota) ? 0 : quota;
|
|
||||||
},
|
|
||||||
// Mapping saved values to objects
|
|
||||||
userQuota() {
|
|
||||||
if (this.user.quota.quota >= 0) {
|
|
||||||
// if value is valid, let's map the quotaOptions or return custom quota
|
|
||||||
let humanQuota = OC.Util.humanFileSize(this.user.quota.quota);
|
|
||||||
let userQuota = this.quotaOptions.find(quota => quota.id === humanQuota);
|
|
||||||
return userQuota ? userQuota : {id:humanQuota, label:humanQuota};
|
|
||||||
} else if (this.user.quota.quota === 'default') {
|
|
||||||
// default quota is replaced by the proper value on load
|
|
||||||
return this.quotaOptions[0];
|
|
||||||
}
|
|
||||||
return this.quotaOptions[1]; // unlimited
|
|
||||||
},
|
|
||||||
|
|
||||||
/* PASSWORD POLICY? */
|
|
||||||
minPasswordLength() {
|
|
||||||
return this.$store.getters.getPasswordPolicyMinLength;
|
|
||||||
},
|
|
||||||
|
|
||||||
/* LANGUAGE */
|
|
||||||
userLanguage() {
|
|
||||||
let availableLanguages = this.languages[0].languages.concat(this.languages[1].languages);
|
|
||||||
let userLang = availableLanguages.find(lang => lang.code === this.user.language);
|
|
||||||
if (typeof userLang !== 'object' && this.user.language !== '') {
|
|
||||||
return {
|
|
||||||
code: this.user.language,
|
|
||||||
name: this.user.language
|
|
||||||
}
|
|
||||||
} else if(this.user.language === '') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return userLang;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/* MENU HANDLING */
|
|
||||||
toggleMenu() {
|
|
||||||
this.openedMenu = !this.openedMenu;
|
|
||||||
},
|
|
||||||
hideMenu() {
|
|
||||||
this.openedMenu = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate avatar url
|
|
||||||
*
|
|
||||||
* @param {string} user The user name
|
|
||||||
* @param {int} size Size integer, default 32
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
generateAvatar(user, size=32) {
|
|
||||||
return OC.generateUrl(
|
|
||||||
'/avatar/{user}/{size}?v={version}',
|
|
||||||
{
|
|
||||||
user: user,
|
|
||||||
size: size,
|
|
||||||
version: oc_userconfig.avatar.version
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format array of groups objects to a string for the popup
|
|
||||||
*
|
|
||||||
* @param {array} groups The groups
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
formatGroupsTitle(groups) {
|
|
||||||
let names = groups.map(group => group.name);
|
|
||||||
return names.slice(2,).join(', ');
|
|
||||||
},
|
|
||||||
|
|
||||||
wipeUserDevices() {
|
|
||||||
this.loading.wipe = true;
|
|
||||||
this.loading.all = true;
|
|
||||||
let userid = this.user.id;
|
|
||||||
return this.$store.dispatch('wipeUserDevices', userid)
|
|
||||||
.then(() => {
|
|
||||||
this.loading.wipe = false
|
|
||||||
this.loading.all = false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteUser() {
|
|
||||||
this.loading.delete = true;
|
|
||||||
this.loading.all = true;
|
|
||||||
let userid = this.user.id;
|
|
||||||
return this.$store.dispatch('deleteUser', userid)
|
|
||||||
.then(() => {
|
|
||||||
this.loading.delete = false
|
|
||||||
this.loading.all = false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
enableDisableUser() {
|
|
||||||
this.loading.delete = true;
|
|
||||||
this.loading.all = true;
|
|
||||||
let userid = this.user.id;
|
|
||||||
let enabled = !this.user.enabled;
|
|
||||||
return this.$store.dispatch('enableDisableUser', {userid, enabled})
|
|
||||||
.then(() => {
|
|
||||||
this.loading.delete = false
|
|
||||||
this.loading.all = false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set user displayName
|
|
||||||
*
|
|
||||||
* @param {string} displayName The display name
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
updateDisplayName() {
|
|
||||||
let displayName = this.$refs.displayName.value;
|
|
||||||
this.loading.displayName = true;
|
|
||||||
this.$store.dispatch('setUserData', {
|
|
||||||
userid: this.user.id,
|
|
||||||
key: 'displayname',
|
|
||||||
value: displayName
|
|
||||||
}).then(() => {
|
|
||||||
this.loading.displayName = false;
|
|
||||||
this.$refs.displayName.value = displayName;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set user password
|
|
||||||
*
|
|
||||||
* @param {string} password The email adress
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
updatePassword() {
|
|
||||||
let password = this.$refs.password.value;
|
|
||||||
this.loading.password = true;
|
|
||||||
this.$store.dispatch('setUserData', {
|
|
||||||
userid: this.user.id,
|
|
||||||
key: 'password',
|
|
||||||
value: password
|
|
||||||
}).then(() => {
|
|
||||||
this.loading.password = false;
|
|
||||||
this.$refs.password.value = ''; // empty & show placeholder
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set user mailAddress
|
|
||||||
*
|
|
||||||
* @param {string} mailAddress The email adress
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
updateEmail() {
|
|
||||||
let mailAddress = this.$refs.mailAddress.value;
|
|
||||||
this.loading.mailAddress = true;
|
|
||||||
this.$store.dispatch('setUserData', {
|
|
||||||
userid: this.user.id,
|
|
||||||
key: 'email',
|
|
||||||
value: mailAddress
|
|
||||||
}).then(() => {
|
|
||||||
this.loading.mailAddress = false;
|
|
||||||
this.$refs.mailAddress.value = mailAddress;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new group and add user to it
|
|
||||||
*
|
|
||||||
* @param {string} groups Group id
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
createGroup(gid) {
|
|
||||||
this.loading = {groups:true, subadmins:true}
|
|
||||||
this.$store.dispatch('addGroup', gid)
|
|
||||||
.then(() => {
|
|
||||||
this.loading = {groups:false, subadmins:false};
|
|
||||||
let userid = this.user.id;
|
|
||||||
this.$store.dispatch('addUserGroup', {userid, gid});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.loading = {groups:false, subadmins:false};
|
|
||||||
});
|
|
||||||
return this.$store.getters.getGroups[this.groups.length];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add user to group
|
|
||||||
*
|
|
||||||
* @param {object} group Group object
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
addUserGroup(group) {
|
|
||||||
if (group.canAdd === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.loading.groups = true;
|
|
||||||
let userid = this.user.id;
|
|
||||||
let gid = group.id;
|
|
||||||
return this.$store.dispatch('addUserGroup', {userid, gid})
|
|
||||||
.then(() => this.loading.groups = false);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove user from group
|
|
||||||
*
|
|
||||||
* @param {object} group Group object
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
removeUserGroup(group) {
|
|
||||||
if (group.canRemove === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.loading.groups = true;
|
|
||||||
let userid = this.user.id;
|
|
||||||
let gid = group.id;
|
|
||||||
return this.$store.dispatch('removeUserGroup', {userid, gid})
|
|
||||||
.then(() => {
|
|
||||||
this.loading.groups = false
|
|
||||||
// remove user from current list if current list is the removed group
|
|
||||||
if (this.$route.params.selectedGroup === gid) {
|
|
||||||
this.$store.commit('deleteUser', userid);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.loading.groups = false
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add user to group
|
|
||||||
*
|
|
||||||
* @param {object} group Group object
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
addUserSubAdmin(group) {
|
|
||||||
this.loading.subadmins = true;
|
|
||||||
let userid = this.user.id;
|
|
||||||
let gid = group.id;
|
|
||||||
return this.$store.dispatch('addUserSubAdmin', {userid, gid})
|
|
||||||
.then(() => this.loading.subadmins = false);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove user from group
|
|
||||||
*
|
|
||||||
* @param {object} group Group object
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
removeUserSubAdmin(group) {
|
|
||||||
this.loading.subadmins = true;
|
|
||||||
let userid = this.user.id;
|
|
||||||
let gid = group.id;
|
|
||||||
return this.$store.dispatch('removeUserSubAdmin', {userid, gid})
|
|
||||||
.then(() => this.loading.subadmins = false);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch quota set request
|
|
||||||
*
|
|
||||||
* @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'}
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
setUserQuota(quota = 'none') {
|
|
||||||
this.loading.quota = true;
|
|
||||||
// ensure we only send the preset id
|
|
||||||
quota = quota.id ? quota.id : quota;
|
|
||||||
this.$store.dispatch('setUserData', {
|
|
||||||
userid: this.user.id,
|
|
||||||
key: 'quota',
|
|
||||||
value: quota
|
|
||||||
}).then(() => this.loading.quota = false);
|
|
||||||
return quota;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate quota string to make sure it's a valid human file size
|
|
||||||
*
|
|
||||||
* @param {string} quota Quota in readable format '5 GB'
|
|
||||||
* @returns {Promise|boolean}
|
|
||||||
*/
|
|
||||||
validateQuota(quota) {
|
|
||||||
// only used for new presets sent through @Tag
|
|
||||||
let validQuota = OC.Util.computerFileSize(quota);
|
|
||||||
if (validQuota !== null && validQuota >= 0) {
|
|
||||||
// unify format output
|
|
||||||
return this.setUserQuota(OC.Util.humanFileSize(OC.Util.computerFileSize(quota)));
|
|
||||||
}
|
|
||||||
// if no valid do not change
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch language set request
|
|
||||||
*
|
|
||||||
* @param {Object} lang language object {code:'en', name:'English'}
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
setUserLanguage(lang) {
|
|
||||||
this.loading.languages = true;
|
|
||||||
// ensure we only send the preset id
|
|
||||||
this.$store.dispatch('setUserData', {
|
|
||||||
userid: this.user.id,
|
|
||||||
key: 'language',
|
|
||||||
value: lang.code
|
|
||||||
}).then(() => this.loading.languages = false);
|
|
||||||
return lang;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch new welcome mail request
|
|
||||||
*/
|
|
||||||
sendWelcomeMail() {
|
|
||||||
this.loading.all = true;
|
|
||||||
this.$store.dispatch('sendWelcomeMail', this.user.id)
|
|
||||||
.then(success => {
|
|
||||||
if (success) {
|
|
||||||
// Show feedback to indicate the success
|
|
||||||
this.feedbackMessage = t('setting', 'Welcome mail sent!');
|
|
||||||
setTimeout(() => {
|
|
||||||
this.feedbackMessage = '';
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
this.loading.all = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -3,13 +3,14 @@ import Vue from 'vue'
|
||||||
import AdminTwoFactor from './components/AdminTwoFactor.vue'
|
import AdminTwoFactor from './components/AdminTwoFactor.vue'
|
||||||
import store from './store/admin-security'
|
import store from './store/admin-security'
|
||||||
|
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
__webpack_nonce__ = btoa(OC.requestToken)
|
__webpack_nonce__ = btoa(OC.requestToken)
|
||||||
|
|
||||||
Vue.prototype.t = t;
|
Vue.prototype.t = t
|
||||||
|
|
||||||
// Not used here but required for legacy templates
|
// Not used here but required for legacy templates
|
||||||
window.OC = window.OC || {};
|
window.OC = window.OC || {}
|
||||||
window.OC.Settings = window.OC.Settings || {};
|
window.OC.Settings = window.OC.Settings || {}
|
||||||
|
|
||||||
store.replaceState(
|
store.replaceState(
|
||||||
OCP.InitialState.loadState('settings', 'mandatory2FAState')
|
OCP.InitialState.loadState('settings', 'mandatory2FAState')
|
||||||
|
|
|
@ -20,17 +20,17 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue'
|
||||||
import VTooltip from 'v-tooltip';
|
import VTooltip from 'v-tooltip'
|
||||||
import { sync } from 'vuex-router-sync';
|
import { sync } from 'vuex-router-sync'
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from './App.vue'
|
||||||
import router from './router';
|
import router from './router'
|
||||||
import store from './store';
|
import store from './store'
|
||||||
|
|
||||||
Vue.use(VTooltip, { defaultHtml: false });
|
Vue.use(VTooltip, { defaultHtml: false })
|
||||||
|
|
||||||
sync(store, router);
|
sync(store, router)
|
||||||
|
|
||||||
// CSP config for webpack dynamic chunk loading
|
// CSP config for webpack dynamic chunk loading
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
@ -43,15 +43,16 @@ __webpack_nonce__ = btoa(OC.requestToken)
|
||||||
__webpack_public_path__ = OC.linkTo('settings', 'js/')
|
__webpack_public_path__ = OC.linkTo('settings', 'js/')
|
||||||
|
|
||||||
// bind to window
|
// bind to window
|
||||||
Vue.prototype.t = t;
|
Vue.prototype.t = t
|
||||||
Vue.prototype.OC = OC;
|
Vue.prototype.OC = OC
|
||||||
Vue.prototype.OCA = OCA;
|
Vue.prototype.OCA = OCA
|
||||||
Vue.prototype.oc_userconfig = oc_userconfig;
|
// eslint-disable-next-line camelcase
|
||||||
|
Vue.prototype.oc_userconfig = oc_userconfig
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
render: h => h(App)
|
render: h => h(App)
|
||||||
}).$mount('#content');
|
}).$mount('#content')
|
||||||
|
|
||||||
export { app, router, store };
|
export { app, router, store }
|
||||||
|
|
|
@ -19,22 +19,23 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue'
|
||||||
import VueClipboard from 'vue-clipboard2';
|
import VueClipboard from 'vue-clipboard2'
|
||||||
import VTooltip from 'v-tooltip';
|
import VTooltip from 'v-tooltip'
|
||||||
|
|
||||||
import AuthTokenSection from './components/AuthTokenSection';
|
import AuthTokenSection from './components/AuthTokenSection'
|
||||||
|
|
||||||
__webpack_nonce__ = btoa(OC.requestToken);
|
// eslint-disable-next-line camelcase
|
||||||
|
__webpack_nonce__ = btoa(OC.requestToken)
|
||||||
|
|
||||||
Vue.use(VueClipboard);
|
Vue.use(VueClipboard)
|
||||||
Vue.use(VTooltip, { defaultHtml: false });
|
Vue.use(VTooltip, { defaultHtml: false })
|
||||||
Vue.prototype.t = t;
|
Vue.prototype.t = t
|
||||||
|
|
||||||
const View = Vue.extend(AuthTokenSection);
|
const View = Vue.extend(AuthTokenSection)
|
||||||
new View({
|
new View({
|
||||||
propsData: {
|
propsData: {
|
||||||
tokens: OCP.InitialState.loadState('settings', 'app_tokens'),
|
tokens: OCP.InitialState.loadState('settings', 'app_tokens'),
|
||||||
canCreateToken: OCP.InitialState.loadState('settings', 'can_create_app_token'),
|
canCreateToken: OCP.InitialState.loadState('settings', 'can_create_app_token')
|
||||||
}
|
}
|
||||||
}).$mount('#security-authtokens');
|
}).$mount('#security-authtokens')
|
||||||
|
|
|
@ -21,14 +21,14 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue'
|
||||||
import Router from 'vue-router';
|
import Router from 'vue-router'
|
||||||
|
|
||||||
// Dynamic loading
|
// Dynamic loading
|
||||||
const Users = () => import('./views/Users');
|
const Users = () => import('./views/Users')
|
||||||
const Apps = () => import('./views/Apps');
|
const Apps = () => import('./views/Apps')
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is the list of routes where the vuejs app will
|
* This is the list of routes where the vuejs app will
|
||||||
|
@ -80,4 +80,4 @@ export default new Router({
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright 2019 Roeland Jago Douma <roeland@famdouma.nl>
|
* @copyright 2019 Roeland Jago Douma <roeland@famdouma.nl>
|
||||||
*
|
*
|
||||||
* @author 2019 Roeland Jago Douma <roeland@famdouma.nl>
|
* @author 2019 Roeland Jago Douma <roeland@famdouma.nl>
|
||||||
|
@ -24,7 +24,13 @@ import Vuex from 'vuex'
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export const mutations = {
|
const state = {
|
||||||
|
enforced: false,
|
||||||
|
enforcedGroups: [],
|
||||||
|
excludedGroups: []
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
setEnforced(state, enabled) {
|
setEnforced(state, enabled) {
|
||||||
Vue.set(state, 'enforced', enabled)
|
Vue.set(state, 'enforced', enabled)
|
||||||
},
|
},
|
||||||
|
@ -36,28 +42,8 @@ export const mutations = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
|
||||||
save ({commit}, ) {
|
|
||||||
commit('setEnabled', false);
|
|
||||||
|
|
||||||
return generateCodes()
|
|
||||||
.then(({codes, state}) => {
|
|
||||||
commit('setEnabled', state.enabled);
|
|
||||||
commit('setTotal', state.total);
|
|
||||||
commit('setUsed', state.used);
|
|
||||||
commit('setCodes', codes);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
strict: process.env.NODE_ENV !== 'production',
|
strict: process.env.NODE_ENV !== 'production',
|
||||||
state: {
|
state,
|
||||||
enforced: false,
|
mutations
|
||||||
enforcedGroups: [],
|
|
||||||
excludedGroups: [],
|
|
||||||
},
|
|
||||||
mutations,
|
|
||||||
actions
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,11 +21,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import axios from 'nextcloud-axios'
|
import axios from 'nextcloud-axios'
|
||||||
import confirmPassword from 'nextcloud-password-confirmation'
|
import confirmPassword from 'nextcloud-password-confirmation'
|
||||||
|
|
||||||
const sanitize = function(url) {
|
const sanitize = function(url) {
|
||||||
return url.replace(/\/$/, ''); // Remove last url slash
|
return url.replace(/\/$/, '') // Remove last url slash
|
||||||
};
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
||||||
|
@ -47,35 +47,35 @@ export default {
|
||||||
*
|
*
|
||||||
* Since Promise.then().catch().then() will always execute the last then
|
* Since Promise.then().catch().then() will always execute the last then
|
||||||
* this.$store.dispatch('action').then will always be executed
|
* this.$store.dispatch('action').then will always be executed
|
||||||
*
|
*
|
||||||
* If you want requireAdmin failure to also catch the API request failure
|
* If you want requireAdmin failure to also catch the API request failure
|
||||||
* you will need to throw a new error in the api.get.catch()
|
* you will need to throw a new error in the api.get.catch()
|
||||||
*
|
*
|
||||||
* e.g
|
* e.g
|
||||||
* api.requireAdmin().then((response) => {
|
* api.requireAdmin().then((response) => {
|
||||||
* api.get('url')
|
* api.get('url')
|
||||||
* .then((response) => {API success})
|
* .then((response) => {API success})
|
||||||
* .catch((error) => {throw error;});
|
* .catch((error) => {throw error;});
|
||||||
* }).catch((error) => {requireAdmin OR API failure});
|
* }).catch((error) => {requireAdmin OR API failure});
|
||||||
*
|
*
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
requireAdmin() {
|
requireAdmin() {
|
||||||
return confirmPassword();
|
return confirmPassword()
|
||||||
},
|
},
|
||||||
get(url) {
|
get(url) {
|
||||||
return axios.get(sanitize(url));
|
return axios.get(sanitize(url))
|
||||||
},
|
},
|
||||||
post(url, data) {
|
post(url, data) {
|
||||||
return axios.post(sanitize(url), data);
|
return axios.post(sanitize(url), data)
|
||||||
},
|
},
|
||||||
patch(url, data) {
|
patch(url, data) {
|
||||||
return axios.patch(sanitize(url), data);
|
return axios.patch(sanitize(url), data)
|
||||||
},
|
},
|
||||||
put(url, data) {
|
put(url, data) {
|
||||||
return axios.put(sanitize(url), data);
|
return axios.put(sanitize(url), data)
|
||||||
},
|
},
|
||||||
delete(url, data) {
|
delete(url, data) {
|
||||||
return axios.delete(sanitize(url), { data: data });
|
return axios.delete(sanitize(url), { data: data })
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
|
@ -20,158 +20,158 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import api from './api';
|
import api from './api'
|
||||||
import Vue from 'vue';
|
import Vue from 'vue'
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
apps: [],
|
apps: [],
|
||||||
categories: [],
|
categories: [],
|
||||||
updateCount: 0,
|
updateCount: 0,
|
||||||
loading: {},
|
loading: {},
|
||||||
loadingList: false,
|
loadingList: false
|
||||||
};
|
}
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
|
|
||||||
APPS_API_FAILURE(state, error) {
|
APPS_API_FAILURE(state, error) {
|
||||||
OC.Notification.showHtml(t('settings','An error occured during the request. Unable to proceed.')+'<br>'+error.error.response.data.data.message, {timeout: 7});
|
OC.Notification.showHtml(t('settings', 'An error occured during the request. Unable to proceed.') + '<br>' + error.error.response.data.data.message, { timeout: 7 })
|
||||||
console.log(state, error);
|
console.error(state, error)
|
||||||
},
|
},
|
||||||
|
|
||||||
initCategories(state, {categories, updateCount}) {
|
initCategories(state, { categories, updateCount }) {
|
||||||
state.categories = categories;
|
state.categories = categories
|
||||||
state.updateCount = updateCount;
|
state.updateCount = updateCount
|
||||||
},
|
},
|
||||||
|
|
||||||
setUpdateCount(state, updateCount) {
|
setUpdateCount(state, updateCount) {
|
||||||
state.updateCount = updateCount;
|
state.updateCount = updateCount
|
||||||
},
|
},
|
||||||
|
|
||||||
addCategory(state, category) {
|
addCategory(state, category) {
|
||||||
state.categories.push(category);
|
state.categories.push(category)
|
||||||
},
|
},
|
||||||
|
|
||||||
appendCategories(state, categoriesArray) {
|
appendCategories(state, categoriesArray) {
|
||||||
// convert obj to array
|
// convert obj to array
|
||||||
state.categories = categoriesArray;
|
state.categories = categoriesArray
|
||||||
},
|
},
|
||||||
|
|
||||||
setAllApps(state, apps) {
|
setAllApps(state, apps) {
|
||||||
state.apps = apps;
|
state.apps = apps
|
||||||
},
|
},
|
||||||
|
|
||||||
setError(state, {appId, error}) {
|
setError(state, { appId, error }) {
|
||||||
if (!Array.isArray(appId)) {
|
if (!Array.isArray(appId)) {
|
||||||
appId = [appId];
|
appId = [appId]
|
||||||
}
|
}
|
||||||
appId.forEach((_id) => {
|
appId.forEach((_id) => {
|
||||||
let app = state.apps.find(app => app.id === _id);
|
let app = state.apps.find(app => app.id === _id)
|
||||||
app.error = error;
|
app.error = error
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
clearError(state, {appId, error}) {
|
clearError(state, { appId, error }) {
|
||||||
let app = state.apps.find(app => app.id === appId);
|
let app = state.apps.find(app => app.id === appId)
|
||||||
app.error = null;
|
app.error = null
|
||||||
},
|
},
|
||||||
|
|
||||||
enableApp(state, {appId, groups}) {
|
enableApp(state, { appId, groups }) {
|
||||||
let app = state.apps.find(app => app.id === appId);
|
let app = state.apps.find(app => app.id === appId)
|
||||||
app.active = true;
|
app.active = true
|
||||||
app.groups = groups;
|
app.groups = groups
|
||||||
},
|
},
|
||||||
|
|
||||||
disableApp(state, appId) {
|
disableApp(state, appId) {
|
||||||
let app = state.apps.find(app => app.id === appId);
|
let app = state.apps.find(app => app.id === appId)
|
||||||
app.active = false;
|
app.active = false
|
||||||
app.groups = [];
|
app.groups = []
|
||||||
if (app.removable) {
|
if (app.removable) {
|
||||||
app.canUnInstall = true;
|
app.canUnInstall = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
uninstallApp(state, appId) {
|
uninstallApp(state, appId) {
|
||||||
state.apps.find(app => app.id === appId).active = false;
|
state.apps.find(app => app.id === appId).active = false
|
||||||
state.apps.find(app => app.id === appId).groups = [];
|
state.apps.find(app => app.id === appId).groups = []
|
||||||
state.apps.find(app => app.id === appId).needsDownload = true;
|
state.apps.find(app => app.id === appId).needsDownload = true
|
||||||
state.apps.find(app => app.id === appId).installed = false;
|
state.apps.find(app => app.id === appId).installed = false
|
||||||
state.apps.find(app => app.id === appId).canUnInstall = false;
|
state.apps.find(app => app.id === appId).canUnInstall = false
|
||||||
state.apps.find(app => app.id === appId).canInstall = true;
|
state.apps.find(app => app.id === appId).canInstall = true
|
||||||
},
|
},
|
||||||
|
|
||||||
updateApp(state, appId) {
|
updateApp(state, appId) {
|
||||||
let app = state.apps.find(app => app.id === appId);
|
let app = state.apps.find(app => app.id === appId)
|
||||||
let version = app.update;
|
let version = app.update
|
||||||
app.update = null;
|
app.update = null
|
||||||
app.version = version;
|
app.version = version
|
||||||
state.updateCount--;
|
state.updateCount--
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
resetApps(state) {
|
resetApps(state) {
|
||||||
state.apps = [];
|
state.apps = []
|
||||||
},
|
},
|
||||||
reset(state) {
|
reset(state) {
|
||||||
state.apps = [];
|
state.apps = []
|
||||||
state.categories = [];
|
state.categories = []
|
||||||
state.updateCount = 0;
|
state.updateCount = 0
|
||||||
},
|
},
|
||||||
startLoading(state, id) {
|
startLoading(state, id) {
|
||||||
if (Array.isArray(id)) {
|
if (Array.isArray(id)) {
|
||||||
id.forEach((_id) => {
|
id.forEach((_id) => {
|
||||||
Vue.set(state.loading, _id, true);
|
Vue.set(state.loading, _id, true)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Vue.set(state.loading, id, true);
|
Vue.set(state.loading, id, true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
stopLoading(state, id) {
|
stopLoading(state, id) {
|
||||||
if (Array.isArray(id)) {
|
if (Array.isArray(id)) {
|
||||||
id.forEach((_id) => {
|
id.forEach((_id) => {
|
||||||
Vue.set(state.loading, _id, false);
|
Vue.set(state.loading, _id, false)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Vue.set(state.loading, id, false);
|
Vue.set(state.loading, id, false)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const getters = {
|
const getters = {
|
||||||
loading(state) {
|
loading(state) {
|
||||||
return function(id) {
|
return function(id) {
|
||||||
return state.loading[id];
|
return state.loading[id]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getCategories(state) {
|
getCategories(state) {
|
||||||
return state.categories;
|
return state.categories
|
||||||
},
|
},
|
||||||
getAllApps(state) {
|
getAllApps(state) {
|
||||||
return state.apps;
|
return state.apps
|
||||||
},
|
},
|
||||||
getUpdateCount(state) {
|
getUpdateCount(state) {
|
||||||
return state.updateCount;
|
return state.updateCount
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
|
|
||||||
enableApp(context, { appId, groups }) {
|
enableApp(context, { appId, groups }) {
|
||||||
let apps;
|
let apps
|
||||||
if (Array.isArray(appId)) {
|
if (Array.isArray(appId)) {
|
||||||
apps = appId;
|
apps = appId
|
||||||
} else {
|
} else {
|
||||||
apps = [appId];
|
apps = [appId]
|
||||||
}
|
}
|
||||||
return api.requireAdmin().then((response) => {
|
return api.requireAdmin().then((response) => {
|
||||||
context.commit('startLoading', apps);
|
context.commit('startLoading', apps)
|
||||||
context.commit('startLoading', 'install');
|
context.commit('startLoading', 'install')
|
||||||
return api.post(OC.generateUrl(`settings/apps/enable`), {appIds: apps, groups: groups})
|
return api.post(OC.generateUrl(`settings/apps/enable`), { appIds: apps, groups: groups })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
context.commit('stopLoading', apps);
|
context.commit('stopLoading', apps)
|
||||||
context.commit('stopLoading', 'install');
|
context.commit('stopLoading', 'install')
|
||||||
apps.forEach(_appId => {
|
apps.forEach(_appId => {
|
||||||
context.commit('enableApp', {appId: _appId, groups: groups});
|
context.commit('enableApp', { appId: _appId, groups: groups })
|
||||||
});
|
})
|
||||||
|
|
||||||
// check for server health
|
// check for server health
|
||||||
return api.get(OC.generateUrl('apps/files'))
|
return api.get(OC.generateUrl('apps/files'))
|
||||||
|
@ -182,146 +182,146 @@ const actions = {
|
||||||
'settings',
|
'settings',
|
||||||
'The app has been enabled but needs to be updated. You will be redirected to the update page in 5 seconds.'
|
'The app has been enabled but needs to be updated. You will be redirected to the update page in 5 seconds.'
|
||||||
),
|
),
|
||||||
t('settings','App update'),
|
t('settings', 'App update'),
|
||||||
function () {
|
function() {
|
||||||
window.location.reload();
|
window.location.reload()
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
)
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
location.reload();
|
location.reload()
|
||||||
}, 5000);
|
}, 5000)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch(() => {
|
||||||
if (!Array.isArray(appId)) {
|
if (!Array.isArray(appId)) {
|
||||||
context.commit('setError', {
|
context.commit('setError', {
|
||||||
appId: apps,
|
appId: apps,
|
||||||
error: t('settings', 'Error: This app can not be enabled because it makes the server unstable')
|
error: t('settings', 'Error: This app can not be enabled because it makes the server unstable')
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
context.commit('stopLoading', apps);
|
context.commit('stopLoading', apps)
|
||||||
context.commit('stopLoading', 'install');
|
context.commit('stopLoading', 'install')
|
||||||
context.commit('setError', {
|
context.commit('setError', {
|
||||||
appId: apps,
|
appId: apps,
|
||||||
error: error.response.data.data.message
|
error: error.response.data.data.message
|
||||||
});
|
})
|
||||||
context.commit('APPS_API_FAILURE', { appId, error});
|
|
||||||
})
|
|
||||||
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
|
|
||||||
},
|
|
||||||
forceEnableApp(context, { appId, groups }) {
|
|
||||||
let apps;
|
|
||||||
if (Array.isArray(appId)) {
|
|
||||||
apps = appId;
|
|
||||||
} else {
|
|
||||||
apps = [appId];
|
|
||||||
}
|
|
||||||
return api.requireAdmin().then(() => {
|
|
||||||
context.commit('startLoading', apps);
|
|
||||||
context.commit('startLoading', 'install');
|
|
||||||
return api.post(OC.generateUrl(`settings/apps/force`), {appId})
|
|
||||||
.then((response) => {
|
|
||||||
// TODO: find a cleaner solution
|
|
||||||
location.reload();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
context.commit('stopLoading', apps);
|
|
||||||
context.commit('stopLoading', 'install');
|
|
||||||
context.commit('setError', {
|
|
||||||
appId: apps,
|
|
||||||
error: error.response.data.data.message
|
|
||||||
});
|
|
||||||
context.commit('APPS_API_FAILURE', { appId, error});
|
|
||||||
})
|
|
||||||
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
|
|
||||||
},
|
|
||||||
disableApp(context, { appId }) {
|
|
||||||
let apps;
|
|
||||||
if (Array.isArray(appId)) {
|
|
||||||
apps = appId;
|
|
||||||
} else {
|
|
||||||
apps = [appId];
|
|
||||||
}
|
|
||||||
return api.requireAdmin().then((response) => {
|
|
||||||
context.commit('startLoading', apps);
|
|
||||||
return api.post(OC.generateUrl(`settings/apps/disable`), {appIds: apps})
|
|
||||||
.then((response) => {
|
|
||||||
context.commit('stopLoading', apps);
|
|
||||||
apps.forEach(_appId => {
|
|
||||||
context.commit('disableApp', _appId);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
context.commit('stopLoading', apps);
|
|
||||||
context.commit('APPS_API_FAILURE', { appId, error })
|
context.commit('APPS_API_FAILURE', { appId, error })
|
||||||
})
|
})
|
||||||
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
|
}).catch((error) => context.commit('API_FAILURE', { appId, error }))
|
||||||
|
},
|
||||||
|
forceEnableApp(context, { appId, groups }) {
|
||||||
|
let apps
|
||||||
|
if (Array.isArray(appId)) {
|
||||||
|
apps = appId
|
||||||
|
} else {
|
||||||
|
apps = [appId]
|
||||||
|
}
|
||||||
|
return api.requireAdmin().then(() => {
|
||||||
|
context.commit('startLoading', apps)
|
||||||
|
context.commit('startLoading', 'install')
|
||||||
|
return api.post(OC.generateUrl(`settings/apps/force`), { appId })
|
||||||
|
.then((response) => {
|
||||||
|
// TODO: find a cleaner solution
|
||||||
|
location.reload()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
context.commit('stopLoading', apps)
|
||||||
|
context.commit('stopLoading', 'install')
|
||||||
|
context.commit('setError', {
|
||||||
|
appId: apps,
|
||||||
|
error: error.response.data.data.message
|
||||||
|
})
|
||||||
|
context.commit('APPS_API_FAILURE', { appId, error })
|
||||||
|
})
|
||||||
|
}).catch((error) => context.commit('API_FAILURE', { appId, error }))
|
||||||
|
},
|
||||||
|
disableApp(context, { appId }) {
|
||||||
|
let apps
|
||||||
|
if (Array.isArray(appId)) {
|
||||||
|
apps = appId
|
||||||
|
} else {
|
||||||
|
apps = [appId]
|
||||||
|
}
|
||||||
|
return api.requireAdmin().then((response) => {
|
||||||
|
context.commit('startLoading', apps)
|
||||||
|
return api.post(OC.generateUrl(`settings/apps/disable`), { appIds: apps })
|
||||||
|
.then((response) => {
|
||||||
|
context.commit('stopLoading', apps)
|
||||||
|
apps.forEach(_appId => {
|
||||||
|
context.commit('disableApp', _appId)
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
context.commit('stopLoading', apps)
|
||||||
|
context.commit('APPS_API_FAILURE', { appId, error })
|
||||||
|
})
|
||||||
|
}).catch((error) => context.commit('API_FAILURE', { appId, error }))
|
||||||
},
|
},
|
||||||
uninstallApp(context, { appId }) {
|
uninstallApp(context, { appId }) {
|
||||||
return api.requireAdmin().then((response) => {
|
return api.requireAdmin().then((response) => {
|
||||||
context.commit('startLoading', appId);
|
context.commit('startLoading', appId)
|
||||||
return api.get(OC.generateUrl(`settings/apps/uninstall/${appId}`))
|
return api.get(OC.generateUrl(`settings/apps/uninstall/${appId}`))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
context.commit('stopLoading', appId);
|
context.commit('stopLoading', appId)
|
||||||
context.commit('uninstallApp', appId);
|
context.commit('uninstallApp', appId)
|
||||||
return true;
|
return true
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
context.commit('stopLoading', appId);
|
context.commit('stopLoading', appId)
|
||||||
context.commit('APPS_API_FAILURE', { appId, error })
|
context.commit('APPS_API_FAILURE', { appId, error })
|
||||||
})
|
})
|
||||||
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
|
}).catch((error) => context.commit('API_FAILURE', { appId, error }))
|
||||||
},
|
},
|
||||||
|
|
||||||
updateApp(context, { appId }) {
|
updateApp(context, { appId }) {
|
||||||
return api.requireAdmin().then((response) => {
|
return api.requireAdmin().then((response) => {
|
||||||
context.commit('startLoading', appId);
|
context.commit('startLoading', appId)
|
||||||
context.commit('startLoading', 'install');
|
context.commit('startLoading', 'install')
|
||||||
return api.get(OC.generateUrl(`settings/apps/update/${appId}`))
|
return api.get(OC.generateUrl(`settings/apps/update/${appId}`))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
context.commit('stopLoading', 'install');
|
context.commit('stopLoading', 'install')
|
||||||
context.commit('stopLoading', appId);
|
context.commit('stopLoading', appId)
|
||||||
context.commit('updateApp', appId);
|
context.commit('updateApp', appId)
|
||||||
return true;
|
return true
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
context.commit('stopLoading', appId);
|
context.commit('stopLoading', appId)
|
||||||
context.commit('stopLoading', 'install');
|
context.commit('stopLoading', 'install')
|
||||||
context.commit('APPS_API_FAILURE', { appId, error })
|
context.commit('APPS_API_FAILURE', { appId, error })
|
||||||
})
|
})
|
||||||
}).catch((error) => context.commit('API_FAILURE', { appId, error }));
|
}).catch((error) => context.commit('API_FAILURE', { appId, error }))
|
||||||
},
|
},
|
||||||
|
|
||||||
getAllApps(context) {
|
getAllApps(context) {
|
||||||
context.commit('startLoading', 'list');
|
context.commit('startLoading', 'list')
|
||||||
return api.get(OC.generateUrl(`settings/apps/list`))
|
return api.get(OC.generateUrl(`settings/apps/list`))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
context.commit('setAllApps', response.data.apps);
|
context.commit('setAllApps', response.data.apps)
|
||||||
context.commit('stopLoading', 'list');
|
context.commit('stopLoading', 'list')
|
||||||
return true;
|
return true
|
||||||
})
|
})
|
||||||
.catch((error) => context.commit('API_FAILURE', error))
|
.catch((error) => context.commit('API_FAILURE', error))
|
||||||
},
|
},
|
||||||
|
|
||||||
getCategories(context) {
|
getCategories(context) {
|
||||||
context.commit('startLoading', 'categories');
|
context.commit('startLoading', 'categories')
|
||||||
return api.get(OC.generateUrl('settings/apps/categories'))
|
return api.get(OC.generateUrl('settings/apps/categories'))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.data.length > 0) {
|
if (response.data.length > 0) {
|
||||||
context.commit('appendCategories', response.data);
|
context.commit('appendCategories', response.data)
|
||||||
context.commit('stopLoading', 'categories');
|
context.commit('stopLoading', 'categories')
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
return false;
|
return false
|
||||||
})
|
})
|
||||||
.catch((error) => context.commit('API_FAILURE', error));
|
.catch((error) => context.commit('API_FAILURE', error))
|
||||||
},
|
}
|
||||||
|
|
||||||
};
|
}
|
||||||
|
|
||||||
export default { state, mutations, getters, actions };
|
export default { state, mutations, getters, actions }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
|
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
*
|
*
|
||||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
@ -21,28 +21,28 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex';
|
import Vuex from 'vuex'
|
||||||
import users from './users';
|
import users from './users'
|
||||||
import apps from './apps';
|
import apps from './apps'
|
||||||
import settings from './settings';
|
import settings from './settings'
|
||||||
import oc from './oc';
|
import oc from './oc'
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
const debug = process.env.NODE_ENV !== 'production';
|
const debug = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
API_FAILURE(state, error) {
|
API_FAILURE(state, error) {
|
||||||
try {
|
try {
|
||||||
let message = error.error.response.data.ocs.meta.message;
|
let message = error.error.response.data.ocs.meta.message
|
||||||
OC.Notification.showHtml(t('settings','An error occured during the request. Unable to proceed.')+'<br>'+message, {timeout: 7});
|
OC.Notification.showHtml(t('settings', 'An error occured during the request. Unable to proceed.') + '<br>' + message, { timeout: 7 })
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
OC.Notification.showTemporary(t('settings','An error occured during the request. Unable to proceed.'));
|
OC.Notification.showTemporary(t('settings', 'An error occured during the request. Unable to proceed.'))
|
||||||
}
|
}
|
||||||
console.log(state, error);
|
console.error(state, error)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
|
@ -54,4 +54,4 @@ export default new Vuex.Store({
|
||||||
strict: debug,
|
strict: debug,
|
||||||
|
|
||||||
mutations
|
mutations
|
||||||
});
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
|
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
*
|
*
|
||||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
@ -20,28 +20,28 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import api from './api';
|
import api from './api'
|
||||||
|
|
||||||
const state = {};
|
const state = {}
|
||||||
const mutations = {};
|
const mutations = {}
|
||||||
const getters = {};
|
const getters = {}
|
||||||
const actions = {
|
const actions = {
|
||||||
/**
|
/**
|
||||||
* Set application config in database
|
* Set application config in database
|
||||||
*
|
*
|
||||||
* @param {Object} context
|
* @param {Object} context store context
|
||||||
* @param {Object} options
|
* @param {Object} options destructuring object
|
||||||
* @param {string} options.app Application name
|
* @param {string} options.app Application name
|
||||||
* @param {boolean} options.key Config key
|
* @param {boolean} options.key Config key
|
||||||
* @param {boolean} options.value Value to set
|
* @param {boolean} options.value Value to set
|
||||||
* @returns{Promise}
|
* @returns{Promise}
|
||||||
*/
|
*/
|
||||||
setAppConfig(context, {app, key, value}) {
|
setAppConfig(context, { app, key, value }) {
|
||||||
return api.requireAdmin().then((response) => {
|
return api.requireAdmin().then((response) => {
|
||||||
return api.post(OC.linkToOCS(`apps/provisioning_api/api/v1/config/apps/${app}/${key}`, 2), {value: value})
|
return api.post(OC.linkToOCS(`apps/provisioning_api/api/v1/config/apps/${app}/${key}`, 2), { value: value })
|
||||||
.catch((error) => {throw error;});
|
.catch((error) => { throw error })
|
||||||
}).catch((error) => context.commit('API_FAILURE', { app, key, value, error }));;
|
}).catch((error) => context.commit('API_FAILURE', { app, key, value, error }))
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export default {state, mutations, getters, actions};
|
export default { state, mutations, getters, actions }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
|
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
*
|
*
|
||||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
@ -20,21 +20,19 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import api from './api';
|
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
serverData: {}
|
serverData: {}
|
||||||
};
|
}
|
||||||
const mutations = {
|
const mutations = {
|
||||||
setServerData(state, data) {
|
setServerData(state, data) {
|
||||||
state.serverData = data;
|
state.serverData = data
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
const getters = {
|
const getters = {
|
||||||
getServerData(state) {
|
getServerData(state) {
|
||||||
return state.serverData;
|
return state.serverData
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
const actions = {};
|
const actions = {}
|
||||||
|
|
||||||
export default {state, mutations, getters, actions};
|
export default { state, mutations, getters, actions }
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue