diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index d48ecc0b..2855f030 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -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');
+ });
}
}
diff --git a/lib/Collaboration/Resources/ResourceProvider.php b/lib/Collaboration/Resources/ResourceProvider.php
new file mode 100644
index 00000000..f7619320
--- /dev/null
+++ b/lib/Collaboration/Resources/ResourceProvider.php
@@ -0,0 +1,96 @@
+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;
+ }
+ }
+}
diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php
index 4ea079a9..e6048feb 100644
--- a/lib/Db/BookmarkMapper.php
+++ b/lib/Db/BookmarkMapper.php
@@ -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)
diff --git a/package-lock.json b/package-lock.json
index 22f39442..b8729292 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index bd1cff9f..72d07663 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/collections.js b/src/collections.js
new file mode 100644
index 00000000..c81d16f8
--- /dev/null
+++ b/src/collections.js
@@ -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)
diff --git a/src/components/FolderPickerDialog.vue b/src/components/FolderPickerDialog.vue
index 69eae88a..33010ac5 100644
--- a/src/components/FolderPickerDialog.vue
+++ b/src/components/FolderPickerDialog.vue
@@ -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() {
diff --git a/src/components/SidebarBookmark.vue b/src/components/SidebarBookmark.vue
index eec77ec9..8523e9a6 100644
--- a/src/components/SidebarBookmark.vue
+++ b/src/components/SidebarBookmark.vue
@@ -95,6 +95,15 @@
{{ t('bookmarks', 'Open file location') }}
+
+
+