MOBILE-3633 notifications: Implement list page
parent
66e7393603
commit
b2ad224950
|
@ -0,0 +1,91 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreContentLinksDelegate, CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
|
||||
/**
|
||||
* Component that displays the actions for a notification.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-notifications-actions',
|
||||
templateUrl: 'addon-notifications-actions.html',
|
||||
})
|
||||
export class AddonNotificationsActionsComponent implements OnInit {
|
||||
|
||||
@Input() contextUrl?: string;
|
||||
@Input() courseId?: number;
|
||||
@Input() data?: Record<string, unknown>; // Extra data to handle the URL.
|
||||
|
||||
actions: CoreContentLinksAction[] = [];
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
if (!this.contextUrl && (!this.data || !this.data.appurl)) {
|
||||
// No URL, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
let actions: CoreContentLinksAction[] = [];
|
||||
|
||||
// Treat appurl first if any.
|
||||
if (this.data?.appurl) {
|
||||
actions = await CoreContentLinksDelegate.instance.getActionsFor(
|
||||
<string> this.data.appurl,
|
||||
this.courseId,
|
||||
undefined,
|
||||
this.data,
|
||||
);
|
||||
}
|
||||
|
||||
if (!actions.length && this.contextUrl) {
|
||||
// No appurl or cannot handle it. Try with contextUrl.
|
||||
actions = await CoreContentLinksDelegate.instance.getActionsFor(this.contextUrl, this.courseId, undefined, this.data);
|
||||
}
|
||||
|
||||
if (!actions.length) {
|
||||
// URL is not supported. Add an action to open it in browser.
|
||||
actions.push({
|
||||
message: 'core.view',
|
||||
icon: 'fas-eye',
|
||||
action: this.openInBrowser.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default action. Open in browser.
|
||||
*
|
||||
* @param siteId Site ID to use.
|
||||
* @param navCtrl NavController.
|
||||
*/
|
||||
protected async openInBrowser(siteId?: string): Promise<void> {
|
||||
const url = <string> this.data?.appurl || this.contextUrl;
|
||||
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
site.openInBrowserWithAutoLogin(url);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<ion-row *ngIf="actions && actions.length > 0" class="justify-content-around">
|
||||
<ion-col *ngFor="let action of actions">
|
||||
<ion-button fill="clear" expand="block" (click)="action.action()">
|
||||
<ion-icon slot="start" name="{{action.icon}}"></ion-icon>
|
||||
{{ action.message | translate }}
|
||||
</ion-button>
|
||||
</ion-col>
|
||||
</ion-row>
|
|
@ -0,0 +1,35 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { AddonNotificationsActionsComponent } from './actions/actions';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonNotificationsActionsComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
],
|
||||
exports: [
|
||||
AddonNotificationsActionsComponent,
|
||||
],
|
||||
})
|
||||
export class AddonNotificationsComponentsModule {}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"errorgetnotifications": "Error getting notifications.",
|
||||
"markallread": "Mark all as read",
|
||||
"notificationpreferences": "Notification preferences",
|
||||
"notifications": "Notifications",
|
||||
"playsound": "Play sound",
|
||||
"therearentnotificationsyet": "There are no notifications."
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'addon.notifications.notifications' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!notificationsLoaded" (ionRefresh)="refreshNotifications($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="notificationsLoaded">
|
||||
<div class="ion-padding" *ngIf="canMarkAllNotificationsAsRead">
|
||||
<ion-button *ngIf="!loadingMarkAllNotificationsAsRead" expand="block" (click)="markAllNotificationsAsRead()"
|
||||
color="light">
|
||||
<ion-icon slot="start" name="fas-check"></ion-icon>
|
||||
{{ 'addon.notifications.markallread' | translate }}
|
||||
</ion-button>
|
||||
<ion-button *ngIf="loadingMarkAllNotificationsAsRead" expand="block" color="light">
|
||||
<ion-spinner></ion-spinner>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<ion-card *ngFor="let notification of notifications">
|
||||
<ion-item class="ion-text-wrap" lines="none">
|
||||
<core-user-avatar *ngIf="notification.useridfrom > 0" [user]="notification" slot="start"
|
||||
[profileUrl]="notification.profileimageurlfrom" [fullname]="notification.userfromfullname"
|
||||
[userId]="notification.useridfrom" [extraIcon]="notification.iconurl"></core-user-avatar>
|
||||
|
||||
<img *ngIf="notification.useridfrom <= 0 && notification.iconurl" [src]="notification.iconurl" alt=""
|
||||
role="presentation" class="core-notification-icon" slot="start">
|
||||
|
||||
<ion-label>
|
||||
<h2>{{ notification.subject }}</h2>
|
||||
<p *ngIf="notification.userfromfullname">{{ notification.userfromfullname }}</p>
|
||||
</ion-label>
|
||||
<ion-note slot="end" class="ion-float-end ion-padding-left ion-text-end">
|
||||
{{ notification.timecreated | coreDateDayOrTime }}
|
||||
<span *ngIf="!notification.timeread">
|
||||
<ion-icon name="fas-circle" color="primary">
|
||||
</ion-icon>
|
||||
</span>
|
||||
</ion-note>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<core-format-text [text]="notification.mobiletext | coreCreateLinks" contextLevel="system"
|
||||
[contextInstanceId]="0" [maxHeight]="notification.displayfullhtml ? 120 : null">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<addon-notifications-actions [contextUrl]="notification.contexturl" [courseId]="notification.courseid"
|
||||
[data]="notification.customdata">
|
||||
</addon-notifications-actions>
|
||||
</ion-card>
|
||||
|
||||
<core-empty-box *ngIf="!notifications || notifications.length <= 0" icon="notifications"
|
||||
[message]="'addon.notifications.therearentnotificationsyet' | translate">
|
||||
</core-empty-box>
|
||||
|
||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMoreNotifications($event)" [error]="loadMoreError">
|
||||
</core-infinite-loading>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,47 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { AddonNotificationsComponentsModule } from '../../components/components.module';
|
||||
import { AddonNotificationsListPage } from './list';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AddonNotificationsListPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreSharedModule,
|
||||
// CoreComponentsModule,
|
||||
AddonNotificationsComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
AddonNotificationsListPage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AddonNotificationsListPageModule {}
|
|
@ -0,0 +1,61 @@
|
|||
:host {
|
||||
.core-notification-icon {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin: 10px !important;
|
||||
}
|
||||
|
||||
.item core-format-text ::ng-deep {
|
||||
.forumpost {
|
||||
border: 1px solid var(--gray-light);
|
||||
width: 100%;
|
||||
margin: 0 0 1em 0;
|
||||
|
||||
td {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--gray-lighter);
|
||||
}
|
||||
|
||||
.picture {
|
||||
width: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subject {
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.userpicture {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.mdl-right {
|
||||
text-align: end;
|
||||
a {
|
||||
display: none;
|
||||
}
|
||||
font {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
.commands {
|
||||
display: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
background-color: var(--gray-light);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreEvents, CoreEventObserver } from '@singletons/events';
|
||||
import { AddonNotifications, AddonNotificationsAnyNotification, AddonNotificationsProvider } from '../../services/notifications';
|
||||
import { AddonNotificationsHelper } from '../../services/notifications-helper';
|
||||
import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
|
||||
|
||||
/**
|
||||
* Page that displays the list of notifications.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-notifications-list',
|
||||
templateUrl: 'list.html',
|
||||
styleUrls: ['list.scss'],
|
||||
})
|
||||
export class AddonNotificationsListPage implements OnInit, OnDestroy {
|
||||
|
||||
notifications: FormattedNotification[] = [];
|
||||
notificationsLoaded = false;
|
||||
canLoadMore = false;
|
||||
loadMoreError = false;
|
||||
canMarkAllNotificationsAsRead = false;
|
||||
loadingMarkAllNotificationsAsRead = false;
|
||||
|
||||
protected isCurrentView?: boolean;
|
||||
protected cronObserver?: CoreEventObserver;
|
||||
protected pushObserver?: Subscription;
|
||||
protected pendingRefresh = false;
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.fetchNotifications();
|
||||
|
||||
this.cronObserver = CoreEvents.on(AddonNotificationsProvider.READ_CRON_EVENT, () => {
|
||||
if (!this.isCurrentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationsLoaded = false;
|
||||
this.refreshNotifications();
|
||||
}, CoreSites.instance.getCurrentSiteId());
|
||||
|
||||
this.pushObserver = CorePushNotificationsDelegate.instance.on('receive').subscribe((notification) => {
|
||||
// New notification received. If it's from current site, refresh the data.
|
||||
if (!this.isCurrentView) {
|
||||
this.pendingRefresh = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CoreUtils.instance.isTrueOrOne(notification.notif) || !CoreSites.instance.isCurrentSite(notification.site)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationsLoaded = false;
|
||||
this.refreshNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get notifications. Gets unread notifications first.
|
||||
*
|
||||
* @param refreh Whether we're refreshing data.
|
||||
* @return Resolved when done.
|
||||
*/
|
||||
protected async fetchNotifications(refresh?: boolean): Promise<void> {
|
||||
this.loadMoreError = false;
|
||||
|
||||
try {
|
||||
const result = await AddonNotificationsHelper.instance.getNotifications(refresh ? [] : this.notifications);
|
||||
|
||||
const notifications = result.notifications.map((notification) => this.formatText(notification));
|
||||
|
||||
if (refresh) {
|
||||
this.notifications = notifications;
|
||||
} else {
|
||||
this.notifications = this.notifications.concat(notifications);
|
||||
}
|
||||
this.canLoadMore = result.canLoadMore;
|
||||
|
||||
this.markNotificationsAsRead(notifications);
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.notifications.errorgetnotifications', true);
|
||||
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
||||
} finally {
|
||||
this.notificationsLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all notifications as read.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async markAllNotificationsAsRead(): Promise<void> {
|
||||
this.loadingMarkAllNotificationsAsRead = true;
|
||||
|
||||
await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.markAllNotificationsAsRead());
|
||||
|
||||
CoreEvents.trigger(AddonNotificationsProvider.READ_CHANGED_EVENT, {}, CoreSites.instance.getCurrentSiteId());
|
||||
|
||||
// All marked as read, refresh the list.
|
||||
this.notificationsLoaded = false;
|
||||
|
||||
await this.refreshNotifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark notifications as read.
|
||||
*
|
||||
* @param notifications Array of notification objects.
|
||||
*/
|
||||
protected async markNotificationsAsRead(notifications: FormattedNotification[]): Promise<void> {
|
||||
if (notifications.length > 0) {
|
||||
const promises = notifications.map(async (notification) => {
|
||||
if (notification.read) {
|
||||
// Already read, don't mark it.
|
||||
return;
|
||||
}
|
||||
|
||||
await AddonNotifications.instance.markNotificationRead(notification.id);
|
||||
});
|
||||
|
||||
await CoreUtils.instance.ignoreErrors(Promise.all(promises));
|
||||
|
||||
await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.invalidateNotificationsList());
|
||||
|
||||
CoreEvents.trigger(AddonNotificationsProvider.READ_CHANGED_EVENT, {}, CoreSites.instance.getCurrentSiteId());
|
||||
}
|
||||
|
||||
// Check if mark all notifications as read is enabled and there are some to read.
|
||||
if (!AddonNotifications.instance.isMarkAllNotificationsAsReadEnabled()) {
|
||||
this.canMarkAllNotificationsAsRead = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.loadingMarkAllNotificationsAsRead = true;
|
||||
|
||||
const unread = await AddonNotifications.instance.getUnreadNotificationsCount();
|
||||
|
||||
this.canMarkAllNotificationsAsRead = unread > 0;
|
||||
} finally {
|
||||
this.loadingMarkAllNotificationsAsRead = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh notifications.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
* @return Promise<any> Promise resolved when done.
|
||||
*/
|
||||
async refreshNotifications(refresher?: CustomEvent<IonRefresher>): Promise<void> {
|
||||
await CoreUtils.instance.ignoreErrors(AddonNotifications.instance.invalidateNotificationsList());
|
||||
|
||||
try {
|
||||
await this.fetchNotifications(true);
|
||||
} finally {
|
||||
refresher?.detail.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load more results.
|
||||
*
|
||||
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
|
||||
*/
|
||||
async loadMoreNotifications(infiniteComplete?: () => void): Promise<void> {
|
||||
try {
|
||||
await this.fetchNotifications();
|
||||
} finally {
|
||||
infiniteComplete?.();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the text of a notification.
|
||||
*
|
||||
* @param notification The notification object.
|
||||
*/
|
||||
protected formatText(notification: AddonNotificationsAnyNotification): FormattedNotification {
|
||||
const formattedNotification: FormattedNotification = notification;
|
||||
formattedNotification.displayfullhtml = this.shouldDisplayFullHtml(notification);
|
||||
formattedNotification.iconurl = formattedNotification.iconurl || undefined; // Make sure the property exists.
|
||||
|
||||
formattedNotification.mobiletext = formattedNotification.displayfullhtml ?
|
||||
notification.fullmessagehtml :
|
||||
CoreTextUtils.instance.replaceNewLines(formattedNotification.mobiletext!.replace(/-{4,}/ig, ''), '<br>');
|
||||
|
||||
return formattedNotification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we should display full HTML of the notification.
|
||||
*
|
||||
* @param notification Notification.
|
||||
* @return Whether to display full HTML.
|
||||
*/
|
||||
protected shouldDisplayFullHtml(notification: FormattedNotification): boolean {
|
||||
return notification.component == 'mod_forum' && notification.eventtype == 'digests';
|
||||
}
|
||||
|
||||
/**
|
||||
* User entered the page.
|
||||
*/
|
||||
ionViewDidEnter(): void {
|
||||
this.isCurrentView = true;
|
||||
|
||||
if (!this.pendingRefresh) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pendingRefresh = false;
|
||||
this.notificationsLoaded = false;
|
||||
|
||||
this.refreshNotifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* User left the page.
|
||||
*/
|
||||
ionViewDidLeave(): void {
|
||||
this.isCurrentView = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.cronObserver?.off();
|
||||
this.pushObserver?.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type FormattedNotification = AddonNotificationsAnyNotification & {
|
||||
displayfullhtml?: boolean; // Whether to display the full HTML of the notification.
|
||||
iconurl?: string;
|
||||
};
|
|
@ -10,7 +10,7 @@
|
|||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item button *ngIf="siteInfo" class="ion-text-wrap" core-user-link [userId]="siteInfo.userid">
|
||||
<core-user-avatar [user]="siteInfo" slot="start"></core-user-avatar>
|
||||
<core-user-avatar [user]="siteInfo" slot="start"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{siteInfo.fullname}}</h2>
|
||||
<p>
|
||||
|
|
|
@ -149,7 +149,7 @@ export class CorePushNotificationsDelegateService {
|
|||
* @param eventName Only receive is permitted.
|
||||
* @return Observer to subscribe.
|
||||
*/
|
||||
on<T = unknown>(eventName: string): Subject<T> {
|
||||
on<T = CorePushNotificationsNotificationBasicData>(eventName: string): Subject<T> {
|
||||
if (typeof this.observables[eventName] == 'undefined') {
|
||||
const eventNames = Object.keys(this.observables).join(', ');
|
||||
this.logger.warn(`'${eventName}' event name is not allowed. Use one of the following: '${eventNames}'.`);
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import moment from 'moment';
|
||||
|
||||
import { CoreTimeUtils } from '@services/utils/time';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
|
||||
/**
|
||||
* Filter to display a date using the day, or the time.
|
||||
*
|
||||
* This shows a short version of a date. Use this filter when you want
|
||||
* the user to visualise when the action was done relatively to today's date.
|
||||
*
|
||||
* For instance, if the action happened during this day it will display the time,
|
||||
* but when the action happened few days ago, it will display the day of the week.
|
||||
*
|
||||
* The older the date is, the more information about it will be displayed.
|
||||
*
|
||||
* This filter expects a timestamp NOT including milliseconds.
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'coreDateDayOrTime',
|
||||
})
|
||||
export class CoreDateDayOrTimePipe implements PipeTransform {
|
||||
|
||||
protected logger: CoreLogger;
|
||||
|
||||
constructor() {
|
||||
this.logger = CoreLogger.getInstance('CoreDateDayOrTimePipe');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a timestamp.
|
||||
*
|
||||
* @param timestamp The UNIX timestamp (without milliseconds).
|
||||
* @return Formatted time.
|
||||
*/
|
||||
transform(timestamp: string | number): string {
|
||||
if (typeof timestamp == 'string') {
|
||||
// Convert the value to a number.
|
||||
const numberTimestamp = parseInt(timestamp, 10);
|
||||
if (isNaN(numberTimestamp)) {
|
||||
this.logger.error('Invalid value received', timestamp);
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
timestamp = numberTimestamp;
|
||||
}
|
||||
|
||||
return moment(timestamp * 1000).calendar(null, {
|
||||
sameDay: CoreTimeUtils.instance.convertPHPToMoment(Translate.instance.instant('core.strftimetime')),
|
||||
lastDay: Translate.instance.instant('core.dflastweekdate'),
|
||||
lastWeek: Translate.instance.instant('core.dflastweekdate'),
|
||||
sameElse: CoreTimeUtils.instance.convertPHPToMoment(Translate.instance.instant('core.strftimedatefullshort')),
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import { CoreSecondsToHMSPipe } from './seconds-to-hms';
|
|||
import { CoreTimeAgoPipe } from './time-ago';
|
||||
import { CoreBytesToSizePipe } from './bytes-to-size';
|
||||
import { CoreDurationPipe } from './duration';
|
||||
import { CoreDateDayOrTimePipe } from './date-day-or-time';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -30,6 +31,7 @@ import { CoreDurationPipe } from './duration';
|
|||
CoreBytesToSizePipe,
|
||||
CoreSecondsToHMSPipe,
|
||||
CoreDurationPipe,
|
||||
CoreDateDayOrTimePipe,
|
||||
],
|
||||
imports: [],
|
||||
exports: [
|
||||
|
@ -40,6 +42,7 @@ import { CoreDurationPipe } from './duration';
|
|||
CoreBytesToSizePipe,
|
||||
CoreSecondsToHMSPipe,
|
||||
CoreDurationPipe,
|
||||
CoreDateDayOrTimePipe,
|
||||
],
|
||||
})
|
||||
export class CorePipesModule {}
|
||||
|
|
|
@ -1016,7 +1016,7 @@ export class CoreSitesProvider {
|
|||
* @param site Site object or siteId to be compared. If not defined, use current site.
|
||||
* @return Whether site or siteId is the current one.
|
||||
*/
|
||||
isCurrentSite(site: string | CoreSite): boolean {
|
||||
isCurrentSite(site?: string | CoreSite): boolean {
|
||||
if (!site || !this.currentSite) {
|
||||
return !!this.currentSite;
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import { CoreDirectivesModule } from '@directives/directives.module';
|
|||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
exports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
|
|
Loading…
Reference in New Issue