Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/comments/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
return [
'routes' => [
['name' => 'Notifications#view', 'url' => '/notifications/view/{id}', 'verb' => 'GET'],
['name' => 'Notifications#dismiss', 'url' => '/notifications/{id}', 'verb' => 'DELETE'],
]
];
32 changes: 32 additions & 0 deletions apps/comments/lib/Controller/NotificationsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\OpenAPI;
use OCP\AppFramework\Http\Attribute\PublicPage;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\Comments\IComment;
Expand Down Expand Up @@ -92,6 +93,37 @@ public function view(string $id): RedirectResponse|NotFoundResponse {
}
}

/**
* Dismiss the mention notification for a comment
*
* @NoAdminRequired
*
* @param string $id ID of the comment
*
* @return DataResponse<Http::STATUS_OK, array{}, array{}>|DataResponse<Http::STATUS_FORBIDDEN, array{}, array{}>|DataResponse<Http::STATUS_NOT_FOUND, array{}, array{}>
*
* 200: Notification dismissed successfully
* 403: Not logged in
* 404: Comment not found
*/
public function dismiss(string $id): DataResponse {
$currentUser = $this->userSession->getUser();
if (!$currentUser instanceof IUser) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}

try {
$comment = $this->commentsManager->get($id);
if ($comment->getObjectType() !== 'files') {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
$this->markProcessed($comment, $currentUser);
return new DataResponse([]);
} catch (\Exception $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
}

/**
* Marks the notification about a comment as processed
*/
Expand Down
29 changes: 29 additions & 0 deletions apps/comments/src/comments-activity-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getCurrentUser } from '@nextcloud/auth'
import axios from '@nextcloud/axios'
import moment from '@nextcloud/moment'
import { generateUrl } from '@nextcloud/router'
import Vue, { type ComponentPublicInstance } from 'vue'
import logger from './logger.js'
import { getComments } from './services/GetComments.js'
import { markCommentsAsRead } from './services/ReadComments.js'

import { PiniaVuePlugin, createPinia } from 'pinia'

Expand Down Expand Up @@ -48,6 +52,31 @@ export function registerCommentsPlugins() {
window.OCA.Activity.registerSidebarEntries(async ({ fileInfo, limit, offset }) => {
const { data: comments } = await getComments({ resourceType: 'files', resourceId: fileInfo.id }, { limit, offset })
logger.debug('Loaded comments', { fileInfo, comments })

// Optimistically clear the unread bubble immediately via the global event bus
// (window._nc_event_bus) so the UI updates without a page refresh.
// fileInfo.node is the underlying @nextcloud/files Node set by the Files sidebar.
const node = fileInfo.node
if (node) {
node.attributes['comments-unread'] = 0
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(window as any)._nc_event_bus?.emit('files:node:updated', node)
}
markCommentsAsRead('files', fileInfo.id, new Date()).catch(() => {})

// Mark mention notifications as read for comments that mention the current user
const currentUser = getCurrentUser()
if (currentUser) {
for (const comment of comments) {
const mentions = Object.values(comment.props?.mentions ?? {}) as { mentionType: string, mentionId: string }[]
const isMentioned = comment.props?.id && mentions.some((m) => m.mentionType === 'user' && m.mentionId === currentUser.uid)
if (isMentioned) {
axios.delete(generateUrl('/apps/comments/notifications/{id}', { id: comment.props.id }))
.catch(() => {})
}
}
}

const { default: CommentView } = await import('./views/ActivityCommentEntry.vue')
// @ts-expect-error Types are broken for Vue2
const CommentsViewObject = Vue.extend(CommentView)
Expand Down
99 changes: 99 additions & 0 deletions apps/comments/tests/Unit/Controller/NotificationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
namespace OCA\Comments\Tests\Unit\Controller;

use OCA\Comments\Controller\NotificationsController;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\Comments\IComment;
Expand Down Expand Up @@ -211,4 +212,102 @@ public function testViewNoFile(): void {
$response = $this->notificationsController->view('42');
$this->assertInstanceOf(NotFoundResponse::class, $response);
}

public function testDismissNotLoggedIn(): void {
$this->session->expects($this->once())
->method('getUser')
->willReturn(null);

$this->commentsManager->expects($this->never())
->method('get');
$this->notificationManager->expects($this->never())
->method('markProcessed');

$response = $this->notificationsController->dismiss('42');
$this->assertInstanceOf(DataResponse::class, $response);
$this->assertSame(403, $response->getStatus());
}

public function testDismissSuccess(): void {
$comment = $this->createMock(IComment::class);
$comment->expects($this->any())
->method('getObjectType')
->willReturn('files');
$comment->expects($this->any())
->method('getId')
->willReturn('1234');

$this->commentsManager->expects($this->once())
->method('get')
->with('42')
->willReturn($comment);

$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->willReturn('user');

$this->session->expects($this->once())
->method('getUser')
->willReturn($user);

$notification = $this->createMock(INotification::class);
$notification->expects($this->any())
->method($this->anything())
->willReturn($notification);

$this->notificationManager->expects($this->once())
->method('createNotification')
->willReturn($notification);
$this->notificationManager->expects($this->once())
->method('markProcessed')
->with($notification);

$response = $this->notificationsController->dismiss('42');
$this->assertInstanceOf(DataResponse::class, $response);
$this->assertSame(200, $response->getStatus());
}

public function testDismissInvalidComment(): void {
$this->commentsManager->expects($this->once())
->method('get')
->with('42')
->willThrowException(new NotFoundException());

$user = $this->createMock(IUser::class);
$this->session->expects($this->once())
->method('getUser')
->willReturn($user);

$this->notificationManager->expects($this->never())
->method('markProcessed');

$response = $this->notificationsController->dismiss('42');
$this->assertInstanceOf(DataResponse::class, $response);
$this->assertSame(404, $response->getStatus());
}

public function testDismissNonFileComment(): void {
$comment = $this->createMock(IComment::class);
$comment->expects($this->any())
->method('getObjectType')
->willReturn('calendar');

$this->commentsManager->expects($this->once())
->method('get')
->with('42')
->willReturn($comment);

$user = $this->createMock(IUser::class);
$this->session->expects($this->once())
->method('getUser')
->willReturn($user);

$this->notificationManager->expects($this->never())
->method('markProcessed');

$response = $this->notificationsController->dismiss('42');
$this->assertInstanceOf(DataResponse::class, $response);
$this->assertSame(404, $response->getStatus());
}
}
16 changes: 8 additions & 8 deletions apps/files/l10n/et_EE.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ OC.L10N.register(
"in %s" : "kaustas %s",
"Transferred from %1$s on %2$s" : "Üleantud kasutajalt %1$s %2$s",
"File Management" : "Failihaldus",
"Home" : "Kodu",
"Home" : "Avaleht",
"Target folder does not exist any more" : "Sihtkausta pole enam olemas",
"Reload current directory" : "Laadi see kaust uuesti",
"Go to the \"{dir}\" directory" : "Mine kausta „{dir}“",
Expand Down Expand Up @@ -241,7 +241,7 @@ OC.L10N.register(
"Adding the file extension \"{new}\" may render the file unreadable." : "„{new}“ faililaiendi lisamine võib muuta faili loetamatuks.",
"Do not show this dialog again." : "Ära näita seda vaadet enam uuesti.",
"Rename file to hidden" : "Oled muutmas faili peidetuks",
"Prefixing a filename with a dot may render the file hidden." : "Kui lisad faili algusesse punkt, siis ta võib muutuda peidetuks.",
"Prefixing a filename with a dot may render the file hidden." : "Kui lisad faili algusesse punkti, siis ta võib muutuda peidetuks.",
"Are you sure you want to rename the file to \"{filename}\"?" : "Kas sa tahad uueks faillinimeks muuta „{filename}“?",
"Cancel" : "Loobu",
"Rename" : "Muuda nime",
Expand All @@ -261,23 +261,23 @@ OC.L10N.register(
"Loading current folder" : "Laadin käesolevat kausta",
"Retry" : "Proovi uuesti",
"No files in here" : "Siin ei ole faile",
"Upload some content or sync with your devices!" : "Laadi sisu üles või süngi oma seadmetega!",
"Upload some content or sync with your devices!" : "Laadi sisu üles või sünkrooni oma seadmetega!",
"Go back" : "Mine tagasi",
"Loading …" : "Andmed on laadimisel…",
"Your files" : "Sinu failid",
"Open in files" : "Ava failirakenduses",
"File cannot be accessed" : "Failile ligipääs puudub",
"File cannot be accessed" : "Puudub ligipääs failile",
"The file could not be found or you do not have permissions to view it. Ask the sender to share it." : "Faili kas ei leidu või sul puudub õigus selle vaatamiseks. Palu saatjat, et ta jagaks antud faili sulle.",
"No search results for “{query}”" : "„{query}“ otsingul pole tulemusi",
"Search for files" : "Otsi faile",
"Allow to restrict filenames to ensure files can be synced with all clients. By default all filenames valid on POSIX (e.g. Linux or macOS) are allowed." : "Luba failinimede piiramine tagamaks, et sünkroniseerimine toimib kõikide platvormide klientide vahel. Vaikimisi on lubatud kõik POSIX-i standardile vastavad failinimed (seda järgivad Linux ja macOS).",
"Allow to restrict filenames to ensure files can be synced with all clients. By default all filenames valid on POSIX (e.g. Linux or macOS) are allowed." : "Luba failinimede piiramine tagamaks, et sünkroonimine toimib kõikide platvormide klientide vahel. Vaikimisi on lubatud kõik POSIX-i standardile vastavad failinimed (seda järgivad Linux ja macOS).",
"After enabling the Windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Kui võtad kasutusele Windowsiga ühilduvad failinimed, siis olemasolevaid mitteühilduvaid faile ei saa enam muuta, aga faili omanik saab failinime muuta ühilduvaks.",
"Failed to toggle Windows filename support" : "Windowsi failinimede toe sisse/välja lülitamine ei õnnestunud",
"Files compatibility" : "Failide ühilduvus",
"Enforce Windows compatibility" : "Kasuta ühilduvust Windowsiga",
"This will block filenames not valid on Windows systems, like using reserved names or special characters. But this will not enforce compatibility of case sensitivity." : "Sellega blokeerid niisuguste failinimede kasutamise, mis Windowsis ei toimiks. See tähendab mõnede nimede ja tähemärkide keelamist. Aga see seadistus ei jõusta suur- ja väiketähtede kasutust.",
"Blank" : "Tühi",
"Unable to create new file from template" : "Faili loomine mallist ebaõnnestus",
"Unable to create new file from template" : "Faili loomine mallist ei õnnestunud",
"Pick a template for {name}" : "Vali mall „{name}“ faili jaoks",
"Create a new file with the selected template" : "Loo valitud malli alusel uus fail",
"Creating file" : "Fail on loomisel",
Expand Down Expand Up @@ -398,9 +398,9 @@ OC.L10N.register(
"Files and folders you recently modified will show up here." : "Failid ja kaustad, mida oled hiljuti muutnud, ilmuvad siia.",
"Search" : "Otsi",
"Search results within your files." : "Otsingutulemused sinu failide seast.",
"No entries found in this folder" : "Selles kaustast ei leitud kirjeid",
"No entries found in this folder" : "Selles kaustast ei leidunud kirjeid",
"Select all" : "Vali kõik",
"Upload too large" : "Üleslaadimine on liiga suur",
"Upload too large" : "Üleslaaditu on liiga suur",
"The files you are trying to upload exceed the maximum size for file uploads on this server." : "Failid, mida sa proovid üles laadida, ületavad serveri poolt üleslaaditavatele failidele määratud maksimaalse suuruse.",
"Renamed \"{oldName}\" to \"{newName}\"" : "Failinimi on muutunud „{oldName}“ ⇨ „{newName}“",
"Clear filter" : "Tühjenda filter",
Expand Down
16 changes: 8 additions & 8 deletions apps/files/l10n/et_EE.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"in %s" : "kaustas %s",
"Transferred from %1$s on %2$s" : "Üleantud kasutajalt %1$s %2$s",
"File Management" : "Failihaldus",
"Home" : "Kodu",
"Home" : "Avaleht",
"Target folder does not exist any more" : "Sihtkausta pole enam olemas",
"Reload current directory" : "Laadi see kaust uuesti",
"Go to the \"{dir}\" directory" : "Mine kausta „{dir}“",
Expand Down Expand Up @@ -239,7 +239,7 @@
"Adding the file extension \"{new}\" may render the file unreadable." : "„{new}“ faililaiendi lisamine võib muuta faili loetamatuks.",
"Do not show this dialog again." : "Ära näita seda vaadet enam uuesti.",
"Rename file to hidden" : "Oled muutmas faili peidetuks",
"Prefixing a filename with a dot may render the file hidden." : "Kui lisad faili algusesse punkt, siis ta võib muutuda peidetuks.",
"Prefixing a filename with a dot may render the file hidden." : "Kui lisad faili algusesse punkti, siis ta võib muutuda peidetuks.",
"Are you sure you want to rename the file to \"{filename}\"?" : "Kas sa tahad uueks faillinimeks muuta „{filename}“?",
"Cancel" : "Loobu",
"Rename" : "Muuda nime",
Expand All @@ -259,23 +259,23 @@
"Loading current folder" : "Laadin käesolevat kausta",
"Retry" : "Proovi uuesti",
"No files in here" : "Siin ei ole faile",
"Upload some content or sync with your devices!" : "Laadi sisu üles või süngi oma seadmetega!",
"Upload some content or sync with your devices!" : "Laadi sisu üles või sünkrooni oma seadmetega!",
"Go back" : "Mine tagasi",
"Loading …" : "Andmed on laadimisel…",
"Your files" : "Sinu failid",
"Open in files" : "Ava failirakenduses",
"File cannot be accessed" : "Failile ligipääs puudub",
"File cannot be accessed" : "Puudub ligipääs failile",
"The file could not be found or you do not have permissions to view it. Ask the sender to share it." : "Faili kas ei leidu või sul puudub õigus selle vaatamiseks. Palu saatjat, et ta jagaks antud faili sulle.",
"No search results for “{query}”" : "„{query}“ otsingul pole tulemusi",
"Search for files" : "Otsi faile",
"Allow to restrict filenames to ensure files can be synced with all clients. By default all filenames valid on POSIX (e.g. Linux or macOS) are allowed." : "Luba failinimede piiramine tagamaks, et sünkroniseerimine toimib kõikide platvormide klientide vahel. Vaikimisi on lubatud kõik POSIX-i standardile vastavad failinimed (seda järgivad Linux ja macOS).",
"Allow to restrict filenames to ensure files can be synced with all clients. By default all filenames valid on POSIX (e.g. Linux or macOS) are allowed." : "Luba failinimede piiramine tagamaks, et sünkroonimine toimib kõikide platvormide klientide vahel. Vaikimisi on lubatud kõik POSIX-i standardile vastavad failinimed (seda järgivad Linux ja macOS).",
"After enabling the Windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Kui võtad kasutusele Windowsiga ühilduvad failinimed, siis olemasolevaid mitteühilduvaid faile ei saa enam muuta, aga faili omanik saab failinime muuta ühilduvaks.",
"Failed to toggle Windows filename support" : "Windowsi failinimede toe sisse/välja lülitamine ei õnnestunud",
"Files compatibility" : "Failide ühilduvus",
"Enforce Windows compatibility" : "Kasuta ühilduvust Windowsiga",
"This will block filenames not valid on Windows systems, like using reserved names or special characters. But this will not enforce compatibility of case sensitivity." : "Sellega blokeerid niisuguste failinimede kasutamise, mis Windowsis ei toimiks. See tähendab mõnede nimede ja tähemärkide keelamist. Aga see seadistus ei jõusta suur- ja väiketähtede kasutust.",
"Blank" : "Tühi",
"Unable to create new file from template" : "Faili loomine mallist ebaõnnestus",
"Unable to create new file from template" : "Faili loomine mallist ei õnnestunud",
"Pick a template for {name}" : "Vali mall „{name}“ faili jaoks",
"Create a new file with the selected template" : "Loo valitud malli alusel uus fail",
"Creating file" : "Fail on loomisel",
Expand Down Expand Up @@ -396,9 +396,9 @@
"Files and folders you recently modified will show up here." : "Failid ja kaustad, mida oled hiljuti muutnud, ilmuvad siia.",
"Search" : "Otsi",
"Search results within your files." : "Otsingutulemused sinu failide seast.",
"No entries found in this folder" : "Selles kaustast ei leitud kirjeid",
"No entries found in this folder" : "Selles kaustast ei leidunud kirjeid",
"Select all" : "Vali kõik",
"Upload too large" : "Üleslaadimine on liiga suur",
"Upload too large" : "Üleslaaditu on liiga suur",
"The files you are trying to upload exceed the maximum size for file uploads on this server." : "Failid, mida sa proovid üles laadida, ületavad serveri poolt üleslaaditavatele failidele määratud maksimaalse suuruse.",
"Renamed \"{oldName}\" to \"{newName}\"" : "Failinimi on muutunud „{oldName}“ ⇨ „{newName}“",
"Clear filter" : "Tühjenda filter",
Expand Down
7 changes: 7 additions & 0 deletions apps/provisioning_api/l10n/lt_LT.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading