Merge pull request #946 from nextcloud/feature/collections
Collaboration linking
This commit is contained in:
@@ -5,10 +5,10 @@ clone:
|
|||||||
|
|
||||||
pipeline:
|
pipeline:
|
||||||
check-app-compatbility:
|
check-app-compatbility:
|
||||||
image: nextcloudci/php7.0:php7.0-17
|
image: nextcloudci/php7.1:php7.1-15
|
||||||
environment:
|
environment:
|
||||||
- APP_NAME=deck
|
- APP_NAME=deck
|
||||||
- CORE_BRANCH=stable15
|
- CORE_BRANCH=master
|
||||||
- DB=sqlite
|
- DB=sqlite
|
||||||
commands:
|
commands:
|
||||||
# Pre-setup steps
|
# Pre-setup steps
|
||||||
@@ -43,7 +43,7 @@ pipeline:
|
|||||||
- DB=sqlite
|
- DB=sqlite
|
||||||
commands:
|
commands:
|
||||||
- composer install
|
- composer install
|
||||||
- ./vendor/bin/parallel-lint --exclude ./vendor/ .
|
- ./vendor/bin/parallel-lint --exclude ./vendor/ --exclude ./lib/Collaboration/ .
|
||||||
when:
|
when:
|
||||||
matrix:
|
matrix:
|
||||||
TESTS: syntax-php7.0
|
TESTS: syntax-php7.0
|
||||||
|
|||||||
10
css/collections.css
Normal file
10
css/collections.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.icon-deck {
|
||||||
|
background-image: url('../img/deck-dark.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-type-deck img {
|
||||||
|
opacity: 0.4 !important;
|
||||||
|
}
|
||||||
|
.resource-type-deck:hover img {
|
||||||
|
opacity: 0.7 !important;
|
||||||
|
}
|
||||||
@@ -654,7 +654,7 @@ input.input-inline {
|
|||||||
min-height: 16px;
|
min-height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popovermenu {
|
.popovermenu:not(.action-item__menu) {
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -21,8 +21,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import app from '../app/App.js';
|
import app from '../app/App.js';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import CollaborationView from '../views/CollaborationView';
|
||||||
|
|
||||||
/* global oc_defaults OC OCP OCA */
|
/* global oc_defaults OC OCP OCA */
|
||||||
app.controller('BoardController', function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService, LabelService, $state, $transitions, $filter, FileService) {
|
app.controller('BoardController', function ($rootScope, $scope, $element, $stateParams, StatusService, BoardService, StackService, CardService, LabelService, $state, $transitions, $filter, FileService) {
|
||||||
|
|
||||||
$scope.sidebar = $rootScope.sidebar;
|
$scope.sidebar = $rootScope.sidebar;
|
||||||
|
|
||||||
@@ -148,6 +151,29 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ComponentVM = new Vue({
|
||||||
|
render: h => h(CollaborationView),
|
||||||
|
data: {
|
||||||
|
model: BoardService.getCurrent()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
$scope.mountCollections = function() {
|
||||||
|
const MountingPoint = document.getElementById('collaborationResources');
|
||||||
|
if (MountingPoint) {
|
||||||
|
ComponentVM.model = BoardService.getCurrent();
|
||||||
|
ComponentVM.$mount(MountingPoint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
$scope.$$postDigest($scope.mountCollections);
|
||||||
|
$scope.$watch(function () {
|
||||||
|
return BoardService.getCurrent();
|
||||||
|
}, function() {
|
||||||
|
ComponentVM.model = BoardService.getCurrent();
|
||||||
|
if ($scope.sidebar.show) {
|
||||||
|
$scope.$$postDigest($scope.mountCollections);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$scope.toggleCompactMode = function() {
|
$scope.toggleCompactMode = function() {
|
||||||
$rootScope.compactMode = !$rootScope.compactMode;
|
$rootScope.compactMode = !$rootScope.compactMode;
|
||||||
localStorage.setItem('deck.compactMode', JSON.stringify($rootScope.compactMode));
|
localStorage.setItem('deck.compactMode', JSON.stringify($rootScope.compactMode));
|
||||||
|
|||||||
69
js/init-collections.js
Normal file
69
js/init-collections.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* @copyright Copyright (c) 2019 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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* global __webpack_nonce__ __webpack_public_path__ OC t n */
|
||||||
|
// eslint-disable-next-line
|
||||||
|
__webpack_nonce__ = btoa(OC.requestToken);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
__webpack_public_path__ = OC.linkTo('deck', 'js/build/');
|
||||||
|
|
||||||
|
import Vue from 'vue';
|
||||||
|
import BoardSelector from './views/BoardSelector';
|
||||||
|
|
||||||
|
import './../css/collections.css';
|
||||||
|
|
||||||
|
((function(OCP) {
|
||||||
|
|
||||||
|
Vue.prototype.$ = $
|
||||||
|
Vue.prototype.t = t
|
||||||
|
Vue.prototype.n = n
|
||||||
|
Vue.prototype.OC = OC
|
||||||
|
|
||||||
|
OCP.Collaboration.registerType('deck', {
|
||||||
|
action: () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.id = 'deck-board-select';
|
||||||
|
const body = document.getElementById('body-user');
|
||||||
|
body.append(container);
|
||||||
|
const ComponentVM = new Vue({
|
||||||
|
render: h => h(BoardSelector),
|
||||||
|
});
|
||||||
|
ComponentVM.$mount(container);
|
||||||
|
ComponentVM.$root.$on('close', () => {
|
||||||
|
ComponentVM.$el.remove();
|
||||||
|
ComponentVM.$destroy();
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
ComponentVM.$root.$on('select', (id) => {
|
||||||
|
resolve(id);
|
||||||
|
ComponentVM.$el.remove();
|
||||||
|
ComponentVM.$destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
typeString: t('deck', 'board'),
|
||||||
|
typeIconClass: 'icon-deck'
|
||||||
|
});
|
||||||
|
})(window.OCP));
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/* global __webpack_nonce__ OC */
|
/* global __webpack_nonce__ __webpack_public_path__ OC t n */
|
||||||
__webpack_nonce__ = btoa(OC.requestToken); // eslint-disable-line no-native-reassign
|
// eslint-disable-next-line
|
||||||
|
__webpack_nonce__ = btoa(OC.requestToken);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
__webpack_public_path__ = OC.linkTo('deck', 'js/build/');
|
||||||
|
|
||||||
// used for building a vendor stylesheet
|
// used for building a vendor stylesheet
|
||||||
import 'ng-sortable/dist/ng-sortable.css';
|
import 'ng-sortable/dist/ng-sortable.css';
|
||||||
|
|||||||
722
js/package-lock.json
generated
722
js/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,6 @@
|
|||||||
"description": "Frontend for the Nextcloud Deck app",
|
"description": "Frontend for the Nextcloud Deck app",
|
||||||
"repository": "https://github.com/nextcloud/deck",
|
"repository": "https://github.com/nextcloud/deck",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "Gruntfile.js",
|
|
||||||
"directories": {
|
"directories": {
|
||||||
"test": "tests"
|
"test": "tests"
|
||||||
},
|
},
|
||||||
@@ -17,25 +16,36 @@
|
|||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"markdown-it": "^8.4.2",
|
"markdown-it": "^8.4.2",
|
||||||
"markdown-it-link-target": "^1.0.2",
|
"markdown-it-link-target": "^1.0.2",
|
||||||
|
"nextcloud-axios": "^0.1.3",
|
||||||
|
"nextcloud-vue": "^0.8.0",
|
||||||
|
"nextcloud-vue-collections": "^0.2.2",
|
||||||
"ng-infinite-scroll": "^1.3.0",
|
"ng-infinite-scroll": "^1.3.0",
|
||||||
"ng-sortable": "^1.3.8",
|
"ng-sortable": "^1.3.8",
|
||||||
"ui-select": "^0.19.8"
|
"ui-select": "^0.19.8",
|
||||||
|
"vue": "^2.6.8",
|
||||||
|
"vuex": "^3.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.4.0",
|
"@babel/core": "^7.4.0",
|
||||||
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
"@babel/polyfill": "^7.4.0",
|
"@babel/polyfill": "^7.4.0",
|
||||||
"@babel/preset-env": "^7.4.2",
|
"@babel/preset-env": "^7.4.2",
|
||||||
"babel-loader": "^8.0.5",
|
"babel-loader": "^8.0.5",
|
||||||
"css-loader": "^2.1.1",
|
"css-loader": "^2.1.1",
|
||||||
"karma": "^4.0.1",
|
"karma": "^4.0.1",
|
||||||
"mini-css-extract-plugin": "^0.5.0",
|
"mini-css-extract-plugin": "^0.5.0",
|
||||||
|
"style-loader": "^0.23.1",
|
||||||
"uglifyjs-webpack-plugin": "^2.1.2",
|
"uglifyjs-webpack-plugin": "^2.1.2",
|
||||||
|
"url-loader": "^1.1.2",
|
||||||
|
"vue-loader": "^15.7.0",
|
||||||
|
"vue-style-loader": "^4.1.2",
|
||||||
|
"vue-template-compiler": "^2.6.8",
|
||||||
"webpack": "^4.29.6",
|
"webpack": "^4.29.6",
|
||||||
"webpack-cli": "^3.3.0",
|
"webpack-cli": "^3.3.0",
|
||||||
"webpack-merge": "^4.2.1"
|
"webpack-merge": "^4.2.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "./node_modules/webpack-cli/bin/cli.js --mode production --config webpack.prod.config.js",
|
"build": "NODE_ENV=production ./node_modules/webpack-cli/bin/cli.js --mode production --config webpack.prod.config.js",
|
||||||
"dev": "./node_modules/webpack-cli/bin/cli.js --mode development --config webpack.dev.config.js",
|
"dev": "./node_modules/webpack-cli/bin/cli.js --mode development --config webpack.dev.config.js",
|
||||||
"watch": "./node_modules/webpack-cli/bin/cli.js --mode development --config webpack.dev.config.js --watch",
|
"watch": "./node_modules/webpack-cli/bin/cli.js --mode development --config webpack.dev.config.js --watch",
|
||||||
"test": "echo \"Warning: no test specified\" && exit 0"
|
"test": "echo \"Warning: no test specified\" && exit 0"
|
||||||
|
|||||||
112
js/views/BoardSelector.vue
Normal file
112
js/views/BoardSelector.vue
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2019 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>
|
||||||
|
<Modal @close="close">
|
||||||
|
<div id="modal-inner" :class="{ 'icon-loading': loading }">
|
||||||
|
<h1>Select a board to add to the collection</h1>
|
||||||
|
<ul v-if="!loading">
|
||||||
|
<li v-for="board in boards" @click="selectedBoard=board.id" :class="{'selected': (selectedBoard === board.id) }">
|
||||||
|
<span class="board-bullet" :style="{ 'backgroundColor': '#' + board.color }"></span>
|
||||||
|
<span>{{ board.title }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<button v-if="!loading" @click="select" class="primary">Select board</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
#modal-inner {
|
||||||
|
width: 90vw;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
padding: 6px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
li:hover, li:focus {
|
||||||
|
background-color: var(--color-background-dark);
|
||||||
|
}
|
||||||
|
li.selected {
|
||||||
|
border: 1px solid var(--color-primary);
|
||||||
|
}
|
||||||
|
.board-bullet {
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
li > span,
|
||||||
|
.avatar {
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
/* global OC */
|
||||||
|
import { Modal } from 'nextcloud-vue/dist/Components/Modal'
|
||||||
|
import { Avatar } from 'nextcloud-vue/dist/Components/Avatar'
|
||||||
|
import axios from 'nextcloud-axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CollaborationView',
|
||||||
|
components: {
|
||||||
|
Modal, Avatar
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
boards: [],
|
||||||
|
selectedBoard: null,
|
||||||
|
loading: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
this.fetchBoards();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchBoards() {
|
||||||
|
axios.get(OC.generateUrl('/apps/deck/boards')).then((response) => {
|
||||||
|
this.boards = response.data
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$root.$emit('close');
|
||||||
|
},
|
||||||
|
select() {
|
||||||
|
this.$root.$emit('select', this.selectedBoard)
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
56
js/views/CollaborationView.vue
Normal file
56
js/views/CollaborationView.vue
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2019 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>
|
||||||
|
<collection-list v-if="boardId" type="deck" :id="boardId" :name="boardTitle"></collection-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { CollectionList } from 'nextcloud-vue-collections';
|
||||||
|
import Vue from 'vue';
|
||||||
|
import PopoverMenu from 'nextcloud-vue/dist/Components/PopoverMenu'
|
||||||
|
|
||||||
|
Vue.component('popover-menu', PopoverMenu);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CollaborationView',
|
||||||
|
computed: {
|
||||||
|
boardId() {
|
||||||
|
if (this.$root.model && this.$root.model.id) {
|
||||||
|
return '' + this.$root.model.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
boardTitle() {
|
||||||
|
if (this.$root.model && this.$root.model.title) {
|
||||||
|
return '' + this.$root.model.title;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
CollectionList: CollectionList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,66 +1,76 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
const { VueLoaderPlugin } = require('vue-loader');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
node: {
|
node: {
|
||||||
fs: 'empty',
|
fs: 'empty',
|
||||||
},
|
},
|
||||||
entry: {
|
entry: {
|
||||||
deck: ['./init.js'],
|
deck: ['./init.js'],
|
||||||
},
|
collections: ['./init-collections.js']
|
||||||
output: {
|
},
|
||||||
filename: '[name].js',
|
output: {
|
||||||
path: __dirname + '/build'
|
filename: '[name].js',
|
||||||
},
|
path: __dirname + '/build'
|
||||||
resolve: {
|
},
|
||||||
modules: [path.resolve(__dirname), 'node_modules'],
|
module: {
|
||||||
},
|
rules: [
|
||||||
module: {
|
{
|
||||||
rules: [
|
test: /\.css$/,
|
||||||
{
|
use: ['vue-style-loader', 'style-loader', 'css-loader']
|
||||||
test: /\.js$/,
|
},
|
||||||
exclude: /node_modules/,
|
{
|
||||||
loader: 'babel-loader',
|
test: /\.vue$/,
|
||||||
query: {
|
loader: 'vue-loader'
|
||||||
presets: ['@babel/preset-env'],
|
},
|
||||||
}
|
{
|
||||||
},
|
test: /\.js$/,
|
||||||
{
|
exclude: /node_modules/,
|
||||||
test: /\.css$/,
|
loader: 'babel-loader',
|
||||||
use: [
|
query: {
|
||||||
MiniCssExtractPlugin.loader,
|
presets: ['@babel/preset-env'],
|
||||||
'css-loader'
|
plugins: ['@babel/plugin-syntax-dynamic-import']
|
||||||
]
|
}
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
},
|
test: /\.scss$/,
|
||||||
optimization: {
|
use: [
|
||||||
splitChunks: {
|
'vue-style-loader',
|
||||||
cacheGroups: {
|
'css-loader',
|
||||||
/* separate vendor chunk for node_modules and legacy scripts */
|
'sass-loader'
|
||||||
commons: {
|
]
|
||||||
test: /[\\/]node_modules[\\/]/,
|
},
|
||||||
name: 'vendor',
|
{
|
||||||
chunks: 'all'
|
test: /\.(png|jpg|gif|svg)$/,
|
||||||
},
|
loader: 'url-loader',
|
||||||
legacy: {
|
options: {
|
||||||
test: /[\\/]legacy[\\/]/,
|
name: '[name].[ext]?[hash]'
|
||||||
name: 'vendor',
|
}
|
||||||
chunks: 'all'
|
},
|
||||||
}
|
]
|
||||||
}
|
},
|
||||||
}
|
/* use external jQuery from server */
|
||||||
},
|
externals: {
|
||||||
/* use external jQuery from server */
|
'jquery': 'jQuery'
|
||||||
externals: {
|
},
|
||||||
'jquery': 'jQuery'
|
resolve: {
|
||||||
},
|
alias: {
|
||||||
plugins: [
|
vue$: 'vue/dist/vue.esm.js'
|
||||||
new MiniCssExtractPlugin('[name].css'),
|
},
|
||||||
new webpack.ProvidePlugin({
|
extensions: ['*', '.js', '.vue', '.json'],
|
||||||
$: 'jquery',
|
modules: [
|
||||||
jQuery: 'jquery'
|
path.resolve(__dirname),
|
||||||
})
|
path.join(__dirname, 'node_modules'),
|
||||||
]
|
'node_modules'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new VueLoaderPlugin(),
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
$: 'jquery',
|
||||||
|
jQuery: 'jquery'
|
||||||
|
})
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ use OCA\Deck\Middleware\ExceptionMiddleware;
|
|||||||
use OCA\Deck\Notification\Notifier;
|
use OCA\Deck\Notification\Notifier;
|
||||||
use OCP\AppFramework\App;
|
use OCP\AppFramework\App;
|
||||||
use OCA\Deck\Middleware\SharingMiddleware;
|
use OCA\Deck\Middleware\SharingMiddleware;
|
||||||
|
use OCP\Collaboration\Resources\IManager;
|
||||||
use OCP\Comments\CommentsEntityEvent;
|
use OCP\Comments\CommentsEntityEvent;
|
||||||
use OCP\IGroup;
|
use OCP\IGroup;
|
||||||
use OCP\IUser;
|
use OCP\IUser;
|
||||||
@@ -100,8 +101,13 @@ class Application extends App {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->registerCollaborationResources();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \OCP\AppFramework\QueryException
|
||||||
|
*/
|
||||||
public function registerNavigationEntry() {
|
public function registerNavigationEntry() {
|
||||||
$container = $this->getContainer();
|
$container = $this->getContainer();
|
||||||
$container->query(INavigationManager::class)->add(function() use ($container) {
|
$container->query(INavigationManager::class)->add(function() use ($container) {
|
||||||
@@ -126,6 +132,9 @@ class Application extends App {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \OCP\AppFramework\QueryException
|
||||||
|
*/
|
||||||
public function registerCommentsEntity() {
|
public function registerCommentsEntity() {
|
||||||
$this->getContainer()->getServer()->getEventDispatcher()->addListener(CommentsEntityEvent::EVENT_ENTITY, function(CommentsEntityEvent $event) {
|
$this->getContainer()->getServer()->getEventDispatcher()->addListener(CommentsEntityEvent::EVENT_ENTITY, function(CommentsEntityEvent $event) {
|
||||||
$event->addEntityCollection('deckCard', function($name) {
|
$event->addEntityCollection('deckCard', function($name) {
|
||||||
@@ -142,9 +151,32 @@ class Application extends App {
|
|||||||
$this->registerCommentsEventHandler();
|
$this->registerCommentsEventHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \OCP\AppFramework\QueryException
|
||||||
|
*/
|
||||||
protected function registerCommentsEventHandler() {
|
protected function registerCommentsEventHandler() {
|
||||||
$this->getContainer()->getServer()->getCommentsManager()->registerEventHandler(function () {
|
$this->getContainer()->getServer()->getCommentsManager()->registerEventHandler(function () {
|
||||||
return $this->getContainer()->query(CommentEventHandler::class);
|
return $this->getContainer()->query(CommentEventHandler::class);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \OCP\AppFramework\QueryException
|
||||||
|
*/
|
||||||
|
protected function registerCollaborationResources() {
|
||||||
|
$version = \OC_Util::getVersion()[0];
|
||||||
|
if ($version < 16) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register Collaboration ResourceProvider
|
||||||
|
*/
|
||||||
|
/** @var IManager $resourceManager */
|
||||||
|
$resourceManager = $this->getContainer()->query(IManager::class);
|
||||||
|
$resourceManager->registerResourceProvider(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
|
||||||
|
\OC::$server->getEventDispatcher()->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', function () {
|
||||||
|
\OCP\Util::addScript('deck', 'build/collections');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
133
lib/Collaboration/Resources/ResourceProvider.php
Normal file
133
lib/Collaboration/Resources/ResourceProvider.php
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2019 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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Deck\Collaboration\Resources;
|
||||||
|
|
||||||
|
|
||||||
|
use OCA\Deck\Db\Acl;
|
||||||
|
use OCA\Deck\Db\Board;
|
||||||
|
use OCA\Deck\Db\BoardMapper;
|
||||||
|
use OCA\Deck\Service\PermissionService;
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||||
|
use OCP\AppFramework\QueryException;
|
||||||
|
use OCP\Collaboration\Resources\IManager;
|
||||||
|
use OCP\Collaboration\Resources\IProvider;
|
||||||
|
use OCP\Collaboration\Resources\IResource;
|
||||||
|
use OCP\Collaboration\Resources\ResourceException;
|
||||||
|
use OCP\IUser;
|
||||||
|
|
||||||
|
class ResourceProvider implements IProvider {
|
||||||
|
|
||||||
|
const RESOURCE_TYPE = 'deck';
|
||||||
|
|
||||||
|
private $boardMapper;
|
||||||
|
private $permissionService;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $nodes = [];
|
||||||
|
|
||||||
|
public function __construct(BoardMapper $boardMapper, PermissionService $permissionService) {
|
||||||
|
$this->boardMapper = $boardMapper;
|
||||||
|
$this->permissionService = $permissionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of a resource
|
||||||
|
*
|
||||||
|
* @param IResource $resource
|
||||||
|
* @return string
|
||||||
|
* @since 15.0.0
|
||||||
|
*/
|
||||||
|
public function getType(): string {
|
||||||
|
return self::RESOURCE_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the rich object data of a resource
|
||||||
|
*
|
||||||
|
* @param IResource $resource
|
||||||
|
* @return array
|
||||||
|
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||||
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
|
* @since 16.0.0
|
||||||
|
*/
|
||||||
|
public function getResourceRichObject(IResource $resource): array {
|
||||||
|
$board = $this->getBoard($resource);
|
||||||
|
$link = \OC::$server->getURLGenerator()->linkToRoute('deck.page.index') . '#!/board/' . $resource->getId();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => self::RESOURCE_TYPE,
|
||||||
|
'id' => $resource->getId(),
|
||||||
|
'name' => $board->getTitle(),
|
||||||
|
'link' => $link,
|
||||||
|
'iconUrl' => \OC::$server->getURLGenerator()->imagePath('deck', 'deck-dark.svg')
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can a user/guest access the collection
|
||||||
|
*
|
||||||
|
* @param IResource $resource
|
||||||
|
* @param IUser|null $user
|
||||||
|
* @return bool
|
||||||
|
* @since 16.0.0
|
||||||
|
*/
|
||||||
|
public function canAccessResource(IResource $resource, ?IUser $user): bool {
|
||||||
|
if ($resource->getType() !== self::RESOURCE_TYPE || !$user instanceof IUser) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$board = $this->getBoard($resource);
|
||||||
|
if ($board === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ($board->getOwner() === $user->getUID()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getBoard(IResource $resource) {
|
||||||
|
try {
|
||||||
|
return $this->boardMapper->find($resource->getId(), false, true);
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
} catch (MultipleObjectsReturnedException $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invalidateAccessCache($boardId = null) {
|
||||||
|
try {
|
||||||
|
/** @var IManager $resourceManager */
|
||||||
|
$resourceManager = \OC::$server->query(IManager::class);
|
||||||
|
} catch (QueryException $e) {
|
||||||
|
}
|
||||||
|
if ($boardId !== null) {
|
||||||
|
$resource = $resourceManager->getResourceForUser(self::RESOURCE_TYPE, (string)$boardId, null);
|
||||||
|
$resourceManager->invalidateAccessCacheForResource($resource);
|
||||||
|
} else {
|
||||||
|
$resourceManager->invalidateAccessCacheForProvider($this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ namespace OCA\Deck\Service;
|
|||||||
|
|
||||||
use OCA\Deck\Activity\ActivityManager;
|
use OCA\Deck\Activity\ActivityManager;
|
||||||
use OCA\Deck\Activity\ChangeSet;
|
use OCA\Deck\Activity\ChangeSet;
|
||||||
|
use OCA\Deck\Collaboration\Resources\ResourceProvider;
|
||||||
use OCA\Deck\Db\Acl;
|
use OCA\Deck\Db\Acl;
|
||||||
use OCA\Deck\Db\AclMapper;
|
use OCA\Deck\Db\AclMapper;
|
||||||
use OCA\Deck\Db\AssignedUsersMapper;
|
use OCA\Deck\Db\AssignedUsersMapper;
|
||||||
@@ -459,6 +460,13 @@ class BoardService {
|
|||||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE);
|
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE);
|
||||||
$this->boardMapper->mapAcl($newAcl);
|
$this->boardMapper->mapAcl($newAcl);
|
||||||
$this->changeHelper->boardChanged($boardId);
|
$this->changeHelper->boardChanged($boardId);
|
||||||
|
$version = \OC_Util::getVersion()[0];
|
||||||
|
if ($version >= 16) {
|
||||||
|
try {
|
||||||
|
$resourceProvider = \OC::$server->query(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
|
||||||
|
$resourceProvider->invalidateAccessCache($boardId);
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
return $newAcl;
|
return $newAcl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,6 +537,13 @@ class BoardService {
|
|||||||
}
|
}
|
||||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $acl, ActivityManager::SUBJECT_BOARD_UNSHARE);
|
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $acl, ActivityManager::SUBJECT_BOARD_UNSHARE);
|
||||||
$this->changeHelper->boardChanged($acl->getBoardId());
|
$this->changeHelper->boardChanged($acl->getBoardId());
|
||||||
|
$version = \OC_Util::getVersion()[0];
|
||||||
|
if ($version >= 16) {
|
||||||
|
try {
|
||||||
|
$resourceProvider = \OC::$server->query(\OCA\Deck\Collaboration\Resources\ResourceProvider::class);
|
||||||
|
$resourceProvider->invalidateAccessCache($acl->getBoardId());
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
return $this->aclMapper->delete($acl);
|
return $this->aclMapper->delete($acl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ class PermissionService {
|
|||||||
* @return bool
|
* @return bool
|
||||||
* @throws NoPermissionException
|
* @throws NoPermissionException
|
||||||
*/
|
*/
|
||||||
public function checkPermission($mapper, $id, $permission) {
|
public function checkPermission($mapper, $id, $permission, $userId = null) {
|
||||||
$boardId = $id;
|
$boardId = $id;
|
||||||
if ($mapper instanceof IPermissionMapper) {
|
if ($mapper instanceof IPermissionMapper) {
|
||||||
$boardId = $mapper->findBoardId($id);
|
$boardId = $mapper->findBoardId($id);
|
||||||
@@ -141,12 +141,12 @@ class PermissionService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->userIsBoardOwner($boardId)) {
|
if ($this->userIsBoardOwner($boardId, $userId)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$acls = $this->aclMapper->findAll($boardId);
|
$acls = $this->aclMapper->findAll($boardId);
|
||||||
$result = $this->userCan($acls, $permission);
|
$result = $this->userCan($acls, $permission, $userId);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -159,10 +159,13 @@ class PermissionService {
|
|||||||
* @param $boardId
|
* @param $boardId
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function userIsBoardOwner($boardId) {
|
public function userIsBoardOwner($boardId, $userId = null) {
|
||||||
|
if ($userId === null) {
|
||||||
|
$userId = $this->userId;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$board = $this->boardMapper->find($boardId);
|
$board = $this->boardMapper->find($boardId);
|
||||||
return $board && $this->userId === $board->getOwner();
|
return $board && $userId === $board->getOwner();
|
||||||
} catch (DoesNotExistException $e) {
|
} catch (DoesNotExistException $e) {
|
||||||
} catch (MultipleObjectsReturnedException $e) {
|
} catch (MultipleObjectsReturnedException $e) {
|
||||||
return false;
|
return false;
|
||||||
@@ -176,17 +179,20 @@ class PermissionService {
|
|||||||
* @param $permission
|
* @param $permission
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function userCan(array $acls, $permission) {
|
public function userCan(array $acls, $permission, $userId = null) {
|
||||||
|
if ($userId === null) {
|
||||||
|
$userId = $this->userId;
|
||||||
|
}
|
||||||
// check for users
|
// check for users
|
||||||
foreach ($acls as $acl) {
|
foreach ($acls as $acl) {
|
||||||
if ($acl->getType() === Acl::PERMISSION_TYPE_USER && $acl->getParticipant() === $this->userId) {
|
if ($acl->getType() === Acl::PERMISSION_TYPE_USER && $acl->getParticipant() === $userId) {
|
||||||
return $acl->getPermission($permission);
|
return $acl->getPermission($permission);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check for groups
|
// check for groups
|
||||||
$hasGroupPermission = false;
|
$hasGroupPermission = false;
|
||||||
foreach ($acls as $acl) {
|
foreach ($acls as $acl) {
|
||||||
if (!$hasGroupPermission && $acl->getType() === Acl::PERMISSION_TYPE_GROUP && $this->groupManager->isInGroup($this->userId, $acl->getParticipant())) {
|
if (!$hasGroupPermission && $acl->getType() === Acl::PERMISSION_TYPE_GROUP && $this->groupManager->isInGroup($userId, $acl->getParticipant())) {
|
||||||
$hasGroupPermission = $acl->getPermission($permission);
|
$hasGroupPermission = $acl->getPermission($permission);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ find -name "*.js" -path '*js/*' -not -path '*js/node_modules*' \
|
|||||||
-not -path '*js/tests*' \
|
-not -path '*js/tests*' \
|
||||||
-not -path '*js/webpack*' \
|
-not -path '*js/webpack*' \
|
||||||
-not -path '*js/public*' \
|
-not -path '*js/public*' \
|
||||||
|
-not -path '*js/views*' \
|
||||||
|
-not -path '*js/init-collections.js' \
|
||||||
-not -path '*build/*' \
|
-not -path '*build/*' \
|
||||||
-not -path '*tests/*' \
|
-not -path '*tests/*' \
|
||||||
-print0 | xargs -0 $ESLINT
|
-print0 | xargs -0 $ESLINT
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ Util::addStyle('activity', 'style');
|
|||||||
Util::addStyle('comments', 'comments');
|
Util::addStyle('comments', 'comments');
|
||||||
Util::addScript('oc-backbone-webdav');
|
Util::addScript('oc-backbone-webdav');
|
||||||
|
|
||||||
Util::addStyle('deck', '../js/build/vendor');
|
//Util::addStyle('deck', '../js/build/vendor');
|
||||||
Util::addScript('deck', 'build/vendor');
|
//Util::addScript('deck', 'build/vendor');
|
||||||
|
|
||||||
Util::addStyle('deck', 'style');
|
Util::addStyle('deck', 'style');
|
||||||
Util::addScript('deck', 'build/deck');
|
Util::addScript('deck', 'build/deck');
|
||||||
@@ -41,6 +41,7 @@ Util::addScript('deck', 'build/deck');
|
|||||||
if (\OC_Util::getVersion()[0] < 14) {
|
if (\OC_Util::getVersion()[0] < 14) {
|
||||||
Util::addStyle('deck', 'comp-13');
|
Util::addStyle('deck', 'comp-13');
|
||||||
}
|
}
|
||||||
|
\OC::$server->getEventDispatcher()->dispatch('\OCP\Collaboration\Resources::loadAdditionalScripts');
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
{{ sharee.participant.displayname }} (<?php p($l->t('Group')); ?>)
|
{{ sharee.participant.displayname }} (<?php p($l->t('Group')); ?>)
|
||||||
</span>
|
</span>
|
||||||
<span class="has-tooltip username" ng-if="sharee.type==OC.Share.SHARE_TYPE_USER">
|
<span class="has-tooltip username" ng-if="sharee.type==OC.Share.SHARE_TYPE_USER">
|
||||||
{{ sharee.participant.displayname }}
|
{{ sharee.participant.displayname }}
|
||||||
</span>
|
</span>
|
||||||
</ui-select-choices>
|
</ui-select-choices>
|
||||||
<ui-select-no-choice>
|
<ui-select-no-choice>
|
||||||
@@ -82,6 +82,8 @@
|
|||||||
<?php p($l->t('Sharing has been disabled for your account.')); ?>
|
<?php p($l->t('Sharing has been disabled for your account.')); ?>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<h1>Collections</h1>
|
||||||
|
<div id="collaborationResources"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div id="board-detail-labels" class="tab commentsTabView" ng-if="params.tab==1">
|
<div id="board-detail-labels" class="tab commentsTabView" ng-if="params.tab==1">
|
||||||
|
|||||||
Reference in New Issue
Block a user