Merge pull request #1642 from dpalou/MOBILE-2631

Mobile 2631
main
Juan Leyva 2018-12-03 16:27:10 +01:00 committed by GitHub
commit af916b10ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 244 additions and 44 deletions

View File

@ -148,6 +148,7 @@
"addon.files.sitefiles": "moodle", "addon.files.sitefiles": "moodle",
"addon.messageoutput_airnotifier.processorsettingsdesc": "local_moodlemobileapp", "addon.messageoutput_airnotifier.processorsettingsdesc": "local_moodlemobileapp",
"addon.messages.addcontact": "message", "addon.messages.addcontact": "message",
"addon.messages.addtofavourites": "message",
"addon.messages.blocknoncontacts": "message", "addon.messages.blocknoncontacts": "message",
"addon.messages.blockuser": "message", "addon.messages.blockuser": "message",
"addon.messages.blockuserconfirm": "message", "addon.messages.blockuserconfirm": "message",
@ -159,6 +160,8 @@
"addon.messages.contactlistempty": "local_moodlemobileapp", "addon.messages.contactlistempty": "local_moodlemobileapp",
"addon.messages.contactname": "local_moodlemobileapp", "addon.messages.contactname": "local_moodlemobileapp",
"addon.messages.contacts": "message", "addon.messages.contacts": "message",
"addon.messages.deleteallconfirm": "message",
"addon.messages.deleteconversationq": "message",
"addon.messages.deletemessage": "local_moodlemobileapp", "addon.messages.deletemessage": "local_moodlemobileapp",
"addon.messages.deletemessageconfirmation": "local_moodlemobileapp", "addon.messages.deletemessageconfirmation": "local_moodlemobileapp",
"addon.messages.errordeletemessage": "local_moodlemobileapp", "addon.messages.errordeletemessage": "local_moodlemobileapp",
@ -181,6 +184,7 @@
"addon.messages.numparticipants": "message", "addon.messages.numparticipants": "message",
"addon.messages.removecontact": "message", "addon.messages.removecontact": "message",
"addon.messages.removecontactconfirm": "local_moodlemobileapp", "addon.messages.removecontactconfirm": "local_moodlemobileapp",
"addon.messages.removefromfavourites": "message",
"addon.messages.showdeletemessages": "local_moodlemobileapp", "addon.messages.showdeletemessages": "local_moodlemobileapp",
"addon.messages.type_blocked": "local_moodlemobileapp", "addon.messages.type_blocked": "local_moodlemobileapp",
"addon.messages.type_offline": "local_moodlemobileapp", "addon.messages.type_offline": "local_moodlemobileapp",

View File

@ -1,5 +1,6 @@
{ {
"addcontact": "Add contact", "addcontact": "Add contact",
"addtofavourites": "Star",
"blocknoncontacts": "Prevent non-contacts from messaging me", "blocknoncontacts": "Prevent non-contacts from messaging me",
"blockuser": "Block user", "blockuser": "Block user",
"blockuserconfirm": "Are you sure you want to block {{$a}}?", "blockuserconfirm": "Are you sure you want to block {{$a}}?",
@ -11,6 +12,8 @@
"contactlistempty": "The contact list is empty", "contactlistempty": "The contact list is empty",
"contactname": "Contact name", "contactname": "Contact name",
"contacts": "Contacts", "contacts": "Contacts",
"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", "deletemessage": "Delete message",
"deletemessageconfirmation": "Are you sure you want to delete this message? It will only be deleted from your messaging history and will still be viewable by the user who sent or received the message.", "deletemessageconfirmation": "Are you sure you want to delete this message? It will only be deleted from your messaging history and will still be viewable by the user who sent or received the message.",
"errordeletemessage": "Error while deleting the message.", "errordeletemessage": "Error while deleting the message.",
@ -33,6 +36,7 @@
"numparticipants": "{{$a}} participants", "numparticipants": "{{$a}} participants",
"removecontact": "Remove contact", "removecontact": "Remove contact",
"removecontactconfirm": "Contact will be removed from your contacts list.", "removecontactconfirm": "Contact will be removed from your contacts list.",
"removefromfavourites": "Unstar",
"showdeletemessages": "Show delete messages", "showdeletemessages": "Show delete messages",
"type_blocked": "Blocked", "type_blocked": "Blocked",
"type_offline": "Offline", "type_offline": "Offline",

View File

@ -3,14 +3,17 @@
<ion-title> <ion-title>
<img *ngIf="conversationImage" class="core-bar-button-image" [src]="conversationImage"> <img *ngIf="conversationImage" class="core-bar-button-image" [src]="conversationImage">
<core-format-text [text]="title"></core-format-text> <core-format-text [text]="title"></core-format-text>
<core-icon *ngIf="conversation && conversation.isfavourite" name="fa-star"></core-icon>
</ion-title> </ion-title>
<ion-buttons end></ion-buttons> <ion-buttons end></ion-buttons>
</ion-navbar> </ion-navbar>
<core-navbar-buttons end> <core-navbar-buttons end>
<core-context-menu> <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.info' | translate" (action)="viewInfo()" [iconAction]="'information-circle'"></core-context-menu-item>
<core-context-menu-item [hidden]="!showInfo || !isGroup" [priority]="999" [content]="'addon.messages.groupinfo' | 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]="!canDelete" [priority]="800" [content]="'addon.messages.showdeletemessages' | translate" (action)="toggleDelete()" [iconAction]="'trash'"></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> </core-context-menu>
</core-navbar-buttons> </core-navbar-buttons>
</ion-header> </ion-header>
@ -54,7 +57,7 @@
<core-empty-box *ngIf="!messages || messages.length <= 0" icon="chatbubbles" [message]="'addon.messages.nomessages' | translate"></core-empty-box> <core-empty-box *ngIf="!messages || messages.length <= 0" icon="chatbubbles" [message]="'addon.messages.nomessages' | translate"></core-empty-box>
</core-loading> </core-loading>
</ion-content> </ion-content>
<ion-footer color="light" class="footer-adjustable" *ngIf="!conversationId || conversation"> <ion-footer color="light" class="footer-adjustable" *ngIf="loaded && (!conversationId || conversation)">
<ion-toolbar color="light" position="bottom"> <ion-toolbar color="light" position="bottom">
<!-- @todo: Check if the user can send messages. --> <!-- @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> <core-send-message-form (onSubmit)="sendMessage($event)" [showKeyboard]="showKeyboard" [placeholder]="'addon.messages.newmessage' | translate" (onResize)="resizeContent()"></core-send-message-form>

View File

@ -75,6 +75,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
groupMessagingEnabled: boolean; groupMessagingEnabled: boolean;
isGroup = false; isGroup = false;
members: any = {}; // Members that wrote a message, indexed by ID. members: any = {}; // Members that wrote a message, indexed by ID.
favouriteIcon = 'fa-star';
deleteIcon = 'trash';
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams, constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider, private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
@ -332,17 +334,52 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* @return {Promise<boolean>} Promise resolved with a boolean: whether the conversation exists or not. * @return {Promise<boolean>} Promise resolved with a boolean: whether the conversation exists or not.
*/ */
protected getConversation(conversationId: number, userId: number): Promise<boolean> { protected getConversation(conversationId: number, userId: number): Promise<boolean> {
let promise; let promise,
fallbackConversation;
// Try to get the conversationId if we don't have it.
if (conversationId) { if (conversationId) {
// Retrieve the conversation. Invalidate data first to get the right unreadcount. promise = Promise.resolve(conversationId);
promise = this.messagesProvider.invalidateConversation(conversationId).then(() => {
return this.messagesProvider.getConversation(conversationId);
});
} else { } else {
// We don't have the conversation ID, check if it exists. promise = this.messagesProvider.getConversationBetweenUsers(userId).then((conversation) => {
promise = this.messagesProvider.getConversationBetweenUsers(userId).catch((error) => { fallbackConversation = conversation;
return conversation.id;
});
}
return promise.then((conversationId) => {
// Retrieve the conversation. Invalidate data first to get the right unreadcount.
return this.messagesProvider.invalidateConversation(conversationId).catch(() => {
// Ignore errors.
}).then(() => {
return this.messagesProvider.getConversation(conversationId);
}).catch((error) => {
// Get conversation failed, use the fallback one if we have it.
if (fallbackConversation) {
return fallbackConversation;
}
return Promise.reject(error);
}).then((conversation) => {
this.conversation = conversation;
if (conversation) {
this.conversationId = conversation.id;
this.title = conversation.name;
this.conversationImage = conversation.imageurl;
this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
this.favouriteIcon = conversation.isfavourite ? 'fa-star-o' : 'fa-star';
if (!this.isGroup) {
this.userId = conversation.userid;
}
return true;
} else {
return false;
}
});
}, (error) => {
// Probably conversation does not exist or user is offline. Try to load offline messages. // Probably conversation does not exist or user is offline. Try to load offline messages.
return this.messagesOffline.getMessages(userId).then((messages) => { return this.messagesOffline.getMessages(userId).then((messages) => {
if (messages && messages.length) { if (messages && messages.length) {
@ -361,25 +398,6 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
}); });
} }
return promise.then((conversation) => {
this.conversation = conversation;
if (conversation) {
this.conversationId = conversation.id;
this.title = conversation.name;
this.conversationImage = conversation.imageurl;
this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
if (!this.isGroup) {
this.userId = conversation.userid;
}
return true;
} else {
return false;
}
});
}
/** /**
* Get the messages of the conversation. Used if group messaging is supported. * Get the messages of the conversation. Used if group messaging is supported.
* *
@ -940,6 +958,60 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
} }
} }
/**
* Change the favourite state of the current conversation.
*
* @param {Function} [done] Function to call when done.
*/
changeFavourite(done?: () => void): void {
this.favouriteIcon = 'spinner';
this.messagesProvider.setFavouriteConversation(this.conversation.id, !this.conversation.isfavourite).then(() => {
this.conversation.isfavourite = !this.conversation.isfavourite;
// Get the conversation data so it's cached. Don't block the user for this.
this.messagesProvider.getConversation(this.conversation.id);
this.eventsProvider.trigger(AddonMessagesProvider.UPDATE_CONVERSATION_LIST_EVENT, {
conversationId: this.conversation.id,
action: 'favourite',
value: this.conversation.isfavourite
}, this.siteId);
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Error changing favourite state.');
}).finally(() => {
this.favouriteIcon = this.conversation.isfavourite ? 'fa-star-o' : 'fa-star';
done && done();
});
}
/**
* Delete the conversation.
*
* @param {Function} [done] Function to call when done.
*/
deleteConversation(done?: () => void): void {
this.domUtils.showConfirm(this.translate.instant('addon.messages.deleteallconfirm')).then(() => {
this.deleteIcon = 'spinner';
return this.messagesProvider.deleteConversation(this.conversation.id).then(() => {
this.eventsProvider.trigger(AddonMessagesProvider.UPDATE_CONVERSATION_LIST_EVENT, {
conversationId: this.conversation.id,
action: 'delete'
}, this.siteId);
this.conversationId = undefined;
this.conversation = undefined;
this.messages = [];
}).finally(() => {
this.deleteIcon = 'trash';
done && done();
});
}).catch((error) => {
this.domUtils.showErrorModalDefault(error, 'Error deleting conversation.');
});
}
/** /**
* Page destroyed. * Page destroyed.
*/ */

View File

@ -73,6 +73,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
protected readChangedObserver: any; protected readChangedObserver: any;
protected cronObserver: any; protected cronObserver: any;
protected openConversationObserver: any; protected openConversationObserver: any;
protected updateConversationListObserver: any;
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService, constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService,
private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams, private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
@ -115,22 +116,22 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
} }
}, this.siteId); }, this.siteId);
// Update discussions when a message is read. // Update conversations when a message is read.
this.readChangedObserver = eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => { this.readChangedObserver = eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => {
if (data.conversationId) { if (data.conversationId) {
const conversation = this.findConversation(data.conversationId); const conversation = this.findConversation(data.conversationId);
if (typeof conversation != 'undefined') { if (typeof conversation != 'undefined') {
// A discussion has been read reset counter. // A conversation has been read reset counter.
conversation.unreadcount = 0; conversation.unreadcount = 0;
// Discussions changed, invalidate them. // Conversations changed, invalidate them.
this.messagesProvider.invalidateConversations(); this.messagesProvider.invalidateConversations();
} }
} }
}, this.siteId); }, this.siteId);
// Update discussions when cron read is executed. // Update conversations when cron read is executed.
this.cronObserver = eventsProvider.on(AddonMessagesProvider.READ_CRON_EVENT, (data) => { this.cronObserver = eventsProvider.on(AddonMessagesProvider.READ_CRON_EVENT, (data) => {
this.refreshData(); this.refreshData();
}, this.siteId); }, this.siteId);
@ -153,6 +154,11 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
}); });
}); });
// Update conversations if we receive an event to do so.
this.updateConversationListObserver = eventsProvider.on(AddonMessagesProvider.UPDATE_CONVERSATION_LIST_EVENT, () => {
this.refreshData();
}, this.siteId);
// If a message push notification is received, refresh the view. // If a message push notification is received, refresh the view.
this.pushObserver = pushNotificationsDelegate.on('receive').subscribe((notification) => { this.pushObserver = pushNotificationsDelegate.on('receive').subscribe((notification) => {
// New message received. If it's from current site, refresh the data. // New message received. If it's from current site, refresh the data.
@ -551,5 +557,6 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
this.readChangedObserver && this.readChangedObserver.off(); this.readChangedObserver && this.readChangedObserver.off();
this.cronObserver && this.cronObserver.off(); this.cronObserver && this.cronObserver.off();
this.openConversationObserver && this.openConversationObserver.off(); this.openConversationObserver && this.openConversationObserver.off();
this.updateConversationListObserver && this.updateConversationListObserver.off();
} }
} }

View File

@ -109,6 +109,21 @@ export class AddonMessagesOfflineProvider {
}); });
} }
/**
* Delete all the messages in a conversation.
*
* @param {number} conversationId Conversation ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
*/
deleteConversationMessages(conversationId: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().deleteRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, {
conversationid: conversationId
});
});
}
/** /**
* Delete a message. * Delete a message.
* *

View File

@ -35,6 +35,7 @@ export class AddonMessagesProvider {
static READ_CRON_EVENT = 'addon_messages_read_cron_event'; static READ_CRON_EVENT = 'addon_messages_read_cron_event';
static OPEN_CONVERSATION_EVENT = 'addon_messages_open_conversation_event'; // Notify that a conversation should be opened. 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 SPLIT_VIEW_LOAD_EVENT = 'addon_messages_split_view_load_event';
static UPDATE_CONVERSATION_LIST_EVENT = 'addon_messages_update_conversation_list_event';
static POLL_INTERVAL = 10000; static POLL_INTERVAL = 10000;
static PUSH_SIMULATION_COMPONENT = 'AddonMessagesPushSimulation'; static PUSH_SIMULATION_COMPONENT = 'AddonMessagesPushSimulation';
@ -105,6 +106,49 @@ export class AddonMessagesProvider {
}); });
} }
/**
* Delete a conversation.
*
* @param {number} conversationId Conversation to delete.
* @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 when the conversation has been deleted.
*/
deleteConversation(conversationId: number, siteId?: string, userId?: number): Promise<any> {
return this.deleteConversations([conversationId], siteId, userId);
}
/**
* Delete several conversations.
*
* @param {number[]} conversationIds Conversations to delete.
* @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 when the conversations have been deleted.
*/
deleteConversations(conversationIds: number[], siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const params = {
userid: userId,
conversationids: conversationIds
};
return site.write('core_message_delete_conversations_by_id', params).then(() => {
const promises = [];
conversationIds.forEach((conversationId) => {
promises.push(this.messagesOffline.deleteConversationMessages(conversationId, site.getId()).catch(() => {
// Ignore errors (shouldn't happen).
}));
});
return Promise.all(promises);
});
});
}
/** /**
* Delete a message (online or offline). * Delete a message (online or offline).
* *
@ -1114,7 +1158,7 @@ export class AddonMessagesProvider {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId(); userId = userId || site.getUserId();
return site.invalidateWsCacheForKey(this.getCacheKeyForConversation(conversationId, userId)); return site.invalidateWsCacheForKey(this.getCacheKeyForConversation(userId, conversationId));
}); });
} }
@ -1697,6 +1741,53 @@ export class AddonMessagesProvider {
}); });
} }
/**
* Set or unset a conversation as favourite.
*
* @param {number} conversationId Conversation ID.
* @param {boolean} set Whether to set or unset it as favourite.
* @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>} Resolved when done.
*/
setFavouriteConversation(conversationId: number, set: boolean, siteId?: string, userId?: number): Promise<any> {
return this.setFavouriteConversations([conversationId], set, siteId, userId);
}
/**
* Set or unset some conversations as favourites.
*
* @param {number[]} conversations Conversation IDs.
* @param {boolean} set Whether to set or unset them as favourites.
* @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>} Resolved when done.
*/
setFavouriteConversations(conversations: number[], set: boolean, siteId?: string, userId?: number): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
const params = {
userid: userId,
conversations: conversations
},
wsName = set ? 'core_message_set_favourite_conversations' : 'core_message_unset_favourite_conversations';
return site.write(wsName, params).then(() => {
// Invalidate the conversations data.
const promises = [];
conversations.forEach((conversationId) => {
promises.push(this.invalidateConversation(conversationId, site.getId(), userId));
});
return Promise.all(promises).catch(() => {
// Ignore errors.
});
});
});
}
/** /**
* Helper method to sort conversations by last message time. * Helper method to sort conversations by last message time.
* *

View File

@ -148,6 +148,7 @@
"addon.files.sitefiles": "Site files", "addon.files.sitefiles": "Site files",
"addon.messageoutput_airnotifier.processorsettingsdesc": "Configure devices", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configure devices",
"addon.messages.addcontact": "Add contact", "addon.messages.addcontact": "Add contact",
"addon.messages.addtofavourites": "Star",
"addon.messages.blocknoncontacts": "Prevent non-contacts from messaging me", "addon.messages.blocknoncontacts": "Prevent non-contacts from messaging me",
"addon.messages.blockuser": "Block user", "addon.messages.blockuser": "Block user",
"addon.messages.blockuserconfirm": "Are you sure you want to block {{$a}}?", "addon.messages.blockuserconfirm": "Are you sure you want to block {{$a}}?",
@ -159,6 +160,8 @@
"addon.messages.contactlistempty": "The contact list is empty", "addon.messages.contactlistempty": "The contact list is empty",
"addon.messages.contactname": "Contact name", "addon.messages.contactname": "Contact name",
"addon.messages.contacts": "Contacts", "addon.messages.contacts": "Contacts",
"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", "addon.messages.deletemessage": "Delete message",
"addon.messages.deletemessageconfirmation": "Are you sure you want to delete this message? It will only be deleted from your messaging history and will still be viewable by the user who sent or received the message.", "addon.messages.deletemessageconfirmation": "Are you sure you want to delete this message? It will only be deleted from your messaging history and will still be viewable by the user who sent or received the message.",
"addon.messages.errordeletemessage": "Error while deleting the message.", "addon.messages.errordeletemessage": "Error while deleting the message.",
@ -181,6 +184,7 @@
"addon.messages.numparticipants": "{{$a}} participants", "addon.messages.numparticipants": "{{$a}} participants",
"addon.messages.removecontact": "Remove contact", "addon.messages.removecontact": "Remove contact",
"addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.", "addon.messages.removecontactconfirm": "Contact will be removed from your contacts list.",
"addon.messages.removefromfavourites": "Unstar",
"addon.messages.showdeletemessages": "Show delete messages", "addon.messages.showdeletemessages": "Show delete messages",
"addon.messages.type_blocked": "Blocked", "addon.messages.type_blocked": "Blocked",
"addon.messages.type_offline": "Offline", "addon.messages.type_offline": "Offline",