From e6ab50150b0a0736212d347e7dde68021c74b415 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 21 Feb 2025 14:44:52 +0100 Subject: [PATCH 01/16] feat: Add a ContextChat provider Signed-off-by: Marcel Klehr --- lib/AppInfo/Application.php | 2 + lib/ContextChat/ContextChatProvider.php | 131 ++++++++++++++++++++++++ lib/Db/BookmarkMapper.php | 52 ++++++++++ lib/Service/BookmarkService.php | 103 ++++--------------- 4 files changed, 207 insertions(+), 81 deletions(-) create mode 100644 lib/ContextChat/ContextChatProvider.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index f676ac0bb..7fd540e7a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -97,6 +97,8 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); $context->registerMiddleware(ExceptionMiddleware::class); + + $context->registerEventListener('OCA\ContextChat\Event\ContentProviderRegisterEvent', 'OCA\Bookmarks\ContextChat\ContextChatProvider'); } /** diff --git a/lib/ContextChat/ContextChatProvider.php b/lib/ContextChat/ContextChatProvider.php new file mode 100644 index 000000000..cd0a77859 --- /dev/null +++ b/lib/ContextChat/ContextChatProvider.php @@ -0,0 +1,131 @@ +register($event); + return; + } + if ($event instanceof CreateEvent || $event instanceof UpdateEvent) { + if ($event->getType() !== TreeMapper::TYPE_BOOKMARK) { + return; + } + $bookmark = $this->bookmarkService->findById($event->getId()); + $item = new ContentItem( + (string)$event->getId(), + $this->getId(), + $bookmark->getTitle(), + $bookmark->getTextContent(), + 'Website', + new \DateTime('@' . $bookmark->getLastmodified()), + [$bookmark->getUserId()] + ); + $this->contentManager->submitContent($this->getAppId(), [$item]); + return; + } + if ($event instanceof BeforeDeleteEvent) { + if ($event->getType() !== TreeMapper::TYPE_BOOKMARK) { + return; + } + $this->contentManager->deleteContent($this->getAppId(), $this->getId(), [(string)$event->getId()]); + return; + } + } + + public function register(ContentProviderRegisterEvent $event): void { + $event->registerContentProvider($this->getAppId(), $this->getId(), self::class); + $this->eventDispatcher->addServiceListener(CreateEvent::class, self::class); + $this->eventDispatcher->addServiceListener(UpdateEvent::class, self::class); + $this->eventDispatcher->addServiceListener(BeforeDeleteEvent::class, self::class); + } + + /** + * The ID of the provider + * + * @return string + * @since 1.1.0 + */ + public function getId(): string { + return 'bookmarks'; + } + + /** + * The ID of the app making the provider avaialble + * + * @return string + * @since 1.1.0 + */ + public function getAppId(): string { + return Application::APP_ID; + } + + /** + * The absolute URL to the content item + * + * @param string $id + * @return string + * @since 1.1.0 + */ + public function getItemUrl(string $id): string { + return $this->bookmarkService->findById(intval($id))->getUrl(); + } + + /** + * Starts the initial import of content items into content chat + * + * @return void + * @since 1.1.0 + */ + public function triggerInitialImport(): void { + $this->userManager->callForAllUsers(function (IUser $user) { + $items = []; + foreach ($this->bookmarkService->getIterator($user->getUID()) as $bookmark) { + $items[] = new ContentItem( + (string)$bookmark->getId(), + $this->getId(), + $bookmark->getTitle(), + $bookmark->getTextContent(), + 'Website', + new \DateTime('@' . $bookmark->getLastmodified()), + [$user->getUID()] + ); + if (count($items) < 25) { + continue; + } + $this->contentManager->submitContent($this->getAppId(), $items); + } + }); + } +} diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index 9c7c370b1..b9c72c78d 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -257,6 +257,21 @@ protected function findEntitiesWithRawQuery(string $query, array $params, array return $entities; } + /** + * @throws \OCP\DB\Exception + */ + protected function getIteratorWithRawQuery(string $query, array $params, array $types): \Generator { + $cursor = $this->db->executeQuery($query, $params, $types); + + $entities = []; + + while ($row = $cursor->fetch()) { + yield $this->mapRowToEntity($row); + } + + $cursor->closeCursor(); + } + /** * Common table expression that lists all items in a given folder, recursively * @param int $folderId @@ -904,4 +919,41 @@ private function _findSharersFor(string $userId) :array { return $share->getOwner(); }, $this->shareMapper->findByUser($userId)); } + + public function getIterator(string $userId, QueryParameters $queryParams): \Generator { + $rootFolder = $this->folderMapper->findRootFolder($userId); + // gives us all bookmarks in this folder, recursively + [$cte, $params, $paramTypes] = $this->_generateCTE($rootFolder->getId(), $queryParams->getSoftDeletedFolders()); + + $qb = $this->db->getQueryBuilder(); + $bookmark_cols = array_map(static function ($c) { + return 'b.' . $c; + }, Bookmark::$columns); + + $qb->select($bookmark_cols); + $qb->groupBy($bookmark_cols); + + $qb->automaticTablePrefix(false); + + $qb + ->from('*PREFIX*bookmarks', 'b') + ->innerJoin('b', 'folder_tree', 'tree', 'tree.item_id = b.id AND tree.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . + ($queryParams->getSoftDeleted() ? ' AND tree.soft_deleted_at is NOT NULL' : ' AND tree.soft_deleted_at is NULL')); + + $this->_filterUrl($qb, $queryParams); + $this->_filterArchived($qb, $queryParams); + $this->_filterUnavailable($qb, $queryParams); + $this->_filterDuplicated($qb, $queryParams); + $this->_filterFolder($qb, $queryParams); + $this->_filterTags($qb, $queryParams); + $this->_filterUntagged($qb, $queryParams); + $this->_filterSearch($qb, $queryParams); + $this->_sortAndPaginate($qb, $queryParams); + + $finalQuery = $cte . ' ' . $qb->getSQL(); + + $params = array_merge($params, $qb->getParameters()); + $paramTypes = array_merge($paramTypes, $qb->getParameterTypes()); + return $this->getIteratorWithRawQuery($finalQuery, $params, $paramTypes); + } } diff --git a/lib/Service/BookmarkService.php b/lib/Service/BookmarkService.php index bdccd75f0..4b6e5c5ed 100644 --- a/lib/Service/BookmarkService.php +++ b/lib/Service/BookmarkService.php @@ -31,88 +31,21 @@ class BookmarkService { public const PROTOCOLS_REGEX = '/^(https?|s?ftp|file|javascript):/i'; - /** - * @var BookmarkMapper - */ - private $bookmarkMapper; - /** - * @var TreeMapper - */ - private $treeMapper; - /** - * @var LinkExplorer - */ - private $linkExplorer; - /** - * @var FolderMapper - */ - private $folderMapper; - /** - * @var TagMapper - */ - private $tagMapper; - /** - * @var BookmarkPreviewer - */ - private $bookmarkPreviewer; - /** - * @var FaviconPreviewer - */ - private $faviconPreviewer; - /** - * @var FolderService - */ - private $folders; - /** - * @var IEventDispatcher - */ - private $eventDispatcher; - /** - * @var TreeCacheManager - */ - private $hashManager; - private $urlNormalizer; - /** - * @var CrawlService - */ - private $crawler; - /** - * @var IJobList - */ - private $jobList; - - /** - * BookmarksService constructor. - * - * @param BookmarkMapper $bookmarkMapper - * @param FolderMapper $folderMapper - * @param TagMapper $tagMapper - * @param TreeMapper $treeMapper - * @param LinkExplorer $linkExplorer - * @param BookmarkPreviewer $bookmarkPreviewer - * @param FaviconPreviewer $faviconPreviewer - * @param FolderService $folders - * @param IEventDispatcher $eventDispatcher - * @param TreeCacheManager $hashManager - * @param Authorizer $authorizer - * @param CrawlService $crawler - * @param IJobList $jobList - */ - public function __construct(BookmarkMapper $bookmarkMapper, FolderMapper $folderMapper, TagMapper $tagMapper, TreeMapper $treeMapper, LinkExplorer $linkExplorer, BookmarkPreviewer $bookmarkPreviewer, FaviconPreviewer $faviconPreviewer, FolderService $folders, IEventDispatcher $eventDispatcher, \OCA\Bookmarks\Service\TreeCacheManager $hashManager, UrlNormalizer $urlNormalizer, CrawlService $crawler, IJobList $jobList) { - $this->bookmarkMapper = $bookmarkMapper; - $this->treeMapper = $treeMapper; - $this->linkExplorer = $linkExplorer; - $this->folderMapper = $folderMapper; - $this->tagMapper = $tagMapper; - $this->bookmarkPreviewer = $bookmarkPreviewer; - $this->faviconPreviewer = $faviconPreviewer; - $this->folders = $folders; - $this->eventDispatcher = $eventDispatcher; - $this->hashManager = $hashManager; - $this->urlNormalizer = $urlNormalizer; - $this->crawler = $crawler; - $this->jobList = $jobList; + public function __construct( + private BookmarkMapper $bookmarkMapper, + private FolderMapper $folderMapper, + private TagMapper $tagMapper, + private TreeMapper $treeMapper, + private LinkExplorer $linkExplorer, + private BookmarkPreviewer $bookmarkPreviewer, + private FaviconPreviewer $faviconPreviewer, + private FolderService $folders, + private IEventDispatcher $eventDispatcher, + private \OCA\Bookmarks\Service\TreeCacheManager $hashManager, + private UrlNormalizer $urlNormalizer, + private IJobList $jobList, + ) { } /** @@ -544,4 +477,12 @@ public function deleteAll(string $userId): void { $this->hashManager->setInvalidationEnabled(true); $this->hashManager->invalidateFolder($rootFolder->getId()); } + + /** + * @param string $userId + * @return \Generator + */ + public function getIterator(string $userId): \Generator { + return $this->bookmarkMapper->getIterator($userId, new QueryParameters()); + } } From 1cb590b29104ac846e93119760428733819c1f37 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 21 Feb 2025 14:55:52 +0100 Subject: [PATCH 02/16] fix: Fix psalm errors Signed-off-by: Marcel Klehr --- lib/ContextChat/ContextChatProvider.php | 6 ++- lib/Service/BookmarkService.php | 2 +- tests/stub.phpstub | 65 +++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/lib/ContextChat/ContextChatProvider.php b/lib/ContextChat/ContextChatProvider.php index cd0a77859..74b245977 100644 --- a/lib/ContextChat/ContextChatProvider.php +++ b/lib/ContextChat/ContextChatProvider.php @@ -20,10 +20,14 @@ use OCA\ContextChat\Public\IContentProvider; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; +use OCP\EventDispatcher\IEventListener; use OCP\IUser; use OCP\IUserManager; -class ContextChatProvider implements IContentProvider { +/** + * @implements IEventListener + */ +class ContextChatProvider implements IContentProvider, IEventListener { public function __construct( private BookmarkService $bookmarkService, diff --git a/lib/Service/BookmarkService.php b/lib/Service/BookmarkService.php index 4b6e5c5ed..1889fba05 100644 --- a/lib/Service/BookmarkService.php +++ b/lib/Service/BookmarkService.php @@ -480,7 +480,7 @@ public function deleteAll(string $userId): void { /** * @param string $userId - * @return \Generator + * @return \Generator */ public function getIterator(string $userId): \Generator { return $this->bookmarkMapper->getIterator($userId, new QueryParameters()); diff --git a/tests/stub.phpstub b/tests/stub.phpstub index d1b681740..d1d9cc3ae 100644 --- a/tests/stub.phpstub +++ b/tests/stub.phpstub @@ -139,3 +139,68 @@ namespace Doctrine\DBAL { public const ARRAY_PARAM_OFFSET = 100; } } + +namespace OCA\ContextChat\Public { +/** + * This interface defines methods to implement a content provider + * @since 1.1.0 + */ +interface IContentProvider { + /** + * The ID of the provider + * + * @return string + * @since 1.1.0 + */ + public function getId(): string; + + /** + * The ID of the app making the provider avaialble + * + * @return string + * @since 1.1.0 + */ + public function getAppId(): string; + + /** + * The absolute URL to the content item + * + * @param string $id + * @return string + * @since 1.1.0 + */ + public function getItemUrl(string $id): string; + + /** + * Starts the initial import of content items into content chat + * + * @return void + * @since 1.1.0 + */ + public function triggerInitialImport(): void; +} + +class ContentManager { + public function registerContentProvider(string $providerClass); + public function submitContent(string $appId, array $items); + public function deleteContent(string $appId, string $providerId, array $itemIds); +} + +class ContentItem { + public function __construct( + string $itemId, + string $providerId, + string $title, + string $content, + string $documentType, + \DateTime $lastModified, + array $users, + ); +} +} + +namespace OCA\ContextChat\Event { +class ContentProviderRegisterEvent { + public function registerContentProvider(string $appId, string $provider, string $class); +} +} \ No newline at end of file From 70fffc303a598decf54550065b9fe151b81f67cd Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Feb 2025 11:04:01 +0100 Subject: [PATCH 03/16] fix(ContextChat): Make context chat work --- lib/AppInfo/Application.php | 3 ++- lib/ContextChat/ContextChatProvider.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 7fd540e7a..5771b1cad 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -98,7 +98,7 @@ public function register(IRegistrationContext $context): void { $context->registerMiddleware(ExceptionMiddleware::class); - $context->registerEventListener('OCA\ContextChat\Event\ContentProviderRegisterEvent', 'OCA\Bookmarks\ContextChat\ContextChatProvider'); + $context->registerEventListener(\OCA\ContextChat\Event\ContentProviderRegisterEvent::class, \OCA\Bookmarks\ContextChat\ContextChatProvider::class); } /** @@ -107,6 +107,7 @@ public function register(IRegistrationContext $context): void { * @throws \Throwable */ public function boot(IBootContext $context): void { + $this->getContainer()->get(\OCA\Bookmarks\ContextChat\ContextChatProvider::class)->register(); $container = $context->getServerContainer(); CreateBookmark::register($container->get(IEventDispatcher::class)); } diff --git a/lib/ContextChat/ContextChatProvider.php b/lib/ContextChat/ContextChatProvider.php index 74b245977..f9c9aa259 100644 --- a/lib/ContextChat/ContextChatProvider.php +++ b/lib/ContextChat/ContextChatProvider.php @@ -68,8 +68,8 @@ public function handle(Event $event): void { } } - public function register(ContentProviderRegisterEvent $event): void { - $event->registerContentProvider($this->getAppId(), $this->getId(), self::class); + public function register(): void { + $this->contentManager->registerContentProvider($this->getAppId(), $this->getId(), self::class); $this->eventDispatcher->addServiceListener(CreateEvent::class, self::class); $this->eventDispatcher->addServiceListener(UpdateEvent::class, self::class); $this->eventDispatcher->addServiceListener(BeforeDeleteEvent::class, self::class); From 2b549237a71ef957459420c7b010bcabc1b3d6ca Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Feb 2025 16:46:56 +0100 Subject: [PATCH 04/16] fix: Psalm issue Signed-off-by: Marcel Klehr --- tests/stub.phpstub | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/stub.phpstub b/tests/stub.phpstub index d1d9cc3ae..4d9db0c33 100644 --- a/tests/stub.phpstub +++ b/tests/stub.phpstub @@ -181,7 +181,7 @@ interface IContentProvider { } class ContentManager { - public function registerContentProvider(string $providerClass); + public function registerContentProvider(string, $appId, string $providerId, string $providerClass); public function submitContent(string $appId, array $items); public function deleteContent(string $appId, string $providerId, array $itemIds); } @@ -200,7 +200,7 @@ class ContentItem { } namespace OCA\ContextChat\Event { -class ContentProviderRegisterEvent { +class ContentProviderRegisterEvent extends \OCP\EventDispatcher\Event { public function registerContentProvider(string $appId, string $provider, string $class); } } \ No newline at end of file From 55cf3290d3b0451b188763062a09feb63d0f661c Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Feb 2025 16:57:39 +0100 Subject: [PATCH 05/16] fix(ContextChat): Listen for new db-level change events Signed-off-by: Marcel Klehr --- lib/ContextChat/ContextChatProvider.php | 10 ++++++---- lib/Db/BookmarkMapper.php | 7 ++++++- lib/Events/InsertEvent.php | 16 ++++++++++++++++ lib/Events/ManipulateEvent.php | 16 ++++++++++++++++ 4 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 lib/Events/InsertEvent.php create mode 100644 lib/Events/ManipulateEvent.php diff --git a/lib/ContextChat/ContextChatProvider.php b/lib/ContextChat/ContextChatProvider.php index f9c9aa259..fe81aec30 100644 --- a/lib/ContextChat/ContextChatProvider.php +++ b/lib/ContextChat/ContextChatProvider.php @@ -12,6 +12,8 @@ use OCA\Bookmarks\Db\TreeMapper; use OCA\Bookmarks\Events\BeforeDeleteEvent; use OCA\Bookmarks\Events\CreateEvent; +use OCA\Bookmarks\Events\InsertEvent; +use OCA\Bookmarks\Events\ManipulateEvent; use OCA\Bookmarks\Events\UpdateEvent; use OCA\Bookmarks\Service\BookmarkService; use OCA\ContextChat\Event\ContentProviderRegisterEvent; @@ -39,10 +41,10 @@ public function __construct( public function handle(Event $event): void { if ($event instanceof ContentProviderRegisterEvent) { - $this->register($event); + $this->register(); return; } - if ($event instanceof CreateEvent || $event instanceof UpdateEvent) { + if ($event instanceof InsertEvent || $event instanceof ManipulateEvent) { if ($event->getType() !== TreeMapper::TYPE_BOOKMARK) { return; } @@ -70,8 +72,8 @@ public function handle(Event $event): void { public function register(): void { $this->contentManager->registerContentProvider($this->getAppId(), $this->getId(), self::class); - $this->eventDispatcher->addServiceListener(CreateEvent::class, self::class); - $this->eventDispatcher->addServiceListener(UpdateEvent::class, self::class); + $this->eventDispatcher->addServiceListener(InsertEvent::class, self::class); + $this->eventDispatcher->addServiceListener(ManipulateEvent::class, self::class); $this->eventDispatcher->addServiceListener(BeforeDeleteEvent::class, self::class); } diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index b9c72c78d..c070c590d 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -9,6 +9,8 @@ namespace OCA\Bookmarks\Db; use OCA\Bookmarks\Events\BeforeDeleteEvent; +use OCA\Bookmarks\Events\InsertEvent; +use OCA\Bookmarks\Events\ManipulateEvent; use OCA\Bookmarks\Exception\AlreadyExistsError; use OCA\Bookmarks\Exception\UrlParseError; use OCA\Bookmarks\Exception\UserLimitExceededError; @@ -788,7 +790,9 @@ public function update(Entity $entity): Bookmark { $entity->setUrl($this->urlNormalizer->normalize($entity->getUrl())); } $entity->setLastmodified(time()); - return parent::update($entity); + parent::update($entity); + $this->eventDispatcher->dispatchTyped(new ManipulateEvent('bookmark', $entity->getId())); + return $entity; } /** @@ -821,6 +825,7 @@ public function insert(Entity $entity): Bookmark { $this->findByUrl($entity->getUserId(), $entity->getUrl()); } catch (DoesNotExistException $e) { parent::insert($entity); + $this->eventDispatcher->dispatchTyped(new InsertEvent('bookmark', $entity->getId())); return $entity; } catch (MultipleObjectsReturnedException $e) { // noop diff --git a/lib/Events/InsertEvent.php b/lib/Events/InsertEvent.php new file mode 100644 index 000000000..3fe08170f --- /dev/null +++ b/lib/Events/InsertEvent.php @@ -0,0 +1,16 @@ + Date: Sat, 22 Feb 2025 16:59:29 +0100 Subject: [PATCH 06/16] fix(ContextChat): refactor registration Signed-off-by: Marcel Klehr --- lib/AppInfo/Application.php | 12 ++++++++++-- lib/ContextChat/ContextChatProvider.php | 3 --- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 5771b1cad..d4f872e9e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -9,12 +9,15 @@ namespace OCA\Bookmarks\AppInfo; use OCA\Bookmarks\Activity\ActivityPublisher; +use OCA\Bookmarks\ContextChat\ContextChatProvider; use OCA\Bookmarks\Dashboard\Frequent; use OCA\Bookmarks\Dashboard\Recent; use OCA\Bookmarks\Events\BeforeDeleteEvent; use OCA\Bookmarks\Events\BeforeSoftDeleteEvent; use OCA\Bookmarks\Events\BeforeSoftUndeleteEvent; use OCA\Bookmarks\Events\CreateEvent; +use OCA\Bookmarks\Events\InsertEvent; +use OCA\Bookmarks\Events\ManipulateEvent; use OCA\Bookmarks\Events\MoveEvent; use OCA\Bookmarks\Events\UpdateEvent; use OCA\Bookmarks\Flow\CreateBookmark; @@ -24,6 +27,7 @@ use OCA\Bookmarks\Reference\BookmarkReferenceProvider; use OCA\Bookmarks\Search\Provider; use OCA\Bookmarks\Service\TreeCacheManager; +use OCA\ContextChat\Event\ContentProviderRegisterEvent; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -98,7 +102,11 @@ public function register(IRegistrationContext $context): void { $context->registerMiddleware(ExceptionMiddleware::class); - $context->registerEventListener(\OCA\ContextChat\Event\ContentProviderRegisterEvent::class, \OCA\Bookmarks\ContextChat\ContextChatProvider::class); + $context->registerEventListener(InsertEvent::class, ContextChatProvider::class); + $context->registerEventListener(ManipulateEvent::class, ContextChatProvider::class); + $context->registerEventListener(BeforeDeleteEvent::class, ContextChatProvider::class); + + $context->registerEventListener(ContentProviderRegisterEvent::class, ContextChatProvider::class); } /** @@ -107,7 +115,7 @@ public function register(IRegistrationContext $context): void { * @throws \Throwable */ public function boot(IBootContext $context): void { - $this->getContainer()->get(\OCA\Bookmarks\ContextChat\ContextChatProvider::class)->register(); + $this->getContainer()->get(ContextChatProvider::class)->register(); $container = $context->getServerContainer(); CreateBookmark::register($container->get(IEventDispatcher::class)); } diff --git a/lib/ContextChat/ContextChatProvider.php b/lib/ContextChat/ContextChatProvider.php index fe81aec30..8f5e4aa83 100644 --- a/lib/ContextChat/ContextChatProvider.php +++ b/lib/ContextChat/ContextChatProvider.php @@ -72,9 +72,6 @@ public function handle(Event $event): void { public function register(): void { $this->contentManager->registerContentProvider($this->getAppId(), $this->getId(), self::class); - $this->eventDispatcher->addServiceListener(InsertEvent::class, self::class); - $this->eventDispatcher->addServiceListener(ManipulateEvent::class, self::class); - $this->eventDispatcher->addServiceListener(BeforeDeleteEvent::class, self::class); } /** From 159c585fcbb8fb62aff2b7f106de76687225294b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Feb 2025 17:07:02 +0100 Subject: [PATCH 07/16] fix(ContextChat): refactor registration Signed-off-by: Marcel Klehr --- lib/AppInfo/Application.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index d4f872e9e..0879e1f73 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -102,11 +102,12 @@ public function register(IRegistrationContext $context): void { $context->registerMiddleware(ExceptionMiddleware::class); - $context->registerEventListener(InsertEvent::class, ContextChatProvider::class); - $context->registerEventListener(ManipulateEvent::class, ContextChatProvider::class); - $context->registerEventListener(BeforeDeleteEvent::class, ContextChatProvider::class); - - $context->registerEventListener(ContentProviderRegisterEvent::class, ContextChatProvider::class); + if (class_exists(ContentProviderRegisterEvent::class)) { + $context->registerEventListener(InsertEvent::class, ContextChatProvider::class); + $context->registerEventListener(ManipulateEvent::class, ContextChatProvider::class); + $context->registerEventListener(BeforeDeleteEvent::class, ContextChatProvider::class); + $context->registerEventListener(ContentProviderRegisterEvent::class, ContextChatProvider::class); + } } /** @@ -115,7 +116,9 @@ public function register(IRegistrationContext $context): void { * @throws \Throwable */ public function boot(IBootContext $context): void { - $this->getContainer()->get(ContextChatProvider::class)->register(); + if (class_exists(ContentProviderRegisterEvent::class)) { + $this->getContainer()->get(ContextChatProvider::class)->register(); + } $container = $context->getServerContainer(); CreateBookmark::register($container->get(IEventDispatcher::class)); } From 54f49b185ba557535147109efc20e72490cd1c22 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Feb 2025 17:31:45 +0100 Subject: [PATCH 08/16] fix: run cs:fix Signed-off-by: Marcel Klehr --- lib/ContextChat/ContextChatProvider.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/ContextChat/ContextChatProvider.php b/lib/ContextChat/ContextChatProvider.php index 8f5e4aa83..2dd4e7b75 100644 --- a/lib/ContextChat/ContextChatProvider.php +++ b/lib/ContextChat/ContextChatProvider.php @@ -11,10 +11,8 @@ use OCA\Bookmarks\AppInfo\Application; use OCA\Bookmarks\Db\TreeMapper; use OCA\Bookmarks\Events\BeforeDeleteEvent; -use OCA\Bookmarks\Events\CreateEvent; use OCA\Bookmarks\Events\InsertEvent; use OCA\Bookmarks\Events\ManipulateEvent; -use OCA\Bookmarks\Events\UpdateEvent; use OCA\Bookmarks\Service\BookmarkService; use OCA\ContextChat\Event\ContentProviderRegisterEvent; use OCA\ContextChat\Public\ContentItem; From af4ff48712a803c5bc795f4453687524e945eb03 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Feb 2025 17:40:03 +0100 Subject: [PATCH 09/16] fix: refactor registration Signed-off-by: Marcel Klehr --- lib/AppInfo/Application.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 0879e1f73..850811079 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -102,12 +102,7 @@ public function register(IRegistrationContext $context): void { $context->registerMiddleware(ExceptionMiddleware::class); - if (class_exists(ContentProviderRegisterEvent::class)) { - $context->registerEventListener(InsertEvent::class, ContextChatProvider::class); - $context->registerEventListener(ManipulateEvent::class, ContextChatProvider::class); - $context->registerEventListener(BeforeDeleteEvent::class, ContextChatProvider::class); - $context->registerEventListener(ContentProviderRegisterEvent::class, ContextChatProvider::class); - } + $context->registerEventListener(ContentProviderRegisterEvent::class, ContextChatProvider::class); } /** @@ -118,6 +113,10 @@ public function register(IRegistrationContext $context): void { public function boot(IBootContext $context): void { if (class_exists(ContentProviderRegisterEvent::class)) { $this->getContainer()->get(ContextChatProvider::class)->register(); + $eventDispatcher = $this->getContainer()->get(IEventDispatcher::class); + $eventDispatcher->addServiceListener(InsertEvent::class, ContextChatProvider::class); + $eventDispatcher->addServiceListener(ManipulateEvent::class, ContextChatProvider::class); + $eventDispatcher->addServiceListener(BeforeDeleteEvent::class, ContextChatProvider::class); } $container = $context->getServerContainer(); CreateBookmark::register($container->get(IEventDispatcher::class)); From 6cadd1da9ae51eefec5b9c361c3a82c46c1468a3 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 22 Feb 2025 19:21:18 +0100 Subject: [PATCH 10/16] fix: fix null pointer exception Signed-off-by: Marcel Klehr --- lib/ContextChat/ContextChatProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ContextChat/ContextChatProvider.php b/lib/ContextChat/ContextChatProvider.php index 2dd4e7b75..26be7fd89 100644 --- a/lib/ContextChat/ContextChatProvider.php +++ b/lib/ContextChat/ContextChatProvider.php @@ -100,7 +100,7 @@ public function getAppId(): string { * @since 1.1.0 */ public function getItemUrl(string $id): string { - return $this->bookmarkService->findById(intval($id))->getUrl(); + return $this->bookmarkService->findById(intval($id))?->getUrl() ?? ''; } /** From 9be6b6692dd92e923931d8573a3e41e8167f8b06 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 28 Jul 2025 09:43:09 +0200 Subject: [PATCH 11/16] fix: Run ContextChat index in per-user bg jobs Signed-off-by: Marcel Klehr --- lib/AppInfo/Application.php | 2 + lib/BackgroundJobs/ContextChatIndexJob.php | 58 ++++++++++++++++++++++ lib/ContextChat/ContextChatProvider.php | 36 +++++--------- 3 files changed, 72 insertions(+), 24 deletions(-) create mode 100644 lib/BackgroundJobs/ContextChatIndexJob.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 850811079..973a7f934 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -111,6 +111,7 @@ public function register(IRegistrationContext $context): void { * @throws \Throwable */ public function boot(IBootContext $context): void { + // Register with ContextChat if (class_exists(ContentProviderRegisterEvent::class)) { $this->getContainer()->get(ContextChatProvider::class)->register(); $eventDispatcher = $this->getContainer()->get(IEventDispatcher::class); @@ -118,6 +119,7 @@ public function boot(IBootContext $context): void { $eventDispatcher->addServiceListener(ManipulateEvent::class, ContextChatProvider::class); $eventDispatcher->addServiceListener(BeforeDeleteEvent::class, ContextChatProvider::class); } + // Register with Nextcloud Flow $container = $context->getServerContainer(); CreateBookmark::register($container->get(IEventDispatcher::class)); } diff --git a/lib/BackgroundJobs/ContextChatIndexJob.php b/lib/BackgroundJobs/ContextChatIndexJob.php new file mode 100644 index 000000000..427b3c8b6 --- /dev/null +++ b/lib/BackgroundJobs/ContextChatIndexJob.php @@ -0,0 +1,58 @@ +contentManager === null) { + return; + } + if (!isset($argument['user'])) { + return; + } + $user = $this->userManager->get($argument['user']); + if ($user === null) { + return; + } + $items = []; + foreach ($this->bookmarkService->getIterator($user->getUID()) as $bookmark) { + $items[] = new ContentItem( + (string)$bookmark->getId(), + $this->getId(), + $bookmark->getTitle(), + $bookmark->getTextContent(), + 'Website', + new \DateTime('@' . $bookmark->getLastmodified()), + [$user->getUID()] + ); + if (count($items) < 25) { + continue; + } + $this->contentManager->submitContent(Application::APP_ID, $items); + } + } +} diff --git a/lib/ContextChat/ContextChatProvider.php b/lib/ContextChat/ContextChatProvider.php index 26be7fd89..5b1081fcc 100644 --- a/lib/ContextChat/ContextChatProvider.php +++ b/lib/ContextChat/ContextChatProvider.php @@ -9,6 +9,7 @@ namespace OCA\Bookmarks\ContextChat; use OCA\Bookmarks\AppInfo\Application; +use OCA\Bookmarks\BackgroundJobs\ContextChatIndexJob; use OCA\Bookmarks\Db\TreeMapper; use OCA\Bookmarks\Events\BeforeDeleteEvent; use OCA\Bookmarks\Events\InsertEvent; @@ -18,8 +19,8 @@ use OCA\ContextChat\Public\ContentItem; use OCA\ContextChat\Public\ContentManager; use OCA\ContextChat\Public\IContentProvider; +use OCP\BackgroundJob\IJobList; use OCP\EventDispatcher\Event; -use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventListener; use OCP\IUser; use OCP\IUserManager; @@ -32,12 +33,15 @@ class ContextChatProvider implements IContentProvider, IEventListener { public function __construct( private BookmarkService $bookmarkService, private IUserManager $userManager, - private ContentManager $contentManager, - private IEventDispatcher $eventDispatcher, + private ?ContentManager $contentManager, + private IJobList $jobList, ) { } public function handle(Event $event): void { + if ($this->contentManager === null) { + return; + } if ($event instanceof ContentProviderRegisterEvent) { $this->register(); return; @@ -47,6 +51,9 @@ public function handle(Event $event): void { return; } $bookmark = $this->bookmarkService->findById($event->getId()); + if ($bookmark === null) { + return; + } $item = new ContentItem( (string)$event->getId(), $this->getId(), @@ -76,17 +83,15 @@ public function register(): void { * The ID of the provider * * @return string - * @since 1.1.0 */ public function getId(): string { return 'bookmarks'; } /** - * The ID of the app making the provider avaialble + * The ID of the app making the provider available * * @return string - * @since 1.1.0 */ public function getAppId(): string { return Application::APP_ID; @@ -97,7 +102,6 @@ public function getAppId(): string { * * @param string $id * @return string - * @since 1.1.0 */ public function getItemUrl(string $id): string { return $this->bookmarkService->findById(intval($id))?->getUrl() ?? ''; @@ -107,26 +111,10 @@ public function getItemUrl(string $id): string { * Starts the initial import of content items into content chat * * @return void - * @since 1.1.0 */ public function triggerInitialImport(): void { $this->userManager->callForAllUsers(function (IUser $user) { - $items = []; - foreach ($this->bookmarkService->getIterator($user->getUID()) as $bookmark) { - $items[] = new ContentItem( - (string)$bookmark->getId(), - $this->getId(), - $bookmark->getTitle(), - $bookmark->getTextContent(), - 'Website', - new \DateTime('@' . $bookmark->getLastmodified()), - [$user->getUID()] - ); - if (count($items) < 25) { - continue; - } - $this->contentManager->submitContent($this->getAppId(), $items); - } + $this->jobList->add(ContextChatIndexJob::class, [$user->getUID()]); }); } } From 748dcf28ac59afa63ac43918c664ba09decd3eb8 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 28 Jul 2025 09:48:55 +0200 Subject: [PATCH 12/16] fix: psalm issues Signed-off-by: Marcel Klehr --- lib/BackgroundJobs/ContextChatIndexJob.php | 4 +++- psalm-baseline.xml | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/BackgroundJobs/ContextChatIndexJob.php b/lib/BackgroundJobs/ContextChatIndexJob.php index 427b3c8b6..79c246519 100644 --- a/lib/BackgroundJobs/ContextChatIndexJob.php +++ b/lib/BackgroundJobs/ContextChatIndexJob.php @@ -9,6 +9,7 @@ namespace OCA\Bookmarks\BackgroundJobs; use OCA\Bookmarks\AppInfo\Application; +use OCA\Bookmarks\ContextChat\ContextChatProvider; use OCA\Bookmarks\Service\BookmarkService; use OCA\ContextChat\Public\ContentItem; use OCA\ContextChat\Public\ContentManager; @@ -23,6 +24,7 @@ public function __construct( private BookmarkService $bookmarkService, private ?ContentManager $contentManager, private IUserManager $userManager, + private ContextChatProvider $provider, ) { parent::__construct($timeFactory); } @@ -42,7 +44,7 @@ protected function run($argument) { foreach ($this->bookmarkService->getIterator($user->getUID()) as $bookmark) { $items[] = new ContentItem( (string)$bookmark->getId(), - $this->getId(), + $this->provider->getId(), $bookmark->getTitle(), $bookmark->getTextContent(), 'Website', diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 5f0c857ed..549f11b4e 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + @@ -40,6 +40,7 @@ createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> + createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> createPositionalParameter(TreeMapper::TYPE_FOLDER)]]> createPositionalParameter(TreeMapper::TYPE_FOLDER)]]> createPositionalParameter(TreeMapper::TYPE_SHARE)]]> From d91141001232ca68acc86f370093fe3abcb7d934 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 28 Jul 2025 09:51:13 +0200 Subject: [PATCH 13/16] fix(ContextChatIndexJob): Fix correctness of index loop Signed-off-by: Marcel Klehr --- lib/BackgroundJobs/ContextChatIndexJob.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/BackgroundJobs/ContextChatIndexJob.php b/lib/BackgroundJobs/ContextChatIndexJob.php index 79c246519..7f1d30323 100644 --- a/lib/BackgroundJobs/ContextChatIndexJob.php +++ b/lib/BackgroundJobs/ContextChatIndexJob.php @@ -55,6 +55,10 @@ protected function run($argument) { continue; } $this->contentManager->submitContent(Application::APP_ID, $items); + $items = []; + } + if (count($items) > 0) { + $this->contentManager->submitContent(Application::APP_ID, $items); } } } From af790a63a746153ed0cfde10e97f884cb9a7b713 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 28 Jul 2025 10:03:04 +0200 Subject: [PATCH 14/16] fix: run cs:fix Signed-off-by: Marcel Klehr --- lib/Db/BookmarkMapper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index c070c590d..8646ab51b 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -942,8 +942,8 @@ public function getIterator(string $userId, QueryParameters $queryParams): \Gene $qb ->from('*PREFIX*bookmarks', 'b') - ->innerJoin('b', 'folder_tree', 'tree', 'tree.item_id = b.id AND tree.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . - ($queryParams->getSoftDeleted() ? ' AND tree.soft_deleted_at is NOT NULL' : ' AND tree.soft_deleted_at is NULL')); + ->innerJoin('b', 'folder_tree', 'tree', 'tree.item_id = b.id AND tree.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) + . ($queryParams->getSoftDeleted() ? ' AND tree.soft_deleted_at is NOT NULL' : ' AND tree.soft_deleted_at is NULL')); $this->_filterUrl($qb, $queryParams); $this->_filterArchived($qb, $queryParams); From 18ce04dc480121d3b49ce22e0f8a3d5020fe8153 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 28 Jul 2025 10:45:47 +0200 Subject: [PATCH 15/16] feat(contextchat): Users have to opt in to context chat integration Signed-off-by: Marcel Klehr --- lib/BackgroundJobs/ContextChatIndexJob.php | 6 ++++ lib/ContextChat/ContextChatProvider.php | 11 ++++++- lib/Controller/WebViewController.php | 6 ++++ lib/Service/UserSettingsService.php | 18 ++++++++-- src/components/Icons.js | 2 ++ src/components/Settings.vue | 38 ++++++++++++++++++++-- 6 files changed, 74 insertions(+), 7 deletions(-) diff --git a/lib/BackgroundJobs/ContextChatIndexJob.php b/lib/BackgroundJobs/ContextChatIndexJob.php index 7f1d30323..4ab6eab32 100644 --- a/lib/BackgroundJobs/ContextChatIndexJob.php +++ b/lib/BackgroundJobs/ContextChatIndexJob.php @@ -11,6 +11,7 @@ use OCA\Bookmarks\AppInfo\Application; use OCA\Bookmarks\ContextChat\ContextChatProvider; use OCA\Bookmarks\Service\BookmarkService; +use OCA\Bookmarks\Service\UserSettingsService; use OCA\ContextChat\Public\ContentItem; use OCA\ContextChat\Public\ContentManager; use OCP\AppFramework\Utility\ITimeFactory; @@ -25,6 +26,7 @@ public function __construct( private ?ContentManager $contentManager, private IUserManager $userManager, private ContextChatProvider $provider, + private UserSettingsService $userSettings, ) { parent::__construct($timeFactory); } @@ -40,6 +42,10 @@ protected function run($argument) { if ($user === null) { return; } + $this->userSettings->setUserId($user->getUID()); + if ($this->userSettings->get('contextchat.enabled') !== 'true') { + return; + } $items = []; foreach ($this->bookmarkService->getIterator($user->getUID()) as $bookmark) { $items[] = new ContentItem( diff --git a/lib/ContextChat/ContextChatProvider.php b/lib/ContextChat/ContextChatProvider.php index 5b1081fcc..cd2ffbc9b 100644 --- a/lib/ContextChat/ContextChatProvider.php +++ b/lib/ContextChat/ContextChatProvider.php @@ -12,9 +12,11 @@ use OCA\Bookmarks\BackgroundJobs\ContextChatIndexJob; use OCA\Bookmarks\Db\TreeMapper; use OCA\Bookmarks\Events\BeforeDeleteEvent; +use OCA\Bookmarks\Events\ChangeEvent; use OCA\Bookmarks\Events\InsertEvent; use OCA\Bookmarks\Events\ManipulateEvent; use OCA\Bookmarks\Service\BookmarkService; +use OCA\Bookmarks\Service\UserSettingsService; use OCA\ContextChat\Event\ContentProviderRegisterEvent; use OCA\ContextChat\Public\ContentItem; use OCA\ContextChat\Public\ContentManager; @@ -35,6 +37,7 @@ public function __construct( private IUserManager $userManager, private ?ContentManager $contentManager, private IJobList $jobList, + private UserSettingsService $userSettings, ) { } @@ -46,6 +49,12 @@ public function handle(Event $event): void { $this->register(); return; } + if (!$event instanceof ChangeEvent) { + return; + } + if ($this->userSettings->get('contextchat.enabled') !== 'true') { + return; + } if ($event instanceof InsertEvent || $event instanceof ManipulateEvent) { if ($event->getType() !== TreeMapper::TYPE_BOOKMARK) { return; @@ -114,7 +123,7 @@ public function getItemUrl(string $id): string { */ public function triggerInitialImport(): void { $this->userManager->callForAllUsers(function (IUser $user) { - $this->jobList->add(ContextChatIndexJob::class, [$user->getUID()]); + $this->jobList->add(ContextChatIndexJob::class, ['user' => $user->getUID()]); }); } } diff --git a/lib/Controller/WebViewController.php b/lib/Controller/WebViewController.php index fdc70b36c..e693a1199 100644 --- a/lib/Controller/WebViewController.php +++ b/lib/Controller/WebViewController.php @@ -15,6 +15,7 @@ use OCA\Bookmarks\Db\PublicFolderMapper; use OCA\Bookmarks\Service\SettingsService; use OCA\Bookmarks\Service\UserSettingsService; +use OCP\App\IAppManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; @@ -23,6 +24,7 @@ use OCP\AppFramework\Http\NotFoundResponse; use OCP\AppFramework\Http\StreamResponse; use OCP\AppFramework\Http\Template\PublicTemplateResponse; +use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; @@ -49,6 +51,8 @@ public function __construct( private \OCA\Bookmarks\Controller\InternalTagsController $tagsController, private UserSettingsService $userSettingsService, private SettingsService $settings, + private IAppManager $appManager, + private IConfig $config, ) { parent::__construct($appName, $request); $this->userId = $userId; @@ -78,6 +82,8 @@ public function index(): AugmentedTemplateResponse { $this->initialState->provideInitialState($this->appName, 'allClicksCount', $this->bookmarkController->countAllClicks()->getData()['item']); $this->initialState->provideInitialState($this->appName, 'withClicksCount', $this->bookmarkController->countWithClicks()->getData()['item']); $this->initialState->provideInitialState($this->appName, 'tags', $this->tagsController->fullTags(true)->getData()); + $this->initialState->provideInitialState($this->appName, 'contextChatInstalled', $this->appManager->isEnabledForUser('context_chat')); + $this->initialState->provideInitialState($this->appName, 'appStoreEnabled', $this->config->getSystemValueBool('appstoreenabled', true)); $settings = $this->userSettingsService->toArray(); $settings['shareapi_allow_links'] = $this->settings->getLinkSharingAllowed(); diff --git a/lib/Service/UserSettingsService.php b/lib/Service/UserSettingsService.php index 85ea9de53..d872743e7 100644 --- a/lib/Service/UserSettingsService.php +++ b/lib/Service/UserSettingsService.php @@ -8,18 +8,21 @@ namespace OCA\Bookmarks\Service; +use OCA\Bookmarks\BackgroundJobs\ContextChatIndexJob; +use OCP\BackgroundJob\IJobList; use OCP\IConfig; use OCP\IL10N; class UserSettingsService { - public const KEYS = ['hasSeenWhatsnew', 'viewMode', 'archive.enabled', 'archive.filePath', 'backup.enabled', 'backup.filePath', 'sorting']; + public const KEYS = ['hasSeenWhatsnew', 'viewMode', 'archive.enabled', 'archive.filePath', 'backup.enabled', 'backup.filePath', 'sorting', 'contextchat.enabled']; public function __construct( private ?string $userId, private string $appName, private IConfig $config, private IL10N $l, + private IJobList $jobList, ) { } @@ -46,7 +49,7 @@ public function get(string $key): string { return $this->config->getAppValue('bookmarks', 'performance.maxBookmarksperAccount', '0'); } if ($key === 'archive.enabled') { - $default = (string)true; + $default = 'true'; } if ($key === 'privacy.enableScraping') { return $this->config->getAppValue($this->appName, 'privacy.enableScraping', 'false'); @@ -55,11 +58,17 @@ public function get(string $key): string { $default = $this->l->t('Bookmarks'); } if ($key === 'backup.enabled') { - $default = (string)false; + $default = 'false'; } if ($key === 'backup.filePath') { $default = $this->l->t('Bookmarks Backups'); } + if ($key === 'contextchat.enabled') { + $default = 'false'; + if ($this->get('archive.enabled') !== 'true') { + return 'false'; + } + } return $this->config->getUserValue( $this->userId, $this->appName, @@ -84,6 +93,9 @@ public function set(string $key, string $value): void { if ($key === 'sorting' && !in_array($value, ['title', 'added', 'clickcount', 'lastmodified', 'index', 'url'], true)) { throw new \ValueError(); } + if ($key === 'contextchat.enabled' && $value === 'true' && $this->get('contextchat.enabled') !== 'true' && $this->get('archive.enabled') === 'true') { + $this->jobList->add(ContextChatIndexJob::class, ['user' => $this->userId]); + } $this->config->setUserValue( $this->userId, $this->appName, diff --git a/src/components/Icons.js b/src/components/Icons.js index 88c4cfb31..201b3bddf 100644 --- a/src/components/Icons.js +++ b/src/components/Icons.js @@ -66,8 +66,10 @@ import UndeleteIcon from 'vue-material-design-icons/Restore.vue' import HotnessZero from 'vue-material-design-icons/Minus.vue' import Hotness from 'vue-material-design-icons/Fire.vue' import BookmarksIcon from './icons/BookmarksIcon.vue' +import ContextChatIcon from 'vue-material-design-icons/ClipboardSearchOutline.vue' export { + ContextChatIcon, FolderPlusIcon, FolderMoveIcon, ContentCopyIcon, diff --git a/src/components/Settings.vue b/src/components/Settings.vue index 783c6b153..8b934cd8b 100644 --- a/src/components/Settings.vue +++ b/src/components/Settings.vue @@ -61,6 +61,19 @@ @click="onChangeBackupPath" /> + + +

{{ t('bookmarks', 'The bookmarks app can automatically make available the textual contents of the websites you bookmark to Context Chat, which allows asking questions about and getting answers based on those contents. This is only available if auto-archiving is enabled.') }}

+

+ {{ t('bookmarks', 'Context chat is currently not installed, but is required for this feature.') }} +

+ + {{ t('bookmarks', 'Enable Context Chat integration') }} + +
+