Merge pull request #946 from nextcloud/feature/collections

Collaboration linking
This commit is contained in:
Julius Härtl
2019-03-27 18:38:35 +01:00
committed by GitHub
18 changed files with 1141 additions and 230 deletions

View File

@@ -5,10 +5,10 @@ clone:
pipeline:
check-app-compatbility:
image: nextcloudci/php7.0:php7.0-17
image: nextcloudci/php7.1:php7.1-15
environment:
- APP_NAME=deck
- CORE_BRANCH=stable15
- CORE_BRANCH=master
- DB=sqlite
commands:
# Pre-setup steps
@@ -43,7 +43,7 @@ pipeline:
- DB=sqlite
commands:
- composer install
- ./vendor/bin/parallel-lint --exclude ./vendor/ .
- ./vendor/bin/parallel-lint --exclude ./vendor/ --exclude ./lib/Collaboration/ .
when:
matrix:
TESTS: syntax-php7.0

10
css/collections.css Normal file
View 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;
}

View File

@@ -654,7 +654,7 @@ input.input-inline {
min-height: 16px;
}
.popovermenu {
.popovermenu:not(.action-item__menu) {
z-index: 999;
opacity: 1;
display: block;

View File

@@ -21,8 +21,11 @@
*/
import app from '../app/App.js';
import Vue from 'vue';
import CollaborationView from '../views/CollaborationView';
/* 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;
@@ -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() {
$rootScope.compactMode = !$rootScope.compactMode;
localStorage.setItem('deck.compactMode', JSON.stringify($rootScope.compactMode));

69
js/init-collections.js Normal file
View 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));

View File

@@ -1,7 +1,10 @@
'use strict';
/* global __webpack_nonce__ OC */
__webpack_nonce__ = btoa(OC.requestToken); // eslint-disable-line no-native-reassign
/* 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/');
// used for building a vendor stylesheet
import 'ng-sortable/dist/ng-sortable.css';

722
js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@
"description": "Frontend for the Nextcloud Deck app",
"repository": "https://github.com/nextcloud/deck",
"version": "1.0.0",
"main": "Gruntfile.js",
"directories": {
"test": "tests"
},
@@ -17,25 +16,36 @@
"babel-polyfill": "^6.26.0",
"markdown-it": "^8.4.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-sortable": "^1.3.8",
"ui-select": "^0.19.8"
"ui-select": "^0.19.8",
"vue": "^2.6.8",
"vuex": "^3.1.0"
},
"devDependencies": {
"@babel/core": "^7.4.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/polyfill": "^7.4.0",
"@babel/preset-env": "^7.4.2",
"babel-loader": "^8.0.5",
"css-loader": "^2.1.1",
"karma": "^4.0.1",
"mini-css-extract-plugin": "^0.5.0",
"style-loader": "^0.23.1",
"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-cli": "^3.3.0",
"webpack-merge": "^4.2.1"
},
"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",
"watch": "./node_modules/webpack-cli/bin/cli.js --mode development --config webpack.dev.config.js --watch",
"test": "echo \"Warning: no test specified\" && exit 0"

112
js/views/BoardSelector.vue Normal file
View 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>

View 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>

View File

@@ -1,66 +1,76 @@
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { VueLoaderPlugin } = require('vue-loader');
module.exports = {
node: {
fs: 'empty',
},
entry: {
deck: ['./init.js'],
},
output: {
filename: '[name].js',
path: __dirname + '/build'
},
resolve: {
modules: [path.resolve(__dirname), 'node_modules'],
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['@babel/preset-env'],
}
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
/* separate vendor chunk for node_modules and legacy scripts */
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all'
},
legacy: {
test: /[\\/]legacy[\\/]/,
name: 'vendor',
chunks: 'all'
}
}
}
},
/* use external jQuery from server */
externals: {
'jquery': 'jQuery'
},
plugins: [
new MiniCssExtractPlugin('[name].css'),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
node: {
fs: 'empty',
},
entry: {
deck: ['./init.js'],
collections: ['./init-collections.js']
},
output: {
filename: '[name].js',
path: __dirname + '/build'
},
module: {
rules: [
{
test: /\.css$/,
use: ['vue-style-loader', 'style-loader', 'css-loader']
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-syntax-dynamic-import']
}
},
{
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url-loader',
options: {
name: '[name].[ext]?[hash]'
}
},
]
},
/* use external jQuery from server */
externals: {
'jquery': 'jQuery'
},
resolve: {
alias: {
vue$: 'vue/dist/vue.esm.js'
},
extensions: ['*', '.js', '.vue', '.json'],
modules: [
path.resolve(__dirname),
path.join(__dirname, 'node_modules'),
'node_modules'
]
},
plugins: [
new VueLoaderPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
};

View File

@@ -32,6 +32,7 @@ use OCA\Deck\Middleware\ExceptionMiddleware;
use OCA\Deck\Notification\Notifier;
use OCP\AppFramework\App;
use OCA\Deck\Middleware\SharingMiddleware;
use OCP\Collaboration\Resources\IManager;
use OCP\Comments\CommentsEntityEvent;
use OCP\IGroup;
use OCP\IUser;
@@ -100,8 +101,13 @@ class Application extends App {
}
});
$this->registerCollaborationResources();
}
/**
* @throws \OCP\AppFramework\QueryException
*/
public function registerNavigationEntry() {
$container = $this->getContainer();
$container->query(INavigationManager::class)->add(function() use ($container) {
@@ -126,6 +132,9 @@ class Application extends App {
});
}
/**
* @throws \OCP\AppFramework\QueryException
*/
public function registerCommentsEntity() {
$this->getContainer()->getServer()->getEventDispatcher()->addListener(CommentsEntityEvent::EVENT_ENTITY, function(CommentsEntityEvent $event) {
$event->addEntityCollection('deckCard', function($name) {
@@ -142,9 +151,32 @@ class Application extends App {
$this->registerCommentsEventHandler();
}
/**
* @throws \OCP\AppFramework\QueryException
*/
protected function registerCommentsEventHandler() {
$this->getContainer()->getServer()->getCommentsManager()->registerEventHandler(function () {
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');
});
}
}

View 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);
}
}
}

View File

@@ -25,6 +25,7 @@ namespace OCA\Deck\Service;
use OCA\Deck\Activity\ActivityManager;
use OCA\Deck\Activity\ChangeSet;
use OCA\Deck\Collaboration\Resources\ResourceProvider;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\AssignedUsersMapper;
@@ -459,6 +460,13 @@ class BoardService {
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $newAcl, ActivityManager::SUBJECT_BOARD_SHARE);
$this->boardMapper->mapAcl($newAcl);
$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;
}
@@ -529,6 +537,13 @@ class BoardService {
}
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $acl, ActivityManager::SUBJECT_BOARD_UNSHARE);
$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);
}

View File

@@ -127,7 +127,7 @@ class PermissionService {
* @return bool
* @throws NoPermissionException
*/
public function checkPermission($mapper, $id, $permission) {
public function checkPermission($mapper, $id, $permission, $userId = null) {
$boardId = $id;
if ($mapper instanceof IPermissionMapper) {
$boardId = $mapper->findBoardId($id);
@@ -141,12 +141,12 @@ class PermissionService {
return false;
}
if ($this->userIsBoardOwner($boardId)) {
if ($this->userIsBoardOwner($boardId, $userId)) {
return true;
}
$acls = $this->aclMapper->findAll($boardId);
$result = $this->userCan($acls, $permission);
$result = $this->userCan($acls, $permission, $userId);
if ($result) {
return true;
}
@@ -159,10 +159,13 @@ class PermissionService {
* @param $boardId
* @return bool
*/
public function userIsBoardOwner($boardId) {
public function userIsBoardOwner($boardId, $userId = null) {
if ($userId === null) {
$userId = $this->userId;
}
try {
$board = $this->boardMapper->find($boardId);
return $board && $this->userId === $board->getOwner();
return $board && $userId === $board->getOwner();
} catch (DoesNotExistException $e) {
} catch (MultipleObjectsReturnedException $e) {
return false;
@@ -176,17 +179,20 @@ class PermissionService {
* @param $permission
* @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
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);
}
}
// check for groups
$hasGroupPermission = false;
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);
}
}

View File

@@ -14,6 +14,8 @@ find -name "*.js" -path '*js/*' -not -path '*js/node_modules*' \
-not -path '*js/tests*' \
-not -path '*js/webpack*' \
-not -path '*js/public*' \
-not -path '*js/views*' \
-not -path '*js/init-collections.js' \
-not -path '*build/*' \
-not -path '*tests/*' \
-print0 | xargs -0 $ESLINT

View File

@@ -32,8 +32,8 @@ Util::addStyle('activity', 'style');
Util::addStyle('comments', 'comments');
Util::addScript('oc-backbone-webdav');
Util::addStyle('deck', '../js/build/vendor');
Util::addScript('deck', 'build/vendor');
//Util::addStyle('deck', '../js/build/vendor');
//Util::addScript('deck', 'build/vendor');
Util::addStyle('deck', 'style');
Util::addScript('deck', 'build/deck');
@@ -41,6 +41,7 @@ Util::addScript('deck', 'build/deck');
if (\OC_Util::getVersion()[0] < 14) {
Util::addStyle('deck', 'comp-13');
}
\OC::$server->getEventDispatcher()->dispatch('\OCP\Collaboration\Resources::loadAdditionalScripts');
?>
<div

View File

@@ -34,7 +34,7 @@
{{ sharee.participant.displayname }} (<?php p($l->t('Group')); ?>)
</span>
<span class="has-tooltip username" ng-if="sharee.type==OC.Share.SHARE_TYPE_USER">
{{ sharee.participant.displayname }}
{{ sharee.participant.displayname }}
</span>
</ui-select-choices>
<ui-select-no-choice>
@@ -82,6 +82,8 @@
<?php p($l->t('Sharing has been disabled for your account.')); ?>
</li>
</ul>
<h1>Collections</h1>
<div id="collaborationResources"></div>
</div>
<div id="board-detail-labels" class="tab commentsTabView" ng-if="params.tab==1">