commit
b2c5bbc0c7
|
@ -147,8 +147,11 @@
|
|||
"addon.files.privatefiles": "moodle",
|
||||
"addon.files.sitefiles": "moodle",
|
||||
"addon.messageoutput_airnotifier.processorsettingsdesc": "local_moodlemobileapp",
|
||||
"addon.messages.acceptandaddcontact": "message",
|
||||
"addon.messages.addcontact": "message",
|
||||
"addon.messages.addcontactconfirm": "message",
|
||||
"addon.messages.addtofavourites": "message",
|
||||
"addon.messages.addtoyourcontacts": "message",
|
||||
"addon.messages.blocknoncontacts": "message",
|
||||
"addon.messages.blockuser": "message",
|
||||
"addon.messages.blockuserconfirm": "message",
|
||||
|
@ -159,7 +162,9 @@
|
|||
"addon.messages.contactblocked": "message",
|
||||
"addon.messages.contactlistempty": "local_moodlemobileapp",
|
||||
"addon.messages.contactname": "local_moodlemobileapp",
|
||||
"addon.messages.contactrequestsent": "message",
|
||||
"addon.messages.contacts": "message",
|
||||
"addon.messages.decline": "message",
|
||||
"addon.messages.deleteallconfirm": "message",
|
||||
"addon.messages.deleteconversationq": "message",
|
||||
"addon.messages.deletemessage": "local_moodlemobileapp",
|
||||
|
@ -168,34 +173,51 @@
|
|||
"addon.messages.errorwhileretrievingcontacts": "local_moodlemobileapp",
|
||||
"addon.messages.errorwhileretrievingdiscussions": "local_moodlemobileapp",
|
||||
"addon.messages.errorwhileretrievingmessages": "local_moodlemobileapp",
|
||||
"addon.messages.errorwhileretrievingusers": "local_moodlemobileapp",
|
||||
"addon.messages.groupinfo": "message",
|
||||
"addon.messages.groupmessages": "message",
|
||||
"addon.messages.info": "message",
|
||||
"addon.messages.isnotinyourcontacts": "message",
|
||||
"addon.messages.message": "message",
|
||||
"addon.messages.messagenotsent": "local_moodlemobileapp",
|
||||
"addon.messages.messagepreferences": "message",
|
||||
"addon.messages.messages": "message",
|
||||
"addon.messages.newmessage": "message",
|
||||
"addon.messages.newmessages": "local_moodlemobileapp",
|
||||
"addon.messages.nocontactrequests": "message",
|
||||
"addon.messages.noncontacts": "message",
|
||||
"addon.messages.nocontactsgetstarted": "message",
|
||||
"addon.messages.nofavourites": "message",
|
||||
"addon.messages.nogroupmessages": "message",
|
||||
"addon.messages.nomessages": "message",
|
||||
"addon.messages.nousersfound": "local_moodlemobileapp",
|
||||
"addon.messages.numparticipants": "message",
|
||||
"addon.messages.removecontact": "message",
|
||||
"addon.messages.removecontactconfirm": "local_moodlemobileapp",
|
||||
"addon.messages.removecontactconfirm": "message",
|
||||
"addon.messages.removefromyourcontacts": "message",
|
||||
"addon.messages.removefromfavourites": "message",
|
||||
"addon.messages.requests": "moodle",
|
||||
"addon.messages.requirecontacttomessage": "message",
|
||||
"addon.messages.searchcombined": "message",
|
||||
"addon.messages.searchnocontactsfound": "message",
|
||||
"addon.messages.searchnomessagesfound": "message",
|
||||
"addon.messages.searchnononcontactsfound": "message",
|
||||
"addon.messages.showdeletemessages": "local_moodlemobileapp",
|
||||
"addon.messages.sendcontactrequest": "message",
|
||||
"addon.messages.type_blocked": "local_moodlemobileapp",
|
||||
"addon.messages.type_offline": "local_moodlemobileapp",
|
||||
"addon.messages.type_online": "local_moodlemobileapp",
|
||||
"addon.messages.type_search": "local_moodlemobileapp",
|
||||
"addon.messages.type_strangers": "local_moodlemobileapp",
|
||||
"addon.messages.unabletomessage": "message",
|
||||
"addon.messages.unblockuser": "message",
|
||||
"addon.messages.unblockuserconfirm": "message",
|
||||
"addon.messages.userwouldliketocontactyou": "message",
|
||||
"addon.messages.warningconversationmessagenotsent": "local_moodlemobileapp",
|
||||
"addon.messages.warningmessagenotsent": "local_moodlemobileapp",
|
||||
"addon.messages.wouldliketocontactyou": "message",
|
||||
"addon.messages.you": "message",
|
||||
"addon.messages.youhaveblockeduser": "message",
|
||||
"addon.mod_assign.acceptsubmissionstatement": "local_moodlemobileapp",
|
||||
"addon.mod_assign.addattempt": "assign",
|
||||
"addon.mod_assign.addnewattempt": "assign",
|
||||
|
@ -1540,6 +1562,7 @@
|
|||
"core.quotausage": "moodle",
|
||||
"core.redirectingtosite": "local_moodlemobileapp",
|
||||
"core.refresh": "moodle",
|
||||
"core.remove": "moodle",
|
||||
"core.required": "moodle",
|
||||
"core.requireduserdatamissing": "local_moodlemobileapp",
|
||||
"core.resources": "moodle",
|
||||
|
|
|
@ -20,11 +20,15 @@ import { CoreComponentsModule } from '@components/components.module';
|
|||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
import { AddonMessagesDiscussionsComponent } from '../components/discussions/discussions';
|
||||
import { AddonMessagesConfirmedContactsComponent } from '../components/confirmed-contacts/confirmed-contacts';
|
||||
import { AddonMessagesContactRequestsComponent } from '../components/contact-requests/contact-requests';
|
||||
import { AddonMessagesContactsComponent } from '../components/contacts/contacts';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonMessagesDiscussionsComponent,
|
||||
AddonMessagesConfirmedContactsComponent,
|
||||
AddonMessagesContactRequestsComponent,
|
||||
AddonMessagesContactsComponent
|
||||
],
|
||||
imports: [
|
||||
|
@ -39,6 +43,8 @@ import { AddonMessagesContactsComponent } from '../components/contacts/contacts'
|
|||
],
|
||||
exports: [
|
||||
AddonMessagesDiscussionsComponent,
|
||||
AddonMessagesConfirmedContactsComponent,
|
||||
AddonMessagesContactRequestsComponent,
|
||||
AddonMessagesContactsComponent
|
||||
]
|
||||
})
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<ion-content>
|
||||
<ion-refresher [enabled]="loaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
<ion-list no-margin>
|
||||
<a ion-item text-wrap *ngFor="let contact of contacts" [title]="contact.fullname" (click)="selectUser(contact.id)" [class.core-split-item-selected]="contact.id == selectedUserId" detail-none>
|
||||
<ion-avatar item-start core-user-avatar [user]="contact" [checkOnline]="true" [linkProfile]="false"></ion-avatar>
|
||||
<h2><core-format-text [text]="contact.fullname"></core-format-text></h2>
|
||||
<core-icon *ngIf="contact.isblocked" name="fa-ban" item-end></core-icon>
|
||||
</a>
|
||||
</ion-list>
|
||||
<core-empty-box *ngIf="!contacts.length" icon="person" [message]="'addon.messages.nocontactsgetstarted' | translate"></core-empty-box>
|
||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError" position="bottom"></core-infinite-loading>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,141 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { Content } from 'ionic-angular';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonMessagesProvider } from '../../providers/messages';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
|
||||
/**
|
||||
* Component that displays the list of confirmed contacts.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-messages-confirmed-contacts',
|
||||
templateUrl: 'addon-messages-confirmed-contacts.html',
|
||||
})
|
||||
export class AddonMessagesConfirmedContactsComponent implements OnInit, OnDestroy {
|
||||
@Output() onUserSelected = new EventEmitter<{userId: number, onInit?: boolean}>();
|
||||
@ViewChild(Content) content: Content;
|
||||
|
||||
loaded = false;
|
||||
canLoadMore = false;
|
||||
loadMoreError = false;
|
||||
contacts = [];
|
||||
selectedUserId: number;
|
||||
|
||||
protected memberInfoObserver;
|
||||
|
||||
constructor(private domUtils: CoreDomUtilsProvider, eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider,
|
||||
private messagesProvider: AddonMessagesProvider) {
|
||||
|
||||
this.onUserSelected = new EventEmitter();
|
||||
|
||||
// Update block status of a user.
|
||||
this.memberInfoObserver = eventsProvider.on(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, (data) => {
|
||||
if (data.userBlocked || data.userUnblocked) {
|
||||
const user = this.contacts.find((user) => user.id == data.userId);
|
||||
if (user) {
|
||||
user.isblocked = data.userBlocked;
|
||||
}
|
||||
} else if (data.contactRemoved) {
|
||||
const index = this.contacts.findIndex((contact) => contact.id == data.userId);
|
||||
if (index >= 0) {
|
||||
this.contacts.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}, sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Component loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.fetchData().then(() => {
|
||||
if (this.contacts.length) {
|
||||
this.selectUser(this.contacts[0].id, true);
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
|
||||
// Workaround for infinite scrolling.
|
||||
this.content.resize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch contacts.
|
||||
*
|
||||
* @param {boolean} [refresh=false] True if we are refreshing contacts, false if we are loading more.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
fetchData(refresh: boolean = false): Promise<any> {
|
||||
this.loadMoreError = false;
|
||||
|
||||
const limitFrom = refresh ? 0 : this.contacts.length;
|
||||
|
||||
return this.messagesProvider.getUserContacts(limitFrom).then((result) => {
|
||||
this.contacts = refresh ? result.contacts : this.contacts.concat(result.contacts);
|
||||
this.canLoadMore = result.canLoadMore;
|
||||
}).catch((error) => {
|
||||
this.loadMoreError = true;
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh contacts.
|
||||
*
|
||||
* @param {any} [refresher] Refresher.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
refreshData(refresher?: any): Promise<any> {
|
||||
return this.messagesProvider.invalidateUserContacts().then(() => {
|
||||
return this.fetchData(true);
|
||||
}).finally(() => {
|
||||
refresher && refresher.complete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load more contacts.
|
||||
*
|
||||
* @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
loadMore(infiniteComplete?: any): Promise<any> {
|
||||
return this.fetchData().finally(() => {
|
||||
infiniteComplete && infiniteComplete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that a contact has been selected.
|
||||
*
|
||||
* @param {number} userId User id.
|
||||
* @param {boolean} [onInit=false] Whether the contact is selected on initial load.
|
||||
*/
|
||||
selectUser(userId: number, onInit: boolean = false): void {
|
||||
this.selectedUserId = userId;
|
||||
this.onUserSelected.emit({userId, onInit});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.memberInfoObserver && this.memberInfoObserver.off();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<ion-content>
|
||||
<ion-refresher [enabled]="loaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
<ion-list no-margin>
|
||||
<a ion-item text-wrap *ngFor="let request of requests" [title]="request.fullname" (click)="selectUser(request.id)" [class.core-split-item-selected]="request.id == selectedUserId" detail-none>
|
||||
<ion-avatar item-start core-user-avatar [user]="request" [checkOnline]="true" [linkProfile]="false"></ion-avatar>
|
||||
<h2><core-format-text [text]="request.fullname"></core-format-text></h2>
|
||||
<p *ngIf="!request.iscontact && !request.confirmedOrDeclined">{{ 'addon.messages.wouldliketocontactyou' | translate }}</p>
|
||||
</a>
|
||||
</ion-list>
|
||||
<core-empty-box *ngIf="!requests.length" icon="person" [message]="'addon.messages.nocontactrequests' | translate"></core-empty-box>
|
||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError" position="bottom"></core-infinite-loading>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,137 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { Content } from 'ionic-angular';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonMessagesProvider } from '../../providers/messages';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
|
||||
/**
|
||||
* Component that displays the list of contact requests.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-messages-contact-requests',
|
||||
templateUrl: 'addon-messages-contact-requests.html',
|
||||
})
|
||||
export class AddonMessagesContactRequestsComponent implements OnInit, OnDestroy {
|
||||
@Output() onUserSelected = new EventEmitter<{userId: number, onInit?: boolean}>();
|
||||
@ViewChild(Content) content: Content;
|
||||
|
||||
loaded = false;
|
||||
canLoadMore = false;
|
||||
loadMoreError = false;
|
||||
requests = [];
|
||||
selectedUserId: number;
|
||||
|
||||
protected memberInfoObserver;
|
||||
|
||||
constructor(private domUtils: CoreDomUtilsProvider, eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider,
|
||||
private messagesProvider: AddonMessagesProvider) {
|
||||
|
||||
// Hide the "Would like to contact you" message when a contact request is confirmed.
|
||||
this.memberInfoObserver = eventsProvider.on(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, (data) => {
|
||||
if (data.contactRequestConfirmed || data.contactRequestDeclined) {
|
||||
const index = this.requests.findIndex((request) => request.id == data.userId);
|
||||
if (index >= 0) {
|
||||
this.requests.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}, sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Component loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.fetchData().then(() => {
|
||||
if (this.requests.length) {
|
||||
this.selectUser(this.requests[0].id, true);
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
|
||||
// Workaround for infinite scrolling.
|
||||
this.content.resize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch contact requests.
|
||||
*
|
||||
* @param {boolean} [refresh=false] True if we are refreshing contact requests, false if we are loading more.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
fetchData(refresh: boolean = false): Promise<any> {
|
||||
this.loadMoreError = false;
|
||||
|
||||
const limitFrom = refresh ? 0 : this.requests.length;
|
||||
|
||||
return this.messagesProvider.getContactRequests(limitFrom).then((result) => {
|
||||
this.requests = refresh ? result.requests : this.requests.concat(result.requests);
|
||||
this.canLoadMore = result.canLoadMore;
|
||||
}).catch((error) => {
|
||||
this.loadMoreError = true;
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh contact requests.
|
||||
*
|
||||
* @param {any} [refresher] Refresher.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
refreshData(refresher?: any): Promise<any> {
|
||||
// Refresh the number of contacts requests to update badges.
|
||||
this.messagesProvider.refreshContactRequestsCount();
|
||||
|
||||
return this.messagesProvider.invalidateContactRequestsCache().then(() => {
|
||||
return this.fetchData(true);
|
||||
}).finally(() => {
|
||||
refresher && refresher.complete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load more contact requests.
|
||||
*
|
||||
* @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
loadMore(infiniteComplete?: any): Promise<any> {
|
||||
return this.fetchData().finally(() => {
|
||||
infiniteComplete && infiniteComplete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that a contact has been selected.
|
||||
*
|
||||
* @param {number} userId User id.
|
||||
* @param {boolean} [onInit=false] Whether the contact is selected on initial load.
|
||||
*/
|
||||
selectUser(userId: number, onInit: boolean = false): void {
|
||||
this.selectedUserId = userId;
|
||||
this.onUserSelected.emit({userId, onInit});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.memberInfoObserver && this.memberInfoObserver.off();
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@
|
|||
<h2>{{ 'core.searchresults' | translate }}</h2>
|
||||
<ion-note item-end>{{ search.results.length }}</ion-note>
|
||||
</ion-item-divider>
|
||||
<a ion-item text-wrap *ngFor="let result of search.results" [title]="result.fullname" (click)="gotoDiscussion(result.userid, result.messageid)" [class.core-split-item-selected]="result.userid == discussionUserId" detail-none>
|
||||
<a ion-item text-wrap *ngFor="let result of search.results" [title]="result.fullname" (click)="gotoDiscussion(result.userid, result.messageid)" [class.core-split-item-selected]="result.userid == discussionUserId" class="addon-message-discussion">
|
||||
<ion-avatar core-user-avatar [user]="result" item-start></ion-avatar>
|
||||
<h2><core-format-text [text]="result.fullname"></core-format-text></h2>
|
||||
<p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage"></core-format-text></p>
|
||||
|
@ -20,15 +20,15 @@
|
|||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="!search.showResults" no-margin>
|
||||
<a ion-item text-wrap *ngFor="let discussion of discussions" [title]="discussion.fullname" (click)="gotoDiscussion(discussion.message.user)" [class.core-split-item-selected]="discussion.message.user == discussionUserId" detail-none>
|
||||
<a ion-item text-wrap *ngFor="let discussion of discussions" [title]="discussion.fullname" (click)="gotoDiscussion(discussion.message.user)" [class.core-split-item-selected]="discussion.message.user == discussionUserId" class="addon-message-discussion">
|
||||
<ion-avatar core-user-avatar [user]="discussion" item-start></ion-avatar>
|
||||
<h2>
|
||||
<core-format-text [text]="discussion.fullname"></core-format-text>
|
||||
<ion-note *ngIf="discussion.message.timecreated > 0 || discussion.unread">
|
||||
<span *ngIf="discussion.unread" class="core-primary-circle"></span>
|
||||
<span *ngIf="discussion.message.timecreated > 0">{{discussion.message.timecreated / 1000 | coreDateDayOrTime}}</span>
|
||||
</ion-note>
|
||||
</h2>
|
||||
<ion-note *ngIf="discussion.message.timecreated > 0 || discussion.unread">
|
||||
<span *ngIf="discussion.unread" class="core-primary-circle"></span>
|
||||
<span *ngIf="discussion.message.timecreated > 0">{{discussion.message.timecreated / 1000 | coreDateDayOrTime}}</span>
|
||||
</ion-note>
|
||||
<p><core-format-text clean="true" singleLine="true" [text]="discussion.message.message"></core-format-text></p>
|
||||
</a>
|
||||
</ion-list>
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
ion-app.app-root addon-messages-discussions {
|
||||
ion-app.app-root .addon-message-discussion {
|
||||
h2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.note {
|
||||
margin: 0;
|
||||
align-self: flex-end;
|
||||
display: inline-flex;
|
||||
font-size: initial;
|
||||
margin-top: 6px;
|
||||
core-format-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.note {
|
||||
position: absolute;
|
||||
@include position(0, 0, null, null);
|
||||
margin: 4px 8px;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
}
|
|
@ -209,7 +209,7 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy {
|
|||
|
||||
return this.messagesProvider.searchMessages(query).then((searchResults) => {
|
||||
this.search.showResults = true;
|
||||
this.search.results = searchResults;
|
||||
this.search.results = searchResults.messages;
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
|
||||
}).finally(() => {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
{
|
||||
"acceptandaddcontact": "Accept and add to contacts",
|
||||
"addcontact": "Add contact",
|
||||
"addcontactconfirm": "Are you sure you want to add {{$a}} to your contacts?",
|
||||
"addtofavourites": "Star",
|
||||
"addtoyourcontacts": "Add to contacts",
|
||||
"blocknoncontacts": "Prevent non-contacts from messaging me",
|
||||
"blockuser": "Block user",
|
||||
"blockuserconfirm": "Are you sure you want to block {{$a}}?",
|
||||
|
@ -11,7 +14,9 @@
|
|||
"contactblocked": "Contact blocked",
|
||||
"contactlistempty": "The contact list is empty",
|
||||
"contactname": "Contact name",
|
||||
"contactrequestsent": "Contact request sent",
|
||||
"contacts": "Contacts",
|
||||
"decline": "Decline",
|
||||
"deleteallconfirm": "Are you sure you would like to delete this entire conversation? This will not delete it for other conversation participants.",
|
||||
"deleteconversation": "Delete conversation",
|
||||
"deletemessage": "Delete message",
|
||||
|
@ -20,32 +25,50 @@
|
|||
"errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
||||
"errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
|
||||
"errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
||||
"errorwhileretrievingusers": "Error while retrieving users from the server.",
|
||||
"groupinfo": "Group info",
|
||||
"groupmessages": "Group messages",
|
||||
"info": "Info",
|
||||
"isnotinyourcontacts": "{{$a}} is not in your contacts",
|
||||
"messagenotsent": "The message was not sent. Please try again later.",
|
||||
"message": "Message",
|
||||
"messagepreferences": "Message preferences",
|
||||
"messages": "Messages",
|
||||
"newmessage": "New message",
|
||||
"newmessages": "New messages",
|
||||
"nocontactrequests": "No contact requests",
|
||||
"noncontacts": "Non-contacts",
|
||||
"nocontactsgetstarted": "Try searching for someone to add them as a contact",
|
||||
"nofavourites": "No favourites",
|
||||
"nogroupmessages": "No group messages",
|
||||
"nomessages": "No messages",
|
||||
"nousersfound": "No users found",
|
||||
"numparticipants": "{{$a}} participants",
|
||||
"removecontact": "Remove contact",
|
||||
"removecontactconfirm": "Contact will be removed from your contacts list.",
|
||||
"removecontactconfirm": "Are you sure you want to remove {{$a}} from your contacts?",
|
||||
"removefromyourcontacts": "Remove from contacts",
|
||||
"removefromfavourites": "Unstar",
|
||||
"requests": "Requests",
|
||||
"requirecontacttomessage": "You need to request {{$a}} to add you as a contact to be able to message them.",
|
||||
"searchcombined": "Search people and messages",
|
||||
"searchnocontactsfound": "No contacts found",
|
||||
"searchnomessagesfound": "No messages found",
|
||||
"searchnononcontactsfound": "No non contacts found",
|
||||
"sendcontactrequest": "Send contact request",
|
||||
"showdeletemessages": "Show delete messages",
|
||||
"type_blocked": "Blocked",
|
||||
"type_offline": "Offline",
|
||||
"type_online": "Online",
|
||||
"type_search": "Search results",
|
||||
"type_strangers": "Others",
|
||||
"unabletomessage": "You are unable to message this user",
|
||||
"unblockuser": "Unblock user",
|
||||
"unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
||||
"userwouldliketocontactyou": "{{$a}} would like to contact you",
|
||||
"warningconversationmessagenotsent": "Couldn't send message(s) to conversation {{conversation}}. {{error}}",
|
||||
"warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
|
||||
"you": "You:"
|
||||
"wouldliketocontactyou": "Would like to contact you",
|
||||
"you": "You:",
|
||||
"youhaveblockeduser": "You have blocked this user in the past",
|
||||
"yourcontactrequestpending": "Your contact request is pending with {{$a}}"
|
||||
}
|
|
@ -25,6 +25,7 @@ import { CoreCronDelegate } from '@providers/cron';
|
|||
import { AddonMessagesSendMessageUserHandler } from './providers/user-send-message-handler';
|
||||
import { AddonMessagesAddContactUserHandler } from './providers/user-add-contact-handler';
|
||||
import { AddonMessagesBlockContactUserHandler } from './providers/user-block-contact-handler';
|
||||
import { AddonMessagesContactRequestLinkHandler } from './providers/contact-request-link-handler';
|
||||
import { AddonMessagesDiscussionLinkHandler } from './providers/discussion-link-handler';
|
||||
import { AddonMessagesIndexLinkHandler } from './providers/index-link-handler';
|
||||
import { AddonMessagesSyncCronHandler } from './providers/sync-cron-handler';
|
||||
|
@ -58,6 +59,7 @@ export const ADDON_MESSAGES_PROVIDERS: any[] = [
|
|||
AddonMessagesSendMessageUserHandler,
|
||||
AddonMessagesAddContactUserHandler,
|
||||
AddonMessagesBlockContactUserHandler,
|
||||
AddonMessagesContactRequestLinkHandler,
|
||||
AddonMessagesDiscussionLinkHandler,
|
||||
AddonMessagesIndexLinkHandler,
|
||||
AddonMessagesSyncCronHandler,
|
||||
|
@ -74,11 +76,13 @@ export class AddonMessagesModule {
|
|||
sitesProvider: CoreSitesProvider, linkHelper: CoreContentLinksHelperProvider, updateManager: CoreUpdateManagerProvider,
|
||||
settingsHandler: AddonMessagesSettingsHandler, settingsDelegate: CoreSettingsDelegate,
|
||||
pushNotificationsDelegate: AddonPushNotificationsDelegate, utils: CoreUtilsProvider,
|
||||
addContactHandler: AddonMessagesAddContactUserHandler, blockContactHandler: AddonMessagesBlockContactUserHandler) {
|
||||
addContactHandler: AddonMessagesAddContactUserHandler, blockContactHandler: AddonMessagesBlockContactUserHandler,
|
||||
contactRequestLinkHandler: AddonMessagesContactRequestLinkHandler) {
|
||||
// Register handlers.
|
||||
mainMenuDelegate.registerHandler(mainmenuHandler);
|
||||
contentLinksDelegate.registerHandler(indexLinkHandler);
|
||||
contentLinksDelegate.registerHandler(discussionLinkHandler);
|
||||
contentLinksDelegate.registerHandler(contactRequestLinkHandler);
|
||||
userDelegate.registerHandler(sendMessageHandler);
|
||||
userDelegate.registerHandler(addContactHandler);
|
||||
userDelegate.registerHandler(blockContactHandler);
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<ion-header>
|
||||
<ion-navbar core-back-button>
|
||||
<ion-title>{{ 'addon.messages.contacts' | translate }}</ion-title>
|
||||
<ion-buttons end>
|
||||
<button ion-button icon-only (click)="gotoSearch()" [attr.aria-label]="'addon.messages.search' | translate">
|
||||
<ion-icon name="search"></ion-icon>
|
||||
</button>
|
||||
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu disappears in some cases. -->
|
||||
<core-context-menu></core-context-menu>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<core-split-view>
|
||||
<ion-content>
|
||||
<core-tabs>
|
||||
<core-tab [title]="'addon.messages.contacts' | translate" (ionSelect)="selectUser('contacts')">
|
||||
<ng-template>
|
||||
<addon-messages-confirmed-contacts (onUserSelected)="selectUser('contacts', $event.userId, $event.onInit)"></addon-messages-confirmed-contacts>
|
||||
</ng-template>
|
||||
</core-tab>
|
||||
<core-tab [title]="'addon.messages.requests' | translate" [badge]="contactRequestsCount" (ionSelect)="selectUser('requests')">
|
||||
<ng-template>
|
||||
<addon-messages-contact-requests (onUserSelected)="selectUser('requests', $event.userId, $event.onInit)"></addon-messages-contact-requests>
|
||||
</ng-template>
|
||||
</core-tab>
|
||||
</core-tabs>
|
||||
</ion-content>
|
||||
</core-split-view>
|
|
@ -0,0 +1,37 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonMessagesContactsPage } from './contacts';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
import { AddonMessagesComponentsModule } from '../../components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonMessagesContactsPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
AddonMessagesComponentsModule,
|
||||
IonicPageModule.forChild(AddonMessagesContactsPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonMessagesContactsPageModule {}
|
|
@ -0,0 +1,117 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { IonicPage, NavController } from 'ionic-angular';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonMessagesProvider } from '../../providers/messages';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreTabsComponent } from '@components/tabs/tabs';
|
||||
|
||||
/**
|
||||
* Page that displays contacts and contact requests.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-messages-contacts' })
|
||||
@Component({
|
||||
selector: 'page-addon-messages-contacts',
|
||||
templateUrl: 'contacts.html',
|
||||
})
|
||||
export class AddonMessagesContactsPage implements OnDestroy {
|
||||
|
||||
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
|
||||
@ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent;
|
||||
|
||||
contactRequestsCount = 0;
|
||||
|
||||
protected loadSplitViewObserver: any;
|
||||
protected siteId: string;
|
||||
protected contactRequestsCountObserver: any;
|
||||
protected conversationUserId: number; // User id of the conversation opened in the split view.
|
||||
protected selectedUserId = {
|
||||
contacts: null, // User id of the selected user in the confirmed contacts tab.
|
||||
requests: null, // User id of the selected user in the contact requests tab.
|
||||
};
|
||||
|
||||
constructor(eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider,
|
||||
private navCtrl: NavController, private messagesProvider: AddonMessagesProvider) {
|
||||
|
||||
this.siteId = sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Update the contact requests badge.
|
||||
this.contactRequestsCountObserver = eventsProvider.on(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, (data) => {
|
||||
this.contactRequestsCount = data.count;
|
||||
}, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Page being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.messagesProvider.getContactRequestsCount(this.siteId); // Badge already updated by the observer.
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the search page.
|
||||
*/
|
||||
gotoSearch(): void {
|
||||
this.navCtrl.push('AddonMessagesSearchPage');
|
||||
}
|
||||
|
||||
/**
|
||||
* User entered the page.
|
||||
*/
|
||||
ionViewDidEnter(): void {
|
||||
this.tabsComponent && this.tabsComponent.ionViewDidEnter();
|
||||
}
|
||||
|
||||
/**
|
||||
* User left the page.
|
||||
*/
|
||||
ionViewDidLeave(): void {
|
||||
this.tabsComponent && this.tabsComponent.ionViewDidLeave();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the selected user and open the conversation in the split view if needed.
|
||||
*
|
||||
* @param {string} tab Active tab: "contacts" or "requests".
|
||||
* @param {number} [userId] Id of the selected user, undefined to use the last selected user in the tab.
|
||||
* @param {boolean} [onInit=false] Whether the contact was selected on initial load.
|
||||
*/
|
||||
selectUser(tab: string, userId?: number, onInit: boolean = false): void {
|
||||
userId = userId || this.selectedUserId[tab];
|
||||
|
||||
if (!userId || userId == this.conversationUserId) {
|
||||
// No user conversation to open or it is already opened.
|
||||
return;
|
||||
}
|
||||
|
||||
if (onInit && !this.splitviewCtrl.isOn()) {
|
||||
// Do not open a conversation by default when split view is not visible.
|
||||
return;
|
||||
}
|
||||
|
||||
this.conversationUserId = userId;
|
||||
this.selectedUserId[tab] = userId;
|
||||
this.splitviewCtrl.push('AddonMessagesDiscussionPage', { userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.contactRequestsCountObserver && this.contactRequestsCountObserver.off();
|
||||
}
|
||||
}
|
|
@ -9,11 +9,15 @@
|
|||
</ion-navbar>
|
||||
<core-navbar-buttons end>
|
||||
<core-context-menu>
|
||||
<core-context-menu-item [hidden]="!showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!showInfo || !isGroup" [priority]="1000" [content]="'addon.messages.groupinfo' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversation" [priority]="800" [content]="(conversation && conversation.isfavourite ? 'addon.messages.removefromfavourites' : 'addon.messages.addtofavourites') | translate" (action)="changeFavourite($event)" [iconAction]="favouriteIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!canDelete" [priority]="400" [content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()" [iconAction]="'trash'"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversationId || isGroup" [priority]="200" [content]="'addon.messages.deleteconversation' | translate" (action)="deleteConversation($event)" [iconAction]="deleteIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!showInfo || !isGroup" [priority]="1000" [content]="'addon.messages.groupinfo' | translate" (action)="viewInfo()"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversation" [priority]="800" [content]="(conversation && conversation.isfavourite ? 'addon.messages.removefromfavourites' : 'addon.messages.addtofavourites') | translate" (action)="changeFavourite($event)" [closeOnClick]="false"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!otherMember || otherMember.isblocked" [priority]="700" [content]="'addon.messages.blockuser' | translate" (action)="blockUser()"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!otherMember || !otherMember.isblocked" [priority]="700" [content]="'addon.messages.unblockuser' | translate" (action)="unblockUser()"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!canDelete" [priority]="400" [content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!groupMessagingEnabled || !conversationId || isGroup" [priority]="200" [content]="'addon.messages.deleteconversation' | translate" (action)="deleteConversation($event)" [closeOnClick]="false"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!otherMember || otherMember.iscontact || requestContactSent || requestContactReceived" [priority]="100" [content]="'addon.messages.addtoyourcontacts' | translate" (action)="createContactRequest()"></core-context-menu-item>
|
||||
<core-context-menu-item [hidden]="!otherMember || !otherMember.iscontact" [priority]="100" [content]="'addon.messages.removefromyourcontacts' | translate" (action)="removeContact()"></core-context-menu-item>
|
||||
</core-context-menu>
|
||||
</core-navbar-buttons>
|
||||
</ion-header>
|
||||
|
@ -21,32 +25,32 @@
|
|||
<core-loading [hideUntil]="loaded">
|
||||
<!-- Load previous messages. -->
|
||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadPrevious($event)" position="top" [error]="loadMoreError"></core-infinite-loading>
|
||||
<ion-list class="addon-messages-discussion-container safe-area-page" [attr.aria-live]="polite">
|
||||
<ion-list class="addon-messages-discussion-container safe-area-page" [class.addon-messages-discussion-group]="isGroup" [attr.aria-live]="'polite'">
|
||||
<ng-container *ngFor="let message of messages; index as index; last as last">
|
||||
<ion-chip *ngIf="message.showDate" class="addon-messages-date" color="light">
|
||||
<ion-label>{{ message.timecreated | coreFormatDate: "LL" }}</ion-label>
|
||||
</ion-chip>
|
||||
<h6 text-center *ngIf="message.showDate" class="addon-messages-date">
|
||||
{{ message.timecreated | coreFormatDate: "LL" }}
|
||||
</h6>
|
||||
|
||||
<ion-chip class="addon-messages-unreadfrom" *ngIf="unreadMessageFrom && message.id == unreadMessageFrom" color="light">
|
||||
<ion-label>{{ 'addon.messages.newmessages' | translate:{$a: title} }}</ion-label>
|
||||
<ion-icon name="arrow-round-down"></ion-icon>
|
||||
</ion-chip>
|
||||
|
||||
<ion-item text-wrap (longPress)="copyMessage(message)" class="addon-message" [class.addon-message-mine]="message.useridfrom == currentUserId" [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
|
||||
<ion-item text-wrap (longPress)="copyMessage(message)" class="addon-message" [class.addon-message-mine]="message.useridfrom == currentUserId" [class.addon-message-not-mine]="message.useridfrom != currentUserId" [class.addon-message-no-user]="!message.showUserData" [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
|
||||
<!-- User data. -->
|
||||
<ion-avatar item-start *ngIf="message.showUserData">
|
||||
<img [src]="members[message.useridfrom].profileimageurl" [alt]="'core.pictureof' | translate:{$a: members[message.useridfrom].fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<h2 *ngIf="message.showUserData">{{ members[message.useridfrom].fullname }}</h2>
|
||||
<h2 class="addon-message-user" >
|
||||
<ion-avatar item-start core-user-avatar [user]="members[message.useridfrom]" [linkProfile]="false" *ngIf="message.showUserData"></ion-avatar>
|
||||
|
||||
<div *ngIf="message.showUserData">{{ members[message.useridfrom].fullname }}</div>
|
||||
|
||||
<ion-note *ngIf="!message.pending">{{ message.timecreated | coreFormatDate: "dftimedate" }}</ion-note>
|
||||
<ion-note *ngIf="message.pending"><ion-icon name="time"></ion-icon></ion-note>
|
||||
</h2>
|
||||
|
||||
<!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. -->
|
||||
<p class="addon-message-text">
|
||||
<core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text"></core-format-text>
|
||||
</p>
|
||||
<ion-note *ngIf="!message.pending">
|
||||
{{ message.timecreated | coreFormatDate: "dftimedate" }}
|
||||
</ion-note>
|
||||
<ion-note *ngIf="message.pending"><ion-icon name="time"></ion-icon></ion-note>
|
||||
|
||||
<button ion-button icon-only clear="true" *ngIf="!message.sending && showDelete" (click)="deleteMessage(message, index)" class="addon-messages-delete-button" [@coreSlideInOut]="'fromRight'" [attr.aria-label]=" 'addon.messages.deletemessage' | translate">
|
||||
<ion-icon name="trash" color="danger"></ion-icon>
|
||||
|
@ -59,7 +63,25 @@
|
|||
</ion-content>
|
||||
<ion-footer color="light" class="footer-adjustable" *ngIf="loaded && (!conversationId || conversation)">
|
||||
<ion-toolbar color="light" position="bottom">
|
||||
<!-- @todo: Check if the user can send messages. -->
|
||||
<core-send-message-form (onSubmit)="sendMessage($event)" [showKeyboard]="showKeyboard" [placeholder]="'addon.messages.newmessage' | translate" (onResize)="resizeContent()"></core-send-message-form>
|
||||
<p *ngIf="footerType == 'unable'" text-center margin-horizontal>{{ 'addon.messages.unabletomessage' | translate }}</p>
|
||||
<div *ngIf="footerType == 'blocked'" padding-horizontal>
|
||||
<p text-center>{{ 'addon.messages.youhaveblockeduser' | translate }}</p>
|
||||
<button ion-button block text-wrap margin-bottom (click)="unblockUser()">{{ 'addon.messages.unblockuser' | translate }}</button>
|
||||
</div>
|
||||
<div *ngIf="footerType == 'requiresContact'" padding-horizontal>
|
||||
<p text-center><strong>{{ 'addon.messages.isnotinyourcontacts' | translate: {$a: otherMember.fullname} }}</strong></p>
|
||||
<p text-center>{{ 'addon.messages.requirecontacttomessage' | translate: {$a: otherMember.fullname} }}</p>
|
||||
<button ion-button block text-wrap margin-bottom (click)="createContactRequest()">{{ 'addon.messages.sendcontactrequest' | translate }}</button>
|
||||
</div>
|
||||
<div *ngIf="footerType == 'requestReceived'" padding-horizontal>
|
||||
<p text-center>{{ 'addon.messages.userwouldliketocontactyou' | translate: {$a: otherMember.fullname} }}</p>
|
||||
<button ion-button block text-wrap margin-bottom (click)="confirmContactRequest()">{{ 'addon.messages.acceptandaddcontact' | translate }}</button>
|
||||
<button ion-button block text-wrap margin-bottom color="light" (click)="declineContactRequest()">{{ 'addon.messages.decline' | translate }}</button>
|
||||
</div>
|
||||
<div *ngIf="footerType == 'requestSent' || (footerType == 'message' && requestContactSent)" padding-horizontal>
|
||||
<p text-center><strong>{{ 'addon.messages.contactrequestsent' | translate }}</strong></p>
|
||||
<p text-center>{{ 'addon.messages.yourcontactrequestpending' | translate: {$a: otherMember.fullname} }}</p>
|
||||
</div>
|
||||
<core-send-message-form *ngIf="footerType == 'message'" (onSubmit)="sendMessage($event)" [showKeyboard]="showKeyboard" [placeholder]="'addon.messages.newmessage' | translate" (onResize)="resizeContent()"></core-send-message-form>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
||||
|
|
|
@ -1,39 +1,93 @@
|
|||
// Messages.
|
||||
$item-message-bg: $gray-lighter !default;
|
||||
$item-message-bg: $white !default;
|
||||
$item-message-note-text: $gray-dark !default;
|
||||
$item-message-note-font-size: 75% !default;
|
||||
$item-message-mine-bg: $blue-light !default;
|
||||
$item-message-mine-bg: $gray-light !default;
|
||||
|
||||
ion-app.app-root page-addon-messages-discussion {
|
||||
ion-content {
|
||||
background-color: $gray-lighter !important;
|
||||
}
|
||||
|
||||
.addon-messages-discussion-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.addon-messages-date,
|
||||
.addon-messages-unreadfrom {
|
||||
margin-top: 10px;
|
||||
.addon-messages-date {
|
||||
font-weight: normal;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.addon-messages-unreadfrom {
|
||||
color: $blue;
|
||||
color: $core-color;
|
||||
background-color: transparent;
|
||||
margin-top: 6px;
|
||||
ion-icon {
|
||||
color: $core-color;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// Message item.
|
||||
.addon-message {
|
||||
max-width: 80%;
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
padding: 10px;
|
||||
margin: 4px;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
@include margin(8px, 8px, 0, 8px);
|
||||
background-color: $item-message-bg;
|
||||
align-self: flex-start;
|
||||
width: auto;
|
||||
width: 90%;
|
||||
max-width: 90%;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
@include core-transition(width);
|
||||
|
||||
core-format-text > p:only-child {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
|
||||
.addon-message-user {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: .5rem!important;
|
||||
margin-top: 0;
|
||||
|
||||
ion-avatar {
|
||||
display: block;
|
||||
min-width: 30px;
|
||||
min-height: 30px;
|
||||
margin: 0;
|
||||
img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
font-weight: 500;
|
||||
flex-grow: 1;
|
||||
@include padding-horizontal(.5rem);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.note {
|
||||
@include text-align('end');
|
||||
color: $item-message-note-text;
|
||||
font-size: $item-message-note-font-size;
|
||||
}
|
||||
}
|
||||
|
||||
&.addon-message-no-user .addon-message-user .note {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.activated {
|
||||
background-color: darken($item-message-bg, 10%);
|
||||
}
|
||||
|
@ -53,12 +107,6 @@ ion-app.app-root page-addon-messages-discussion {
|
|||
display: inline-flex;
|
||||
}
|
||||
|
||||
.note {
|
||||
align-self: flex-end;
|
||||
color: $item-message-note-text;
|
||||
font-size: $item-message-note-font-size;
|
||||
@include margin(null, null, null, 10px);
|
||||
}
|
||||
.addon-messages-delete-button {
|
||||
min-height: initial;
|
||||
line-height: initial;
|
||||
|
@ -76,11 +124,22 @@ ion-app.app-root page-addon-messages-discussion {
|
|||
}
|
||||
}
|
||||
|
||||
.addon-messages-discussion-group .addon-message + .addon-message-no-user,
|
||||
.addon-message.addon-message-mine + .addon-message-no-user.addon-message-mine,
|
||||
.addon-message.addon-message-not-mine + .addon-message-no-user.addon-message-not-mine {
|
||||
h2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
margin-top: -4px;
|
||||
padding-top: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
// Defines when an item-message is the user's.
|
||||
.addon-message-mine {
|
||||
background-color: $item-message-mine-bg;
|
||||
align-self: flex-end;
|
||||
max-width: 80%;
|
||||
|
||||
&.activated {
|
||||
background-color: darken($item-message-mine-bg, 10%);
|
||||
|
@ -97,12 +156,6 @@ ion-app.app-root page-addon-messages-discussion {
|
|||
}
|
||||
}
|
||||
|
||||
.addon-message .item-content,
|
||||
.addon-message-mine .item-content {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.toolbar-title {
|
||||
img {
|
||||
@include margin-horizontal(null, 6px);
|
||||
|
|
|
@ -57,6 +57,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
protected keyboardObserver: any;
|
||||
protected scrollBottom = true;
|
||||
protected viewDestroyed = false;
|
||||
protected memberInfoObserver: any;
|
||||
protected showLoadingModal = false; // Whether to show a loading modal while fetching data.
|
||||
|
||||
conversationId: number; // Conversation ID. Undefined if it's a new individual conversation.
|
||||
conversation: any; // The conversation object (if it exists).
|
||||
|
@ -77,6 +79,10 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
members: any = {}; // Members that wrote a message, indexed by ID.
|
||||
favouriteIcon = 'fa-star';
|
||||
deleteIcon = 'trash';
|
||||
otherMember: any; // Other member information (individual conversations only).
|
||||
footerType: 'message' | 'blocked' | 'requiresContact' | 'requestSent' | 'requestReceived' | 'unable';
|
||||
requestContactSent = false;
|
||||
requestContactReceived = false;
|
||||
|
||||
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
|
||||
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
|
||||
|
@ -108,6 +114,13 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
}
|
||||
}
|
||||
}, this.siteId);
|
||||
|
||||
// Refresh data if info of a mamber of the conversation have changed.
|
||||
this.memberInfoObserver = eventsProvider.on(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, (data) => {
|
||||
if (data.userId && (this.members[data.userId] || this.otherMember && data.userId == this.otherMember.id)) {
|
||||
this.fetchData();
|
||||
}
|
||||
}, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,6 +173,25 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
const backViewPage = this.navCtrl.getPrevious() && this.navCtrl.getPrevious().component.name;
|
||||
this.showInfo = !backViewPage || backViewPage !== 'CoreUserProfilePage';
|
||||
|
||||
// Recalculate footer position when keyboard is shown or hidden.
|
||||
this.keyboardObserver = this.eventsProvider.on(CoreEventsProvider.KEYBOARD_CHANGE, (kbHeight) => {
|
||||
this.content.resize();
|
||||
});
|
||||
|
||||
this.fetchData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to fetch the conversation data.
|
||||
*
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
protected fetchData(): Promise<any> {
|
||||
let loader;
|
||||
if (this.showLoadingModal) {
|
||||
loader = this.domUtils.showModalLoading();
|
||||
}
|
||||
|
||||
if (!this.groupMessagingEnabled && this.userId) {
|
||||
// Get the user profile to retrieve the user fullname and image.
|
||||
this.userProvider.getProfile(this.userId, undefined, true).then((user) => {
|
||||
|
@ -171,7 +203,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
}
|
||||
|
||||
// Synchronize messages if needed.
|
||||
this.messagesSync.syncDiscussion(this.conversationId, this.userId).catch(() => {
|
||||
return this.messagesSync.syncDiscussion(this.conversationId, this.userId).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then((warnings) => {
|
||||
if (warnings && warnings[0]) {
|
||||
|
@ -185,8 +217,22 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
// Fetch the messages for the first time.
|
||||
return this.fetchMessages();
|
||||
}
|
||||
}).then(() => {
|
||||
let promise;
|
||||
if (this.userId) {
|
||||
promise = this.messagesProvider.getMemberInfo(this.userId);
|
||||
} else {
|
||||
// Group conversation.
|
||||
promise = Promise.resolve(null);
|
||||
}
|
||||
|
||||
return promise.then((member) => {
|
||||
this.otherMember = member;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.otherMember = null;
|
||||
|
||||
// Fetch the messages for the first time.
|
||||
return this.fetchMessages().then(() => {
|
||||
if (!this.title && this.messages.length) {
|
||||
|
@ -207,11 +253,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
this.resizeContent();
|
||||
this.loaded = true;
|
||||
this.setPolling(); // Make sure we're polling messages.
|
||||
});
|
||||
|
||||
// Recalculate footer position when keyboard is shown or hidden.
|
||||
this.keyboardObserver = this.eventsProvider.on(CoreEventsProvider.KEYBOARD_CHANGE, (kbHeight) => {
|
||||
this.content.resize();
|
||||
this.setContactRequestInfo();
|
||||
this.setFooterType();
|
||||
loader && loader.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -985,6 +1029,71 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate whether there are pending contact requests.
|
||||
*/
|
||||
protected setContactRequestInfo(): void {
|
||||
this.requestContactSent = false;
|
||||
this.requestContactReceived = false;
|
||||
if (this.otherMember && !this.otherMember.iscontact) {
|
||||
this.requestContactSent = this.otherMember.contactrequests.some((request) => {
|
||||
return request.userid == this.currentUserId && request.requesteduserid == this.otherMember.id;
|
||||
});
|
||||
this.requestContactReceived = this.otherMember.contactrequests.some((request) => {
|
||||
return request.userid == this.otherMember.id && request.requesteduserid == this.currentUserId;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate what to display in the footer.
|
||||
*/
|
||||
protected setFooterType(): void {
|
||||
if (!this.otherMember) {
|
||||
// Group conversation or group messaging not available.
|
||||
this.footerType = 'message';
|
||||
} else if (this.otherMember.isblocked) {
|
||||
this.footerType = 'blocked';
|
||||
} else if (this.requestContactReceived) {
|
||||
this.footerType = 'requestReceived';
|
||||
} else if (this.otherMember.canmessage) {
|
||||
this.footerType = 'message';
|
||||
} else if (this.requestContactSent) {
|
||||
this.footerType = 'requestSent';
|
||||
} else if (this.otherMember.requirescontact) {
|
||||
this.footerType = 'requiresContact';
|
||||
} else {
|
||||
this.footerType = 'unable';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation modal to block the user of the individual conversation.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when user is blocked or dialog is cancelled.
|
||||
*/
|
||||
blockUser(): Promise<any> {
|
||||
if (!this.otherMember) {
|
||||
// Should never happen.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
const template = this.translate.instant('addon.messages.blockuserconfirm', {$a: this.otherMember.fullname});
|
||||
const okText = this.translate.instant('addon.messages.blockuser');
|
||||
|
||||
return this.domUtils.showConfirm(template, undefined, okText).then(() => {
|
||||
const modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
this.showLoadingModal = true;
|
||||
|
||||
return this.messagesProvider.blockContact(this.otherMember.id).finally(() => {
|
||||
modal.dismiss();
|
||||
this.showLoadingModal = false;
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the conversation.
|
||||
*
|
||||
|
@ -1012,6 +1121,131 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation modal to unblock the user of the individual conversation.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when user is unblocked or dialog is cancelled.
|
||||
*/
|
||||
unblockUser(): Promise<any> {
|
||||
if (!this.otherMember) {
|
||||
// Should never happen.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
const template = this.translate.instant('addon.messages.unblockuserconfirm', {$a: this.otherMember.fullname});
|
||||
const okText = this.translate.instant('addon.messages.unblockuser');
|
||||
|
||||
return this.domUtils.showConfirm(template, undefined, okText).then(() => {
|
||||
const modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
this.showLoadingModal = true;
|
||||
|
||||
return this.messagesProvider.unblockContact(this.otherMember.id).finally(() => {
|
||||
modal.dismiss();
|
||||
this.showLoadingModal = false;
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation modal to send a contact request to the other user of the individual conversation.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when the request is sent or the dialog is cancelled.
|
||||
*/
|
||||
createContactRequest(): Promise<any> {
|
||||
if (!this.otherMember) {
|
||||
// Should never happen.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
const template = this.translate.instant('addon.messages.addcontactconfirm', { $a: this.otherMember.fullname });
|
||||
const okText = this.translate.instant('core.add');
|
||||
|
||||
return this.domUtils.showConfirm(template, undefined, okText).then(() => {
|
||||
const modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
this.showLoadingModal = true;
|
||||
|
||||
return this.messagesProvider.createContactRequest(this.otherMember.id).finally(() => {
|
||||
modal.dismiss();
|
||||
this.showLoadingModal = false;
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms the contact request of the other user of the individual conversation.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when the request is confirmed.
|
||||
*/
|
||||
confirmContactRequest(): Promise<any> {
|
||||
if (!this.otherMember) {
|
||||
// Should never happen.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
const modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
this.showLoadingModal = true;
|
||||
|
||||
return this.messagesProvider.confirmContactRequest(this.otherMember.id).finally(() => {
|
||||
modal.dismiss();
|
||||
this.showLoadingModal = false;
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Declines the contact request of the other user of the individual conversation.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when the request is confirmed.
|
||||
*/
|
||||
declineContactRequest(): Promise<any> {
|
||||
if (!this.otherMember) {
|
||||
// Should never happen.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
const modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
this.showLoadingModal = true;
|
||||
|
||||
return this.messagesProvider.declineContactRequest(this.otherMember.id).finally(() => {
|
||||
modal.dismiss();
|
||||
this.showLoadingModal = false;
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation modal to remove the other user of the conversation from contacts.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when the request is sent or the dialog is cancelled.
|
||||
*/
|
||||
removeContact(): Promise<any> {
|
||||
if (!this.otherMember) {
|
||||
// Should never happen.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
const template = this.translate.instant('addon.messages.removecontactconfirm', { $a: this.otherMember.fullname });
|
||||
const okText = this.translate.instant('core.remove');
|
||||
|
||||
return this.domUtils.showConfirm(template, undefined, okText).then(() => {
|
||||
const modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
this.showLoadingModal = true;
|
||||
|
||||
return this.messagesProvider.removeContact(this.otherMember.id).finally(() => {
|
||||
modal.dismiss();
|
||||
this.showLoadingModal = false;
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
|
@ -1020,6 +1254,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
|
|||
this.unsetPolling();
|
||||
this.syncObserver && this.syncObserver.off();
|
||||
this.keyboardObserver && this.keyboardObserver.off();
|
||||
this.memberInfoObserver && this.memberInfoObserver.off();
|
||||
this.viewDestroyed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<ion-navbar core-back-button>
|
||||
<ion-title>{{ 'addon.messages.messages' | translate }}</ion-title>
|
||||
<ion-buttons end>
|
||||
<button ion-button icon-only (click)="gotoContacts($event)" [attr.aria-label]="'addon.messages.contacts' | translate">
|
||||
<ion-icon name="person"></ion-icon> <!-- @todo: Display number of pending requests. -->
|
||||
<button ion-button icon-only (click)="gotoSearch()" [attr.aria-label]="'addon.messages.search' | translate">
|
||||
<ion-icon name="search"></ion-icon>
|
||||
</button>
|
||||
<button ion-button icon-only (click)="gotoSettings($event)" [attr.aria-label]="'addon.messages.messagepreferences' | translate">
|
||||
<ion-icon name="cog"></ion-icon>
|
||||
|
@ -19,34 +19,13 @@
|
|||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-search-box *ngIf="search.enabled" (onSubmit)="searchMessage($event)" (onClear)="clearSearch($event)" [placeholder]=" 'addon.messages.message' | translate" autocorrect="off" spellcheck="false" lengthCheck="2" [disabled]="!loaded"></core-search-box>
|
||||
|
||||
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
|
||||
|
||||
<!-- Search results. -->
|
||||
<ion-list *ngIf="search.showResults" no-margin>
|
||||
<ion-item-divider color="light">
|
||||
<h2>{{ 'core.searchresults' | translate }}</h2>
|
||||
<ion-note item-end>{{ search.results.length }}</ion-note>
|
||||
</ion-item-divider>
|
||||
|
||||
<a ion-item text-wrap *ngFor="let result of search.results" [title]="result.fullname" (click)="gotoConversation(result.conversationid, result.userid, result.messageid)" [class.core-split-item-selected]="(result.conversationid && result.conversationid == selectedConversationId) || (result.userid && result.userid == selectedUserId)" detail-none>
|
||||
<ion-avatar core-user-avatar [user]="result" [linkProfile]="false" item-start></ion-avatar>
|
||||
<h2>
|
||||
<p>
|
||||
<core-format-text [text]="result.fullname"></core-format-text>
|
||||
<core-icon name="fa-ban" *ngIf="result.isblocked" [attr.aria-label]="'addon.messages.contactblocked' | translate"></core-icon>
|
||||
</p>
|
||||
<ion-note *ngIf="result.lastmessagedate > 0">
|
||||
{{result.lastmessagedate | coreDateDayOrTime}}
|
||||
</ion-note>
|
||||
</h2>
|
||||
<p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage" class="addon-message-last-message"></core-format-text></p>
|
||||
</a>
|
||||
</ion-list>
|
||||
|
||||
<!-- Conversations. -->
|
||||
<ion-list *ngIf="!search.showResults">
|
||||
<a ion-item text-wrap (click)="gotoContacts($event)" [attr.aria-label]="'addon.messages.contacts' | translate" class="addon-message-discussion">
|
||||
<ion-icon name="person" item-start></ion-icon>
|
||||
<h2>{{ 'addon.messages.contacts' | translate }}</h2>
|
||||
<ion-badge *ngIf="contactRequestsCount > 0" item-end>{{contactRequestsCount}}</ion-badge>
|
||||
</a>
|
||||
<ion-list>
|
||||
<!-- Favourite conversations. -->
|
||||
<ion-item-divider color="light" text-wrap *ngIf="favourites.conversations" (click)="toggle(favourites)" class="core-expandable">
|
||||
<core-icon *ngIf="!favourites.expanded" name="fa-caret-right" item-start></core-icon>
|
||||
|
@ -94,16 +73,13 @@
|
|||
</ion-item>
|
||||
</div>
|
||||
</ion-list>
|
||||
|
||||
<!-- Search didn't get any result. -->
|
||||
<core-empty-box *ngIf="(!search.results || search.results.length <= 0) && search.showResults" icon="search" [message]="'core.noresults' | translate"></core-empty-box>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
</core-split-view>
|
||||
|
||||
<!-- Template to render a list of conversations. -->
|
||||
<ng-template #conversationsTemplate let-conversations="conversations">
|
||||
<a ion-item text-wrap *ngFor="let conversation of conversations" [title]="conversation.name" detail-none (click)="gotoConversation(conversation.id, conversation.userid)" [class.core-split-item-selected]="(conversation.id && conversation.id == selectedConversationId) || (conversation.userid && conversation.userid == selectedUserId)" id="addon-message-conversation-{{ conversation.id ? conversation.id : 'user-' + conversation.userid }}">
|
||||
<a ion-item text-wrap *ngFor="let conversation of conversations" [title]="conversation.name" (click)="gotoConversation(conversation.id, conversation.userid)" [class.core-split-item-selected]="(conversation.id && conversation.id == selectedConversationId) || (conversation.userid && conversation.userid == selectedUserId)" class="addon-message-discussion" id="addon-message-conversation-{{ conversation.id ? conversation.id : 'user-' + conversation.userid }}">
|
||||
<!-- Group conversation image. -->
|
||||
<ion-avatar item-start *ngIf="conversation.type != typeIndividual && conversation.imageurl">
|
||||
<img [src]="conversation.imageurl" [alt]="conversation.name" core-external-content>
|
||||
|
@ -113,18 +89,18 @@
|
|||
<ion-avatar *ngIf="conversation.type == typeIndividual" core-user-avatar [user]="conversation.otherUser" [linkProfile]="false" [checkOnline]="conversation.showonlinestatus" item-start></ion-avatar>
|
||||
|
||||
<h2>
|
||||
<p>
|
||||
<core-format-text [text]="conversation.name"></core-format-text>
|
||||
<core-icon name="fa-ban" *ngIf="conversation.isblocked" [attr.aria-label]="'addon.messages.contactblocked' | translate"></core-icon>
|
||||
</p>
|
||||
<ion-note *ngIf="conversation.lastmessagedate > 0 || conversation.unreadcount">
|
||||
<ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
|
||||
<span *ngIf="conversation.lastmessagedate > 0">{{conversation.lastmessagedate | coreDateDayOrTime}}</span>
|
||||
</ion-note>
|
||||
<core-format-text [text]="conversation.name"></core-format-text>
|
||||
<core-icon name="fa-ban" *ngIf="conversation.isblocked" [attr.aria-label]="'addon.messages.contactblocked' | translate"></core-icon>
|
||||
</h2>
|
||||
<p><core-format-text *ngIf="conversation.subname" [text]="conversation.subname"></core-format-text></p>
|
||||
<p>
|
||||
<span *ngIf="conversation.sentfromcurrentuser">{{ 'addon.messages.you' | translate }}</span> <core-format-text clean="true" singleLine="true" [text]="conversation.lastmessage" class="addon-message-last-message"></core-format-text>
|
||||
<ion-note *ngIf="conversation.lastmessagedate > 0 || conversation.unreadcount">
|
||||
<ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
|
||||
<span *ngIf="conversation.lastmessagedate > 0">{{conversation.lastmessagedate | coreDateDayOrTime}}</span>
|
||||
</ion-note>
|
||||
<p *ngIf="conversation.subname"><core-format-text [text]="conversation.subname"></core-format-text></p>
|
||||
<p class="addon-message-last-message">
|
||||
<span *ngIf="conversation.sentfromcurrentuser" class="addon-message-last-message-user">{{ 'addon.messages.you' | translate }}</span>
|
||||
<core-format-text *ngIf="conversation.type != typeIndividual && conversation.members[0]" [text]="conversation.members[0].fullname + ':'" class="addon-message-last-message-user"></core-format-text>
|
||||
<core-format-text clean="true" singleLine="true" [text]="conversation.lastmessage" class="addon-message-last-message-text"></core-format-text>
|
||||
</p>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
ion-app.app-root page-addon-messages-group-conversations {
|
||||
h2 {
|
||||
.addon-message-last-message {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
.note {
|
||||
margin: 0;
|
||||
align-self: flex-end;
|
||||
display: inline-flex;
|
||||
font-size: initial;
|
||||
}
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
core-format-text.addon-message-last-message {
|
||||
display: inline;
|
||||
.addon-message-last-message-user {
|
||||
white-space: nowrap;
|
||||
color: $black;
|
||||
@include margin(null, 2px, null, null)
|
||||
}
|
||||
|
||||
.addon-message-last-message-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { IonicPage, Platform, NavParams, Content } from 'ionic-angular';
|
||||
import { IonicPage, Platform, NavController, NavParams, Content } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
|
@ -21,7 +21,6 @@ import { AddonMessagesProvider } from '../../providers/messages';
|
|||
import { AddonMessagesOfflineProvider } from '../../providers/messages-offline';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreUserProvider } from '@core/user/providers/user';
|
||||
|
@ -42,13 +41,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
loadingMessage: string;
|
||||
selectedConversationId: number;
|
||||
selectedUserId: number;
|
||||
search = {
|
||||
enabled: false,
|
||||
showResults: false,
|
||||
results: [],
|
||||
loading: '',
|
||||
text: ''
|
||||
};
|
||||
contactRequestsCount = 0;
|
||||
favourites: any = {
|
||||
type: null,
|
||||
favourites: true
|
||||
|
@ -74,14 +67,15 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
protected cronObserver: any;
|
||||
protected openConversationObserver: any;
|
||||
protected updateConversationListObserver: any;
|
||||
protected contactRequestsCountObserver: any;
|
||||
protected memberInfoObserver: any;
|
||||
|
||||
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService,
|
||||
private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
|
||||
private appProvider: CoreAppProvider, platform: Platform, utils: CoreUtilsProvider,
|
||||
private navCtrl: NavController, platform: Platform, utils: CoreUtilsProvider,
|
||||
pushNotificationsDelegate: AddonPushNotificationsDelegate, private messagesOffline: AddonMessagesOfflineProvider,
|
||||
private userProvider: CoreUserProvider) {
|
||||
|
||||
this.search.loading = translate.instant('core.searching');
|
||||
this.loadingString = translate.instant('core.loading');
|
||||
this.siteId = sitesProvider.getCurrentSiteId();
|
||||
this.currentUserId = sitesProvider.getCurrentSiteUserId();
|
||||
|
@ -166,6 +160,32 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
this.refreshData();
|
||||
}
|
||||
});
|
||||
|
||||
// Update the contact requests badge.
|
||||
this.contactRequestsCountObserver = eventsProvider.on(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, (data) => {
|
||||
this.contactRequestsCount = data.count;
|
||||
}, this.siteId);
|
||||
|
||||
// Update block status of a user.
|
||||
this.memberInfoObserver = eventsProvider.on(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, (data) => {
|
||||
if (!data.userBlocked && !data.userUnblocked) {
|
||||
// The block status has not changed, ignore.
|
||||
return;
|
||||
}
|
||||
|
||||
const updateConversations = (conversations: any[]): void => {
|
||||
if (!conversations || conversations.length <= 0) {
|
||||
return;
|
||||
}
|
||||
const conversation = conversations.find((conv) => conv.userid == data.userId);
|
||||
if (conversation) {
|
||||
conversation.isblocked = data.userBlocked;
|
||||
}
|
||||
};
|
||||
|
||||
updateConversations(this.individual.conversations);
|
||||
updateConversations(this.favourites.conversations);
|
||||
}, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -195,6 +215,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.messagesProvider.getContactRequestsCount(this.siteId); // Badge is updated by the observer.
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,7 +226,6 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
*/
|
||||
protected fetchData(): Promise<any> {
|
||||
this.loadingMessage = this.loadingString;
|
||||
this.search.enabled = this.messagesProvider.isSearchMessagesEnabled();
|
||||
|
||||
// Load the first conversations of each type.
|
||||
const promises = [];
|
||||
|
@ -481,6 +502,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
if (refresher) {
|
||||
// Actions to take if refresh comes from the user.
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, undefined, this.siteId);
|
||||
this.messagesProvider.refreshContactRequestsCount(this.siteId);
|
||||
refresher.complete();
|
||||
}
|
||||
});
|
||||
|
@ -515,36 +537,10 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Clear search and show conversations again.
|
||||
* Navigate to the search page.
|
||||
*/
|
||||
clearSearch(): void {
|
||||
this.loaded = false;
|
||||
this.search.showResults = false;
|
||||
this.search.text = ''; // Reset searched string.
|
||||
this.fetchData().finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search messages cotaining text.
|
||||
*
|
||||
* @param {string} query Text to search for.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
searchMessage(query: string): Promise<any> {
|
||||
this.appProvider.closeKeyboard();
|
||||
this.loaded = false;
|
||||
this.loadingMessage = this.search.loading;
|
||||
|
||||
return this.messagesProvider.searchMessages(query).then((searchResults) => {
|
||||
this.search.showResults = true;
|
||||
this.search.results = searchResults;
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
gotoSearch(): void {
|
||||
this.navCtrl.push('AddonMessagesSearchPage');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -558,5 +554,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
this.cronObserver && this.cronObserver.off();
|
||||
this.openConversationObserver && this.openConversationObserver.off();
|
||||
this.updateConversationListObserver && this.updateConversationListObserver.off();
|
||||
this.contactRequestsCountObserver && this.contactRequestsCountObserver.off();
|
||||
this.memberInfoObserver && this.memberInfoObserver.off();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<ion-header>
|
||||
<ion-navbar core-back-button>
|
||||
<ion-title>{{ 'addon.messages.searchcombined' | translate }}</ion-title>
|
||||
<ion-buttons end>
|
||||
<!-- Add an empty context menu so discussion page can add items in split view, otherwise the menu disappears in some cases. -->
|
||||
<core-context-menu></core-context-menu>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<core-split-view>
|
||||
<ion-content>
|
||||
<core-search-box (onSubmit)="search($event)" (onClear)="clearSearch($event)" [disabled]="disableSearch" autocorrect="off" [spellcheck]="false" [autoFocus]="true" [lengthCheck]="1"></core-search-box>
|
||||
<core-loading [hideUntil]="!displaySearching" [message]="'core.searching' | translate">
|
||||
<ion-list *ngIf="displayResults">
|
||||
<ng-container *ngTemplateOutlet="resultsTemplate; context: {item: contacts}"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="resultsTemplate; context: {item: nonContacts}"></ng-container>
|
||||
<ng-container *ngTemplateOutlet="resultsTemplate; context: {item: messages}"></ng-container>
|
||||
<!-- The infinite loading cannot be inside the ng-template, it fails because it doesn't find ion-content. -->
|
||||
<core-infinite-loading [enabled]="messages.canLoadMore" (action)="search(query, 'messages', $event)" [error]="messages.loadMoreError"></core-infinite-loading>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
</core-split-view>
|
||||
|
||||
<!-- Template to render a list of results -->
|
||||
<ng-template #resultsTemplate let-item="item">
|
||||
<ion-item-divider color="light" text-wrap>{{ item.titleString | translate }}</ion-item-divider>
|
||||
<ion-item text-wrap *ngIf="item.results.length == 0">
|
||||
{{ item.emptyString | translate }}
|
||||
</ion-item>
|
||||
|
||||
<!-- List of results -->
|
||||
<a ion-item text-wrap *ngFor="let result of item.results" [title]="result.fullname" (click)="openDiscussion(result.id)" [class.core-split-item-selected]="result.id == selectedUserId" class="addon-message-discussion">
|
||||
<ion-avatar item-start core-user-avatar [user]="result" [checkOnline]="true" [linkProfile]="false"></ion-avatar>
|
||||
<h2>
|
||||
<core-format-text [text]="result.fullname"></core-format-text>
|
||||
<core-icon name="fa-ban" *ngIf="result.isblocked" [attr.aria-label]="'addon.messages.contactblocked' | translate"></core-icon>
|
||||
</h2>
|
||||
<ion-note *ngIf="result.lastmessagedate > 0">
|
||||
{{result.lastmessagedate | coreDateDayOrTime}}
|
||||
</ion-note>
|
||||
<core-format-text *ngIf="result.lastmessage" clean="true" singleLine="true" [text]="result.lastmessage"></core-format-text>
|
||||
</a>
|
||||
|
||||
<!-- Load more button for contacts and non-contacts -->
|
||||
<ng-container *ngIf="item.type != 'messages'">
|
||||
<div padding-horizontal *ngIf="item.canLoadMore && !item.loadingMore">
|
||||
<button ion-button block color="light" (click)="search(query, item.type)">
|
||||
{{ 'core.loadmore' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="item.loadingMore" padding text-center>
|
||||
<ion-spinner></ion-spinner>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
|
@ -0,0 +1,37 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonMessagesSearchPage } from './search';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
import { AddonMessagesComponentsModule } from '../../components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonMessagesSearchPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
AddonMessagesComponentsModule,
|
||||
IonicPageModule.forChild(AddonMessagesSearchPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonMessagesSearchPageModule {}
|
|
@ -0,0 +1,244 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { IonicPage } from 'ionic-angular';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonMessagesProvider } from '../../providers/messages';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
|
||||
/**
|
||||
* Page for searching users.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-messages-search' })
|
||||
@Component({
|
||||
selector: 'page-addon-messages-search',
|
||||
templateUrl: 'search.html',
|
||||
})
|
||||
export class AddonMessagesSearchPage implements OnDestroy {
|
||||
|
||||
disableSearch = false;
|
||||
displaySearching = false;
|
||||
displayResults = false;
|
||||
query = '';
|
||||
contacts = {
|
||||
type: 'contacts',
|
||||
titleString: 'addon.messages.contacts',
|
||||
emptyString: 'addon.messages.searchnocontactsfound',
|
||||
results: [],
|
||||
canLoadMore: false,
|
||||
loadingMore: false
|
||||
};
|
||||
nonContacts = {
|
||||
type: 'noncontacts',
|
||||
titleString: 'addon.messages.noncontacts',
|
||||
emptyString: 'addon.messages.searchnononcontactsfound',
|
||||
results: [],
|
||||
canLoadMore: false,
|
||||
loadingMore: false
|
||||
};
|
||||
messages = {
|
||||
type: 'messages',
|
||||
titleString: 'addon.messages.messages',
|
||||
emptyString: 'addon.messages.searchnomessagesfound',
|
||||
results: [],
|
||||
canLoadMore: false,
|
||||
loadingMore: false,
|
||||
loadMoreError: false
|
||||
};
|
||||
selectedUserId = null;
|
||||
|
||||
protected memberInfoObserver;
|
||||
|
||||
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
|
||||
|
||||
constructor(private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, eventsProvider: CoreEventsProvider,
|
||||
sitesProvider: CoreSitesProvider, private messagesProvider: AddonMessagesProvider) {
|
||||
|
||||
// Update block status of a user.
|
||||
this.memberInfoObserver = eventsProvider.on(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, (data) => {
|
||||
if (!data.userBlocked && !data.userUnblocked) {
|
||||
// The block status has not changed, ignore.
|
||||
return;
|
||||
}
|
||||
|
||||
const contact = this.contacts.results.find((user) => user.id == data.userId);
|
||||
if (contact) {
|
||||
contact.isblocked = data.userBlocked;
|
||||
} else {
|
||||
const nonContact = this.nonContacts.results.find((user) => user.id == data.userId);
|
||||
if (nonContact) {
|
||||
nonContact.isblocked = data.userBlocked;
|
||||
}
|
||||
}
|
||||
|
||||
this.messages.results.forEach((message: any): void => {
|
||||
if (message.userid == data.userId) {
|
||||
message.isblocked = data.userBlocked;
|
||||
}
|
||||
});
|
||||
}, sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear search.
|
||||
*/
|
||||
clearSearch(): void {
|
||||
this.query = '';
|
||||
this.displayResults = false;
|
||||
this.splitviewCtrl.emptyDetails();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new search or load more results.
|
||||
*
|
||||
* @param {string} query Text to search for.
|
||||
* @param {strings} loadMore Load more contacts, noncontacts or messages. If undefined, start a new search.
|
||||
* @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
search(query: string, loadMore?: 'contacts' | 'noncontacts' | 'messages', infiniteComplete?: any): Promise<any> {
|
||||
this.appProvider.closeKeyboard();
|
||||
|
||||
this.query = query;
|
||||
this.disableSearch = true;
|
||||
this.displaySearching = !loadMore;
|
||||
|
||||
const promises = [];
|
||||
let newContacts = [];
|
||||
let newNonContacts = [];
|
||||
let newMessages = [];
|
||||
let canLoadMoreContacts = false;
|
||||
let canLoadMoreNonContacts = false;
|
||||
let canLoadMoreMessages = false;
|
||||
|
||||
if (!loadMore || loadMore == 'contacts' || loadMore == 'noncontacts') {
|
||||
const limitNum = loadMore ? AddonMessagesProvider.LIMIT_SEARCH : AddonMessagesProvider.LIMIT_INITIAL_USER_SEARCH;
|
||||
let limitFrom = 0;
|
||||
if (loadMore == 'contacts') {
|
||||
limitFrom = this.contacts.results.length;
|
||||
this.contacts.loadingMore = true;
|
||||
} else if (loadMore == 'noncontacts') {
|
||||
limitFrom = this.nonContacts.results.length;
|
||||
this.nonContacts.loadingMore = true;
|
||||
}
|
||||
|
||||
promises.push(
|
||||
this.messagesProvider.searchUsers(query, limitFrom, limitNum).then((result) => {
|
||||
if (!loadMore || loadMore == 'contacts') {
|
||||
newContacts = result.contacts;
|
||||
canLoadMoreContacts = result.canLoadMoreContacts;
|
||||
}
|
||||
if (!loadMore || loadMore == 'noncontacts') {
|
||||
newNonContacts = result.nonContacts;
|
||||
canLoadMoreNonContacts = result.canLoadMoreNonContacts;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (!loadMore || loadMore == 'messages') {
|
||||
let limitFrom = 0;
|
||||
if (loadMore == 'messages') {
|
||||
limitFrom = this.messages.results.length;
|
||||
this.messages.loadingMore = true;
|
||||
}
|
||||
|
||||
promises.push(
|
||||
this.messagesProvider.searchMessages(query, undefined, limitFrom).then((result) => {
|
||||
newMessages = result.messages;
|
||||
canLoadMoreMessages = result.canLoadMore;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
if (!loadMore) {
|
||||
this.contacts.results = [];
|
||||
this.nonContacts.results = [];
|
||||
this.messages.results = [];
|
||||
}
|
||||
|
||||
this.displayResults = true;
|
||||
|
||||
if (!loadMore || loadMore == 'contacts') {
|
||||
this.contacts.results.push(...newContacts);
|
||||
this.contacts.canLoadMore = canLoadMoreContacts;
|
||||
}
|
||||
|
||||
if (!loadMore || loadMore == 'noncontacts') {
|
||||
this.nonContacts.results.push(...newNonContacts);
|
||||
this.nonContacts.canLoadMore = canLoadMoreNonContacts;
|
||||
}
|
||||
|
||||
if (!loadMore || loadMore == 'messages') {
|
||||
this.messages.results.push(...newMessages);
|
||||
this.messages.canLoadMore = canLoadMoreMessages;
|
||||
this.messages.loadMoreError = false;
|
||||
}
|
||||
|
||||
if (!loadMore) {
|
||||
if (this.contacts.results.length > 0) {
|
||||
this.openDiscussion(this.contacts.results[0].id, true);
|
||||
} else if (this.nonContacts.results.length > 0) {
|
||||
this.openDiscussion(this.nonContacts.results[0].id, true);
|
||||
} else if (this.messages.results.length > 0) {
|
||||
this.openDiscussion(this.messages.results[0].userid, true);
|
||||
}
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingusers', true);
|
||||
|
||||
if (loadMore == 'messages') {
|
||||
this.messages.loadMoreError = true;
|
||||
}
|
||||
}).finally(() => {
|
||||
this.disableSearch = false;
|
||||
this.displaySearching = false;
|
||||
|
||||
if (loadMore == 'contacts') {
|
||||
this.contacts.loadingMore = false;
|
||||
} else if (loadMore == 'noncontacts') {
|
||||
this.nonContacts.loadingMore = false;
|
||||
} else if (loadMore == 'messages') {
|
||||
this.messages.loadingMore = false;
|
||||
}
|
||||
|
||||
infiniteComplete && infiniteComplete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a discussion in the split view.
|
||||
*
|
||||
* @param {number} userId User id.
|
||||
* @param {boolean} [onInit=false] Whether the tser was selected on initial load.
|
||||
*/
|
||||
openDiscussion(userId: number, onInit: boolean = false): void {
|
||||
if (!onInit || this.splitviewCtrl.isOn()) {
|
||||
this.selectedUserId = userId;
|
||||
this.splitviewCtrl.push('AddonMessagesDiscussionPage', { userId });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.memberInfoObserver && this.memberInfoObserver.off();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { AddonMessagesProvider } from './messages';
|
||||
|
||||
/**
|
||||
* Content links handler for a contact requests.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesContactRequestLinkHandler extends CoreContentLinksHandlerBase {
|
||||
name = 'AddonMessagesContactRequestLinkHandler';
|
||||
pattern = /\/message\/pendingcontactrequests\.php/;
|
||||
|
||||
constructor(private linkHelper: CoreContentLinksHelperProvider, private messagesProvider: AddonMessagesProvider) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
*
|
||||
* @param {string[]} siteIds List of sites the URL belongs to.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
return [{
|
||||
action: (siteId, navCtrl?): void => {
|
||||
// Always use redirect to make it the new history root (to avoid "loops" in history).
|
||||
this.linkHelper.goInSite(navCtrl, 'AddonMessagesContactsPage', {}, siteId);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||
* If not defined, defaults to true.
|
||||
*
|
||||
* @param {string} siteId The site ID.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
|
||||
*/
|
||||
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
|
||||
return this.messagesProvider.isPluginEnabled(siteId).then((enabled) => {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.messagesProvider.isGroupMessagingEnabled();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -43,6 +43,8 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
|
|||
loading: true
|
||||
};
|
||||
|
||||
protected updating = false;
|
||||
|
||||
constructor(private messagesProvider: AddonMessagesProvider, private sitesProvider: CoreSitesProvider,
|
||||
private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider,
|
||||
private localNotificationsProvider: CoreLocalNotificationsProvider, private textUtils: CoreTextUtilsProvider,
|
||||
|
@ -57,10 +59,15 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
|
|||
this.updateBadge(data.siteId);
|
||||
});
|
||||
|
||||
eventsProvider.on(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, (data) => {
|
||||
this.updateBadge(data.siteId, data.count);
|
||||
});
|
||||
|
||||
// Reset info on logout.
|
||||
eventsProvider.on(CoreEventsProvider.LOGOUT, (data) => {
|
||||
this.handler.badge = '';
|
||||
this.handler.loading = true;
|
||||
this.updating = false;
|
||||
});
|
||||
|
||||
// If a message push notification is received, refresh the count.
|
||||
|
@ -103,23 +110,55 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
|
|||
/**
|
||||
* Triggers an update for the badge number and loading status. Mandatory if showBadge is enabled.
|
||||
*
|
||||
* @param {string} siteId Site ID or current Site if undefined.
|
||||
* @param {string} [siteId] Site ID or current Site if undefined.
|
||||
* @param {number} [contactRequestsCount] Number of contact requests, if known.
|
||||
*/
|
||||
updateBadge(siteId?: string): void {
|
||||
updateBadge(siteId?: string, contactRequestsCount?: number): void {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
if (!siteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messagesProvider.getUnreadConversationsCount(undefined, siteId).then((unread) => {
|
||||
// Leave badge enter if there is a 0+ or a 0.
|
||||
this.handler.badge = parseInt(unread, 10) > 0 ? unread : '';
|
||||
// Update badge.
|
||||
this.pushNotificationsProvider.updateAddonCounter('AddonMessages', unread, siteId);
|
||||
if (this.updating) {
|
||||
// An update is already in prgoress.
|
||||
return;
|
||||
}
|
||||
|
||||
this.updating = true;
|
||||
|
||||
const promises = [];
|
||||
let unreadCount = 0;
|
||||
let unreadPlus = false;
|
||||
|
||||
promises.push(this.messagesProvider.getUnreadConversationsCount(undefined, siteId).then((unread) => {
|
||||
unreadCount = parseInt(unread, 10);
|
||||
unreadPlus = (typeof unread === 'string' && unread.slice(-1) === '+');
|
||||
}).catch(() => {
|
||||
this.handler.badge = '';
|
||||
// Ignore error.
|
||||
}));
|
||||
|
||||
// Get the number of contact requests in 3.6+ sites if needed.
|
||||
if (contactRequestsCount == null && this.messagesProvider.isGroupMessagingEnabled()) {
|
||||
promises.push(this.messagesProvider.getContactRequestsCount(siteId).then((count) => {
|
||||
contactRequestsCount = count;
|
||||
}).catch(() => {
|
||||
// Ignore errors
|
||||
}));
|
||||
}
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
const totalCount = unreadCount + (contactRequestsCount || 0);
|
||||
if (totalCount > 0) {
|
||||
this.handler.badge = totalCount + (unreadPlus ? '+' : '');
|
||||
} else {
|
||||
this.handler.badge = '';
|
||||
}
|
||||
|
||||
// Update badge.
|
||||
this.pushNotificationsProvider.updateAddonCounter('AddonMessages', totalCount, siteId);
|
||||
}).finally(() => {
|
||||
this.handler.loading = false;
|
||||
this.updating = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ import { AddonMessagesOfflineProvider } from './messages-offline';
|
|||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSite } from '@classes/site';
|
||||
|
||||
/**
|
||||
* Service to handle messages.
|
||||
|
@ -28,7 +30,6 @@ import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper';
|
|||
@Injectable()
|
||||
export class AddonMessagesProvider {
|
||||
protected ROOT_CACHE_KEY = 'mmaMessages:';
|
||||
protected LIMIT_SEARCH_MESSAGES = 50;
|
||||
protected LIMIT_MESSAGES = AddonMessagesProvider.LIMIT_MESSAGES;
|
||||
static NEW_MESSAGE_EVENT = 'addon_messages_new_message_event';
|
||||
static READ_CHANGED_EVENT = 'addon_messages_read_changed_event';
|
||||
|
@ -36,6 +37,8 @@ export class AddonMessagesProvider {
|
|||
static OPEN_CONVERSATION_EVENT = 'addon_messages_open_conversation_event'; // Notify that a conversation should be opened.
|
||||
static SPLIT_VIEW_LOAD_EVENT = 'addon_messages_split_view_load_event';
|
||||
static UPDATE_CONVERSATION_LIST_EVENT = 'addon_messages_update_conversation_list_event';
|
||||
static MEMBER_INFO_CHANGED_EVENT = 'addon_messages_member_changed_event';
|
||||
static CONTACT_REQUESTS_COUNT_EVENT = 'addon_messages_contact_requests_count_event';
|
||||
static POLL_INTERVAL = 10000;
|
||||
static PUSH_SIMULATION_COMPONENT = 'AddonMessagesPushSimulation';
|
||||
|
||||
|
@ -44,7 +47,10 @@ export class AddonMessagesProvider {
|
|||
static MESSAGE_PRIVACY_SITE = 2; // Privacy setting for being messaged by anyone on the site.
|
||||
static MESSAGE_CONVERSATION_TYPE_INDIVIDUAL = 1; // An individual conversation.
|
||||
static MESSAGE_CONVERSATION_TYPE_GROUP = 2; // A group conversation.
|
||||
static LIMIT_CONTACTS = 50;
|
||||
static LIMIT_MESSAGES = 50;
|
||||
static LIMIT_INITIAL_USER_SEARCH = 3;
|
||||
static LIMIT_SEARCH = 50;
|
||||
|
||||
static NOTIFICATION_PREFERENCES_KEY = 'message_provider_moodle_instantmessage';
|
||||
|
||||
|
@ -53,7 +59,7 @@ export class AddonMessagesProvider {
|
|||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider,
|
||||
private userProvider: CoreUserProvider, private messagesOffline: AddonMessagesOfflineProvider,
|
||||
private utils: CoreUtilsProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||
private emulatorHelper: CoreEmulatorHelperProvider) {
|
||||
private emulatorHelper: CoreEmulatorHelperProvider, private eventsProvider: CoreEventsProvider) {
|
||||
this.logger = logger.getInstance('AddonMessagesProvider');
|
||||
}
|
||||
|
||||
|
@ -63,6 +69,7 @@ export class AddonMessagesProvider {
|
|||
* @param {number} userId User ID of the person to add.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
* @deprecated since Moodle 3.6
|
||||
*/
|
||||
addContact(userId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
|
@ -101,7 +108,90 @@ export class AddonMessagesProvider {
|
|||
}
|
||||
|
||||
return promise.then(() => {
|
||||
return this.invalidateAllContactsCache(site.getUserId(), site.getId());
|
||||
return this.invalidateAllMemberInfo(userId, site).finally(() => {
|
||||
const data = { userId, userBlocked: true };
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, data, site.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm a contact request from another user.
|
||||
*
|
||||
* @param {number} userId ID of the user who made the contact request.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
* @since 3.6
|
||||
*/
|
||||
confirmContactRequest(userId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
userid: userId,
|
||||
requesteduserid: site.getUserId(),
|
||||
};
|
||||
|
||||
return site.write('core_message_confirm_contact_request', params).then(() => {
|
||||
return this.utils.allPromises([
|
||||
this.invalidateAllMemberInfo(userId, site),
|
||||
this.invalidateContactsCache(site.id),
|
||||
this.invalidateUserContacts(site.id),
|
||||
this.refreshContactRequestsCount(site.id),
|
||||
]).finally(() => {
|
||||
const data = { userId, contactRequestConfirmed: true };
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, data, site.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a contact request to another user.
|
||||
*
|
||||
* @param {number} userId ID of the receiver of the contact request.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
* @since 3.6
|
||||
*/
|
||||
createContactRequest(userId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
userid: site.getUserId(),
|
||||
requesteduserid: userId,
|
||||
};
|
||||
|
||||
return site.write('core_message_create_contact_request', params).then(() => {
|
||||
return this.invalidateAllMemberInfo(userId, site).finally(() => {
|
||||
const data = { userId, contactRequestCreated: true };
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, data, site.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decline a contact request from another user.
|
||||
*
|
||||
* @param {number} userId ID of the user who made the contact request.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
* @since 3.6
|
||||
*/
|
||||
declineContactRequest(userId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
userid: userId,
|
||||
requesteduserid: site.getUserId(),
|
||||
};
|
||||
|
||||
return site.write('core_message_decline_contact_request', params).then(() => {
|
||||
return this.utils.allPromises([
|
||||
this.invalidateAllMemberInfo(userId, site),
|
||||
this.refreshContactRequestsCount(site.id),
|
||||
]).finally(() => {
|
||||
const data = { userId, contactRequestDeclined: true };
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, data, site.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -247,6 +337,33 @@ export class AddonMessagesProvider {
|
|||
return this.ROOT_CACHE_KEY + 'contacts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for comfirmed contacts.
|
||||
*
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getCacheKeyForUserContacts(): string {
|
||||
return this.ROOT_CACHE_KEY + 'userContacts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for contact requests.
|
||||
*
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getCacheKeyForContactRequests(): string {
|
||||
return this.ROOT_CACHE_KEY + 'contactRequests';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for contact requests count.
|
||||
*
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getCacheKeyForContactRequestsCount(): string {
|
||||
return this.ROOT_CACHE_KEY + 'contactRequestsCount';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for a discussion.
|
||||
*
|
||||
|
@ -332,6 +449,17 @@ export class AddonMessagesProvider {
|
|||
return this.getCommonCacheKeyForUserConversations(userId) + ':' + type + ':' + favourites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for member info.
|
||||
*
|
||||
* @param {number} userId User ID.
|
||||
* @param {number} otherUserId The other user ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getCacheKeyForMemberInfo(userId: number, otherUserId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'memberInfo:' + userId + ':' + otherUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common cache key for get user conversations.
|
||||
*
|
||||
|
@ -356,6 +484,7 @@ export class AddonMessagesProvider {
|
|||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Resolved with the WS data.
|
||||
* @deprecated since Moodle 3.6
|
||||
*/
|
||||
getAllContacts(siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
@ -377,7 +506,7 @@ export class AddonMessagesProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get all the blocked contacts of the current user.
|
||||
* Get all the users blocked by the current user.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Resolved with the WS data.
|
||||
|
@ -403,6 +532,7 @@ export class AddonMessagesProvider {
|
|||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Resolved with the WS data.
|
||||
* @deprecated since Moodle 3.6
|
||||
*/
|
||||
getContacts(siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
|
@ -430,6 +560,114 @@ export class AddonMessagesProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of user contacts.
|
||||
*
|
||||
* @param {number} [limitFrom=0] Position of the first contact to fetch.
|
||||
* @param {number} [limitNum] Number of contacts to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<{contacts: any[], canLoadMore: boolean}>} Resolved with the list of user contacts.
|
||||
* @since 3.6
|
||||
*/
|
||||
getUserContacts(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS , siteId?: string):
|
||||
Promise<{contacts: any[], canLoadMore: boolean}> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
userid: site.getUserId(),
|
||||
limitfrom: limitFrom,
|
||||
limitnum: limitNum <= 0 ? 0 : limitNum + 1
|
||||
};
|
||||
const preSets = {
|
||||
cacheKey: this.getCacheKeyForUserContacts()
|
||||
};
|
||||
|
||||
return site.read('core_message_get_user_contacts', params, preSets).then((contacts) => {
|
||||
if (!contacts || !contacts.length) {
|
||||
return { contacts: [], canLoadMore: false };
|
||||
}
|
||||
|
||||
this.userProvider.storeUsers(contacts, site.id);
|
||||
|
||||
if (limitNum <= 0) {
|
||||
return { contacts, canLoadMore: false };
|
||||
}
|
||||
|
||||
return {
|
||||
contacts: contacts.slice(0, limitNum),
|
||||
canLoadMore: contacts.length > limitNum
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the contact request sent to the current user.
|
||||
*
|
||||
* @param {number} [limitFrom=0] Position of the first contact request to fetch.
|
||||
* @param {number} [limitNum] Number of contact requests to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<{requests: any[], canLoadMore: boolean}>} Resolved with the list of contact requests.
|
||||
* @since 3.6
|
||||
*/
|
||||
getContactRequests(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS, siteId?: string):
|
||||
Promise<{requests: any[], canLoadMore: boolean}> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const data = {
|
||||
userid: site.getUserId(),
|
||||
limitfrom: limitFrom,
|
||||
limitnum: limitNum <= 0 ? 0 : limitNum + 1
|
||||
};
|
||||
const preSets = {
|
||||
cacheKey: this.getCacheKeyForContactRequests()
|
||||
};
|
||||
|
||||
return site.read('core_message_get_contact_requests', data, preSets).then((requests) => {
|
||||
if (!requests || !requests.length) {
|
||||
return { requests: [], canLoadMore: false };
|
||||
}
|
||||
|
||||
this.userProvider.storeUsers(requests, site.id);
|
||||
|
||||
if (limitNum <= 0) {
|
||||
return { requests, canLoadMore: false };
|
||||
}
|
||||
|
||||
return {
|
||||
requests: requests.slice(0, limitNum),
|
||||
canLoadMore: requests.length > limitNum
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of contact requests sent to the current user.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<number>} Resolved with the number of contact requests.
|
||||
* @since 3.6
|
||||
*/
|
||||
getContactRequestsCount(siteId?: string): Promise<number> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const data = {
|
||||
userid: site.getUserId(),
|
||||
};
|
||||
const preSets = {
|
||||
cacheKey: this.getCacheKeyForContactRequestsCount(),
|
||||
typeExpected: 'number'
|
||||
};
|
||||
|
||||
return site.read('core_message_get_received_contact_requests_count', data, preSets).then((count) => {
|
||||
// Notify the new count so all badges are updated.
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, { count }, site.id);
|
||||
|
||||
return count;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a conversation by the conversation ID.
|
||||
*
|
||||
|
@ -491,18 +729,20 @@ export class AddonMessagesProvider {
|
|||
* @param {boolean} [newestFirst=true] Whether to order messages by newest first.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @param {number} [userId] User ID. If not defined, current user in the site.
|
||||
* @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise.
|
||||
* @return {Promise<any>} Promise resolved with the response.
|
||||
* @since 3.6
|
||||
*/
|
||||
getConversationBetweenUsers(otherUserId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean,
|
||||
messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2,
|
||||
newestFirst: boolean = true, siteId?: string, userId?: number): Promise<any> {
|
||||
newestFirst: boolean = true, siteId?: string, userId?: number, preferCache?: boolean): Promise<any> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
const preSets = {
|
||||
cacheKey: this.getCacheKeyForConversationBetweenUsers(userId, otherUserId)
|
||||
cacheKey: this.getCacheKeyForConversationBetweenUsers(userId, otherUserId),
|
||||
omitExpires: !!preferCache,
|
||||
},
|
||||
params: any = {
|
||||
userid: userId,
|
||||
|
@ -900,6 +1140,40 @@ export class AddonMessagesProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation member info by user id, works even if no conversation betwen the users exists.
|
||||
*
|
||||
* @param {number} otherUserId The other user ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @param {number} [userId] User ID. If not defined, current user in the site.
|
||||
* @return {Promise<any>} Promise resolved with the member info.
|
||||
* @since 3.6
|
||||
*/
|
||||
getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
const preSets = {
|
||||
cacheKey: this.getCacheKeyForMemberInfo(userId, otherUserId)
|
||||
},
|
||||
params: any = {
|
||||
referenceuserid: userId,
|
||||
userids: [otherUserId],
|
||||
includecontactrequests: 1,
|
||||
includeprivacyinfo: 1,
|
||||
};
|
||||
|
||||
return site.read('core_message_get_member_info', params, preSets).then((members) => {
|
||||
if (!members || members.length < 1) {
|
||||
// Should never happen.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
return members[0];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for the get message preferences call.
|
||||
*
|
||||
|
@ -1146,6 +1420,42 @@ export class AddonMessagesProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate user contacts cache.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
invalidateUserContacts(siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getCacheKeyForUserContacts());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate contact requests cache.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
invalidateContactRequestsCache(siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getCacheKeyForContactRequests());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate contact requests count cache.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
invalidateContactRequestsCountCache(siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getCacheKeyForContactRequestsCount());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate conversation.
|
||||
*
|
||||
|
@ -1256,6 +1566,22 @@ export class AddonMessagesProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate member info cache.
|
||||
*
|
||||
* @param {number} otherUserId The other user ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @param {number} [userId] User ID. If not defined, current user in the site.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
invalidateMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
return site.invalidateWsCacheForKey(this.getCacheKeyForMemberInfo(userId, otherUserId));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate get message preferences.
|
||||
*
|
||||
|
@ -1268,6 +1594,31 @@ export class AddonMessagesProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate all cache entries with member info.
|
||||
*
|
||||
* @param {number} userId Id of the user to invalidate.
|
||||
* @param {CoreSite} site Site object.
|
||||
* @return {Promie<any>} Promise resolved when done.
|
||||
*/
|
||||
protected invalidateAllMemberInfo(userId: number, site: CoreSite): Promise<any> {
|
||||
return this.utils.allPromises([
|
||||
this.invalidateMemberInfo(userId, site.id),
|
||||
this.invalidateUserContacts(site.id),
|
||||
this.invalidateContactRequestsCache(site.id),
|
||||
this.invalidateConversations(site.id),
|
||||
this.getConversationBetweenUsers(userId, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
|
||||
site.id, undefined, true).then((conversation) => {
|
||||
return this.utils.allPromises([
|
||||
this.invalidateConversation(conversation.id),
|
||||
this.invalidateConversationMembers(conversation.id, site.id),
|
||||
]);
|
||||
}).catch(() => {
|
||||
// The conversation does not exist or we can't fetch it now, ignore it.
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the a user is blocked by the current user.
|
||||
*
|
||||
|
@ -1276,6 +1627,12 @@ export class AddonMessagesProvider {
|
|||
* @return {Promise<boolean>} Resolved with boolean, rejected when we do not know.
|
||||
*/
|
||||
isBlocked(userId: number, siteId?: string): Promise<boolean> {
|
||||
if (this.isGroupMessagingEnabled()) {
|
||||
return this.getMemberInfo(userId, siteId).then((member) => {
|
||||
return member.isblocked;
|
||||
});
|
||||
}
|
||||
|
||||
return this.getBlockedContacts(siteId).then((blockedContacts) => {
|
||||
if (!blockedContacts.users || blockedContacts.users.length < 1) {
|
||||
return false;
|
||||
|
@ -1295,6 +1652,12 @@ export class AddonMessagesProvider {
|
|||
* @return {Promise<boolean>} Resolved with boolean, rejected when we do not know.
|
||||
*/
|
||||
isContact(userId: number, siteId?: string): Promise<boolean> {
|
||||
if (this.isGroupMessagingEnabled()) {
|
||||
return this.getMemberInfo(userId, siteId).then((member) => {
|
||||
return member.iscontact;
|
||||
});
|
||||
}
|
||||
|
||||
return this.getContacts(siteId).then((contacts) => {
|
||||
return ['online', 'offline'].some((type) => {
|
||||
if (contacts[type] && contacts[type].length > 0) {
|
||||
|
@ -1438,6 +1801,21 @@ export class AddonMessagesProvider {
|
|||
return this.sitesProvider.getCurrentSite().write('core_message_mark_all_messages_as_read', params, preSets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the number of contact requests sent to the current user.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<number>} Resolved with the number of contact requests.
|
||||
* @since 3.6
|
||||
*/
|
||||
refreshContactRequestsCount(siteId?: string): Promise<number> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.invalidateContactRequestsCountCache(siteId).then(() => {
|
||||
return this.getContactRequestsCount(siteId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a contact.
|
||||
*
|
||||
|
@ -1455,7 +1833,17 @@ export class AddonMessagesProvider {
|
|||
};
|
||||
|
||||
return site.write('core_message_delete_contacts', params, preSets).then(() => {
|
||||
return this.invalidateContactsCache(site.getId());
|
||||
if (this.isGroupMessagingEnabled()) {
|
||||
return this.utils.allPromises([
|
||||
this.invalidateUserContacts(site.id),
|
||||
this.invalidateAllMemberInfo(userId, site),
|
||||
]).then(() => {
|
||||
const data = { userId, contactRemoved: true };
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, data, site.id);
|
||||
});
|
||||
} else {
|
||||
return this.invalidateContactsCache(site.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1496,28 +1884,91 @@ export class AddonMessagesProvider {
|
|||
/**
|
||||
* Search for all the messges with a specific text.
|
||||
*
|
||||
* @param {string} query The query string
|
||||
* @param {number} [userId] The user ID. If not defined, current user.
|
||||
* @param {number} [from=0] Position of the first result to get. Defaults to 0.
|
||||
* @param {number} [limit] Number of results to get. Defaults to LIMIT_SEARCH_MESSAGES.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the results.
|
||||
* @param {string} query The query string.
|
||||
* @param {number} [userId] The user ID. If not defined, current user.
|
||||
* @param {number} [limitFrom=0] Position of the first result to get. Defaults to 0.
|
||||
* @param {number} [limitNum] Number of results to get. Defaults to AddonMessagesProvider.LIMIT_SEARCH.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the results.
|
||||
*/
|
||||
searchMessages(query: string, userId?: number, from: number = 0, limit: number = this.LIMIT_SEARCH_MESSAGES, siteId?: string):
|
||||
Promise<any> {
|
||||
searchMessages(query: string, userId?: number, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH,
|
||||
siteId?: string): Promise<{messages: any[], canLoadMore: boolean}> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const param = {
|
||||
const params = {
|
||||
userid: userId || site.getUserId(),
|
||||
search: query,
|
||||
limitfrom: from,
|
||||
limitnum: limit
|
||||
limitfrom: limitFrom,
|
||||
limitnum: limitNum <= 0 ? 0 : limitNum + 1
|
||||
},
|
||||
preSets = {
|
||||
getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
|
||||
};
|
||||
|
||||
return site.read('core_message_data_for_messagearea_search_messages', param, preSets).then((searchResults) => {
|
||||
return searchResults.contacts;
|
||||
return site.read('core_message_data_for_messagearea_search_messages', params, preSets).then((result) => {
|
||||
if (!result.contacts || !result.contacts.length) {
|
||||
return { messages: [], canLoadMore: false };
|
||||
}
|
||||
|
||||
result.contacts.forEach((result) => {
|
||||
result.id = result.userid;
|
||||
});
|
||||
|
||||
this.userProvider.storeUsers(result.contacts, site.id);
|
||||
|
||||
if (limitNum <= 0) {
|
||||
return { messages: result.contacts, canLoadMore: false };
|
||||
}
|
||||
|
||||
return {
|
||||
messages: result.contacts.slice(0, limitNum),
|
||||
canLoadMore: result.contacts.length > limitNum
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for users.
|
||||
*
|
||||
* @param {string} query Text to search for.
|
||||
* @param {number} [limitFrom=0] Position of the first found user to fetch.
|
||||
* @param {number} [limitNum] Number of found users to fetch. Defaults to AddonMessagesProvider.LIMIT_SEARCH.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Resolved with two lists of found users: contacts and non-contacts.
|
||||
* @since 3.6
|
||||
*/
|
||||
searchUsers(query: string, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, siteId?: string):
|
||||
Promise<{contacts: any[], nonContacts: any[], canLoadMoreContacts: boolean, canLoadMoreNonContacts: boolean}> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const data = {
|
||||
userid: site.getUserId(),
|
||||
search: query,
|
||||
limitfrom: limitFrom,
|
||||
limitnum: limitNum <= 0 ? 0 : limitNum + 1
|
||||
},
|
||||
preSets = {
|
||||
getFromCache: false // Always try to get updated data. If it fails, it will get it from cache.
|
||||
};
|
||||
|
||||
return site.read('core_message_message_search_users', data, preSets).then((result) => {
|
||||
const contacts = result.contacts || [];
|
||||
const nonContacts = result.noncontacts || [];
|
||||
|
||||
this.userProvider.storeUsers(contacts, site.id);
|
||||
this.userProvider.storeUsers(nonContacts, site.id);
|
||||
|
||||
if (limitNum <= 0) {
|
||||
return { contacts, nonContacts, canLoadMoreContacts: false, canLoadMoreNonContacts: false };
|
||||
}
|
||||
|
||||
return {
|
||||
contacts: contacts.slice(0, limitNum),
|
||||
nonContacts: nonContacts.slice(0, limitNum),
|
||||
canLoadMoreContacts: contacts.length > limitNum,
|
||||
canLoadMoreNonContacts: nonContacts.length > limitNum
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1918,7 +2369,10 @@ export class AddonMessagesProvider {
|
|||
}
|
||||
|
||||
return promise.then(() => {
|
||||
return this.invalidateAllContactsCache(site.getUserId(), site.getId());
|
||||
return this.invalidateAllMemberInfo(userId, site).finally(() => {
|
||||
const data = { userId, userUnblocked: true };
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.MEMBER_INFO_CHANGED_EVENT, data, site.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -95,14 +95,14 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle
|
|||
|
||||
this.messagesProvider.isContact(user.id).then((isContact) => {
|
||||
if (isContact) {
|
||||
const template = this.translate.instant('addon.messages.removecontactconfirm'),
|
||||
title = this.translate.instant('addon.messages.removecontact');
|
||||
const message = this.translate.instant('addon.messages.removecontactconfirm', {$a: user.fullname});
|
||||
const okText = this.translate.instant('core.remove');
|
||||
|
||||
return this.domUtils.showConfirm(template, title, title).then(() => {
|
||||
return this.domUtils.showConfirm(message, undefined, okText).then(() => {
|
||||
return this.messagesProvider.removeContact(user.id);
|
||||
});
|
||||
} else {
|
||||
return this.messagesProvider.addContact(user.id);
|
||||
return this.addContact(user);
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
|
@ -125,10 +125,12 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle
|
|||
protected checkButton(userId: number): Promise<void> {
|
||||
this.updateButton(userId, {spinner: true});
|
||||
|
||||
const groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled();
|
||||
|
||||
return this.messagesProvider.isContact(userId).then((isContact) => {
|
||||
if (isContact) {
|
||||
this.updateButton(userId, {
|
||||
title: 'addon.messages.removecontact',
|
||||
title: groupMessagingEnabled ? 'addon.messages.removefromyourcontacts' : 'addon.messages.removecontact',
|
||||
class: 'addon-messages-removecontact-handler',
|
||||
icon: 'remove',
|
||||
hidden: false,
|
||||
|
@ -136,7 +138,7 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle
|
|||
});
|
||||
} else {
|
||||
this.updateButton(userId, {
|
||||
title: 'addon.messages.addcontact',
|
||||
title: groupMessagingEnabled ? 'addon.messages.addtoyourcontacts' : 'addon.messages.addcontact',
|
||||
class: 'addon-messages-addcontact-handler',
|
||||
icon: 'add',
|
||||
hidden: false,
|
||||
|
@ -160,6 +162,42 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle
|
|||
this.eventsProvider.trigger(CoreUserDelegate.UPDATE_HANDLER_EVENT, { handler: this.name, data: data, userId: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a contact or send a contact request if group messaging is enabled.
|
||||
*
|
||||
* @param {any} user User to add as contact.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected addContact(user: any): Promise<any> {
|
||||
if (!this.messagesProvider.isGroupMessagingEnabled()) {
|
||||
return this.messagesProvider.addContact(user.id);
|
||||
}
|
||||
|
||||
return this.messagesProvider.getMemberInfo(user.id).then((member) => {
|
||||
const currentUserId = this.sitesProvider.getCurrentSiteUserId();
|
||||
const requestSent = member.contactrequests.some((request) => {
|
||||
return request.userid == currentUserId && request.requesteduserid == user.id;
|
||||
});
|
||||
|
||||
if (requestSent) {
|
||||
const message = this.translate.instant('addon.messages.yourcontactrequestpending', {$a: user.fullname});
|
||||
|
||||
return this.domUtils.showAlert(null, message);
|
||||
}
|
||||
|
||||
const message = this.translate.instant('addon.messages.addcontactconfirm', {$a: user.fullname});
|
||||
const okText = this.translate.instant('core.add');
|
||||
|
||||
return this.domUtils.showConfirm(message, undefined, okText).then(() => {
|
||||
return this.messagesProvider.createContactRequest(user.id);
|
||||
}).then(() => {
|
||||
const message = this.translate.instant('addon.messages.contactrequestsent');
|
||||
|
||||
return this.domUtils.showAlert(null, message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroyed method.
|
||||
*/
|
||||
|
|
|
@ -48,7 +48,7 @@ export class AddonNotificationsProvider {
|
|||
protected formatNotificationsData(notifications: any[]): void {
|
||||
notifications.forEach((notification) => {
|
||||
// Set message to show.
|
||||
if (notification.contexturl && notification.contexturl.indexOf('/mod/forum/')) {
|
||||
if (notification.contexturl && notification.contexturl.indexOf('/mod/forum/') >= 0) {
|
||||
notification.mobiletext = notification.smallmessage;
|
||||
} else {
|
||||
notification.mobiletext = notification.fullmessage;
|
||||
|
|
|
@ -147,8 +147,11 @@
|
|||
"addon.files.privatefiles": "Private files",
|
||||
"addon.files.sitefiles": "Site files",
|
||||
"addon.messageoutput_airnotifier.processorsettingsdesc": "Configure devices",
|
||||
"addon.messages.acceptandaddcontact": "Accept and add to contacts",
|
||||
"addon.messages.addcontact": "Add contact",
|
||||
"addon.messages.addcontactconfirm": "Are you sure you want to add {{$a}} to your contacts?",
|
||||
"addon.messages.addtofavourites": "Star",
|
||||
"addon.messages.addtoyourcontacts": "Add to contacts",
|
||||
"addon.messages.blocknoncontacts": "Prevent non-contacts from messaging me",
|
||||
"addon.messages.blockuser": "Block user",
|
||||
"addon.messages.blockuserconfirm": "Are you sure you want to block {{$a}}?",
|
||||
|
@ -159,7 +162,9 @@
|
|||
"addon.messages.contactblocked": "Contact blocked",
|
||||
"addon.messages.contactlistempty": "The contact list is empty",
|
||||
"addon.messages.contactname": "Contact name",
|
||||
"addon.messages.contactrequestsent": "Contact request sent",
|
||||
"addon.messages.contacts": "Contacts",
|
||||
"addon.messages.decline": "Decline",
|
||||
"addon.messages.deleteallconfirm": "Are you sure you would like to delete this entire conversation? This will not delete it for other conversation participants.",
|
||||
"addon.messages.deleteconversation": "Delete conversation",
|
||||
"addon.messages.deletemessage": "Delete message",
|
||||
|
@ -168,34 +173,52 @@
|
|||
"addon.messages.errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
||||
"addon.messages.errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
|
||||
"addon.messages.errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
||||
"addon.messages.errorwhileretrievingusers": "Error while retrieving users from the server.",
|
||||
"addon.messages.groupinfo": "Group info",
|
||||
"addon.messages.groupmessages": "Group messages",
|
||||
"addon.messages.info": "Info",
|
||||
"addon.messages.isnotinyourcontacts": "{{$a}} is not in your contacts",
|
||||
"addon.messages.message": "Message",
|
||||
"addon.messages.messagenotsent": "The message was not sent. Please try again later.",
|
||||
"addon.messages.messagepreferences": "Message preferences",
|
||||
"addon.messages.messages": "Messages",
|
||||
"addon.messages.newmessage": "New message",
|
||||
"addon.messages.newmessages": "New messages",
|
||||
"addon.messages.nocontactrequests": "No contact requests",
|
||||
"addon.messages.nocontactsgetstarted": "Try searching for someone to add them as a contact",
|
||||
"addon.messages.nofavourites": "No favourites",
|
||||
"addon.messages.nogroupmessages": "No group messages",
|
||||
"addon.messages.nomessages": "No messages",
|
||||
"addon.messages.noncontacts": "Non-contacts",
|
||||
"addon.messages.nousersfound": "No users found",
|
||||
"addon.messages.numparticipants": "{{$a}} participants",
|
||||
"addon.messages.removecontact": "Remove contact",
|
||||
"addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.",
|
||||
"addon.messages.removecontactconfirm": "Are you sure you want to remove {{$a}} from your contacts?",
|
||||
"addon.messages.removefromfavourites": "Unstar",
|
||||
"addon.messages.removefromyourcontacts": "Remove from contacts",
|
||||
"addon.messages.requests": "Requests",
|
||||
"addon.messages.requirecontacttomessage": "You need to request {{$a}} to add you as a contact to be able to message them.",
|
||||
"addon.messages.searchcombined": "Search people and messages",
|
||||
"addon.messages.searchnocontactsfound": "No contacts found",
|
||||
"addon.messages.searchnomessagesfound": "No messages found",
|
||||
"addon.messages.searchnononcontactsfound": "No non contacts found",
|
||||
"addon.messages.sendcontactrequest": "Send contact request",
|
||||
"addon.messages.showdeletemessages": "Show delete messages",
|
||||
"addon.messages.type_blocked": "Blocked",
|
||||
"addon.messages.type_offline": "Offline",
|
||||
"addon.messages.type_online": "Online",
|
||||
"addon.messages.type_search": "Search results",
|
||||
"addon.messages.type_strangers": "Others",
|
||||
"addon.messages.unabletomessage": "You are unable to message this user",
|
||||
"addon.messages.unblockuser": "Unblock user",
|
||||
"addon.messages.unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
|
||||
"addon.messages.userwouldliketocontactyou": "{{$a}} would like to contact you",
|
||||
"addon.messages.warningconversationmessagenotsent": "Couldn't send message(s) to conversation {{conversation}}. {{error}}",
|
||||
"addon.messages.warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
|
||||
"addon.messages.wouldliketocontactyou": "Would like to contact you",
|
||||
"addon.messages.you": "You:",
|
||||
"addon.messages.youhaveblockeduser": "You have blocked this user in the past",
|
||||
"addon.messages.yourcontactrequestpending": "Your contact request is pending with {{$a}}",
|
||||
"addon.mod_assign.acceptsubmissionstatement": "Please accept the submission statement.",
|
||||
"addon.mod_assign.addattempt": "Allow another attempt",
|
||||
"addon.mod_assign.addnewattempt": "Add a new attempt",
|
||||
|
@ -1540,6 +1563,7 @@
|
|||
"core.quotausage": "You have currently used {{$a.used}} of your {{$a.total}} limit.",
|
||||
"core.redirectingtosite": "You will be redirected to the site.",
|
||||
"core.refresh": "Refresh",
|
||||
"core.remove": "Remove",
|
||||
"core.required": "Required",
|
||||
"core.requireduserdatamissing": "This user lacks some required profile data. Please enter the data in your site and try again.<br>{{$a}}",
|
||||
"core.resources": "Resources",
|
||||
|
|
|
@ -54,11 +54,7 @@ export class CoreContextMenuPopoverComponent {
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (!item.iconAction) {
|
||||
this.logger.warn('Items with action must have an icon action to work', item);
|
||||
|
||||
return false;
|
||||
} else if (item.iconAction == 'spinner') {
|
||||
if (item.iconAction == 'spinner') {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
|||
@Input() icon?: string; // Icon to be shown on the navigation bar. Default: Kebab menu icon.
|
||||
@Input() title?: string; // Aria label and text to be shown on the top of the popover.
|
||||
|
||||
hideMenu: boolean;
|
||||
hideMenu = true; // It will be unhidden when items are added.
|
||||
ariaLabel: string;
|
||||
protected items: CoreContextMenuItemComponent[] = [];
|
||||
protected itemsMovedToParent: CoreContextMenuItemComponent[] = [];
|
||||
|
|
|
@ -188,6 +188,7 @@
|
|||
"quotausage": "You have currently used {{$a.used}} of your {{$a.total}} limit.",
|
||||
"redirectingtosite": "You will be redirected to the site.",
|
||||
"refresh": "Refresh",
|
||||
"remove": "Remove",
|
||||
"required": "Required",
|
||||
"requireduserdatamissing": "This user lacks some required profile data. Please enter the data in your site and try again.<br>{{$a}}",
|
||||
"resources": "Resources",
|
||||
|
|
|
@ -184,9 +184,4 @@ ion-app.app-root {
|
|||
width: min-content;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
// Message item.
|
||||
.item-message core-format-text > p:only-child {
|
||||
display: inline;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue