commit
						8f8457d6ee
					
				@ -177,6 +177,7 @@
 | 
			
		||||
  "addon.messages.contactname": "local_moodlemobileapp",
 | 
			
		||||
  "addon.messages.contactrequestsent": "message",
 | 
			
		||||
  "addon.messages.contacts": "message",
 | 
			
		||||
  "addon.messages.conversationactions": "message",
 | 
			
		||||
  "addon.messages.decline": "message",
 | 
			
		||||
  "addon.messages.deleteallconfirm": "message",
 | 
			
		||||
  "addon.messages.deleteallselfconfirm": "message",
 | 
			
		||||
@ -198,6 +199,7 @@
 | 
			
		||||
  "addon.messages.messagepreferences": "message",
 | 
			
		||||
  "addon.messages.messages": "message",
 | 
			
		||||
  "addon.messages.muteconversation": "message",
 | 
			
		||||
  "addon.messages.mutedconversation": "message",
 | 
			
		||||
  "addon.messages.newmessage": "message",
 | 
			
		||||
  "addon.messages.newmessages": "local_moodlemobileapp",
 | 
			
		||||
  "addon.messages.nocontactrequests": "message",
 | 
			
		||||
@ -216,9 +218,6 @@
 | 
			
		||||
  "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.selfconversation": "message",
 | 
			
		||||
  "addon.messages.selfconversationdefaultmessage": "message",
 | 
			
		||||
  "addon.messages.sendcontactrequest": "message",
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@
 | 
			
		||||
    "contactname": "Contact name",
 | 
			
		||||
    "contactrequestsent": "Contact request sent",
 | 
			
		||||
    "contacts": "Contacts",
 | 
			
		||||
    "conversationactions": "Conversation actions menu",
 | 
			
		||||
    "decline": "Decline",
 | 
			
		||||
    "deleteallconfirm": "Are you sure you would like to delete this entire conversation? This will not delete it for other conversation participants.",
 | 
			
		||||
    "deleteallselfconfirm": "Are you sure you would like to delete this entire personal conversation?",
 | 
			
		||||
@ -37,6 +38,7 @@
 | 
			
		||||
    "messagepreferences": "Message preferences",
 | 
			
		||||
    "messages": "Messages",
 | 
			
		||||
    "muteconversation": "Mute",
 | 
			
		||||
    "mutedconversation": "Muted conversation",
 | 
			
		||||
    "newmessage": "New message",
 | 
			
		||||
    "newmessages": "New messages",
 | 
			
		||||
    "nocontactrequests": "No contact requests",
 | 
			
		||||
@ -55,9 +57,6 @@
 | 
			
		||||
    "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",
 | 
			
		||||
    "selfconversation": "Personal space",
 | 
			
		||||
    "selfconversationdefaultmessage": "Save draft messages, links, notes etc. to access later.",
 | 
			
		||||
    "sendcontactrequest": "Send contact request",
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,16 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-navbar core-back-button>
 | 
			
		||||
        <ion-title>
 | 
			
		||||
            <img *ngIf="loaded && !otherMember && conversationImage" class="core-bar-button-image" [src]="conversationImage" alt="" onError="this.src='assets/img/group-avatar.png'" core-external-content role="presentation" [siteId]="siteId || null">
 | 
			
		||||
            <img *ngIf="loaded && !otherMember && conversationImage" class="core-bar-button-image" [src]="conversationImage" [alt]="title" onError="this.src='assets/img/group-avatar.png'" core-external-content role="presentation" [siteId]="siteId || null">
 | 
			
		||||
            <ion-avatar *ngIf="loaded && otherMember" class="core-bar-button-image" core-user-avatar [user]="otherMember" [linkProfile]="false" [checkOnline]="otherMember.showonlinestatus" item-start (click)="showInfo && viewInfo()"></ion-avatar>
 | 
			
		||||
            <core-format-text [text]="title" (click)="showInfo && !isGroup && viewInfo()"></core-format-text>
 | 
			
		||||
            <core-icon *ngIf="conversation && conversation.isfavourite" name="fa-star"></core-icon>
 | 
			
		||||
            <core-icon *ngIf="conversation && conversation.ismuted" name="volume-off"></core-icon>
 | 
			
		||||
            <core-icon *ngIf="conversation && conversation.isfavourite" name="fa-star" [label]="'core.favourites' | translate"></core-icon>
 | 
			
		||||
            <core-icon *ngIf="conversation && conversation.ismuted" name="volume-off" [label]="'addon.messages.mutedconversation' | translate"></core-icon>
 | 
			
		||||
        </ion-title>
 | 
			
		||||
        <ion-buttons end></ion-buttons>
 | 
			
		||||
    </ion-navbar>
 | 
			
		||||
    <core-navbar-buttons end>
 | 
			
		||||
        <core-context-menu>
 | 
			
		||||
        <core-context-menu [aria-label]="'addon.messages.conversationactions' | translate">
 | 
			
		||||
            <core-context-menu-item [hidden]="isSelf || !showInfo || isGroup" [priority]="1000" [content]="'addon.messages.info' | translate" (action)="viewInfo()" iconAction="information-circle"></core-context-menu-item>
 | 
			
		||||
            <core-context-menu-item [hidden]="isSelf || !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)" [closeOnClick]="false" [iconAction]="favouriteIcon"></core-context-menu-item>
 | 
			
		||||
@ -47,7 +47,7 @@
 | 
			
		||||
 | 
			
		||||
                <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. -->
 | 
			
		||||
                    <h2 class="addon-message-user" >
 | 
			
		||||
                    <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>
 | 
			
		||||
@ -64,6 +64,7 @@
 | 
			
		||||
                    <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>
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <div class="tail" *ngIf="message.showTail"></div>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
        </ion-list>
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,9 @@ ion-app.app-root page-addon-messages-discussion {
 | 
			
		||||
        min-height: 0;
 | 
			
		||||
        position: relative;
 | 
			
		||||
        @include core-transition(width);
 | 
			
		||||
        // This is needed to display bubble tails.
 | 
			
		||||
        overflow: visible;
 | 
			
		||||
        contain: none;
 | 
			
		||||
 | 
			
		||||
        core-format-text > p:only-child {
 | 
			
		||||
            display: inline;
 | 
			
		||||
@ -127,6 +130,15 @@ ion-app.app-root page-addon-messages-discussion {
 | 
			
		||||
                line-height: initial;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .tail {
 | 
			
		||||
            content: '';
 | 
			
		||||
            width: 0;
 | 
			
		||||
            height: 0;
 | 
			
		||||
            border: 0.5rem solid transparent;
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            touch-action: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .addon-message.addon-message-mine + .addon-message-no-user.addon-message-mine,
 | 
			
		||||
@ -158,6 +170,18 @@ ion-app.app-root page-addon-messages-discussion {
 | 
			
		||||
              height: 16px;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .tail {
 | 
			
		||||
            @include position(null, 0, 0, null);
 | 
			
		||||
            @include margin-horizontal(null, -0.5rem);
 | 
			
		||||
            border-bottom-color: $item-message-mine-bg;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .addon-message-not-mine .tail {
 | 
			
		||||
        @include position(null, null, 0, 0);
 | 
			
		||||
        @include margin-horizontal(-0.5rem, null);
 | 
			
		||||
        border-bottom-color: $item-message-bg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .toolbar-title {
 | 
			
		||||
 | 
			
		||||
@ -384,6 +384,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
 | 
			
		||||
        this.messages.forEach((message, index): any => {
 | 
			
		||||
            message.showDate = this.showDate(message, this.messages[index - 1]);
 | 
			
		||||
            message.showUserData = this.showUserData(message, this.messages[index - 1]);
 | 
			
		||||
            message.showTail = this.showTail(message, this.messages[index + 1]);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Call resize to recalculate the dimensions.
 | 
			
		||||
@ -1046,6 +1047,17 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
 | 
			
		||||
            (!prevMessage || prevMessage.useridfrom != message.useridfrom || message.showDate);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if a css tail should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {any} message Current message where to show the user info.
 | 
			
		||||
     * @param {any} [nextMessage] Next message.
 | 
			
		||||
     * @return {boolean} Whether user data should be shown.
 | 
			
		||||
     */
 | 
			
		||||
    showTail(message: any, nextMessage?: any): boolean {
 | 
			
		||||
        return !nextMessage || nextMessage.useridfrom != message.useridfrom || nextMessage.showDate;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggles delete state.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -101,7 +101,7 @@
 | 
			
		||||
        <h2>
 | 
			
		||||
            <core-format-text [text]="conversation.name"></core-format-text>
 | 
			
		||||
            <core-icon name="fa-ban" *ngIf="conversation.isblocked" [label]="'addon.messages.contactblocked' | translate"></core-icon>
 | 
			
		||||
            <core-icon *ngIf="conversation.ismuted" name="volume-off"></core-icon>
 | 
			
		||||
            <core-icon *ngIf="conversation.ismuted" name="volume-off" [label]="'addon.messages.mutedconversation' | translate"></core-icon>
 | 
			
		||||
        </h2>
 | 
			
		||||
        <ion-note *ngIf="conversation.lastmessagedate > 0 || conversation.unreadcount">
 | 
			
		||||
            <ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
 | 
			
		||||
 | 
			
		||||
@ -266,7 +266,12 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
        const promises = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(this.fetchConversationCounts());
 | 
			
		||||
        promises.push(this.messagesProvider.getContactRequestsCount(this.siteId));  // View updated by the event observer.
 | 
			
		||||
 | 
			
		||||
        // View updated by the events observers.
 | 
			
		||||
        promises.push(this.messagesProvider.getContactRequestsCount(this.siteId));
 | 
			
		||||
        if (refreshUnreadCounts) {
 | 
			
		||||
            promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.all(promises).then(() => {
 | 
			
		||||
            if (typeof this.favourites.expanded == 'undefined') {
 | 
			
		||||
@ -276,9 +281,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
                    // We don't know which option it belongs to, so we need to fetch the data for all of them.
 | 
			
		||||
                    const promises = [];
 | 
			
		||||
 | 
			
		||||
                    promises.push(this.fetchDataForOption(this.favourites, false, refreshUnreadCounts));
 | 
			
		||||
                    promises.push(this.fetchDataForOption(this.group, false, refreshUnreadCounts));
 | 
			
		||||
                    promises.push(this.fetchDataForOption(this.individual, false, refreshUnreadCounts));
 | 
			
		||||
                    promises.push(this.fetchDataForOption(this.favourites, false));
 | 
			
		||||
                    promises.push(this.fetchDataForOption(this.group, false));
 | 
			
		||||
                    promises.push(this.fetchDataForOption(this.individual, false));
 | 
			
		||||
 | 
			
		||||
                    return Promise.all(promises).then(() => {
 | 
			
		||||
                        // All conversations have been loaded, find the one we need to load and expand its option.
 | 
			
		||||
@ -286,13 +291,13 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
                        if (conversation) {
 | 
			
		||||
                            const option = this.getConversationOption(conversation);
 | 
			
		||||
 | 
			
		||||
                            return this.expandOption(option, refreshUnreadCounts);
 | 
			
		||||
                            return this.expandOption(option);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            // Conversation not found, just open the default option.
 | 
			
		||||
                            this.calculateExpandedStatus();
 | 
			
		||||
 | 
			
		||||
                            // Now load the data for the expanded option.
 | 
			
		||||
                            return this.fetchDataForExpandedOption(refreshUnreadCounts);
 | 
			
		||||
                            return this.fetchDataForExpandedOption();
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
@ -302,7 +307,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Now load the data for the expanded option.
 | 
			
		||||
            return this.fetchDataForExpandedOption(refreshUnreadCounts);
 | 
			
		||||
            return this.fetchDataForExpandedOption();
 | 
			
		||||
        }).catch((error) => {
 | 
			
		||||
            this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true);
 | 
			
		||||
        }).finally(() => {
 | 
			
		||||
@ -314,9 +319,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
     * Calculate which option should be expanded initially.
 | 
			
		||||
     */
 | 
			
		||||
    protected calculateExpandedStatus(): void {
 | 
			
		||||
        this.favourites.expanded = this.favourites.count != 0;
 | 
			
		||||
        this.group.expanded = this.favourites.count == 0 && this.group.count != 0;
 | 
			
		||||
        this.individual.expanded = this.favourites.count == 0 && this.group.count == 0;
 | 
			
		||||
        this.favourites.expanded = this.favourites.count != 0 && !this.group.unread && !this.individual.unread;
 | 
			
		||||
        this.group.expanded = !this.favourites.expanded && this.group.count != 0 && !this.individual.unread;
 | 
			
		||||
        this.individual.expanded = !this.favourites.expanded && !this.group.expanded;
 | 
			
		||||
 | 
			
		||||
        this.loadCurrentListElement();
 | 
			
		||||
    }
 | 
			
		||||
@ -324,26 +329,16 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch data for the expanded option.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts.
 | 
			
		||||
     * @return {Promise<any>} Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected fetchDataForExpandedOption(refreshUnreadCounts: boolean = true): Promise<any> {
 | 
			
		||||
    protected fetchDataForExpandedOption(): Promise<any> {
 | 
			
		||||
        const expandedOption = this.getExpandedOption();
 | 
			
		||||
 | 
			
		||||
        if (expandedOption) {
 | 
			
		||||
            return this.fetchDataForOption(expandedOption, false, refreshUnreadCounts);
 | 
			
		||||
        } else {
 | 
			
		||||
            // All options are collapsed, update the counts.
 | 
			
		||||
            const promises = [];
 | 
			
		||||
 | 
			
		||||
            promises.push(this.fetchConversationCounts());
 | 
			
		||||
            if (refreshUnreadCounts) {
 | 
			
		||||
                // View updated by event observer.
 | 
			
		||||
                promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId));
 | 
			
		||||
            return this.fetchDataForOption(expandedOption, false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            return Promise.all(promises);
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -351,10 +346,10 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
     *
 | 
			
		||||
     * @param {any} option The option to fetch data for.
 | 
			
		||||
     * @param {boolean} [loadingMore} Whether we are loading more data or just the first ones.
 | 
			
		||||
     * @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts.
 | 
			
		||||
     * @param {booleam} [getCounts] Whether to get counts data.
 | 
			
		||||
     * @return {Promise<any>} Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    fetchDataForOption(option: any, loadingMore?: boolean, refreshUnreadCounts: boolean = true): Promise<void> {
 | 
			
		||||
    fetchDataForOption(option: any, loadingMore?: boolean, getCounts?: boolean): Promise<void> {
 | 
			
		||||
        option.loadMoreError = false;
 | 
			
		||||
 | 
			
		||||
        const limitFrom = loadingMore ? option.conversations.length : 0,
 | 
			
		||||
@ -375,12 +370,11 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
            promises.push(this.messagesOffline.getAllMessages().then((data) => {
 | 
			
		||||
                offlineMessages = data;
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
            promises.push(this.fetchConversationCounts());
 | 
			
		||||
            if (refreshUnreadCounts) {
 | 
			
		||||
                // View updated by the event observer.
 | 
			
		||||
                promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (getCounts) {
 | 
			
		||||
            promises.push(this.fetchConversationCounts());
 | 
			
		||||
            promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return Promise.all(promises).then(() => {
 | 
			
		||||
@ -650,7 +644,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
            option.expanded = false;
 | 
			
		||||
            this.loadCurrentListElement();
 | 
			
		||||
        } else {
 | 
			
		||||
            this.expandOption(option).catch((error) => {
 | 
			
		||||
            // Pass getCounts=true to update the counts everytime the user expands an option.
 | 
			
		||||
            this.expandOption(option, true).catch((error) => {
 | 
			
		||||
                this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
@ -660,10 +655,10 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
     * Expand a certain option.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {any} option The option to expand.
 | 
			
		||||
     * @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts.
 | 
			
		||||
     * @param {booleam} [getCounts] Whether to get counts data.
 | 
			
		||||
     * @return {Promise<any>} Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected expandOption(option: any, refreshUnreadCounts: boolean = true): Promise<any> {
 | 
			
		||||
    protected expandOption(option: any, getCounts?: boolean): Promise<any> {
 | 
			
		||||
        // Collapse all and expand the right one.
 | 
			
		||||
        this.favourites.expanded = false;
 | 
			
		||||
        this.group.expanded = false;
 | 
			
		||||
@ -672,7 +667,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
 | 
			
		||||
        option.expanded = true;
 | 
			
		||||
        option.loading = true;
 | 
			
		||||
 | 
			
		||||
        return this.fetchDataForOption(option, false, refreshUnreadCounts).then(() => {
 | 
			
		||||
        return this.fetchDataForOption(option, false, getCounts).then(() => {
 | 
			
		||||
            this.loadCurrentListElement();
 | 
			
		||||
        }).catch((error) => {
 | 
			
		||||
            option.expanded = false;
 | 
			
		||||
 | 
			
		||||
@ -18,22 +18,22 @@
 | 
			
		||||
                <!-- 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-empty-box *ngIf="displayResults && !contacts.results.length && !nonContacts.results.length && !messages.results.length" icon="search" [message]="'core.noresults' | translate"></core-empty-box>
 | 
			
		||||
        </core-loading>
 | 
			
		||||
    </ion-content>
 | 
			
		||||
</core-split-view>
 | 
			
		||||
 | 
			
		||||
<!-- Template to render a list of results -->
 | 
			
		||||
<ng-template #resultsTemplate let-item="item">
 | 
			
		||||
    <ng-container *ngIf="item.results.length > 0">
 | 
			
		||||
        <ion-item-divider 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)="openConversation(result)" [class.core-split-item-selected]="result == selectedResult" 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-format-text [text]="result.fullname" [highlight]="result.highlightName"></core-format-text>
 | 
			
		||||
                <core-icon name="fa-ban" *ngIf="result.isblocked" [label]="'addon.messages.contactblocked' | translate"></core-icon>
 | 
			
		||||
            </h2>
 | 
			
		||||
            <ion-note *ngIf="result.lastmessagedate > 0">
 | 
			
		||||
@ -41,7 +41,7 @@
 | 
			
		||||
            </ion-note>
 | 
			
		||||
            <p class="addon-message-last-message">
 | 
			
		||||
                <span *ngIf="result.sentfromcurrentuser" class="addon-message-last-message-user">{{ 'addon.messages.you' | translate }}</span>
 | 
			
		||||
            <core-format-text clean="true" singleLine="true" [text]="result.lastmessage" class="addon-message-last-message-text"></core-format-text>
 | 
			
		||||
                <core-format-text clean="true" singleLine="true" [text]="result.lastmessage" [highlight]="result.highlightMessage" class="addon-message-last-message-text"></core-format-text>
 | 
			
		||||
            </p>
 | 
			
		||||
        </a>
 | 
			
		||||
 | 
			
		||||
@ -56,4 +56,5 @@
 | 
			
		||||
                <ion-spinner></ion-spinner>
 | 
			
		||||
            </div>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
</ng-template>
 | 
			
		||||
@ -38,7 +38,6 @@ export class AddonMessagesSearchPage implements OnDestroy {
 | 
			
		||||
    contacts = {
 | 
			
		||||
        type: 'contacts',
 | 
			
		||||
        titleString: 'addon.messages.contacts',
 | 
			
		||||
        emptyString: 'addon.messages.searchnocontactsfound',
 | 
			
		||||
        results: [],
 | 
			
		||||
        canLoadMore: false,
 | 
			
		||||
        loadingMore: false
 | 
			
		||||
@ -46,7 +45,6 @@ export class AddonMessagesSearchPage implements OnDestroy {
 | 
			
		||||
    nonContacts = {
 | 
			
		||||
        type: 'noncontacts',
 | 
			
		||||
        titleString: 'addon.messages.noncontacts',
 | 
			
		||||
        emptyString: 'addon.messages.searchnononcontactsfound',
 | 
			
		||||
        results: [],
 | 
			
		||||
        canLoadMore: false,
 | 
			
		||||
        loadingMore: false
 | 
			
		||||
@ -54,7 +52,6 @@ export class AddonMessagesSearchPage implements OnDestroy {
 | 
			
		||||
    messages = {
 | 
			
		||||
        type: 'messages',
 | 
			
		||||
        titleString: 'addon.messages.messages',
 | 
			
		||||
        emptyString: 'addon.messages.searchnomessagesfound',
 | 
			
		||||
        results: [],
 | 
			
		||||
        canLoadMore: false,
 | 
			
		||||
        loadingMore: false,
 | 
			
		||||
@ -178,17 +175,20 @@ export class AddonMessagesSearchPage implements OnDestroy {
 | 
			
		||||
            if (!loadMore || loadMore == 'contacts') {
 | 
			
		||||
                this.contacts.results.push(...newContacts);
 | 
			
		||||
                this.contacts.canLoadMore = canLoadMoreContacts;
 | 
			
		||||
                this.setHighlight(newContacts, true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!loadMore || loadMore == 'noncontacts') {
 | 
			
		||||
                this.nonContacts.results.push(...newNonContacts);
 | 
			
		||||
                this.nonContacts.canLoadMore = canLoadMoreNonContacts;
 | 
			
		||||
                this.setHighlight(newNonContacts, true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!loadMore || loadMore == 'messages') {
 | 
			
		||||
                this.messages.results.push(...newMessages);
 | 
			
		||||
                this.messages.canLoadMore = canLoadMoreMessages;
 | 
			
		||||
                this.messages.loadMoreError = false;
 | 
			
		||||
                this.setHighlight(newMessages, false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!loadMore) {
 | 
			
		||||
@ -241,6 +241,19 @@ export class AddonMessagesSearchPage implements OnDestroy {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the highlight values for each entry.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {any[]} results Results to highlight.
 | 
			
		||||
     * @param {boolean} isUser Whether the results are from a user search or from a message search.
 | 
			
		||||
     */
 | 
			
		||||
    setHighlight(results: any[], isUser: boolean): void {
 | 
			
		||||
        results.forEach((result) => {
 | 
			
		||||
            result.highlightName = isUser ? this.query : undefined;
 | 
			
		||||
            result.highlightMessage = !isUser ? this.query : undefined;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component destroyed.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -1050,6 +1050,11 @@ ion-modal,
 | 
			
		||||
  contain: size layout style;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Highlight text.
 | 
			
		||||
.matchtext {
 | 
			
		||||
  background-color: $core-text-hightlight-background-color;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Styles for desktop apps only.
 | 
			
		||||
ion-app.platform-desktop {
 | 
			
		||||
  video::-webkit-media-text-track-display {
 | 
			
		||||
 | 
			
		||||
@ -177,6 +177,7 @@
 | 
			
		||||
    "addon.messages.contactname": "Contact name",
 | 
			
		||||
    "addon.messages.contactrequestsent": "Contact request sent",
 | 
			
		||||
    "addon.messages.contacts": "Contacts",
 | 
			
		||||
    "addon.messages.conversationactions": "Conversation actions menu",
 | 
			
		||||
    "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.deleteallselfconfirm": "Are you sure you would like to delete this entire personal conversation?",
 | 
			
		||||
@ -198,6 +199,7 @@
 | 
			
		||||
    "addon.messages.messagepreferences": "Message preferences",
 | 
			
		||||
    "addon.messages.messages": "Messages",
 | 
			
		||||
    "addon.messages.muteconversation": "Mute",
 | 
			
		||||
    "addon.messages.mutedconversation": "Muted conversation",
 | 
			
		||||
    "addon.messages.newmessage": "New message",
 | 
			
		||||
    "addon.messages.newmessages": "New messages",
 | 
			
		||||
    "addon.messages.nocontactrequests": "No contact requests",
 | 
			
		||||
@ -216,9 +218,6 @@
 | 
			
		||||
    "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.selfconversation": "Personal space",
 | 
			
		||||
    "addon.messages.selfconversationdefaultmessage": "Save draft messages, links, notes etc. to access later.",
 | 
			
		||||
    "addon.messages.sendcontactrequest": "Send contact request",
 | 
			
		||||
 | 
			
		||||
@ -31,10 +31,10 @@ import { Subject } from 'rxjs';
 | 
			
		||||
})
 | 
			
		||||
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.
 | 
			
		||||
    @Input() title?: string; // Text to be shown on the top of the popover.
 | 
			
		||||
    @Input('aria-label') ariaLabel?: string; // Aria label to be shown on the top of the popover.
 | 
			
		||||
 | 
			
		||||
    hideMenu = true; // It will be unhidden when items are added.
 | 
			
		||||
    ariaLabel: string;
 | 
			
		||||
    expanded = false;
 | 
			
		||||
    protected items: CoreContextMenuItemComponent[] = [];
 | 
			
		||||
    protected itemsMovedToParent: CoreContextMenuItemComponent[] = [];
 | 
			
		||||
@ -70,7 +70,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit(): void {
 | 
			
		||||
        this.icon = this.icon || 'more';
 | 
			
		||||
        this.ariaLabel = this.title || this.translate.instant('core.info');
 | 
			
		||||
        this.ariaLabel = this.ariaLabel || this.title || this.translate.instant('core.info');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
<ion-list [id]="uniqueId" role="menu">
 | 
			
		||||
    <ion-list-header *ngIf="title">{{title}}</ion-list-header>
 | 
			
		||||
    <a ion-item text-wrap *ngFor="let item of items" core-link [capture]="item.captureLink" [autoLogin]="item.autoLogin" [href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction" [hidden]="item.hidden" [attr.detail-none]="!item.href || item.iconAction" role="menuitem" [attr.aria-controls]="uniqueId">
 | 
			
		||||
    <a ion-item text-wrap *ngFor="let item of items" core-link [capture]="item.captureLink" [autoLogin]="item.autoLogin" [href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction" [hidden]="item.hidden" [attr.detail-none]="!item.href || item.iconAction" role="menuitem">
 | 
			
		||||
        <core-icon *ngIf="item.iconDescription" [name]="item.iconDescription" [label]="item.ariaDescription" item-start></core-icon>
 | 
			
		||||
        <core-format-text [clean]="true" [text]="item.content"></core-format-text>
 | 
			
		||||
        <core-icon *ngIf="(item.href || item.action) && item.iconAction && item.iconAction != 'spinner'" [name]="item.iconAction" item-end></core-icon>
 | 
			
		||||
 | 
			
		||||
@ -55,6 +55,7 @@ export class CoreFormatTextDirective implements OnChanges {
 | 
			
		||||
                                 // If you want to avoid this use class="inline" at the same time to use display: inline-block.
 | 
			
		||||
    @Input() fullOnClick?: boolean | string; // Whether it should open a new page with the full contents on click.
 | 
			
		||||
    @Input() fullTitle?: string; // Title to use in full view. Defaults to "Description".
 | 
			
		||||
    @Input() highlight?: string; // Text to highlight.
 | 
			
		||||
    @Output() afterRender?: EventEmitter<any>; // Called when the data is rendered.
 | 
			
		||||
 | 
			
		||||
    protected element: HTMLElement;
 | 
			
		||||
@ -348,7 +349,7 @@ export class CoreFormatTextDirective implements OnChanges {
 | 
			
		||||
 | 
			
		||||
            // Apply format text function.
 | 
			
		||||
            return this.textUtils.formatText(this.text, this.utils.isTrueOrOne(this.clean),
 | 
			
		||||
                this.utils.isTrueOrOne(this.singleLine));
 | 
			
		||||
                this.utils.isTrueOrOne(this.singleLine), undefined, this.highlight);
 | 
			
		||||
        }).then((formatted) => {
 | 
			
		||||
            const div = document.createElement('div'),
 | 
			
		||||
                canTreatVimeo = site && site.isVersionGreaterEqualThan(['3.3.4', '3.4']);
 | 
			
		||||
 | 
			
		||||
@ -396,9 +396,10 @@ export class CoreTextUtilsProvider {
 | 
			
		||||
     * @param {boolean} [clean] Whether HTML tags should be removed.
 | 
			
		||||
     * @param {boolean} [singleLine] Whether new lines should be removed. Only valid if clean is true.
 | 
			
		||||
     * @param {number} [shortenLength] Number of characters to shorten the text.
 | 
			
		||||
     * @param {number} [highlight] Text to highlight.
 | 
			
		||||
     * @return {Promise<string>} Promise resolved with the formatted text.
 | 
			
		||||
     */
 | 
			
		||||
    formatText(text: string, clean?: boolean, singleLine?: boolean, shortenLength?: number): Promise<string> {
 | 
			
		||||
    formatText(text: string, clean?: boolean, singleLine?: boolean, shortenLength?: number, highlight?: string): Promise<string> {
 | 
			
		||||
        return this.treatMultilangTags(text).then((formatted) => {
 | 
			
		||||
            if (clean) {
 | 
			
		||||
                formatted = this.cleanTags(formatted, singleLine);
 | 
			
		||||
@ -406,6 +407,9 @@ export class CoreTextUtilsProvider {
 | 
			
		||||
            if (shortenLength > 0) {
 | 
			
		||||
                formatted = this.shortenText(formatted, shortenLength);
 | 
			
		||||
            }
 | 
			
		||||
            if (highlight) {
 | 
			
		||||
                formatted = this.highlightText(formatted, highlight);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return formatted;
 | 
			
		||||
        });
 | 
			
		||||
@ -452,6 +456,25 @@ export class CoreTextUtilsProvider {
 | 
			
		||||
        return /<[a-z][\s\S]*>/i.test(text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Highlight all occurrences of a certain text inside another text. It will add some HTML code to highlight it.
 | 
			
		||||
     *
 | 
			
		||||
     * @param {string} text Full text.
 | 
			
		||||
     * @param {string} searchText Text to search and highlight.
 | 
			
		||||
     * @return {string} Highlighted text.
 | 
			
		||||
     */
 | 
			
		||||
    highlightText(text: string, searchText: string): string {
 | 
			
		||||
        if (!text || typeof text != 'string') {
 | 
			
		||||
            return '';
 | 
			
		||||
        } else if (!searchText) {
 | 
			
		||||
            return text;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const regex = new RegExp('(' + searchText + ')', 'gi');
 | 
			
		||||
 | 
			
		||||
        return text.replace(regex, '<span class="matchtext">$1</span>');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if HTML content is blank.
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -263,6 +263,8 @@ $core-rte-min-height: 80px;
 | 
			
		||||
 | 
			
		||||
$core-toolbar-button-image-width: 32px;
 | 
			
		||||
 | 
			
		||||
$core-text-hightlight-background-color: lighten($blue, 40%) !default;
 | 
			
		||||
 | 
			
		||||
// Timer variables.
 | 
			
		||||
$core-timer-warn-color:     $red !default;
 | 
			
		||||
$core-timer-iterations:     15 !default;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user