зеркало из https://github.com/nextcloud/spreed.git
Adjust approach for search results for listable rooms
Added "/listable-room" endpoint for listing listable rooms with a similar result format like the "/room" endpoint. Switched left sidebar to use Conversation components for displaying results. Signed-off-by: Vincent Petry <vincent@nextcloud.com>
This commit is contained in:
Родитель
f4d26223b3
Коммит
353206e1e6
|
@ -183,6 +183,14 @@ return [
|
|||
'apiVersion' => 'v(1|2|3)',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Room#getListedRooms',
|
||||
'url' => '/api/{apiVersion}/listed-room',
|
||||
'verb' => 'GET',
|
||||
'requirements' => [
|
||||
'apiVersion' => 'v3',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'Room#createRoom',
|
||||
'url' => '/api/{apiVersion}/room',
|
||||
|
|
|
@ -4,6 +4,63 @@
|
|||
* Base endpoint for API v2 is: `/ocs/v2.php/apps/spreed/api/v2`
|
||||
* Base endpoint for API v3 is: `/ocs/v2.php/apps/spreed/api/v3`
|
||||
|
||||
## Get listed conversations
|
||||
|
||||
* Method: `GET`
|
||||
* Endpoint: `/listed-room`
|
||||
|
||||
* Response:
|
||||
- Status code:
|
||||
+ `200 OK`
|
||||
+ `401 Unauthorized` when the user is not logged in
|
||||
|
||||
- Header:
|
||||
|
||||
field | type | Description
|
||||
------|------|------------
|
||||
`searchTerm` | string | search term
|
||||
|
||||
- Data:
|
||||
Array of conversations, each conversation has at least:
|
||||
|
||||
field | type | API | Description
|
||||
------|------|-----|------------
|
||||
`token` | string | * | Token identifier of the conversation which is used for further interaction
|
||||
`type` | int | * | See list of conversation types in the [constants list](constants.md#Conversation-types)
|
||||
`name` | string | * | Name of the conversation (can also be empty)
|
||||
`displayName` | string | * | `name` if non empty, otherwise it falls back to a list of participants
|
||||
`participantType` | int | * | Permissions level of the current user
|
||||
`attendeeId` | int | v3 | Unique attendee id
|
||||
`attendeePin` | string | v3 | Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute)
|
||||
`actorType` | string | v3 | Currently known `users|guests|emails|groups`
|
||||
`actorId` | string | v3 | The unique identifier for the given actor type
|
||||
`participantInCall` | bool | 🏴 v1 | Flag if the current user is in the call (deprecated, use `participantFlags` instead)
|
||||
`participantFlags` | int | * | Flags of the current user (only available with `in-call-flags` capability)
|
||||
`readOnly` | int | * | Read-only state for the current user (only available with `read-only-rooms` capability)
|
||||
`listable` | int | * | Listable scope for the room (only available with `listable-rooms` capability)
|
||||
`count` | int | 🏴 v1 | **Deprecated:** ~~Number of active users~~ - always returns `0`
|
||||
`numGuests` | int | 🏴 v1 | Number of active guests
|
||||
`lastPing` | int | * | Timestamp of the last ping of the current user (should be used for sorting)
|
||||
`sessionId` | string | * | `'0'` if not connected, otherwise a 512 character long string
|
||||
`hasPassword` | bool | * | Flag if the conversation has a password
|
||||
`hasCall` | bool | * | Flag if the conversation has an active call
|
||||
`canStartCall` | bool | * | Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability)
|
||||
`canDeleteConversation` | bool | 🆕 v2 | Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations)
|
||||
`canLeaveConversation` | bool | 🆕 v2 | Flag if the user can leave the conversation (not possible for the last user with moderator permissions)
|
||||
`lastActivity` | int | * | Timestamp of the last activity in the conversation, in seconds and UTC time zone
|
||||
`isFavorite` | bool | * | Flag if the conversation is favorited by the user
|
||||
`notificationLevel` | int | * | The notification level for the user (one of `Participant::NOTIFY_*` (1-3))
|
||||
`lobbyState` | int | * | Webinary lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability)
|
||||
`lobbyTimer` | int | * | Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)
|
||||
`sipEnabled` | int | v3 | SIP enable status (0-1)
|
||||
`canEnableSIP` | int | v3 | Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation
|
||||
`unreadMessages` | int | * | Number of unread chat messages in the conversation (only available with `chat-v2` capability)
|
||||
`unreadMention` | bool | * | Flag if the user was mentioned since their last visit
|
||||
`lastReadMessage` | int | * | ID of the last read message in a room (only available with `chat-read-marker` capability)
|
||||
`lastMessage` | message | * | Last message in a conversation if available, otherwise empty
|
||||
`objectType` | string | * | The type of object that the conversation is associated with; "share:password" if the conversation is used to request a password for a share, otherwise empty
|
||||
`objectId` | string | * | Share token if "objectType" is "share:password", otherwise empty
|
||||
|
||||
## Get user´s conversations
|
||||
|
||||
* Method: `GET`
|
||||
|
|
|
@ -12,6 +12,11 @@ Explanations:
|
|||
* Event name: `OCA\Talk\Controller\RoomController::EVENT_BEFORE_ROOMS_GET`
|
||||
* Since: 8.0.0
|
||||
|
||||
### Search listed conversations
|
||||
|
||||
* Event class: `OCA\Talk\Events\UserEvent`
|
||||
* Event name: `OCA\Talk\Controller\RoomController::EVENT_BEFORE_LISTED_ROOMS_GET`
|
||||
* Since: 11.0.0
|
||||
|
||||
### Create conversation
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ use OCP\UserStatus\IUserStatus;
|
|||
|
||||
class RoomController extends AEnvironmentAwareController {
|
||||
public const EVENT_BEFORE_ROOMS_GET = self::class . '::preGetRooms';
|
||||
public const EVENT_BEFORE_LISTED_ROOMS_GET = self::class . '::preGetListedRooms';
|
||||
|
||||
/** @var string|null */
|
||||
protected $userId;
|
||||
|
@ -219,6 +220,37 @@ class RoomController extends AEnvironmentAwareController {
|
|||
return new DataResponse($return, Http::STATUS_OK, $this->getTalkHashHeader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Search listed rooms
|
||||
*
|
||||
* @NoAdminRequired
|
||||
*
|
||||
* @param string $searchTerm search term
|
||||
* @return DataResponse
|
||||
*/
|
||||
public function getListedRooms(string $searchTerm = ''): DataResponse {
|
||||
$event = new UserEvent($this->userId);
|
||||
$this->dispatcher->dispatch(self::EVENT_BEFORE_LISTED_ROOMS_GET, $event);
|
||||
|
||||
$rooms = $this->manager->getListedRoomsForUser($this->userId, $searchTerm);
|
||||
|
||||
$return = [];
|
||||
foreach ($rooms as $room) {
|
||||
try {
|
||||
$roomData = $this->formatRoom($room, null);
|
||||
// since formatRoom will break early due to having no participant,
|
||||
// we populate the remaining base attributes here
|
||||
$return[] = $this->populateBaseRoomData($roomData, $room, $this->userId);
|
||||
// TODO: should we populate more ?
|
||||
} catch (RoomNotFoundException $e) {
|
||||
} catch (\RuntimeException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return new DataResponse($return, Http::STATUS_OK);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @PublicPage
|
||||
*
|
||||
|
@ -599,20 +631,12 @@ class RoomController extends AEnvironmentAwareController {
|
|||
$attendee = $currentParticipant->getAttendee();
|
||||
$userId = $attendee->getActorType() === Attendee::ACTOR_USERS ? $attendee->getActorId() : '';
|
||||
|
||||
$roomData = $this->populateBaseRoomData($roomData, $room, $userId);
|
||||
|
||||
$roomData = array_merge($roomData, [
|
||||
'name' => $room->getName(),
|
||||
'displayName' => $room->getDisplayName($userId),
|
||||
'objectType' => $room->getObjectType(),
|
||||
'objectId' => $room->getObjectId(),
|
||||
'participantType' => $attendee->getParticipantType(),
|
||||
'readOnly' => $room->getReadOnly(),
|
||||
'listable' => $room->getListable(),
|
||||
'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
|
||||
'lastActivity' => $lastActivity,
|
||||
'isFavorite' => $attendee->isFavorite(),
|
||||
'notificationLevel' => $attendee->getNotificationLevel(),
|
||||
'lobbyState' => $room->getLobbyState(),
|
||||
'lobbyTimer' => $lobbyTimer,
|
||||
]);
|
||||
if ($this->getAPIVersion() >= 3) {
|
||||
if ($this->talkConfig->isSIPConfigured()) {
|
||||
|
@ -732,6 +756,33 @@ class RoomController extends AEnvironmentAwareController {
|
|||
return $roomData;
|
||||
}
|
||||
|
||||
protected function populateBaseRoomData(array $roomData, Room $room, $userId) {
|
||||
$lastActivity = $room->getLastActivity();
|
||||
if ($lastActivity instanceof \DateTimeInterface) {
|
||||
$lastActivity = $lastActivity->getTimestamp();
|
||||
} else {
|
||||
$lastActivity = 0;
|
||||
}
|
||||
|
||||
$lobbyTimer = $room->getLobbyTimer();
|
||||
if ($lobbyTimer instanceof \DateTimeInterface) {
|
||||
$lobbyTimer = $lobbyTimer->getTimestamp();
|
||||
} else {
|
||||
$lobbyTimer = 0;
|
||||
}
|
||||
|
||||
return array_merge($roomData, [
|
||||
'name' => $room->getName(),
|
||||
'displayName' => $room->getDisplayName($userId),
|
||||
'objectType' => $room->getObjectType(),
|
||||
'objectId' => $room->getObjectId(),
|
||||
'readOnly' => $room->getReadOnly(),
|
||||
'listable' => $room->getListable(),
|
||||
'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
|
||||
'lobbyState' => $room->getLobbyState(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Room $room
|
||||
* @param Participant $participant
|
||||
|
|
|
@ -347,7 +347,7 @@ class Manager {
|
|||
$allowedRoomTypes = [Room::GROUP_CALL, Room::PUBLIC_CALL];
|
||||
$allowedListedTypes = [Room::LISTABLE_ALL];
|
||||
if (!$this->isGuestUser($userId)) {
|
||||
$listedType[] = Room::LISTABLE_USERS;
|
||||
$allowedListedTypes[] = Room::LISTABLE_USERS;
|
||||
}
|
||||
$query = $this->db->getQueryBuilder();
|
||||
$query->select('r.*')
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
:highlighted="counterShouldBePrimary">
|
||||
<strong>{{ item.unreadMessages }}</strong>
|
||||
</AppNavigationCounter>
|
||||
<template slot="actions">
|
||||
<template v-if="!isSearchResult" slot="actions">
|
||||
<ActionButton v-if="canFavorite"
|
||||
:icon="iconFavorite"
|
||||
@click.prevent.exact="toggleFavoriteConversation">
|
||||
|
@ -124,6 +124,10 @@ export default {
|
|||
ConversationIcon,
|
||||
},
|
||||
props: {
|
||||
isSearchResult: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
|
|
|
@ -47,12 +47,11 @@
|
|||
<template v-if="searchResultsListedConversations.length !== 0">
|
||||
<Caption
|
||||
:title="t('spreed', 'Listed conversations')" />
|
||||
<li role="presentation">
|
||||
<!-- FIXME: use ConversationsList instead ? -->
|
||||
<ConversationsOptionsList
|
||||
:items="searchResultsListedConversations"
|
||||
@click="joinConversation" />
|
||||
</li>
|
||||
<Conversation
|
||||
v-for="item of searchResultsListedConversations"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:is-search-result="true" />
|
||||
</template>
|
||||
<template v-if="searchResultsUsers.length !== 0">
|
||||
<Caption
|
||||
|
@ -114,6 +113,7 @@
|
|||
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
|
||||
import Caption from '../Caption'
|
||||
import ConversationsList from './ConversationsList/ConversationsList'
|
||||
import Conversation from './ConversationsList/Conversation'
|
||||
import ConversationsOptionsList from '../ConversationsOptionsList'
|
||||
import Hint from '../Hint'
|
||||
import SearchBox from './SearchBox/SearchBox'
|
||||
|
@ -144,6 +144,7 @@ export default {
|
|||
Hint,
|
||||
SearchBox,
|
||||
NewGroupConversation,
|
||||
Conversation,
|
||||
},
|
||||
|
||||
mixins: [
|
||||
|
@ -286,18 +287,7 @@ export default {
|
|||
async fetchListedConversations() {
|
||||
this.listedConversationsLoading = true
|
||||
const response = await searchListedConversations(this.searchText)
|
||||
if (response.data.ocs.data?.entries?.length) {
|
||||
this.searchResultsListedConversations = response.data.ocs.data.entries.map((result) => {
|
||||
return {
|
||||
// TODO: extract token ?
|
||||
id: result.resourceUrl,
|
||||
label: result.title,
|
||||
icon: result.icon,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.searchResultsListedConversations = []
|
||||
}
|
||||
this.searchResultsListedConversations = response.data.ocs.data
|
||||
this.listedConversationsLoading = false
|
||||
},
|
||||
|
||||
|
@ -308,10 +298,6 @@ export default {
|
|||
this.focusInitialise()
|
||||
},
|
||||
|
||||
async joinConversation(item) {
|
||||
console.log('TODO: Implement join conversation: ', item)
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new conversation with the selected group/user/circle
|
||||
* @param {Object} item The autocomplete suggestion to start a conversation with
|
||||
|
|
|
@ -90,19 +90,29 @@ const checkTalkVersionHash = function(response) {
|
|||
|
||||
/**
|
||||
* Fetch listed conversations
|
||||
* @param {string} searchText The string that will be used in the search query.
|
||||
* @param {string} searchTerm The string that will be used in the search query.
|
||||
*/
|
||||
const searchListedConversations = async function(searchText) {
|
||||
const searchListedConversations = async function(searchTerm) {
|
||||
try {
|
||||
// use search provider to find listed conversations
|
||||
return axios.get(generateOcsUrl('search/providers/talk-listed-conversations', 2) + 'search', {
|
||||
const response = await axios.get(generateOcsUrl('apps/spreed/api/v3', 2) + 'listed-room', {
|
||||
params: {
|
||||
term: searchText,
|
||||
format: 'json',
|
||||
searchTerm,
|
||||
},
|
||||
})
|
||||
|
||||
if (maintenanceWarning) {
|
||||
maintenanceWarning.hideToast()
|
||||
maintenanceWarning = null
|
||||
}
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.debug('Error while searching listedk conversations: ', error)
|
||||
if (error.response && error.response.status === 503 && !maintenanceWarning) {
|
||||
maintenanceWarning = showError(t('spreed', 'Nextcloud is in maintenance mode, please reload the page'), {
|
||||
timeout: TOAST_PERMANENT_TIMEOUT,
|
||||
})
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче