mirror of https://github.com/nextcloud/bookmarks
First pass at implementing projects
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
parent
8e63225541
commit
164fd3af2a
|
@ -8,7 +8,10 @@
|
|||
|
||||
namespace OCA\Bookmarks\AppInfo;
|
||||
|
||||
use Closure;
|
||||
use OC\EventDispatcher\SymfonyAdapter;
|
||||
use OCA\Bookmarks\Activity\ActivityPublisher;
|
||||
use OCA\Bookmarks\Collaboration\Resources\ResourceProvider;
|
||||
use OCA\Bookmarks\Dashboard\Frequent;
|
||||
use OCA\Bookmarks\Dashboard\Recent;
|
||||
use OCA\Bookmarks\Events\BeforeDeleteEvent;
|
||||
|
@ -26,6 +29,7 @@ use OCP\AppFramework\Bootstrap\IBootContext;
|
|||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
||||
use OCP\Collaboration\Resources\IProviderManager;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Group\Events\UserAddedEvent;
|
||||
use OCP\Group\Events\UserRemovedEvent;
|
||||
|
@ -33,6 +37,7 @@ use OCP\IRequest;
|
|||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use OCP\User\Events\BeforeUserDeletedEvent;
|
||||
use OCP\Util;
|
||||
|
||||
class Application extends App implements IBootstrap {
|
||||
public const APP_ID = 'bookmarks';
|
||||
|
@ -77,8 +82,22 @@ class Application extends App implements IBootstrap {
|
|||
$context->registerMiddleware(ExceptionMiddleware::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function boot(IBootContext $context): void {
|
||||
$container = $context->getServerContainer();
|
||||
CreateBookmark::register($container->get(IEventDispatcher::class));
|
||||
$context->injectFn(Closure::fromCallable([$this, 'registerCollaborationResources']));
|
||||
}
|
||||
|
||||
protected function registerCollaborationResources(IProviderManager $resourceManager, SymfonyAdapter $symfonyAdapter): void {
|
||||
$resourceManager->registerResourceProvider(ResourceProvider::class);
|
||||
|
||||
$symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () {
|
||||
Util::addScript('bookmarks', 'bookmarks-collections');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<?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\Bookmark;
|
||||
use OCA\Bookmarks\Db\BookmarkMapper;
|
||||
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;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class ResourceProvider implements IProvider {
|
||||
public const RESOURCE_TYPE = 'bookmarks';
|
||||
/**
|
||||
* @var BookmarkMapper
|
||||
*/
|
||||
private $bookmarkMapper;
|
||||
/**
|
||||
* @var IURLGenerator
|
||||
*/
|
||||
private $url;
|
||||
/**
|
||||
* @var Authorizer
|
||||
*/
|
||||
private $authorizer;
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
public function __construct(BookmarkMapper $bookmarkMapper, IURLGenerator $url, Authorizer $authorizer, LoggerInterface $logger) {
|
||||
$this->bookmarkMapper = $bookmarkMapper;
|
||||
$this->url = $url;
|
||||
$this->authorizer = $authorizer;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getType(): string {
|
||||
return self::RESOURCE_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getResourceRichObject(IResource $resource): array {
|
||||
$bookmark = $this->getBookmark($resource);
|
||||
$favicon = $this->url->linkToRouteAbsolute('bookmarks.internal_bookmark.get_bookmark_favicon', ['id' => $bookmark->getId()]);
|
||||
$resourceUrl = $this->url->linkToRouteAbsolute('bookmarks.web_view.indexbookmark', ['bookmark' => $bookmark->getId()]);
|
||||
|
||||
return [
|
||||
'type' => self::RESOURCE_TYPE,
|
||||
'id' => $resource->getId(),
|
||||
'name' => $bookmark->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;
|
||||
}
|
||||
$bookmark = $this->getBookmark($resource);
|
||||
if ($bookmark === null) {
|
||||
return false;
|
||||
}
|
||||
if ($bookmark->getUserId() === $user->getUID()) {
|
||||
return true;
|
||||
}
|
||||
$permissions = $this->authorizer->getUserPermissionsForBookmark($user->getUID(), $bookmark->getId());
|
||||
return Authorizer::hasPermission(Authorizer::PERM_READ, $permissions);
|
||||
}
|
||||
|
||||
private function getBookmark(IResource $resource) : ?Bookmark {
|
||||
try {
|
||||
return $this->bookmarkMapper->find((int) $resource->getId());
|
||||
} catch (MultipleObjectsReturnedException|DoesNotExistException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -178,7 +178,7 @@ class BookmarkMapper extends QBMapper {
|
|||
* @throws DoesNotExistException if not found
|
||||
* @throws MultipleObjectsReturnedException if more than one result
|
||||
*/
|
||||
public function find(int $id): Entity {
|
||||
public function find(int $id): Bookmark {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb
|
||||
->select(Bookmark::$columns)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "bookmarks",
|
||||
"version": "10.0.3",
|
||||
"version": "10.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bookmarks",
|
||||
"version": "10.0.3",
|
||||
"version": "10.1.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@nextcloud/auth": "^1.3.0",
|
||||
|
@ -23,6 +23,7 @@
|
|||
"humanize-duration": "^3.27.1",
|
||||
"linkify-it": "^3.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
"nextcloud-vue-collections": "^0.9.0",
|
||||
"sanitize-html": "^2.7.0",
|
||||
"vue": "^2.6.14",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
|
@ -8880,6 +8881,39 @@
|
|||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/nextcloud-vue-collections": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/nextcloud-vue-collections/-/nextcloud-vue-collections-0.9.0.tgz",
|
||||
"integrity": "sha512-GItjPWV4O53CNRPxdRegjEpZUM2ZV1mun2gVM/tLr3Nc2WzchgjAEzHjLxWomdW7kRv0sFJNS20udYJ2wEX76Q==",
|
||||
"dependencies": {
|
||||
"@nextcloud/axios": "^1.5.0",
|
||||
"@nextcloud/browserslist-config": "^1.0.0",
|
||||
"@nextcloud/router": "^1.2.0",
|
||||
"@nextcloud/vue": "^3.1.2",
|
||||
"lodash": "^4.17.20",
|
||||
"vue": "^2.6.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nextcloud/vue": "^3.1.2",
|
||||
"vue": "^2.6.12"
|
||||
}
|
||||
},
|
||||
"node_modules/nextcloud-vue-collections/node_modules/@nextcloud/browserslist-config": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-1.0.0.tgz",
|
||||
"integrity": "sha512-f+sKpdLZXkODV+OY39K1M+Spmd4RgxmtEXmNn4Bviv4R7uBFHXuw+JX9ZdfDeOryfHjJ/TRQxQEp0GMpBwZFUw=="
|
||||
},
|
||||
"node_modules/nextcloud-vue-collections/node_modules/@nextcloud/router": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-1.2.0.tgz",
|
||||
"integrity": "sha512-kn9QsL9LuhkIMaSSgdiqRL3SZ6PatuAjXUiyq343BbSnI99Oc5eJH8kU6cT2AHije7wKy/tK8Xe3VQuVO32SZQ==",
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz",
|
||||
|
@ -21229,6 +21263,34 @@
|
|||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"peer": true
|
||||
},
|
||||
"nextcloud-vue-collections": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/nextcloud-vue-collections/-/nextcloud-vue-collections-0.9.0.tgz",
|
||||
"integrity": "sha512-GItjPWV4O53CNRPxdRegjEpZUM2ZV1mun2gVM/tLr3Nc2WzchgjAEzHjLxWomdW7kRv0sFJNS20udYJ2wEX76Q==",
|
||||
"requires": {
|
||||
"@nextcloud/axios": "^1.5.0",
|
||||
"@nextcloud/browserslist-config": "^1.0.0",
|
||||
"@nextcloud/router": "^1.2.0",
|
||||
"@nextcloud/vue": "^3.1.2",
|
||||
"lodash": "^4.17.20",
|
||||
"vue": "^2.6.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextcloud/browserslist-config": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/browserslist-config/-/browserslist-config-1.0.0.tgz",
|
||||
"integrity": "sha512-f+sKpdLZXkODV+OY39K1M+Spmd4RgxmtEXmNn4Bviv4R7uBFHXuw+JX9ZdfDeOryfHjJ/TRQxQEp0GMpBwZFUw=="
|
||||
},
|
||||
"@nextcloud/router": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-1.2.0.tgz",
|
||||
"integrity": "sha512-kn9QsL9LuhkIMaSSgdiqRL3SZ6PatuAjXUiyq343BbSnI99Oc5eJH8kU6cT2AHije7wKy/tK8Xe3VQuVO32SZQ==",
|
||||
"requires": {
|
||||
"core-js": "^3.6.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.2.1.tgz",
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"humanize-duration": "^3.27.1",
|
||||
"linkify-it": "^3.0.3",
|
||||
"lodash": "^4.17.21",
|
||||
"nextcloud-vue-collections": "^0.9.0",
|
||||
"sanitize-html": "^2.7.0",
|
||||
"vue": "^2.6.14",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import FolderPickerDialog from './components/FolderPickerDialog'
|
||||
|
||||
// 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/')
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
},
|
||||
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()
|
||||
})
|
||||
})
|
||||
},
|
||||
typeString: t('bookmarks', 'Link to a bookmark folder'),
|
||||
typeIconClass: 'icon-favorite',
|
||||
})
|
||||
})(window.OCP, window.OC)
|
|
@ -28,7 +28,7 @@ export default {
|
|||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
@ -40,6 +40,7 @@ export default {
|
|||
methods: {
|
||||
onSelect(folderId) {
|
||||
this.$emit('input', folderId)
|
||||
this.$emit('select', folderId)
|
||||
this.$emit('close')
|
||||
},
|
||||
onClose() {
|
||||
|
|
|
@ -95,6 +95,15 @@
|
|||
<a class="button" :href="archivedFile" target="_blank"><span class="icon-files-dark" /> {{ t('bookmarks', 'Open file location') }}</a>
|
||||
</div>
|
||||
</AppSidebarTab>
|
||||
<AppSidebarTab id="bookmark-projects"
|
||||
:name="t('bookmarks', 'Projects')"
|
||||
icon="icon-projects"
|
||||
:order="1">
|
||||
<CollectionList v-if="bookmark"
|
||||
:id="''+bookmark.id"
|
||||
:name="bookmark.title"
|
||||
type="bookmarks" />
|
||||
</AppSidebarTab>
|
||||
</AppSidebar>
|
||||
</template>
|
||||
<script>
|
||||
|
@ -106,6 +115,7 @@ import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
|||
import RichContenteditable from '@nextcloud/vue/dist/Components/RichContenteditable'
|
||||
import FileDocumentIcon from 'vue-material-design-icons/FileDocument'
|
||||
import FolderIcon from 'vue-material-design-icons/Folder'
|
||||
import { CollectionList } from 'nextcloud-vue-collections'
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
|
||||
|
@ -116,7 +126,7 @@ const MAX_RELATIVE_DATE = 1000 * 60 * 60 * 24 * 7 // one week
|
|||
|
||||
export default {
|
||||
name: 'SidebarBookmark',
|
||||
components: { AppSidebar, AppSidebarTab, Multiselect, Actions, ActionButton, RichContenteditable, FileDocumentIcon, FolderIcon },
|
||||
components: { AppSidebar, AppSidebarTab, Multiselect, Actions, ActionButton, RichContenteditable, FileDocumentIcon, FolderIcon, CollectionList },
|
||||
data() {
|
||||
return {
|
||||
title: '',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
script('bookmarks', 'bookmarks-main');
|
||||
\OC::$server->getEventDispatcher()->dispatch('\OCP\Collaboration\Resources::loadAdditionalScripts');
|
||||
?>
|
||||
<div id="vue-content"></div>
|
||||
|
|
|
@ -8,3 +8,4 @@ webpackConfig.entry['service-worker'] = path.join(__dirname, 'src', 'service-wor
|
|||
webpackConfig.entry.flow = path.join(__dirname, 'src', 'flow.js')
|
||||
webpackConfig.entry.dashboard = path.join(__dirname, 'src', 'dashboard.js')
|
||||
webpackConfig.entry.talk = path.join(__dirname, 'src', 'talk.js')
|
||||
webpackConfig.entry.collections = path.join(__dirname, 'src', 'collections.js')
|
||||
|
|
Loading…
Reference in New Issue