diff --git a/appinfo/info.xml b/appinfo/info.xml
index 49d3b8a1ba9..29ceaa2c6bb 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -24,6 +24,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
Jan-Christoph Borchardt
Jennifer Piperek
Joas Schilling
+ Mario Danic
Marco Ambrosini
Talk
diff --git a/css/icons.scss b/css/icons.scss
index d7ecf01d96b..5b3077858aa 100644
--- a/css/icons.scss
+++ b/css/icons.scss
@@ -33,6 +33,9 @@
.icon-changelog {
background-image: url('../img/changelog.svg');
}
+ .icon-notes {
+ background-image: url('../img/notes.svg');
+ }
// "forced-white" needs to be included in the class name as the UserBubble
// does not accept several classes.
diff --git a/docs/capabilities.md b/docs/capabilities.md
index 03c878d7d75..f87a75fdeee 100644
--- a/docs/capabilities.md
+++ b/docs/capabilities.md
@@ -51,3 +51,4 @@ title: Capabilities
* `force-mute` - "forceMute" signaling messages can be sent to mute other participants.
* `conversation-v2` - The conversations API v2 is less load heavy and should be used by clients when available. Check the difference in the [Conversation API documentation](conversation.md).
* `chat-reference-id` - an optional referenceId can be sent with a chat message to be able to identify it in parallel get requests to earlier fade out a temporary message
+* `notes` - The notes conversation type is available and users can toggle on and off the conversation
diff --git a/docs/constants.md b/docs/constants.md
index 86ecff848d0..e2d109e321c 100644
--- a/docs/constants.md
+++ b/docs/constants.md
@@ -7,6 +7,7 @@ title: Constants
* `2` group
* `3` public
* `4` changelog
+* `5` notes
## Read-only states
* `0` read-write
@@ -35,7 +36,7 @@ title: Constants
## Actor types of chat messages
* `guests` - guest users
* `users` - logged-in users
-* `bots` - used by commands (actor-id is the used `/command`) and the changelog conversation (actor-id is `changelog`)
+* `bots` - used by commands (actor-id is the used `/command`), the changelog conversation (actor-id is `changelog`) and the notes conversation (actor-id is `notes`)
## Webinary lobby states
* `0` no lobby
diff --git a/docs/conversation.md b/docs/conversation.md
index 254179803c5..2ca14b365f8 100644
--- a/docs/conversation.md
+++ b/docs/conversation.md
@@ -55,7 +55,7 @@
field | type | Description
------|------|------------
- `roomType` | int |
+ `roomType` | int | See [list of conversation types](constants.md#Conversation-types), but changelog and notes are not supported on this endpoint
`invite` | string | user id (`roomType = 1`), group id (`roomType = 2` - optional), circle id (`roomType = 2`, `source = 'circles'`], only available with `circles-support` capability))
`source` | string | The source for the invite, only supported on `roomType = 2` for `groups` and `circles` (only available with `circles-support` capability)
`roomName` | string | conversation name (Not available for `roomType = 1`)
diff --git a/img/notes.svg b/img/notes.svg
new file mode 100644
index 00000000000..5e4233a9f73
--- /dev/null
+++ b/img/notes.svg
@@ -0,0 +1,60 @@
+
+
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index fcdff6582aa..8553dca65c1 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -24,7 +24,7 @@
use OCA\Talk\Activity\Listener as ActivityListener;
use OCA\Talk\Capabilities;
-use OCA\Talk\Chat\Changelog\Listener as ChangelogListener;
+use OCA\Talk\Chat\SpecialRoom\Listener as SpecialRoomListener;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\Command\Listener as CommandListener;
use OCA\Talk\Chat\Parser\Listener as ParserListener;
@@ -103,7 +103,7 @@ public function register(): void {
CommandListener::register($dispatcher);
CollaboratorsListener::register($dispatcher);
ResourceListener::register($dispatcher);
- ChangelogListener::register($dispatcher);
+ SpecialRoomListener::register($dispatcher);
ShareListener::register($dispatcher);
Operation::register($dispatcher);
diff --git a/lib/BackgroundJob/RemoveEmptyRooms.php b/lib/BackgroundJob/RemoveEmptyRooms.php
index 592a59a8e6c..05641815a02 100644
--- a/lib/BackgroundJob/RemoveEmptyRooms.php
+++ b/lib/BackgroundJob/RemoveEmptyRooms.php
@@ -63,7 +63,7 @@ protected function run($argument): void {
}
public function callback(Room $room): void {
- if ($room->getType() === Room::CHANGELOG_CONVERSATION) {
+ if ($room->getType() === Room::CHANGELOG_CONVERSATION || $room->getType() === Room::NOTES_CONVERSATION) {
return;
}
diff --git a/lib/Capabilities.php b/lib/Capabilities.php
index caa5f86c38e..a2156842309 100644
--- a/lib/Capabilities.php
+++ b/lib/Capabilities.php
@@ -32,17 +32,13 @@
class Capabilities implements IPublicCapability {
- /** @var IConfig */
- protected $serverConfig;
/** @var Config */
protected $talkConfig;
/** @var IUserSession */
protected $userSession;
- public function __construct(IConfig $serverConfig,
- Config $talkConfig,
+ public function __construct(Config $talkConfig,
IUserSession $userSession) {
- $this->serverConfig = $serverConfig;
$this->talkConfig = $talkConfig;
$this->userSession = $userSession;
}
@@ -53,23 +49,7 @@ public function getCapabilities(): array {
return [];
}
- $maxChatLength = 1000;
- if (version_compare($this->serverConfig->getSystemValueString('version', '0.0.0'), '16.0.2', '>=')) {
- $maxChatLength = ChatManager::MAX_CHAT_LENGTH;
- }
-
- $attachments = [
- 'allowed' => $user instanceof IUser,
- ];
- if ($user instanceof IUser) {
- $attachments['folder'] = $this->talkConfig->getAttachmentFolder($user->getUID());
- }
-
- $conversations = [
- 'can-create' => $user instanceof IUser && !$this->talkConfig->isNotAllowedToCreateConversations($user),
- ];
-
- return [
+ $capabilities = [
'spreed' => [
'features' => [
'audio',
@@ -99,13 +79,30 @@ public function getCapabilities(): array {
'chat-reference-id',
],
'config' => [
- 'attachments' => $attachments,
+ 'attachments' => [
+ 'allowed' => false,
+ ],
'chat' => [
- 'max-length' => $maxChatLength,
+ 'max-length' => ChatManager::MAX_CHAT_LENGTH,
+ ],
+ 'conversations' => [
+ 'can-create' => false
],
- 'conversations' => $conversations,
],
],
];
+
+ if ($user instanceof IUser) {
+ $capabilities['spreed']['features'][] = 'notes';
+
+ $capabilities['spreed']['config']['attachments'] = [
+ 'allowed' => true,
+ 'folder' => $this->talkConfig->getAttachmentFolder($user->getUID()),
+ ];
+
+ $capabilities['spreed']['config']['conversations']['can-create'] = !$this->talkConfig->isNotAllowedToCreateConversations($user);
+ }
+
+ return $capabilities;
}
}
diff --git a/lib/Chat/ChatManager.php b/lib/Chat/ChatManager.php
index aa704f5ef5f..f94b3454def 100644
--- a/lib/Chat/ChatManager.php
+++ b/lib/Chat/ChatManager.php
@@ -115,8 +115,8 @@ public function addSystemMessage(Room $chat, string $actorType, string $actorId,
* @param string $message
* @return IComment
*/
- public function addChangelogMessage(Room $chat, string $message): IComment {
- $comment = $this->commentsManager->create('guests', 'changelog', 'chat', (string) $chat->getId());
+ public function addSpecialMessage(Room $chat, string $type, string $message): IComment {
+ $comment = $this->commentsManager->create('guests', $type, 'chat', (string) $chat->getId());
$comment->setMessage($message, self::MAX_CHAT_LENGTH);
$comment->setCreationDateTime($this->timeFactory->getDateTime());
diff --git a/lib/Chat/Parser/Listener.php b/lib/Chat/Parser/Listener.php
index fbda032e6df..11d2c26ecf7 100644
--- a/lib/Chat/Parser/Listener.php
+++ b/lib/Chat/Parser/Listener.php
@@ -50,13 +50,23 @@ public static function register(IEventDispatcher $dispatcher): void {
}
/** @var Changelog $parser */
- $parser = \OC::$server->query(Changelog::class);
+ $changelogParser = \OC::$server->query(Changelog::class);
try {
- $parser->parseMessage($message);
+ $changelogParser->parseMessage($message);
+ $event->stopPropagation();
+ } catch (\OutOfBoundsException $e) {
+ // Unknown message, ignore
+ }
+
+ /** @var Notes $parser */
+ $notesParser = \OC::$server->query(Notes::class);
+ try {
+ $notesParser->parseMessage($message);
$event->stopPropagation();
} catch (\OutOfBoundsException $e) {
// Unknown message, ignore
}
+
}, -75);
$dispatcher->addListener(MessageParser::EVENT_MESSAGE_PARSE, static function(ChatMessageEvent $event) {
diff --git a/lib/Chat/Parser/Notes.php b/lib/Chat/Parser/Notes.php
new file mode 100644
index 00000000000..5bc8c4f993b
--- /dev/null
+++ b/lib/Chat/Parser/Notes.php
@@ -0,0 +1,43 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\Talk\Chat\Parser;
+
+use OCA\Talk\Model\Message;
+
+class Notes {
+
+ /**
+ * @param Message $chatMessage
+ * @throws \OutOfBoundsException
+ */
+ public function parseMessage(Message $chatMessage): void {
+
+ if ($chatMessage->getActorType() !== 'guests' ||
+ $chatMessage->getActorId() !== 'notes') {
+ throw new \OutOfBoundsException('Not notes');
+ }
+
+ $l = $chatMessage->getL10n();
+ $chatMessage->setActor('bots', 'notes', $l->t('My notes'));
+ }
+}
diff --git a/lib/Chat/Changelog/Listener.php b/lib/Chat/SpecialRoom/Listener.php
similarity index 94%
rename from lib/Chat/Changelog/Listener.php
rename to lib/Chat/SpecialRoom/Listener.php
index 8ce066b27df..8cd3a21b02a 100644
--- a/lib/Chat/Changelog/Listener.php
+++ b/lib/Chat/SpecialRoom/Listener.php
@@ -20,7 +20,7 @@
*
*/
-namespace OCA\Talk\Chat\Changelog;
+namespace OCA\Talk\Chat\SpecialRoom;
use OCA\Talk\Controller\RoomController;
use OCA\Talk\Events\UserEvent;
@@ -46,6 +46,8 @@ public function __construct(Manager $manager) {
}
public function preGetRooms(string $userId): void {
+ $this->manager->createNotesIfNeeded($userId);
+
if (!$this->manager->userHasNewChangelog($userId)) {
return;
}
diff --git a/lib/Chat/Changelog/Manager.php b/lib/Chat/SpecialRoom/Manager.php
similarity index 70%
rename from lib/Chat/Changelog/Manager.php
rename to lib/Chat/SpecialRoom/Manager.php
index 6b3371289c3..cfb30030894 100644
--- a/lib/Chat/Changelog/Manager.php
+++ b/lib/Chat/SpecialRoom/Manager.php
@@ -20,15 +20,18 @@
*
*/
-namespace OCA\Talk\Chat\Changelog;
+namespace OCA\Talk\Chat\SpecialRoom;
use OCA\Talk\Chat\ChatManager;
+use OCA\Talk\Exceptions\ParticipantNotFoundException;
+use OCA\Talk\Room;
use OCA\Talk\Manager as RoomManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\IL10N;
+
class Manager {
/** @var IConfig */
@@ -54,16 +57,59 @@ public function __construct(IConfig $config,
$this->l = $l;
}
+ public function getNotesForUser(string $userId): int {
+ return (int) $this->config->getUserValue($userId, 'spreed', 'notes', '2');
+ }
+
public function getChangelogForUser(string $userId): int {
- return (int) $this->config->getUserValue($userId, 'spreed', 'changelog', 0);
+ return (int) $this->config->getUserValue($userId, 'spreed', 'changelog', '0');
}
public function userHasNewChangelog(string $userId): bool {
return $this->getChangelogForUser($userId) < count($this->getChangelogs());
}
+ public function createNotesIfNeeded(string $userId): void {
+ if ($this->getNotesForUser($userId) === 2) {
+ $room = $this->roomManager->getSpecialRoom($userId, Room::NOTES_CONVERSATION);
+ $this->setNotesConversationAsFavorite($room, $userId);
+ $this->addNotesWelcomeMessages($room, $userId);
+ }
+ }
+
+ public function setNotesConversationAsFavorite(Room $room, string $userId): void {
+ try {
+ $participant = $room->getParticipant($userId);
+ $participant->setFavorite(true);
+ } catch (ParticipantNotFoundException $e) {
+ // do nothing
+ }
+ }
+
+ public function addNotesWelcomeMessages(Room $room, string $userId): void {
+ $notesWelcomeMessages = $this->getNotesWelcomeMessages();
+ foreach ($notesWelcomeMessages as $key => $welcomeMessage) {
+ if ($welcomeMessage === '') {
+ continue;
+ }
+ $this->chatManager->addSpecialMessage($room, 'notes', $welcomeMessage);
+ }
+
+ $this->config->setUserValue($userId, 'spreed', 'notes', 1);
+ }
+
+ public function getNotesWelcomeMessages(): array {
+ return [
+ $this->l->t(
+ "Welcome to your notes!"
+ . "\nYou can use this conversation to share notes between your different devices."
+ . " When you deleted it, you can recreate it via the settings."
+ )
+ ];
+ }
+
public function updateChangelog(string $userId): void {
- $room = $this->roomManager->getChangelogRoom($userId);
+ $room = $this->roomManager->getSpecialRoom($userId, Room::CHANGELOG_CONVERSATION);
$logs = $this->getChangelogs();
$hasReceivedLog = $this->getChangelogForUser($userId);
@@ -72,7 +118,7 @@ public function updateChangelog(string $userId): void {
if ($key < $hasReceivedLog || $changelog === '') {
continue;
}
- $this->chatManager->addChangelogMessage($room, $changelog);
+ $this->chatManager->addSpecialMessage($room, 'changelog', $changelog);
}
$this->config->setUserValue($userId, 'spreed', 'changelog', count($this->getChangelogs()));
diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php
index 6eee773a280..0a3ddaacfef 100644
--- a/lib/Controller/RoomController.php
+++ b/lib/Controller/RoomController.php
@@ -490,6 +490,11 @@ protected function formatRoomV2(Room $room, ?Participant $currentParticipant): a
&& $currentParticipant->hasModeratorPermissions(false);
$roomData['canLeaveConversation'] = !$roomData['canDeleteConversation']
|| ($room->getType() !== Room::ONE_TO_ONE_CALL && $room->getNumberOfParticipants() > 1);
+
+ if ($room->getType() === Room::NOTES_CONVERSATION) {
+ $roomData['canDeleteConversation'] = true;
+ $roomData['canLeaveConversation'] = false;
+ }
}
// FIXME This should not be done, but currently all the clients use it to get the avatar of the user …
@@ -1032,7 +1037,7 @@ protected function removeSelfFromRoomLogic(Room $room, Participant $participant)
&& $room->getNumberOfModerators() === 1) {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
- } else if ($room->getType() !== Room::CHANGELOG_CONVERSATION &&
+ } else if ($room->getType() !== Room::CHANGELOG_CONVERSATION && $room->getType() !== Room::NOTES_CONVERSATION &&
$room->getNumberOfParticipants() === 1) {
$room->deleteRoom();
return new DataResponse();
diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php
index 8b96818dc1c..f465bd7e95e 100644
--- a/lib/Controller/SettingsController.php
+++ b/lib/Controller/SettingsController.php
@@ -25,6 +25,10 @@
namespace OCA\Talk\Controller;
use OCA\Files_Sharing\SharedStorage;
+use OCA\Talk\Chat\SpecialRoom\Manager as SpecialRoomManager;
+use OCA\Talk\Exceptions\RoomNotFoundException;
+use OCA\Talk\Manager;
+use OCA\Talk\Room;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
@@ -38,6 +42,10 @@
class SettingsController extends OCSController {
+ /** @var Manager */
+ protected $talkManager;
+ /** @var SpecialRoomManager */
+ protected $specialRoomManager;
/** @var IRootFolder */
protected $rootFolder;
/** @var IConfig */
@@ -49,11 +57,15 @@ class SettingsController extends OCSController {
public function __construct(string $appName,
IRequest $request,
+ Manager $talkManager,
+ SpecialRoomManager $specialRoomManager,
IRootFolder $rootFolder,
IConfig $config,
ILogger $logger,
?string $userId) {
parent::__construct($appName, $request);
+ $this->talkManager = $talkManager;
+ $this->specialRoomManager = $specialRoomManager;
$this->rootFolder = $rootFolder;
$this->config = $config;
$this->logger = $logger;
@@ -72,8 +84,26 @@ public function setUserSetting(string $key, ?string $value): DataResponse {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
}
+ if ($key === 'notes' && $value === '1') {
+ // Setting it to 2 so createNotesIfNeeded can create the conversation and set it to 1 again
+ $value = '2';
+ }
+
$this->config->setUserValue($this->userId, 'spreed', $key, $value);
+
+ if ($key === 'notes') {
+ if ($value === '0') {
+ try {
+ $room = $this->talkManager->getSpecialRoom($this->userId, Room::NOTES_CONVERSATION, false);
+ $room->deleteRoom();
+ } catch (RoomNotFoundException $e) {
+ }
+ } else {
+ $this->specialRoomManager->createNotesIfNeeded($this->userId);
+ }
+ }
+
return new DataResponse();
}
@@ -95,6 +125,9 @@ protected function validateUserSetting(string $setting, ?string $value): bool {
}
return false;
}
+ if ($setting === 'notes') {
+ return $value === '0' || $value === '1';
+ }
return false;
}
diff --git a/lib/Manager.php b/lib/Manager.php
index 002afb624df..e28bd3a6f9e 100644
--- a/lib/Manager.php
+++ b/lib/Manager.php
@@ -23,7 +23,7 @@
namespace OCA\Talk;
-use OCA\Talk\Chat\Changelog;
+use OCA\Talk\Chat\SpecialRoom;
use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Events\CreateRoomTokenEvent;
use OCA\Talk\Events\RoomEvent;
@@ -555,16 +555,21 @@ public function createPublicRoom(string $name = '', string $objectType = '', str
}
/**
- * Makes sure the user is part of a changelog room and returns it
*
* @param string $userId
+ * @param int type
+ * @param bool $createIfMissing
* @return Room
*/
- public function getChangelogRoom(string $userId): Room {
+ public function getSpecialRoom(string $userId, int $type, bool $createIfMissing = true): Room {
+ if ($type !== Room::CHANGELOG_CONVERSATION && $type !== Room::NOTES_CONVERSATION) {
+ throw new \OutOfBoundsException('Unsupported type');
+ }
+
$query = $this->db->getQueryBuilder();
$query->select('*')
->from('talk_rooms')
- ->where($query->expr()->eq('type', $query->createNamedParameter(Room::CHANGELOG_CONVERSATION, IQueryBuilder::PARAM_INT)))
+ ->where($query->expr()->eq('type', $query->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('name', $query->createNamedParameter($userId)));
$result = $query->execute();
@@ -572,9 +577,22 @@ public function getChangelogRoom(string $userId): Room {
$result->closeCursor();
if ($row === false) {
- $room = $this->createRoom(Room::CHANGELOG_CONVERSATION, $userId);
- $room->addUsers(['userId' => $userId]);
- $room->setReadOnly(Room::READ_ONLY);
+ if (!$createIfMissing) {
+ throw new RoomNotFoundException('Conversation does not exist');
+ }
+ $room = $this->createRoom($type, $userId);
+ if ($type === ROOM::NOTES_CONVERSATION) {
+ $room->addUsers([
+ 'userId' => $userId,
+ 'participantType' => Participant::OWNER,
+ ]);
+ $room->setReadOnly(Room::READ_ONLY);
+ } else {
+ $room->addUsers(['userId' => $userId]);
+ if ($type === ROOM::CHANGELOG_CONVERSATION) {
+ $room->setReadOnly(Room::READ_ONLY);
+ }
+ }
return $room;
}
@@ -687,11 +705,15 @@ public function resolveRoomDisplayName(Room $room, string $userId): string {
if ($room->getType() === Room::CHANGELOG_CONVERSATION) {
return $this->l->t('Talk updates ✅');
}
+
+ if ($room->getType() === Room::NOTES_CONVERSATION) {
+ return $this->l->t('My notes');
+ }
+
if ($userId === '' && $room->getType() !== Room::PUBLIC_CALL) {
return $this->l->t('Private conversation');
}
-
if ($room->getType() !== Room::ONE_TO_ONE_CALL && $room->getName() === '') {
$room->setName($this->getRoomNameByParticipants($room));
}
diff --git a/lib/Room.php b/lib/Room.php
index 3e7d2fdd328..5a78af412b9 100644
--- a/lib/Room.php
+++ b/lib/Room.php
@@ -56,6 +56,7 @@ class Room {
public const GROUP_CALL = 2;
public const PUBLIC_CALL = 3;
public const CHANGELOG_CONVERSATION = 4;
+ public const NOTES_CONVERSATION = 5;
public const READ_WRITE = 0;
public const READ_ONLY = 1;
diff --git a/lib/TInitialState.php b/lib/TInitialState.php
index 7452436de6d..1a43d233780 100644
--- a/lib/TInitialState.php
+++ b/lib/TInitialState.php
@@ -87,6 +87,11 @@ protected function publishInitialStateForUser(IUser $user, IRootFolder $rootFold
} catch (NoUserException $e) {
}
}
+
+ $this->initialStateService->provideInitialState(
+ 'talk', 'my_notes',
+ $this->serverConfig->getUserValue($user->getUID(), 'spreed', 'notes', '0') !== '0'
+ );
}
protected function publishInitialStateForGuest(): void {
@@ -107,5 +112,10 @@ protected function publishInitialStateForGuest(): void {
'talk', 'attachment_folder',
''
);
+
+ $this->initialStateService->provideInitialState(
+ 'talk', 'my_notes',
+ false
+ );
}
}
diff --git a/src/components/ConversationIcon.vue b/src/components/ConversationIcon.vue
index 0d551db6d62..4dfba5182bf 100644
--- a/src/components/ConversationIcon.vue
+++ b/src/components/ConversationIcon.vue
@@ -95,6 +95,8 @@ export default {
return 'icon-contacts'
} else if (this.item.type === CONVERSATION.TYPE.PUBLIC) {
return 'icon-public'
+ } else if (this.item.type === CONVERSATION.TYPE.NOTES) {
+ return 'icon-notes'
}
return ''
@@ -121,6 +123,10 @@ $icon-size: 44px;
background-size: $icon-size;
}
+ &.icon-notes {
+ background-size: $icon-size;
+ }
+
&.icon-public,
&.icon-contacts,
&.icon-password,
diff --git a/src/components/LeftSidebar/ConversationsList/Conversation.vue b/src/components/LeftSidebar/ConversationsList/Conversation.vue
index 8557312f55f..bad90cf641d 100644
--- a/src/components/LeftSidebar/ConversationsList/Conversation.vue
+++ b/src/components/LeftSidebar/ConversationsList/Conversation.vue
@@ -56,28 +56,30 @@
{{ t('spreed', 'Copy link') }}
-
-
-
-
- {{ t('spreed', 'All messages') }}
-
-
- {{ t('spreed', '@-mentions only') }}
-
-
- {{ t('spreed', 'Off') }}
-
+
+
+
+
+
+ {{ t('spreed', 'All messages') }}
+
+
+ {{ t('spreed', '@-mentions only') }}
+
+
+ {{ t('spreed', 'Off') }}
+
+
@@ -90,7 +92,7 @@
icon="icon-delete-critical"
class="critical"
@click.prevent.exact="deleteConversation">
- {{ t('spreed', 'Delete conversation') }}
+ {{ labelDeleteConversation }}
@@ -145,6 +147,9 @@ export default {
counterShouldBePrimary() {
return this.item.unreadMention || (this.item.unreadMessages && this.item.type === CONVERSATION.TYPE.ONE_TO_ONE)
},
+ isMyNotesConversation() {
+ return this.item.type === CONVERSATION.TYPE.NOTES
+ },
linkToConversation() {
return window.location.protocol + '//' + window.location.host + generateUrl('/call/' + this.item.token)
},
@@ -166,6 +171,12 @@ export default {
isNotifyNever() {
return this.item.notificationLevel === PARTICIPANT.NOTIFY.NEVER
},
+ labelDeleteConversation() {
+ if (this.item.type === CONVERSATION.TYPE.NOTES) {
+ return t('spreed', 'Delete my notes')
+ }
+ return t('spreed', 'Delete conversation')
+ },
canDeleteConversation() {
return this.item.canDeleteConversation
},
@@ -184,7 +195,8 @@ export default {
return ''
}
- if (this.shortLastChatMessageAuthor === '') {
+ if (this.shortLastChatMessageAuthor === ''
+ || this.item.type === CONVERSATION.TYPE.NOTES) {
return this.simpleLastChatMessage
}
diff --git a/src/components/LeftSidebar/LeftSidebar.vue b/src/components/LeftSidebar/LeftSidebar.vue
index 48e3050ee00..41fc5fe0f09 100644
--- a/src/components/LeftSidebar/LeftSidebar.vue
+++ b/src/components/LeftSidebar/LeftSidebar.vue
@@ -84,12 +84,20 @@
-
- {{ t('spreed', 'Default location for attachments') }}
+
+
+
+
@@ -108,7 +116,10 @@ import {
createGroupConversation, createOneToOneConversation,
searchPossibleConversations,
} from '../../services/conversationsService'
-import { setAttachmentFolder } from '../../services/settingsService'
+import {
+ setAttachmentFolder,
+ toggleMyNotes,
+} from '../../services/settingsService'
import { CONVERSATION } from '../../constants'
import { loadState } from '@nextcloud/initial-state'
import NewGroupConversation from './NewGroupConversation/NewGroupConversation'
@@ -140,6 +151,7 @@ export default {
isCirclesEnabled: loadState('talk', 'circles_enabled'),
canStartConversations: loadState('talk', 'start_conversations'),
attachmentFolderLoading: true,
+ myNotesEnabled: loadState('talk', 'my_notes'),
}
},
@@ -285,6 +297,12 @@ export default {
this.attachmentFolderLoading = false
})
},
+
+ async toggleMyNotes() {
+ this.myNotesEnabled = !this.myNotesEnabled
+ await toggleMyNotes(this.myNotesEnabled)
+ EventBus.$emit('shouldRefreshConversations')
+ },
},
}
diff --git a/src/components/MessagesList/MessagesGroup/AuthorAvatar.vue b/src/components/MessagesList/MessagesGroup/AuthorAvatar.vue
index 27ee34e9865..9b14787e419 100644
--- a/src/components/MessagesList/MessagesGroup/AuthorAvatar.vue
+++ b/src/components/MessagesList/MessagesGroup/AuthorAvatar.vue
@@ -34,6 +34,8 @@
+
>_
@@ -67,6 +69,9 @@ export default {
isChangelog() {
return this.authorType === 'bots' && this.authorId === 'changelog'
},
+ isNotes() {
+ return this.authorType === 'bots' && this.authorId === 'notes'
+ },
isUser() {
return this.authorType === 'users'
},
diff --git a/src/components/MessagesList/MessagesList.vue b/src/components/MessagesList/MessagesList.vue
index 0dfb32cb49b..abfa3e44634 100644
--- a/src/components/MessagesList/MessagesList.vue
+++ b/src/components/MessagesList/MessagesList.vue
@@ -249,7 +249,7 @@ export default {
return message2 // Is there a previous message
&& (
message1.actorType !== 'bots' // Don't group messages of commands and bots
- || message1.actorId === 'changelog') // Apart from the changelog bot
+ || message1.actorId === 'changelog' || message1.actorId === 'notes') // Apart from the changelog and notes bot
&& (message1.systemMessage.length === 0) === (message2.systemMessage.length === 0) // Only group system messages with each others
&& message1.actorType === message2.actorType // To have the same author, the type
&& message1.actorId === message2.actorId // and the id of the author must be the same
diff --git a/src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue b/src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue
index 05514dcf844..508d90d1438 100644
--- a/src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue
+++ b/src/components/RightSidebar/Participants/ParticipantsList/Participant/Participant.vue
@@ -203,7 +203,7 @@ export default {
},
showModeratorLabel() {
return this.isModerator
- && [CONVERSATION.TYPE.ONE_TO_ONE, CONVERSATION.TYPE.CHANGELOG].indexOf(this.conversation.type) === -1
+ && [CONVERSATION.TYPE.ONE_TO_ONE, CONVERSATION.TYPE.CHANGELOG, CONVERSATION.TYPE.NOTES].indexOf(this.conversation.type) === -1
},
canModerate() {
return this.participantType !== PARTICIPANT.TYPE.OWNER && !this.isSelf && this.selfIsModerator
diff --git a/src/components/TopBar/CallButton.vue b/src/components/TopBar/CallButton.vue
index 2edd3bd3c4a..c531ec908cd 100644
--- a/src/components/TopBar/CallButton.vue
+++ b/src/components/TopBar/CallButton.vue
@@ -81,6 +81,7 @@ export default {
participantFlags: PARTICIPANT.CALL_FLAG.DISCONNECTED,
participantType: PARTICIPANT.TYPE.USER,
readOnly: CONVERSATION.STATE.READ_ONLY,
+ type: CONVERSATION.TYPE.GROUP,
hasCall: false,
canStartCall: false,
lobbyState: WEBINAR.LOBBY.NONE,
@@ -152,6 +153,7 @@ export default {
showStartCallButton() {
return this.conversation.readOnly === CONVERSATION.STATE.READ_WRITE
+ && this.conversation.type !== CONVERSATION.TYPE.NOTES
&& this.participant.inCall === PARTICIPANT.CALL_FLAG.DISCONNECTED
},
diff --git a/src/constants.js b/src/constants.js
index 6a56f20fd27..5bc87b40450 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -32,6 +32,7 @@ export const CONVERSATION = {
GROUP: 2,
PUBLIC: 3,
CHANGELOG: 4,
+ NOTES: 5,
},
}
export const PARTICIPANT = {
diff --git a/src/services/settingsService.js b/src/services/settingsService.js
index 65e262658bf..72599627e6e 100644
--- a/src/services/settingsService.js
+++ b/src/services/settingsService.js
@@ -36,6 +36,20 @@ const setAttachmentFolder = async function(path) {
})
}
+/**
+ * Gets the conversation token for a given file id
+ *
+ * @param {bool} enabled The name of the folder
+ * @returns {Object} The axios response
+ */
+const toggleMyNotes = async function(enabled) {
+ return axios.post(generateOcsUrl('apps/spreed/api/v1/settings', 2) + 'user', {
+ key: 'notes',
+ value: enabled ? '1' : '0',
+ })
+}
+
export {
setAttachmentFolder,
+ toggleMyNotes,
}
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index e7fda0f1477..8abce88ec5f 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -36,9 +36,9 @@
*/
class FeatureContext implements Context, SnippetAcceptingContext {
- /** @var array[] */
+ /** @var string[] */
protected static $identifierToToken;
- /** @var array[] */
+ /** @var string[] */
protected static $tokenToIdentifier;
/** @var array[] */
protected static $sessionIdToUser;
@@ -135,7 +135,7 @@ public function userIsParticipantOfRooms($user, TableNode $formData = null) {
$rooms = $this->getDataFromResponse($this->response);
$rooms = array_filter($rooms, function($room) {
- return $room['type'] !== 4;
+ return $room['type'] !== 4 && $room['type'] !== 5;
});
if ($formData === null) {
@@ -178,6 +178,43 @@ public function userIsParticipantOfRooms($user, TableNode $formData = null) {
}, $rooms, $formData->getHash()));
}
+ /**
+ * @Then /^user "([^"]*)" has (the|no) notes conversation$/
+ *
+ * @param string $user
+ * @param string $hasConversation
+ */
+ public function userHasNotesConversation(string $user, string $hasConversation): void {
+ $this->setCurrentUser($user);
+ $this->sendRequest('GET', '/apps/spreed/api/v1/room');
+ $this->assertStatusCode($this->response, 200);
+
+ $rooms = $this->getDataFromResponse($this->response);
+
+ $rooms = array_filter($rooms, function($room) {
+ return $room['type'] === 5;
+ });
+
+ $notesIndex = $user . '-notes';
+ if ($hasConversation === 'the') {
+ Assert::assertCount(1, $rooms);
+ $room = array_pop($rooms);
+
+ self::$identifierToToken[$notesIndex] = $room['token'];
+ self::$tokenToIdentifier[$room['token']] = $notesIndex;
+ } else {
+ Assert::assertEmpty($rooms);
+
+ if (isset(self::$identifierToToken[$notesIndex])) {
+ $notesToken = self::$identifierToToken[$notesIndex];
+ unset(
+ self::$tokenToIdentifier[$notesToken],
+ self::$identifierToToken[$notesIndex]
+ );
+ }
+ }
+ }
+
/**
* @Then /^user "([^"]*)" (is|is not) participant of room "([^"]*)"$/
*
@@ -201,7 +238,7 @@ public function userIsParticipantOfRoom($user, $isOrNotParticipant, $identifier)
$rooms = $this->getDataFromResponse($this->response);
$rooms = array_filter($rooms, function($room) {
- return $room['type'] !== 4;
+ return $room['type'] !== 4 && $room['type'] !== 5;
});
if ($isParticipant) {
@@ -249,6 +286,23 @@ private function guestIsParticipantOfRoom($guest, $isOrNotParticipant, $identifi
$this->assertStatusCode($this->response, 404);
}
+ /**
+ * @Then /^user "([^"]*)" sets setting "([^"]*)" to "([^"]*)" with (\d+)$/
+ *
+ * @param string $user
+ * @param string $setting
+ * @param string $value
+ * @param int $statusCode
+ */
+ public function userSetsSetting(string $user, string $setting, string $value, int $statusCode): void {
+ $this->setCurrentUser($user);
+ $this->sendRequest('POST', '/apps/spreed/api/v1/settings/user', [
+ 'key' => $setting,
+ 'value' => $value,
+ ]);
+ $this->assertStatusCode($this->response, $statusCode);
+ }
+
/**
* @Then /^user "([^"]*)" creates room "([^"]*)"$/
*
@@ -812,7 +866,7 @@ public function userSeesTheFollowingMessagesInRoom($user, $identifier, $statusCo
'actorDisplayName' => $message['actorDisplayName'],
// TODO test timestamp; it may require using Runkit, php-timecop
// or something like that.
- 'message' => $message['message'],
+ 'message' => str_replace("\n", '\n', $message['message']),
'messageParameters' => json_encode($message['messageParameters']),
];
if ($includeParents) {
@@ -985,12 +1039,13 @@ public function assureUserExists($user) {
$response = $this->userExists($user);
if ($response->getStatusCode() !== 200) {
$this->createUser($user);
- // Set a display name different than the user ID to be able to
- // ensure in the tests that the right value was returned.
- $this->setUserDisplayName($user);
- $response = $this->userExists($user);
- $this->assertStatusCode($response, 200);
}
+
+ // Set a display name different than the user ID to be able to
+ // ensure in the tests that the right value was returned.
+ $this->setUserDisplayName($user);
+ $response = $this->userExists($user);
+ $this->assertStatusCode($response, 200);
}
private function userExists($user) {
diff --git a/tests/integration/features/conversation/notes.feature b/tests/integration/features/conversation/notes.feature
new file mode 100644
index 00000000000..d65b910b8d7
--- /dev/null
+++ b/tests/integration/features/conversation/notes.feature
@@ -0,0 +1,28 @@
+Feature: notes
+ Background:
+ Given user "participant1" exists
+
+ Scenario: Notes is enabled by default and can be toggled
+ Given user "participant1" has the notes conversation
+ When user "participant1" sets setting "notes" to "0" with 200
+ Then user "participant1" has no notes conversation
+ When user "participant1" sets setting "notes" to "1" with 200
+ Then user "participant1" has the notes conversation
+
+ Scenario: Notes is always new
+ Given user "participant1" has the notes conversation
+ Then user "participant1" sees the following messages in room "participant1-notes" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters |
+ | participant1-notes | bots | notes | My notes | Welcome to your notes!\nYou can use this conversation to share notes between your different devices. When you deleted it, you can recreate it via the settings. | [] |
+ When user "participant1" sends message "Another note" to room "participant1-notes" with 201
+ Then user "participant1" sees the following messages in room "participant1-notes" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters |
+ | participant1-notes | users | participant1 | participant1-displayname | Another note | [] |
+ | participant1-notes | bots | notes | My notes | Welcome to your notes!\nYou can use this conversation to share notes between your different devices. When you deleted it, you can recreate it via the settings. | [] |
+ When user "participant1" sets setting "notes" to "0" with 200
+ Then user "participant1" has no notes conversation
+ When user "participant1" sets setting "notes" to "1" with 200
+ Then user "participant1" has the notes conversation
+ And user "participant1" sees the following messages in room "participant1-notes" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters |
+ | participant1-notes | bots | notes | My notes | Welcome to your notes!\nYou can use this conversation to share notes between your different devices. When you deleted it, you can recreate it via the settings. | [] |
diff --git a/tests/php/CapabilitiesTest.php b/tests/php/CapabilitiesTest.php
index eea12ab44b3..bf1d3925d7c 100644
--- a/tests/php/CapabilitiesTest.php
+++ b/tests/php/CapabilitiesTest.php
@@ -27,7 +27,6 @@
use OCA\Talk\Capabilities;
use OCA\Talk\Config;
use OCP\Capabilities\IPublicCapability;
-use OCP\IConfig;
use OCP\IUser;
use OCP\IUserSession;
use PHPUnit\Framework\MockObject\MockObject;
@@ -35,8 +34,6 @@
class CapabilitiesTest extends TestCase {
- /** @var IConfig|MockObject */
- protected $serverConfig;
/** @var Config|MockObject */
protected $talkConfig;
/** @var IUserSession|MockObject */
@@ -44,14 +41,12 @@ class CapabilitiesTest extends TestCase {
public function setUp(): void {
parent::setUp();
- $this->serverConfig = $this->createMock(IConfig::class);
$this->talkConfig = $this->createMock(Config::class);
$this->userSession = $this->createMock(IUserSession::class);
}
public function testGetCapabilitiesGuest(): void {
$capabilities = new Capabilities(
- $this->serverConfig,
$this->talkConfig,
$this->userSession
);
@@ -63,11 +58,6 @@ public function testGetCapabilitiesGuest(): void {
$this->talkConfig->expects($this->never())
->method('isDisabledForUser');
- $this->serverConfig->expects($this->once())
- ->method('getSystemValueString')
- ->with('version', '0.0.0')
- ->willReturn('16.0.1');
-
$this->assertInstanceOf(IPublicCapability::class, $capabilities);
$this->assertSame([
'spreed' => [
@@ -103,7 +93,7 @@ public function testGetCapabilitiesGuest(): void {
'allowed' => false,
],
'chat' => [
- 'max-length' => 1000,
+ 'max-length' => 32000,
],
'conversations' => [
'can-create' => false,
@@ -127,7 +117,6 @@ public function dataGetCapabilitiesUserAllowed(): array {
*/
public function testGetCapabilitiesUserAllowed(bool $isNotAllowed, bool $canCreate): void {
$capabilities = new Capabilities(
- $this->serverConfig,
$this->talkConfig,
$this->userSession
);
@@ -155,11 +144,6 @@ public function testGetCapabilitiesUserAllowed(bool $isNotAllowed, bool $canCrea
->with($user)
->willReturn($isNotAllowed);
- $this->serverConfig->expects($this->once())
- ->method('getSystemValueString')
- ->with('version', '0.0.0')
- ->willReturn('16.0.2');
-
$this->assertInstanceOf(IPublicCapability::class, $capabilities);
$this->assertSame([
'spreed' => [
@@ -189,6 +173,7 @@ public function testGetCapabilitiesUserAllowed(bool $isNotAllowed, bool $canCrea
'circles-support',
'force-mute',
'chat-reference-id',
+ 'notes',
],
'config' => [
'attachments' => [
@@ -208,7 +193,6 @@ public function testGetCapabilitiesUserAllowed(bool $isNotAllowed, bool $canCrea
public function testGetCapabilitiesUserDisallowed(): void {
$capabilities = new Capabilities(
- $this->serverConfig,
$this->talkConfig,
$this->userSession
);
@@ -223,9 +207,6 @@ public function testGetCapabilitiesUserDisallowed(): void {
->with($user)
->willReturn(true);
- $this->serverConfig->expects($this->never())
- ->method('getSystemValueString');
-
$this->assertInstanceOf(IPublicCapability::class, $capabilities);
$this->assertSame([], $capabilities->getCapabilities());
}