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…
Reference in New Issue