Projects: Add bookmarks folders

remove bookmark picker from collections.js for now as we don't have a picker, yet.

Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
Marcel Klehr 2022-03-13 16:34:35 +01:00
parent ddeb5fc953
commit 74fb4510f6
8 changed files with 155 additions and 75 deletions

View File

@ -11,6 +11,7 @@ namespace OCA\Bookmarks\AppInfo;
use Closure;
use OC\EventDispatcher\SymfonyAdapter;
use OCA\Bookmarks\Activity\ActivityPublisher;
use OCA\Bookmarks\Collaboration\Resources\FolderResourceProvider;
use OCA\Bookmarks\Collaboration\Resources\ResourceProvider;
use OCA\Bookmarks\Dashboard\Frequent;
use OCA\Bookmarks\Dashboard\Recent;
@ -95,6 +96,7 @@ class Application extends App implements IBootstrap {
protected function registerCollaborationResources(IProviderManager $resourceManager, SymfonyAdapter $symfonyAdapter): void {
$resourceManager->registerResourceProvider(ResourceProvider::class);
$resourceManager->registerResourceProvider(FolderResourceProvider::class);
$symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () {
Util::addScript('bookmarks', 'bookmarks-collections');

View File

@ -0,0 +1,90 @@
<?php
/*
* Copyright (c) 2022. The Nextcloud Bookmarks contributors.
*
* This file is licensed under the Affero General Public License version 3 or later. See the COPYING file.
*/
namespace OCA\Bookmarks\Collaboration\Resources;
use OCA\Bookmarks\Db\Folder;
use OCA\Bookmarks\Db\FolderMapper;
use OCA\Bookmarks\Service\Authorizer;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\Collaboration\Resources\IProvider;
use OCP\Collaboration\Resources\IResource;
use OCP\IURLGenerator;
use OCP\IUser;
class FolderResourceProvider implements IProvider {
public const RESOURCE_TYPE = 'bookmarks-folder';
/**
* @var IURLGenerator
*/
private $url;
/**
* @var Authorizer
*/
private $authorizer;
/**
* @var FolderMapper
*/
private $folderMapper;
public function __construct(IURLGenerator $url, Authorizer $authorizer, FolderMapper $folderMapper) {
$this->url = $url;
$this->authorizer = $authorizer;
$this->folderMapper = $folderMapper;
}
/**
* @inheritDoc
*/
public function getType(): string {
return self::RESOURCE_TYPE;
}
/**
* @inheritDoc
*/
public function getResourceRichObject(IResource $resource): array {
$folder = $this->getFolder($resource);
$favicon = $this->url->imagePath('bookmarks', 'bookmarks-black.svg');
$resourceUrl = $this->url->linkToRouteAbsolute('bookmarks.web_view.indexfolder', ['folder' => $folder->getId()]);
return [
'type' => self::RESOURCE_TYPE,
'id' => $resource->getId(),
'name' => $folder->getTitle(),
'link' => $resourceUrl,
'iconUrl' => $favicon,
];
}
/**
* @inheritDoc
*/
public function canAccessResource(IResource $resource, ?IUser $user): bool {
if ($resource->getType() !== self::RESOURCE_TYPE || !($user instanceof IUser)) {
return false;
}
$folder = $this->getFolder($resource);
if ($folder === null) {
return false;
}
if ($folder->getUserId() === $user->getUID()) {
return true;
}
$permissions = $this->authorizer->getUserPermissionsForFolder($user->getUID(), $folder->getId());
return Authorizer::hasPermission(Authorizer::PERM_READ, $permissions);
}
private function getFolder(IResource $resource) : ?Folder {
try {
return $this->folderMapper->find((int) $resource->getId());
} catch (MultipleObjectsReturnedException|DoesNotExistException $e) {
return null;
}
}
}

View File

@ -59,11 +59,11 @@ class FolderMapper extends QBMapper {
/**
* @param int $id
* @return Entity
* @return Folder
* @throws DoesNotExistException if not found
* @throws MultipleObjectsReturnedException if more than one result
*/
public function find(int $id): Entity {
public function find(int $id): Folder {
$qb = $this->db->getQueryBuilder();
$qb
->select('*')

View File

@ -6,70 +6,46 @@
import Vue from 'vue'
import FolderPickerDialog from './components/FolderPickerDialog'
import { Store } from 'vuex'
import deepClone from 'clone-deep'
import store, { actions } from './store'
import AppGlobal from './mixins/AppGlobal'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
// eslint-disable-next-line no-unexpected-multiline
(function(OCP, OC) {
// eslint-disable-next-line
__webpack_nonce__ = btoa(OC.requestToken)
// eslint-disable-next-line
__webpack_public_path__ = OC.linkTo('bookmarks', 'js/')
// eslint-disable-next-line
__webpack_nonce__ = btoa(OC.requestToken)
// eslint-disable-next-line
__webpack_public_path__ = OC.linkTo('bookmarks', 'js/')
Vue.mixin(AppGlobal)
Vue.directive('tooltip', Tooltip)
Vue.prototype.t = t
Vue.prototype.n = n
Vue.prototype.OC = OC
OCP.Collaboration.registerType('bookmarks', {
action: () => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
container.id = 'bookmarks-bookmark-select'
const body = document.getElementById('body-user')
body.appendChild(container)
const ComponentVM = new Vue({
render: h => h(FolderPickerDialog),
})
ComponentVM.$mount(container)
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
reject(new Error('User cancelled resource selection'))
})
ComponentVM.$root.$on('select', (id) => {
resolve(id)
ComponentVM.$el.remove()
ComponentVM.$destroy()
})
OCP.Collaboration.registerType('bookmarks-folder', {
action: () => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
container.id = 'bookmarks-bookmark-folder-select'
const body = document.getElementById('body-user')
body.appendChild(container)
const ComponentVM = new Vue({
render: h => h(FolderPickerDialog),
store: new Store(deepClone(store)),
})
},
typeString: t('bookmarks', 'Link to a bookmark'),
typeIconClass: 'icon-favorite',
})
OCP.Collaboration.registerType('bookmarks::folder', {
action: () => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
container.id = 'bookmarks-bookmark-folder-select'
const body = document.getElementById('body-user')
body.appendChild(container)
const ComponentVM = new Vue({
render: h => h(FolderPickerDialog),
})
ComponentVM.$mount(container)
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
reject(new Error('User cancelled resource selection'))
})
ComponentVM.$root.$on('select', (id) => {
resolve(id)
ComponentVM.$el.remove()
ComponentVM.$destroy()
})
ComponentVM.$store.dispatch(actions.LOAD_FOLDERS, true)
ComponentVM.$mount(container)
window.bookmarksPicker = ComponentVM
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
reject(new Error('User cancelled resource selection'))
})
},
typeString: t('bookmarks', 'Link to a bookmark folder'),
typeIconClass: 'icon-favorite',
})
})(window.OCP, window.OC)
ComponentVM.$root.$on('select', (id) => {
resolve(id)
ComponentVM.$el.remove()
ComponentVM.$destroy()
})
})
},
typeString: t('bookmarks', 'Link to a bookmark folder'),
typeIconClass: 'icon-favorite',
})

View File

@ -105,12 +105,7 @@ export default {
.currentfolder h2 {
margin: 0;
}
.currentfolder h2 .material-design-icon {
position: relative;
top: 5px;
margin: 0 15px;
display: flex;
}
.treefolder__title .material-design-icon {

View File

@ -39,11 +39,12 @@ export default {
created() {},
methods: {
onSelect(folderId) {
this.$root.$emit('select', folderId)
this.$emit('input', folderId)
this.$emit('select', folderId)
this.$emit('close')
},
onClose() {
this.$root.$emit('close')
this.$emit('close')
},
},

View File

@ -95,7 +95,8 @@
<a class="button" :href="archivedFile" target="_blank"><span class="icon-files-dark" /> {{ t('bookmarks', 'Open file location') }}</a>
</div>
</AppSidebarTab>
<AppSidebarTab id="bookmark-projects"
<AppSidebarTab v-if="!isPublic"
id="bookmark-projects"
:name="t('bookmarks', 'Projects')"
icon="icon-projects"
:order="1">

View File

@ -11,7 +11,10 @@
:title="folder.title"
:active.sync="activeTab"
@close="onClose">
<AppSidebarTab id="folder-details" :name="t('bookmarks', 'Details')" icon="icon-info">
<AppSidebarTab id="folder-details"
:name="t('bookmarks', 'Details')"
icon="icon-info"
:order="0">
<h3>{{ t('bookmarks', 'Owner') }}</h3>
<UserBubble :user="folder.userId" :display-name="folder.userId" />
<h3>{{ t('bookmarks', 'Bookmarks') }}</h3>
@ -20,7 +23,8 @@
<AppSidebarTab v-if="isSharable"
id="folder-sharing"
:name="t('bookmarks', 'Sharing')"
icon="icon-shared">
icon="icon-shared"
:order="1">
<div class="participant-select">
<figure :class="{'icon-user': true, 'share__avatar': true }" />
<Multiselect v-model="participant"
@ -108,6 +112,16 @@
</div>
</div>
</AppSidebarTab>
<AppSidebarTab v-if="!isPublic"
id="bookmark-projects"
:name="t('bookmarks', 'Projects')"
icon="icon-projects"
:order="2">
<CollectionList v-if="folder"
:id="''+folder.id"
:name="folder.title"
type="bookmarks-folder" />
</AppSidebarTab>
</AppSidebar>
</template>
<script>
@ -128,10 +142,11 @@ import { actions, mutations } from '../store/'
import EyeIcon from 'vue-material-design-icons/Eye'
import PencilIcon from 'vue-material-design-icons/Pencil'
import ShareAllIcon from 'vue-material-design-icons/ShareAll'
import { CollectionList } from 'nextcloud-vue-collections'
export default {
name: 'SidebarFolder',
components: { AppSidebar, AppSidebarTab, Avatar, Multiselect, ActionButton, ActionCheckbox, Actions, UserBubble, ActionSeparator, EyeIcon, PencilIcon, ShareAllIcon },
components: { AppSidebar, AppSidebarTab, Avatar, Multiselect, ActionButton, ActionCheckbox, Actions, UserBubble, ActionSeparator, EyeIcon, PencilIcon, ShareAllIcon, CollectionList },
data() {
return {
participantSearchResults: [],