commit
30ebd46706
|
@ -0,0 +1,22 @@
|
|||
const { resolve } = require('path');
|
||||
const webpackMerge = require('webpack-merge');
|
||||
const { dev, prod } = require('@ionic/app-scripts/config/webpack.config');
|
||||
|
||||
const customConfig = {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@addon': resolve('./src/addon'),
|
||||
'@classes': resolve('./src/classes'),
|
||||
'@core': resolve('./src/core'),
|
||||
'@providers': resolve('./src/providers'),
|
||||
'@components': resolve('./src/components'),
|
||||
'@directives': resolve('./src/directives/directives.module'),
|
||||
'@pipes': resolve('./src/pipes/pipes.module')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
dev: webpackMerge(dev, customConfig),
|
||||
prod: webpackMerge(prod, customConfig),
|
||||
}
|
10
package.json
10
package.json
|
@ -6,6 +6,9 @@
|
|||
"name": "Moodle Pty Ltd.",
|
||||
"email": "mobile@moodle.com"
|
||||
},
|
||||
"config": {
|
||||
"ionic_webpack": "./config/webpack.config.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/moodlehq/moodlemobile2.git"
|
||||
|
@ -26,6 +29,7 @@
|
|||
"ionic:build:before": "gulp"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^5.2.5",
|
||||
"@angular/common": "5.0.0",
|
||||
"@angular/compiler": "5.0.0",
|
||||
"@angular/compiler-cli": "5.0.0",
|
||||
|
@ -34,9 +38,11 @@
|
|||
"@angular/http": "5.0.0",
|
||||
"@angular/platform-browser": "5.0.0",
|
||||
"@angular/platform-browser-dynamic": "5.0.0",
|
||||
"@ionic-native/badge": "^4.5.3",
|
||||
"@ionic-native/camera": "^4.5.2",
|
||||
"@ionic-native/clipboard": "^4.3.2",
|
||||
"@ionic-native/core": "4.3.0",
|
||||
"@ionic-native/device": "^4.5.3",
|
||||
"@ionic-native/file": "^4.3.3",
|
||||
"@ionic-native/file-transfer": "^4.3.3",
|
||||
"@ionic-native/globalization": "^4.3.2",
|
||||
|
@ -45,6 +51,7 @@
|
|||
"@ionic-native/local-notifications": "^4.4.0",
|
||||
"@ionic-native/media-capture": "^4.5.2",
|
||||
"@ionic-native/network": "^4.3.2",
|
||||
"@ionic-native/push": "^4.5.3",
|
||||
"@ionic-native/splash-screen": "4.3.0",
|
||||
"@ionic-native/sqlite": "^4.3.2",
|
||||
"@ionic-native/status-bar": "4.3.0",
|
||||
|
@ -76,7 +83,8 @@
|
|||
"gulp-rename": "^1.2.2",
|
||||
"gulp-slash": "^1.1.3",
|
||||
"through": "^2.3.8",
|
||||
"typescript": "2.4.2"
|
||||
"typescript": "2.4.2",
|
||||
"webpack-merge": "^4.1.2"
|
||||
},
|
||||
"browser": {
|
||||
"electron": false
|
||||
|
|
|
@ -16,10 +16,10 @@ import { NgModule } from '@angular/core';
|
|||
import { AddonCalendarProvider } from './providers/calendar';
|
||||
import { AddonCalendarHelperProvider } from './providers/helper';
|
||||
import { AddonCalendarMainMenuHandler } from './providers/mainmenu-handler';
|
||||
import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate';
|
||||
import { CoreInitDelegate } from '../../providers/init';
|
||||
import { CoreLocalNotificationsProvider } from '../../providers/local-notifications';
|
||||
import { CoreLoginHelperProvider } from '../../core/login/providers/helper';
|
||||
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
|
||||
import { CoreInitDelegate } from '@providers/init';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreComponentsModule } from '../../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../../directives/directives.module';
|
||||
import { CorePipesModule } from '../../../../pipes/pipes.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
import { CorePipesModule } from '@pipes';
|
||||
import { AddonCalendarEventPage } from './event';
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -17,11 +17,11 @@ import { IonicPage, Content, NavParams } from 'ionic-angular';
|
|||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||
import { CoreCoursesProvider } from '../../../../core/courses/providers/courses';
|
||||
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||
import { CoreLocalNotificationsProvider } from '../../../../providers/local-notifications';
|
||||
import { CoreCourseProvider } from '../../../../core/course/providers/course';
|
||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import * as moment from 'moment';
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreComponentsModule } from '../../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../../directives/directives.module';
|
||||
import { CorePipesModule } from '../../../../pipes/pipes.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
import { CorePipesModule } from '@pipes';
|
||||
import { AddonCalendarListPage } from './list';
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -17,15 +17,15 @@ import { IonicPage, Content, PopoverController, NavParams, NavController } from
|
|||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||
import { CoreCoursesProvider } from '../../../../core/courses/providers/courses';
|
||||
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||
import { CoreLocalNotificationsProvider } from '../../../../providers/local-notifications';
|
||||
import { CoreCoursePickerMenuPopoverComponent } from '../../../../components/course-picker-menu/course-picker-menu-popover';
|
||||
import { CoreEventsProvider } from '../../../../providers/events';
|
||||
import { CoreAppProvider } from '../../../../providers/app';
|
||||
import { CoreSplitViewComponent } from '../../../../components/split-view/split-view';
|
||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
|
||||
/**
|
||||
* Page that displays the list of calendar events.
|
||||
|
|
|
@ -16,7 +16,7 @@ import { NgModule } from '@angular/core';
|
|||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonCalendarSettingsPage } from './settings';
|
||||
import { CorePipesModule } from '../../../../pipes/pipes.module';
|
||||
import { CorePipesModule } from '@pipes';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { IonicPage } from 'ionic-angular';
|
||||
import { AddonCalendarProvider } from '../../providers/calendar';
|
||||
import { CoreEventsProvider } from '../../../../providers/events';
|
||||
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
|
||||
/**
|
||||
* Page that displays the calendar settings.
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '../../../providers/logger';
|
||||
import { CoreSitesProvider } from '../../../providers/sites';
|
||||
import { CoreSite } from '../../../classes/site';
|
||||
import { CoreCoursesProvider } from '../../../core/courses/providers/courses';
|
||||
import { CoreTimeUtilsProvider } from '../../../providers/utils/time';
|
||||
import { CoreGroupsProvider } from '../../../providers/groups';
|
||||
import { CoreConstants } from '../../../core/constants';
|
||||
import { CoreLocalNotificationsProvider } from '../../../providers/local-notifications';
|
||||
import { CoreConfigProvider } from '../../../providers/config';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreGroupsProvider } from '@providers/groups';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { CoreConfigProvider } from '@providers/config';
|
||||
|
||||
/**
|
||||
* Service to handle calendar events.
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '../../../providers/logger';
|
||||
import { CoreCourseProvider } from '../../../core/course/providers/course';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding lists of courses and categories.
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AddonCalendarProvider } from './calendar';
|
||||
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/mainmenu/providers/delegate';
|
||||
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate';
|
||||
|
||||
/**
|
||||
* Handler to inject an option into main menu.
|
||||
|
@ -22,7 +22,7 @@ import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/main
|
|||
@Injectable()
|
||||
export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler {
|
||||
name = 'AddonCalendar';
|
||||
priority = 400;
|
||||
priority = 900;
|
||||
|
||||
constructor(private calendarProvider: AddonCalendarProvider) { }
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { NgModule } from '@angular/core';
|
|||
import { AddonFilesProvider } from './providers/files';
|
||||
import { AddonFilesHelperProvider } from './providers/helper';
|
||||
import { AddonFilesMainMenuHandler } from './providers/mainmenu-handler';
|
||||
import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate';
|
||||
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreComponentsModule } from '../../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../../directives/directives.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
import { AddonFilesListPage } from './list';
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
import { Component, ViewChild, OnDestroy } from '@angular/core';
|
||||
import { IonicPage, NavParams, NavController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '../../../../providers/app';
|
||||
import { CoreEventsProvider } from '../../../../providers/events';
|
||||
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { AddonFilesProvider } from '../../providers/files';
|
||||
import { AddonFilesHelperProvider } from '../../providers/helper';
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreSitesProvider } from '../../../providers/sites';
|
||||
import { CoreMimetypeUtilsProvider } from '../../../providers/utils/mimetype';
|
||||
import { CoreSite } from '../../../classes/site';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { Md5 } from 'ts-md5/dist/md5';
|
||||
|
||||
/**
|
||||
|
@ -35,7 +35,7 @@ export class AddonFilesProvider {
|
|||
* @return {boolean} Whether the WS is available, false otherwise.
|
||||
*/
|
||||
canGetPrivateFilesInfo(): boolean {
|
||||
return this.sitesProvider.getCurrentSite().wsAvailable('core_user_get_private_files_info');
|
||||
return this.sitesProvider.wsAvailableInCurrentSite('core_user_get_private_files_info');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreSitesProvider } from '../../../providers/sites';
|
||||
import { CoreDomUtilsProvider } from '../../../providers/utils/dom';
|
||||
import { CoreFileUploaderHelperProvider } from '../../../core/fileuploader/providers/helper';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreFileUploaderHelperProvider } from '@core/fileuploader/providers/helper';
|
||||
import { AddonFilesProvider } from './files';
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { AddonFilesProvider } from './files';
|
||||
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/mainmenu/providers/delegate';
|
||||
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate';
|
||||
|
||||
/**
|
||||
* Handler to inject an option into main menu.
|
||||
|
@ -22,7 +22,7 @@ import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/main
|
|||
@Injectable()
|
||||
export class AddonFilesMainMenuHandler implements CoreMainMenuHandler {
|
||||
name = 'AddonFiles';
|
||||
priority = 200;
|
||||
priority = 400;
|
||||
|
||||
constructor(private filesProvider: AddonFilesProvider) { }
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
import { CorePipesModule } from '@pipes';
|
||||
import { AddonMessagesDiscussionsComponent } from '../components/discussions/discussions';
|
||||
import { AddonMessagesContactsComponent } from '../components/contacts/contacts';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonMessagesDiscussionsComponent,
|
||||
AddonMessagesContactsComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
exports: [
|
||||
AddonMessagesDiscussionsComponent,
|
||||
AddonMessagesContactsComponent
|
||||
]
|
||||
})
|
||||
export class AddonMessagesComponentsModule {}
|
|
@ -0,0 +1,31 @@
|
|||
<ion-content>
|
||||
<ion-refresher [enabled]="loaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-search-box (onSubmit)="search($event)" (onClear])="clearSearch($event)" [placeholder]=" 'addon.messages.contactname' | translate" autocorrect="off" spellcheck="false" lengthCheck="2" [disabled]="!loaded"></core-search-box>
|
||||
|
||||
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
|
||||
<core-empty-box *ngIf="!hasContacts && searchString == ''" icon="person" [message]="'addon.messages.contactlistempty' | translate"></core-empty-box>
|
||||
|
||||
<core-empty-box *ngIf="!hasContacts && searchString != ''" icon="person" [message]="'addon.messages.nousersfound' | translate"></core-empty-box>
|
||||
|
||||
<ion-list *ngFor="let contactType of contactTypes" no-margin>
|
||||
<ng-container *ngIf="contacts[contactType] && (contacts[contactType].length > 0 || contactType === searchType)">
|
||||
<ion-item-divider color="light">
|
||||
<h2>{{ 'addon.messages.type_' + contactType | translate }}</h2>
|
||||
<ion-note item-end>{{ contacts[contactType].length }}</ion-note>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngFor="let contact of contacts[contactType]">
|
||||
<!-- Don't show deleted users -->
|
||||
<ion-item text-wrap *ngIf="contact.profileimageurl || contact.profileimageurlsmall" [title]="contact.fullname" (click)="gotoDiscussion(contact.id)" [class.core-split-item-selected]="contact.id == discussionUserId" detail-none>
|
||||
<ion-avatar item-start>
|
||||
<img src="{{contact.profileimageurl || contact.profileimageurlsmall}}" [alt]="'core.pictureof' | translate:{$a: contact.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<h2><core-format-text [text]="contact.fullname"></core-format-text></h2>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,12 @@
|
|||
addon-messages-discussions {
|
||||
h2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.note {
|
||||
margin: 0;
|
||||
align-self: flex-end;
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonMessagesProvider } from '../../providers/messages';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
|
||||
/**
|
||||
* Component that displays the list of contacts.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-messages-contacts',
|
||||
templateUrl: 'contacts.html',
|
||||
})
|
||||
export class AddonMessagesContactsComponent {
|
||||
|
||||
protected currentUserId: number;
|
||||
protected searchingMessages: string;
|
||||
protected loadingMessages: string;
|
||||
protected siteId: string;
|
||||
protected noSearchTypes = ['online', 'offline', 'blocked', 'strangers'];
|
||||
|
||||
loaded = false;
|
||||
discussionUserId: number;
|
||||
contactTypes = this.noSearchTypes;
|
||||
searchType = 'search';
|
||||
loadingMessage = '';
|
||||
hasContacts = false;
|
||||
contacts = {
|
||||
search: []
|
||||
};
|
||||
searchString = '';
|
||||
|
||||
constructor(sitesProvider: CoreSitesProvider, translate: TranslateService, private appProvider: CoreAppProvider,
|
||||
private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
|
||||
private eventsProvider: CoreEventsProvider) {
|
||||
|
||||
this.currentUserId = sitesProvider.getCurrentSiteUserId();
|
||||
this.siteId = sitesProvider.getCurrentSiteId();
|
||||
this.searchingMessages = translate.instant('core.searching');
|
||||
this.loadingMessages = translate.instant('core.loading');
|
||||
this.loadingMessage = this.loadingMessages;
|
||||
|
||||
this.discussionUserId = navParams.get('discussionUserId') || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (this.discussionUserId) {
|
||||
// There is a discussion to load, open the discussion in a new state.
|
||||
this.gotoDiscussion(this.discussionUserId);
|
||||
}
|
||||
|
||||
this.fetchData().then(() => {
|
||||
if (!this.discussionUserId && this.hasContacts) {
|
||||
let contact;
|
||||
for (const x in this.contacts) {
|
||||
if (this.contacts[x].length > 0) {
|
||||
contact = this.contacts[x][0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (contact) {
|
||||
// Take first and load it.
|
||||
this.gotoDiscussion(contact.id, true);
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the data.
|
||||
*
|
||||
* @param {any} [refresher] Refresher.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
refreshData(refresher?: any): Promise<any> {
|
||||
let promise;
|
||||
|
||||
if (this.searchString) {
|
||||
// User has searched, update the search.
|
||||
promise = this.performSearch(this.searchString);
|
||||
} else {
|
||||
// Update contacts.
|
||||
promise = this.messagesProvider.invalidateAllContactsCache(this.currentUserId).then(() => {
|
||||
return this.fetchData();
|
||||
});
|
||||
}
|
||||
|
||||
return promise.finally(() => {
|
||||
refresher.complete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch contacts.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected fetchData(): Promise<any> {
|
||||
this.loadingMessage = this.loadingMessages;
|
||||
|
||||
return this.messagesProvider.getAllContacts().then((contacts) => {
|
||||
for (const x in contacts) {
|
||||
if (contacts[x].length > 0) {
|
||||
this.contacts[x] = this.sortUsers(contacts[x]);
|
||||
} else {
|
||||
this.contacts[x] = [];
|
||||
}
|
||||
}
|
||||
|
||||
this.clearSearch();
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort user list by fullname
|
||||
* @param {any[]} list List to sort.
|
||||
* @return {any[]} Sorted list.
|
||||
*/
|
||||
protected sortUsers(list: any[]): any[] {
|
||||
return list.sort((a, b) => {
|
||||
const compareA = a.fullname.toLowerCase(),
|
||||
compareB = b.fullname.toLowerCase();
|
||||
|
||||
return compareA.localeCompare(compareB);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear search and show all contacts again.
|
||||
*/
|
||||
clearSearch(): void {
|
||||
this.searchString = ''; // Reset searched string.
|
||||
this.contactTypes = this.noSearchTypes;
|
||||
|
||||
this.hasContacts = false;
|
||||
for (const x in this.contacts) {
|
||||
if (this.contacts[x].length > 0) {
|
||||
this.hasContacts = true;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search users from the UI.
|
||||
*
|
||||
* @param {string} query Text to search for.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
search(query: string): Promise<any> {
|
||||
this.appProvider.closeKeyboard();
|
||||
|
||||
this.loaded = false;
|
||||
this.loadingMessage = this.searchingMessages;
|
||||
|
||||
return this.performSearch(query).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the search of users.
|
||||
*
|
||||
* @param {string} query Text to search for.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
protected performSearch(query: string): Promise<any> {
|
||||
return this.messagesProvider.searchContacts(query).then((result) => {
|
||||
this.hasContacts = result.length > 0;
|
||||
this.searchString = query;
|
||||
this.contactTypes = ['search'];
|
||||
|
||||
this.contacts['search'] = this.sortUsers(result);
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingcontacts', true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a particular discussion.
|
||||
*
|
||||
* @param {number} discussionUserId Discussion Id to load.
|
||||
* @param {boolean} [onlyWithSplitView=false] Only go to Discussion if split view is on.
|
||||
*/
|
||||
gotoDiscussion(discussionUserId: number, onlyWithSplitView: boolean = false): void {
|
||||
this.discussionUserId = discussionUserId;
|
||||
|
||||
const params = {
|
||||
discussion: discussionUserId,
|
||||
onlyWithSplitView: onlyWithSplitView
|
||||
};
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.SPLIT_VIEW_LOAD_EVENT, params, this.siteId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<ion-content>
|
||||
<ion-refresher [enabled]="loaded" (ionRefresh)="refreshData($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<core-search-box *ngIf="search.enabled" (onSubmit)="searchMessage($event)" (onClear)="clearSearch($event)" [placeholder]=" 'addon.messages.message' | translate" autocorrect="off" spellcheck="false" lengthCheck="2" [disabled]="!loaded"></core-search-box>
|
||||
|
||||
<core-loading [hideUntil]="loaded" [message]="loadingMessage">
|
||||
|
||||
<core-empty-box *ngIf="(!discussions || discussions.length <= 0) && !search.showResults" icon="chatbubbles" [message]="'addon.messages.nomessages' | translate"></core-empty-box>
|
||||
|
||||
<core-empty-box *ngIf="(!search.results || search.results.length <= 0) && search.showResults" icon="search" [message]="'core.noresults' | translate"></core-empty-box>
|
||||
|
||||
<ion-list *ngIf="search.showResults" no-margin>
|
||||
<ion-item-divider color="light">
|
||||
<h2>{{ 'core.searchresults' | translate }}</h2>
|
||||
<ion-note item-end>{{ search.results.length }}</ion-note>
|
||||
</ion-item-divider>
|
||||
<ion-item text-wrap *ngFor="let result of search.results" [title]="result.fullname" (click)="gotoDiscussion(result.userid, result.messageid)" [class.core-split-item-selected]="result.userid == discussionUserId" detail-none>
|
||||
<ion-avatar item-start>
|
||||
<img src="{{result.profileimageurl}}" [alt]="'core.pictureof' | translate:{$a: result.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<h2><core-format-text [text]="result.fullname"></core-format-text></h2>
|
||||
<p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage"></core-format-text></p>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="!search.showResults" no-margin>
|
||||
<ion-item text-wrap *ngFor="let discussion of discussions" [title]="discussion.fullname" (click)="gotoDiscussion(discussion.message.user)" [class.core-split-item-selected]="discussion.message.user == discussionUserId" detail-none>
|
||||
<ion-avatar item-start>
|
||||
<img src="{{discussion.profileimageurl}}" [alt]="'core.pictureof' | translate:{$a: discussion.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<h2>
|
||||
<core-format-text [text]="discussion.fullname"></core-format-text>
|
||||
<ion-note *ngIf="discussion.message.timecreated > 0 || discussion.unread">
|
||||
<div *ngIf="discussion.message.timecreated > 0">{{discussion.message.timecreated / 1000 | coreDateDayOrTime}}</div>
|
||||
<div text-right *ngIf="discussion.unread" class="core-primary-circle"></div>
|
||||
</ion-note>
|
||||
</h2>
|
||||
<p><core-format-text clean="true" singleLine="true" [text]="discussion.message.message"></core-format-text></p>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,12 @@
|
|||
addon-messages-discussions {
|
||||
h2 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.note {
|
||||
margin: 0;
|
||||
align-self: flex-end;
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { Platform, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonMessagesProvider } from '../../providers/messages';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
|
||||
|
||||
/**
|
||||
* Component that displays the list of discussions.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'addon-messages-discussions',
|
||||
templateUrl: 'discussions.html',
|
||||
})
|
||||
export class AddonMessagesDiscussionsComponent implements OnDestroy {
|
||||
protected newMessagesObserver: any;
|
||||
protected readChangedObserver: any;
|
||||
protected cronObserver: any;
|
||||
protected appResumeSubscription: any;
|
||||
protected loadingMessages: string;
|
||||
protected siteId: string;
|
||||
|
||||
loaded = false;
|
||||
loadingMessage: string;
|
||||
discussions: any;
|
||||
discussionUserId: number;
|
||||
pushObserver: any;
|
||||
search = {
|
||||
enabled: false,
|
||||
showResults: false,
|
||||
results: [],
|
||||
loading: '',
|
||||
text: ''
|
||||
};
|
||||
|
||||
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService,
|
||||
private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
|
||||
private appProvider: CoreAppProvider, platform: Platform, utils: CoreUtilsProvider,
|
||||
pushNotificationsDelegate: AddonPushNotificationsDelegate) {
|
||||
|
||||
this.search.loading = translate.instant('core.searching');
|
||||
this.loadingMessages = translate.instant('core.loading');
|
||||
this.siteId = sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Update discussions when new message is received.
|
||||
this.newMessagesObserver = eventsProvider.on(AddonMessagesProvider.NEW_MESSAGE_EVENT, (data) => {
|
||||
if (data.userId) {
|
||||
const discussion = this.discussions.find((disc) => {
|
||||
return disc.message.user == data.userId;
|
||||
});
|
||||
|
||||
if (typeof discussion == 'undefined') {
|
||||
this.loaded = false;
|
||||
this.refreshData().finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
} else {
|
||||
// An existing discussion has a new message, update the last message.
|
||||
discussion.message.message = data.message;
|
||||
discussion.message.timecreated = data.timecreated;
|
||||
}
|
||||
}
|
||||
}, this.siteId);
|
||||
|
||||
// Update discussions when a message is read.
|
||||
this.readChangedObserver = eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => {
|
||||
if (data.userId) {
|
||||
const discussion = this.discussions.find((disc) => {
|
||||
return disc.message.user == data.userId;
|
||||
});
|
||||
|
||||
if (typeof discussion != 'undefined') {
|
||||
// A discussion has been read reset counter.
|
||||
discussion.unread = false;
|
||||
|
||||
// Discussions changed, invalidate them.
|
||||
this.messagesProvider.invalidateDiscussionsCache();
|
||||
}
|
||||
}
|
||||
}, this.siteId);
|
||||
|
||||
// Update discussions when cron read is executed.
|
||||
this.cronObserver = eventsProvider.on(AddonMessagesProvider.READ_CRON_EVENT, (data) => {
|
||||
this.refreshData();
|
||||
}, this.siteId);
|
||||
|
||||
// Refresh the view when the app is resumed.
|
||||
this.appResumeSubscription = platform.resume.subscribe(() => {
|
||||
if (!this.loaded) {
|
||||
return;
|
||||
}
|
||||
this.loaded = false;
|
||||
this.refreshData();
|
||||
});
|
||||
|
||||
this.discussionUserId = navParams.get('discussionUserId') || false;
|
||||
|
||||
// If a message push notification is received, refresh the view.
|
||||
this.pushObserver = pushNotificationsDelegate.on('receive').subscribe((notification) => {
|
||||
// New message received. If it's from current site, refresh the data.
|
||||
if (utils.isFalseOrZero(notification.notif) && notification.site == this.siteId) {
|
||||
this.refreshData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
if (this.discussionUserId) {
|
||||
// There is a discussion to load, open the discussion in a new state.
|
||||
this.gotoDiscussion(this.discussionUserId);
|
||||
}
|
||||
|
||||
this.fetchData().then(() => {
|
||||
if (!this.discussionUserId && this.discussions.length > 0) {
|
||||
// Take first and load it.
|
||||
this.gotoDiscussion(this.discussions[0].message.user, undefined, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the data.
|
||||
*
|
||||
* @param {any} [refresher] Refresher.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
refreshData(refresher?: any): Promise<any> {
|
||||
return this.messagesProvider.invalidateDiscussionsCache().then(() => {
|
||||
return this.fetchData().finally(() => {
|
||||
if (refresher) {
|
||||
// Actions to take if refresh comes from the user.
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, undefined, this.siteId);
|
||||
refresher.complete();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch discussions.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected fetchData(): Promise<any> {
|
||||
this.loadingMessage = this.loadingMessages;
|
||||
this.search.enabled = this.messagesProvider.isSearchMessagesEnabled();
|
||||
|
||||
return this.messagesProvider.getDiscussions().then((discussions) => {
|
||||
// Convert to an array for sorting.
|
||||
const discussionsSorted = [];
|
||||
for (const userId in discussions) {
|
||||
discussions[userId].unread = !!discussions[userId].unread;
|
||||
discussionsSorted.push(discussions[userId]);
|
||||
}
|
||||
|
||||
this.discussions = discussionsSorted.sort((a, b) => {
|
||||
return b.message.timecreated - a.message.timecreated;
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true);
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear search and show discussions again.
|
||||
*/
|
||||
clearSearch(): void {
|
||||
this.loaded = false;
|
||||
this.search.showResults = false;
|
||||
this.search.text = ''; // Reset searched string.
|
||||
this.fetchData().finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search messages cotaining text.
|
||||
*
|
||||
* @param {string} query Text to search for.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
searchMessage(query: string): Promise<any> {
|
||||
this.appProvider.closeKeyboard();
|
||||
this.loaded = false;
|
||||
this.loadingMessage = this.search.loading;
|
||||
|
||||
return this.messagesProvider.searchMessages(query).then((searchResults) => {
|
||||
this.search.showResults = true;
|
||||
this.search.results = searchResults;
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a particular discussion.
|
||||
*
|
||||
* @param {number} discussionUserId Discussion Id to load.
|
||||
* @param {number} [messageId] Message to scroll after loading the discussion. Used when searching.
|
||||
* @param {boolean} [onlyWithSplitView=false] Only go to Discussion if split view is on.
|
||||
*/
|
||||
gotoDiscussion(discussionUserId: number, messageId?: number, onlyWithSplitView: boolean = false): void {
|
||||
this.discussionUserId = discussionUserId;
|
||||
|
||||
const params = {
|
||||
discussion: discussionUserId,
|
||||
onlyWithSplitView: onlyWithSplitView
|
||||
};
|
||||
if (messageId) {
|
||||
params['message'] = messageId;
|
||||
}
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.SPLIT_VIEW_LOAD_EVENT, params, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.newMessagesObserver && this.newMessagesObserver.off();
|
||||
this.readChangedObserver && this.readChangedObserver.off();
|
||||
this.cronObserver && this.cronObserver.off();
|
||||
this.appResumeSubscription && this.appResumeSubscription.unsubscribe();
|
||||
this.pushObserver && this.pushObserver.unsubscribe();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"addcontact": "Add contact",
|
||||
"blockcontact": "Block contact",
|
||||
"blockcontactconfirm": "You will no longer receive messages from this contact.",
|
||||
"blocknoncontacts": "Prevent non-contacts from messaging me",
|
||||
"contactlistempty": "The contact list is empty",
|
||||
"contactname": "Contact name",
|
||||
"contacts": "Contacts",
|
||||
"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.",
|
||||
"errordeletemessage": "Error while deleting the message.",
|
||||
"errorwhileretrievingcontacts": "Error while retrieving contacts from the server.",
|
||||
"errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.",
|
||||
"errorwhileretrievingmessages": "Error while retrieving messages from the server.",
|
||||
"messagenotsent": "The message was not sent. Please try again later.",
|
||||
"message": "Message",
|
||||
"messagepreferences": "Message preferences",
|
||||
"messages": "Messages",
|
||||
"newmessage": "New message",
|
||||
"newmessages": "New messages",
|
||||
"nomessages": "No messages",
|
||||
"nousersfound": "No users found",
|
||||
"removecontact": "Remove contact",
|
||||
"removecontactconfirm": "Contact will be removed from your contacts list.",
|
||||
"type_blocked": "Blocked",
|
||||
"type_offline": "Offline",
|
||||
"type_online": "Online",
|
||||
"type_search": "Search results",
|
||||
"type_strangers": "Others",
|
||||
"unblockcontact": "Unblock contact",
|
||||
"warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}"
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Network } from '@ionic-native/network';
|
||||
import { AddonMessagesProvider } from './providers/messages';
|
||||
import { AddonMessagesOfflineProvider } from './providers/messages-offline';
|
||||
import { AddonMessagesSyncProvider } from './providers/sync';
|
||||
import { AddonMessagesMainMenuHandler } from './providers/mainmenu-handler';
|
||||
import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate';
|
||||
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
|
||||
import { CoreUserDelegate } from '@core/user/providers/user-delegate';
|
||||
import { CoreCronDelegate } from '@providers/cron';
|
||||
import { AddonMessagesSendMessageUserHandler } from './providers/user-send-message-handler';
|
||||
import { AddonMessagesAddContactUserHandler } from './providers/user-add-contact-handler';
|
||||
import { AddonMessagesBlockContactUserHandler } from './providers/user-block-contact-handler';
|
||||
import { AddonMessagesDiscussionLinkHandler } from './providers/discussion-link-handler';
|
||||
import { AddonMessagesIndexLinkHandler } from './providers/index-link-handler';
|
||||
import { AddonMessagesSyncCronHandler } from './providers/sync-cron-handler';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { CoreSettingsDelegate } from '@core/settings/providers/delegate';
|
||||
import { AddonMessagesSettingsHandler } from './providers/settings-handler';
|
||||
import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
imports: [
|
||||
],
|
||||
providers: [
|
||||
AddonMessagesProvider,
|
||||
AddonMessagesOfflineProvider,
|
||||
AddonMessagesSyncProvider,
|
||||
AddonMessagesMainMenuHandler,
|
||||
AddonMessagesSendMessageUserHandler,
|
||||
AddonMessagesAddContactUserHandler,
|
||||
AddonMessagesBlockContactUserHandler,
|
||||
AddonMessagesDiscussionLinkHandler,
|
||||
AddonMessagesIndexLinkHandler,
|
||||
AddonMessagesSyncCronHandler,
|
||||
AddonMessagesSettingsHandler
|
||||
]
|
||||
})
|
||||
export class AddonMessagesModule {
|
||||
constructor(mainMenuDelegate: CoreMainMenuDelegate, mainmenuHandler: AddonMessagesMainMenuHandler,
|
||||
contentLinksDelegate: CoreContentLinksDelegate, indexLinkHandler: AddonMessagesIndexLinkHandler,
|
||||
discussionLinkHandler: AddonMessagesDiscussionLinkHandler, sendMessageHandler: AddonMessagesSendMessageUserHandler,
|
||||
userDelegate: CoreUserDelegate, cronDelegate: CoreCronDelegate, syncHandler: AddonMessagesSyncCronHandler,
|
||||
network: Network, messagesSync: AddonMessagesSyncProvider, appProvider: CoreAppProvider,
|
||||
localNotifications: CoreLocalNotificationsProvider, messagesProvider: AddonMessagesProvider,
|
||||
sitesProvider: CoreSitesProvider, linkHelper: CoreContentLinksHelperProvider,
|
||||
settingsHandler: AddonMessagesSettingsHandler, settingsDelegate: CoreSettingsDelegate,
|
||||
pushNotificationsDelegate: AddonPushNotificationsDelegate, utils: CoreUtilsProvider,
|
||||
addContactHandler: AddonMessagesAddContactUserHandler, blockContactHandler: AddonMessagesBlockContactUserHandler) {
|
||||
// Register handlers.
|
||||
mainMenuDelegate.registerHandler(mainmenuHandler);
|
||||
contentLinksDelegate.registerHandler(indexLinkHandler);
|
||||
contentLinksDelegate.registerHandler(discussionLinkHandler);
|
||||
userDelegate.registerHandler(sendMessageHandler);
|
||||
userDelegate.registerHandler(addContactHandler);
|
||||
userDelegate.registerHandler(blockContactHandler);
|
||||
cronDelegate.register(syncHandler);
|
||||
cronDelegate.register(mainmenuHandler);
|
||||
settingsDelegate.registerHandler(settingsHandler);
|
||||
|
||||
// Sync some discussions when device goes online.
|
||||
network.onConnect().subscribe(() => {
|
||||
messagesSync.syncAllDiscussions(undefined, true);
|
||||
});
|
||||
|
||||
const notificationClicked = (notification: any): void => {
|
||||
messagesProvider.isMessagingEnabledForSite(notification.site).then(() => {
|
||||
sitesProvider.isFeatureDisabled('$mmSideMenuDelegate_mmaMessages', notification.site).then((disabled) => {
|
||||
if (disabled) {
|
||||
// Messages are disabled, stop.
|
||||
return;
|
||||
}
|
||||
|
||||
messagesProvider.invalidateDiscussionsCache().finally(() => {
|
||||
linkHelper.goInSite(undefined, 'AddonMessagesIndexPage', undefined, notification.site);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (appProvider.isDesktop()) {
|
||||
// Listen for clicks in simulated push notifications.
|
||||
localNotifications.registerClick(AddonMessagesProvider.PUSH_SIMULATION_COMPONENT, notificationClicked);
|
||||
}
|
||||
|
||||
// Register push notification clicks.
|
||||
pushNotificationsDelegate.on('click').subscribe((notification) => {
|
||||
if (utils.isFalseOrZero(notification.notif)) {
|
||||
notificationClicked(notification);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
||||
<ion-buttons end></ion-buttons>
|
||||
</ion-navbar>
|
||||
<core-navbar-buttons end>
|
||||
<button ion-button icon-only clear="true" (click)="toggleDelete()" [hidden]="!canDelete">
|
||||
<ion-icon name="trash"></ion-icon>
|
||||
</button>
|
||||
<a [hidden]="!showProfileLink" core-user-link [userId]="userId" [attr.aria-label]=" 'core.user.viewprofile' | translate">
|
||||
<img class="button core-bar-button-image" [src]="profileLink" core-external-content onError="this.src='assets/img/user-avatar.png'">
|
||||
</a>
|
||||
</core-navbar-buttons>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<!-- Load previous messages. -->
|
||||
<ion-infinite-scroll [enabled]="canLoadMore" (ionInfinite)="loadPrevious($event)" position="top">
|
||||
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
<ion-list class="addon-messages-discussion-container" [attr.aria-live]="polite">
|
||||
<ng-container *ngFor="let message of messages; index as index; last as last">
|
||||
<ion-chip *ngIf="showDate(message, messages[index - 1])" class="addon-messages-date" color="light">
|
||||
<ion-label>{{ message.timecreated | coreFormatDate: "LL" }}</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip class="addon-messages-unreadfrom" *ngIf="message.unreadFrom" color="light">
|
||||
<ion-label>{{ 'addon.messages.newmessages' | translate:{$a: title} }}</ion-label>
|
||||
<ion-icon name="arrow-round-down"></ion-icon>
|
||||
</ion-chip>
|
||||
|
||||
<ion-item text-wrap (longPress)="copyMessage(message.smallmessage)" class="addon-message" [class.addon-message-mine]="message.useridfrom == currentUserId" [@coreSlideInOut]="message.useridfrom == currentUserId ? '' : 'fromLeft'">
|
||||
<!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. -->
|
||||
<p class="addon-message-text">
|
||||
<core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text"></core-format-text>
|
||||
</p>
|
||||
<ion-note *ngIf="!message.pending">
|
||||
{{ message.timecreated | coreFormatDate: "dftimedate" }}
|
||||
</ion-note>
|
||||
<ion-note *ngIf="message.pending"><ion-icon name="time"></ion-icon></ion-note>
|
||||
|
||||
<button ion-button icon-only clear="true" *ngIf="!message.sending && showDelete" (click)="deleteMessage(message, index)" class="addon-messages-delete-button" [@coreSlideInOut]="'fromRight'" [attr.aria-label]=" 'addon.messages.deletemessage' | translate">
|
||||
<ion-icon name="trash" color="danger"></ion-icon>
|
||||
</button>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
<core-empty-box *ngIf="!messages || messages.length <= 0" icon="chatbubbles" [message]="'addon.messages.nomessages' | translate"></core-empty-box>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
<ion-footer color="light" class="footer-adjustable">
|
||||
<ion-toolbar color="light" position="bottom">
|
||||
<core-send-message-form (onSubmit)="sendMessage($event)" [showKeyboard]="showKeyboard" [placeholder]="'addon.messages.newmessage' | translate" (onResize)="resizeContent()"></core-send-message-form>
|
||||
</ion-toolbar>
|
||||
</ion-footer>
|
|
@ -0,0 +1,35 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonMessagesDiscussionPage } from './discussion';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
import { CorePipesModule } from '@pipes';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonMessagesDiscussionPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
IonicPageModule.forChild(AddonMessagesDiscussionPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonMessagesDiscussionPageModule {}
|
|
@ -0,0 +1,109 @@
|
|||
// Messages.
|
||||
$item-message-bg: $gray-lighter !default;
|
||||
$item-message-note-text: $gray-dark !default;
|
||||
$item-message-note-font-size: 75% !default;
|
||||
$item-message-mine-bg: $blue-light !default;
|
||||
|
||||
|
||||
page-addon-messages-discussion {
|
||||
.addon-messages-discussion-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.addon-messages-date,
|
||||
.addon-messages-unreadfrom {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.addon-messages-unreadfrom {
|
||||
color: $blue;
|
||||
}
|
||||
|
||||
// Message item.
|
||||
.addon-message {
|
||||
max-width: 80%;
|
||||
border: 0;
|
||||
border-radius: 16px;
|
||||
padding: 10px;
|
||||
margin: 4px;
|
||||
background-color: $item-message-bg;
|
||||
align-self: flex-start;
|
||||
width: auto;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
@include core-transition(width);
|
||||
|
||||
&.activated {
|
||||
background-color: darken($item-message-bg, 10%);
|
||||
}
|
||||
|
||||
&.item-block .item-inner {
|
||||
border-bottom: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.addon-message-text {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.note {
|
||||
align-self: flex-end;
|
||||
color: $item-message-note-text;
|
||||
font-size: $item-message-note-font-size;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.addon-messages-delete-button {
|
||||
min-height: initial;
|
||||
line-height: initial;
|
||||
margin: 0 0 0 10px;
|
||||
height: auto;
|
||||
-webkit-align-self: flex-end;
|
||||
-ms-flex-item-align: end;
|
||||
align-self: flex-end;
|
||||
vertical-align: middle;
|
||||
|
||||
.icon {
|
||||
font-size: 1.4em;
|
||||
line-height: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Defines when an item-message is the user's.
|
||||
.addon-message-mine {
|
||||
background-color: $item-message-mine-bg;
|
||||
align-self: flex-end;
|
||||
max-width: 80%;
|
||||
|
||||
&.activated {
|
||||
background-color: darken($item-message-mine-bg, 10%);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
margin-right: -3px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: -2px;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.addon-message .item-content,
|
||||
.addon-message-mine .item-content{
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,689 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { IonicPage, NavParams, NavController, Content } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonMessagesProvider } from '../../providers/messages';
|
||||
import { AddonMessagesSyncProvider } from '../../providers/sync';
|
||||
import { CoreUserProvider } from '@core/user/providers/user';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { coreSlideInOut } from '@classes/animations';
|
||||
import { Md5 } from 'ts-md5/dist/md5';
|
||||
import * as moment from 'moment';
|
||||
|
||||
/**
|
||||
* Page that displays a message discussion page.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-messages-discussion' })
|
||||
@Component({
|
||||
selector: 'page-addon-messages-discussion',
|
||||
templateUrl: 'discussion.html',
|
||||
animations: [coreSlideInOut]
|
||||
})
|
||||
export class AddonMessagesDiscussionPage implements OnDestroy {
|
||||
@ViewChild(Content) content: Content;
|
||||
|
||||
protected siteId: string;
|
||||
protected fetching: boolean;
|
||||
protected polling;
|
||||
protected logger;
|
||||
|
||||
protected unreadMessageFrom = 0;
|
||||
protected messagesBeingSent = 0;
|
||||
protected pagesLoaded = 1;
|
||||
protected lastMessage = {text: '', timecreated: 0};
|
||||
protected keepMessageMap = {};
|
||||
protected syncObserver: any;
|
||||
protected oldContentHeight = 0;
|
||||
|
||||
userId: number;
|
||||
currentUserId: number;
|
||||
title: string;
|
||||
profileLink: string;
|
||||
showProfileLink: boolean;
|
||||
loaded = false;
|
||||
showKeyboard = false;
|
||||
canLoadMore = false;
|
||||
messages = [];
|
||||
showDelete = false;
|
||||
canDelete = false;
|
||||
scrollBottom = true;
|
||||
|
||||
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, navParams: NavParams,
|
||||
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private messagesProvider: AddonMessagesProvider, logger: CoreLoggerProvider,
|
||||
private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, private translate: TranslateService) {
|
||||
this.siteId = sitesProvider.getCurrentSiteId();
|
||||
this.currentUserId = sitesProvider.getCurrentSiteUserId();
|
||||
|
||||
this.logger = logger.getInstance('AddonMessagesDiscussionPage');
|
||||
|
||||
this.userId = navParams.get('userId');
|
||||
this.showKeyboard = navParams.get('showKeyboard');
|
||||
|
||||
// Refresh data if this discussion is synchronized automatically.
|
||||
this.syncObserver = eventsProvider.on(AddonMessagesSyncProvider.AUTO_SYNCED, (data) => {
|
||||
if (data.userId == this.userId) {
|
||||
// Fetch messages.
|
||||
this.fetchData();
|
||||
|
||||
// Show first warning if any.
|
||||
if (data.warnings && data.warnings[0]) {
|
||||
this.domUtils.showErrorModal(data.warnings[0]);
|
||||
}
|
||||
}
|
||||
}, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new message to the message list.
|
||||
*
|
||||
* @param {any} message Message to be added.
|
||||
* @param {boolean} [keep=true] If set the keep flag or not.
|
||||
*/
|
||||
protected addMessage(message: any, keep: boolean = true): void {
|
||||
// Use smallmessage instead of message ID because ID changes when a message is read.
|
||||
message.hash = Md5.hashStr(message.smallmessage) + '#' + message.timecreated + '#' + message.useridfrom;
|
||||
if (typeof this.keepMessageMap[message.hash] === 'undefined') {
|
||||
// Message not added to the list. Add it now.
|
||||
this.messages.push(message);
|
||||
}
|
||||
// Message needs to be kept in the list.
|
||||
this.keepMessageMap[message.hash] = keep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a message if it shouldn't be in the list anymore.
|
||||
*
|
||||
* @param {string} hash Hash of the message to be removed.
|
||||
*/
|
||||
protected removeMessage(hash: any): void {
|
||||
if (this.keepMessageMap[hash]) {
|
||||
// Selected to keep it, clear the flag.
|
||||
this.keepMessageMap[hash] = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.keepMessageMap[hash];
|
||||
|
||||
const position = this.messages.findIndex((message) => {
|
||||
return message.hash == hash;
|
||||
});
|
||||
if (position > 0) {
|
||||
this.messages.splice(position, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the page has loaded. This event only happens once per page being created.
|
||||
* If a page leaves but is cached, then this event will not fire again on a subsequent viewing.
|
||||
* Setup code for the page.
|
||||
*/
|
||||
ionViewDidLoad(): void {
|
||||
// Disable the profile button if we're already coming from a profile.
|
||||
const backViewPage = this.navCtrl.getPrevious() && this.navCtrl.getPrevious().component.name;
|
||||
this.showProfileLink = !backViewPage || backViewPage !== 'CoreUserProfilePage';
|
||||
|
||||
// Get the user profile to retrieve the user fullname and image.
|
||||
this.userProvider.getProfile(this.userId, undefined, true).then((user) => {
|
||||
if (!this.title) {
|
||||
this.title = user.fullname;
|
||||
}
|
||||
this.profileLink = user.profileimageurl;
|
||||
});
|
||||
|
||||
// Synchronize messages if needed.
|
||||
this.messagesSync.syncDiscussion(this.userId).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then((warnings) => {
|
||||
if (warnings && warnings[0]) {
|
||||
this.domUtils.showErrorModal(warnings[0]);
|
||||
}
|
||||
|
||||
// Fetch the messages for the first time.
|
||||
return this.fetchData().then(() => {
|
||||
if (!this.title && this.messages.length) {
|
||||
// Didn't receive the fullname via argument. Try to get it from messages.
|
||||
// It's possible that name cannot be resolved when no messages were yet exchanged.
|
||||
if (this.messages[0].useridto != this.currentUserId) {
|
||||
this.title = this.messages[0].usertofullname || '';
|
||||
} else {
|
||||
this.title = this.messages[0].userfromfullname || '';
|
||||
}
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
|
||||
}).finally(() => {
|
||||
this.checkCanDelete();
|
||||
this.resizeContent();
|
||||
this.loaded = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the page has fully entered and is now the active page.
|
||||
* This event will fire, whether it was the first load or a cached page.
|
||||
*/
|
||||
ionViewDidEnter(): void {
|
||||
this.setPolling();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the page is about to leave and no longer be the active page.
|
||||
*/
|
||||
ionViewWillLeave(): void {
|
||||
this.unsetPolling();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to fetch messages.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
protected fetchData(): Promise<any> {
|
||||
this.logger.debug(`Polling new messages for discussion with user '${this.userId}'`);
|
||||
if (this.messagesBeingSent > 0) {
|
||||
// We do not poll while a message is being sent or we could confuse the user.
|
||||
// Otherwise, his message would disappear from the list, and he'd have to wait for the interval to check for messages.
|
||||
return Promise.reject(null);
|
||||
} else if (this.fetching) {
|
||||
// Already fetching.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
this.fetching = true;
|
||||
|
||||
// Wait for synchronization process to finish.
|
||||
return this.messagesSync.waitForSync(this.userId).then(() => {
|
||||
// Fetch messages. Invalidate the cache before fetching.
|
||||
return this.messagesProvider.invalidateDiscussionCache(this.userId).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
}).then(() => {
|
||||
return this.getDiscussion(this.pagesLoaded);
|
||||
}).then((messages) => {
|
||||
// Check if we are at the bottom to scroll it after render.
|
||||
this.scrollBottom = this.content.scrollHeight - this.content.scrollTop === this.content.contentHeight;
|
||||
|
||||
if (this.messagesBeingSent > 0) {
|
||||
// Ignore polling due to a race condition.
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
// Add new messages to the list and mark the messages that should still be displayed.
|
||||
messages.forEach((message) => {
|
||||
this.addMessage(message);
|
||||
});
|
||||
|
||||
// Remove messages that shouldn't be in the list anymore.
|
||||
for (const hash in this.keepMessageMap) {
|
||||
this.removeMessage(hash);
|
||||
}
|
||||
|
||||
// Sort the messages.
|
||||
this.messagesProvider.sortMessages(this.messages);
|
||||
|
||||
// Notify that there can be a new message.
|
||||
this.notifyNewMessage();
|
||||
|
||||
// Mark retrieved messages as read if they are not.
|
||||
this.markMessagesAsRead();
|
||||
}).finally(() => {
|
||||
this.fetching = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a discussion. Can load several "pages".
|
||||
*
|
||||
* @param {number} pagesToLoad Number of pages to load.
|
||||
* @param {number} [lfReceivedUnread=0] Number of unread received messages already fetched, so fetch will be done from this.
|
||||
* @param {number} [lfReceivedRead=0] Number of read received messages already fetched, so fetch will be done from this.
|
||||
* @param {number} [lfSentUnread=0] Number of unread sent messages already fetched, so fetch will be done from this.
|
||||
* @param {number} [lfSentRead=0] Number of read sent messages already fetched, so fetch will be done from this.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
protected getDiscussion(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, lfSentUnread: number = 0,
|
||||
lfSentRead: number = 0): Promise<any> {
|
||||
|
||||
// Only get offline messages if we're loading the first "page".
|
||||
const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0;
|
||||
|
||||
// Get next messages.
|
||||
return this.messagesProvider.getDiscussion(this.userId, excludePending, lfReceivedUnread, lfReceivedRead, lfSentUnread,
|
||||
lfSentRead).then((result) => {
|
||||
|
||||
pagesToLoad--;
|
||||
if (pagesToLoad > 0 && result.canLoadMore) {
|
||||
// More pages to load. Calculate new limit froms.
|
||||
result.messages.forEach((message) => {
|
||||
if (!message.pending) {
|
||||
if (message.useridfrom == this.userId) {
|
||||
if (message.read) {
|
||||
lfReceivedRead++;
|
||||
} else {
|
||||
lfReceivedUnread++;
|
||||
}
|
||||
} else {
|
||||
if (message.read) {
|
||||
lfSentRead++;
|
||||
} else {
|
||||
lfSentUnread++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Get next messages.
|
||||
return this.getDiscussion(pagesToLoad, lfReceivedUnread, lfReceivedRead, lfSentUnread, lfSentRead)
|
||||
.then((nextMessages) => {
|
||||
return result.messages.concat(nextMessages);
|
||||
});
|
||||
} else {
|
||||
// No more messages to load, return them.
|
||||
this.canLoadMore = result.canLoadMore;
|
||||
|
||||
return result.messages;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark messages as read.
|
||||
*/
|
||||
protected markMessagesAsRead(): void {
|
||||
let readChanged = false;
|
||||
const promises = [];
|
||||
|
||||
if (this.messagesProvider.isMarkAllMessagesReadEnabled()) {
|
||||
let messageUnreadFound = false;
|
||||
// Mark all messages at a time if one messages is unread.
|
||||
for (const x in this.messages) {
|
||||
const message = this.messages[x];
|
||||
// If an unread message is found, mark all messages as read.
|
||||
if (message.useridfrom != this.currentUserId && message.read == 0) {
|
||||
messageUnreadFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (messageUnreadFound) {
|
||||
this.setUnreadLabelPosition();
|
||||
promises.push(this.messagesProvider.markAllMessagesRead(this.userId).then(() => {
|
||||
readChanged = true;
|
||||
// Mark all messages as read.
|
||||
this.messages.forEach((message) => {
|
||||
message.read = 1;
|
||||
});
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
this.setUnreadLabelPosition();
|
||||
// Mark each message as read one by one.
|
||||
this.messages.forEach((message) => {
|
||||
// If the message is unread, call this.messagesProvider.markMessageRead.
|
||||
if (message.useridfrom != this.currentUserId && message.read == 0) {
|
||||
promises.push(this.messagesProvider.markMessageRead(message.id).then(() => {
|
||||
readChanged = true;
|
||||
message.read = 1;
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
if (readChanged) {
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.READ_CHANGED_EVENT, {
|
||||
userId: this.userId
|
||||
}, this.siteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the last message found so discussions list controller can tell if last message should be updated.
|
||||
*/
|
||||
protected notifyNewMessage(): void {
|
||||
const last = this.messages[this.messages.length - 1];
|
||||
|
||||
let trigger = false;
|
||||
if (!last) {
|
||||
this.lastMessage = {text: '', timecreated: 0};
|
||||
trigger = true;
|
||||
} else if (last.text !== this.lastMessage.text || last.timecreated !== this.lastMessage.timecreated) {
|
||||
this.lastMessage = {text: last.text, timecreated: last.timecreated};
|
||||
trigger = true;
|
||||
}
|
||||
|
||||
if (trigger) {
|
||||
// Update discussions last message.
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.NEW_MESSAGE_EVENT, {
|
||||
userId: this.userId,
|
||||
message: this.lastMessage.text,
|
||||
timecreated: this.lastMessage.timecreated
|
||||
}, this.siteId);
|
||||
|
||||
// Update navBar links and buttons.
|
||||
const newCanDelete = (last && last.id && this.messages.length == 1) || this.messages.length > 1;
|
||||
if (this.canDelete != newCanDelete) {
|
||||
this.checkCanDelete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the place where the unread label position has to be.
|
||||
*/
|
||||
protected setUnreadLabelPosition(): void {
|
||||
if (this.unreadMessageFrom != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let previousMessageRead = false;
|
||||
|
||||
for (const x in this.messages) {
|
||||
const message = this.messages[x];
|
||||
if (message.useridfrom != this.currentUserId) {
|
||||
// Place unread from message label only once.
|
||||
message.unreadFrom = message.read == 0 && previousMessageRead;
|
||||
|
||||
if (message.unreadFrom) {
|
||||
// Save where the label is placed.
|
||||
this.unreadMessageFrom = parseInt(message.id, 10);
|
||||
break;
|
||||
}
|
||||
|
||||
previousMessageRead = message.read != 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Do not update the message unread from label on next refresh.
|
||||
if (this.unreadMessageFrom == 0) {
|
||||
// Using negative to indicate the label is not placed but should not be placed.
|
||||
this.unreadMessageFrom = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's any message in the list that can be deleted.
|
||||
*/
|
||||
protected checkCanDelete(): void {
|
||||
// All messages being sent should be at the end of the list.
|
||||
const first = this.messages[0];
|
||||
this.canDelete = first && !first.sending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide unread label when sending messages.
|
||||
*/
|
||||
protected hideUnreadLabel(): void {
|
||||
if (this.unreadMessageFrom > 0) {
|
||||
for (const x in this.messages) {
|
||||
const message = this.messages[x];
|
||||
if (message.id == this.unreadMessageFrom) {
|
||||
message.unreadFrom = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Label hidden.
|
||||
this.unreadMessageFrom = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until fetching is false.
|
||||
* @return {Promise<void>} Resolved when done.
|
||||
*/
|
||||
protected waitForFetch(): Promise<void> {
|
||||
if (!this.fetching) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const deferred = this.utils.promiseDefer();
|
||||
|
||||
setTimeout(() => {
|
||||
return this.waitForFetch().finally(() => {
|
||||
deferred.resolve();
|
||||
});
|
||||
}, 400);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a polling to get new messages every certain time.
|
||||
*/
|
||||
protected setPolling(): void {
|
||||
if (!this.polling) {
|
||||
// Start polling.
|
||||
this.polling = setInterval(() => {
|
||||
this.fetchData().catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
}, AddonMessagesProvider.POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset polling.
|
||||
*/
|
||||
protected unsetPolling(): void {
|
||||
if (this.polling) {
|
||||
this.logger.debug(`Cancelling polling for conversation with user '${this.userId}'`);
|
||||
clearInterval(this.polling);
|
||||
this.polling = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy message to clipboard
|
||||
*
|
||||
* @param {string} text Message text to be copied.
|
||||
*/
|
||||
copyMessage(text: string): void {
|
||||
this.utils.copyToClipboard(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to delete a message.
|
||||
*
|
||||
* @param {any} message Message object to delete.
|
||||
* @param {number} index Index where the mesasge is to delete it from the view.
|
||||
*/
|
||||
deleteMessage(message: any, index: number): void {
|
||||
const langKey = message.pending ? 'core.areyousure' : 'addon.messages.deletemessageconfirmation';
|
||||
this.domUtils.showConfirm(this.translate.instant(langKey)).then(() => {
|
||||
const modal = this.domUtils.showModalLoading('core.deleting', true);
|
||||
|
||||
this.messagesProvider.deleteMessage(message).then(() => {
|
||||
// Remove message from the list without having to wait for re-fetch.
|
||||
this.messages.splice(index, 1);
|
||||
this.removeMessage(message.hash);
|
||||
this.notifyNewMessage();
|
||||
|
||||
this.fetchData(); // Re-fetch messages to update cached data.
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errordeletemessage', true);
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to load previous messages.
|
||||
*
|
||||
* @param {any} [infiniteScroll] Infinite scroll object.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
loadPrevious(infiniteScroll: any): Promise<any> {
|
||||
// If there is an ongoing fetch, wait for it to finish.
|
||||
return this.waitForFetch().finally(() => {
|
||||
this.pagesLoaded++;
|
||||
|
||||
this.fetchData().catch((error) => {
|
||||
this.pagesLoaded--;
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingmessages', true);
|
||||
}).finally(() => {
|
||||
infiniteScroll.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Content or scroll has been resized. For content, only call it if it's been added on top.
|
||||
*/
|
||||
resizeContent(): void {
|
||||
let top = this.content.getContentDimensions().scrollTop;
|
||||
this.content.resize();
|
||||
|
||||
// Wait for new content height to be calculated.
|
||||
setTimeout(() => {
|
||||
// Visible content size changed, maintain the bottom position.
|
||||
if (this.content && this.content.contentHeight != this.oldContentHeight) {
|
||||
if (!top) {
|
||||
top = this.content.getContentDimensions().scrollTop;
|
||||
}
|
||||
|
||||
top += this.oldContentHeight - this.content.contentHeight;
|
||||
this.oldContentHeight = this.content.contentHeight;
|
||||
|
||||
this.content.scrollTo(0, top, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll bottom when render has finished.
|
||||
*/
|
||||
scrollToBottom(): void {
|
||||
// Check if scroll is at bottom. If so, scroll bottom after rendering since there might be something new.
|
||||
if (this.scrollBottom) {
|
||||
// Need a timeout to leave time to the view to be rendered.
|
||||
setTimeout(() => {
|
||||
this.content.scrollToBottom(0);
|
||||
});
|
||||
this.scrollBottom = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the server.
|
||||
* @param {string} text Message text.
|
||||
*/
|
||||
sendMessage(text: string): void {
|
||||
let message;
|
||||
|
||||
this.hideUnreadLabel();
|
||||
|
||||
this.showDelete = false;
|
||||
this.scrollBottom = true;
|
||||
|
||||
message = {
|
||||
pending: true,
|
||||
sending: true,
|
||||
useridfrom: this.currentUserId,
|
||||
smallmessage: text,
|
||||
text: text,
|
||||
timecreated: new Date().getTime()
|
||||
};
|
||||
this.addMessage(message, false);
|
||||
|
||||
this.messagesBeingSent++;
|
||||
|
||||
// If there is an ongoing fetch, wait for it to finish.
|
||||
// Otherwise, if a message is sent while fetching it could disappear until the next fetch.
|
||||
this.waitForFetch().finally(() => {
|
||||
this.messagesProvider.sendMessage(this.userId, text).then((data) => {
|
||||
let promise;
|
||||
|
||||
this.messagesBeingSent--;
|
||||
|
||||
if (data.sent) {
|
||||
// Message was sent, fetch messages right now.
|
||||
promise = this.fetchData();
|
||||
} else {
|
||||
promise = Promise.reject(null);
|
||||
}
|
||||
|
||||
promise.catch(() => {
|
||||
// Fetch failed or is offline message, mark the message as sent.
|
||||
// If fetch is successful there's no need to mark it because the fetch will already show the message received.
|
||||
message.sending = false;
|
||||
if (data.sent) {
|
||||
// Message sent to server, not pending anymore.
|
||||
message.pending = false;
|
||||
} else if (data.message) {
|
||||
message.timecreated = data.message.timecreated;
|
||||
}
|
||||
|
||||
this.notifyNewMessage();
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.messagesBeingSent--;
|
||||
|
||||
// Only close the keyboard if an error happens.
|
||||
// We want the user to be able to send multiple messages without the keyboard being closed.
|
||||
this.appProvider.closeKeyboard();
|
||||
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.messages.messagenotsent', true);
|
||||
this.removeMessage(message.hash);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check date should be shown on message list for the current message.
|
||||
* If date has changed from previous to current message it should be shown.
|
||||
*
|
||||
* @param {any} message Current message where to show the date.
|
||||
* @param {any} [prevMessage] Previous message where to compare the date with.
|
||||
* @return {boolean} If date has changed and should be shown.
|
||||
*/
|
||||
showDate(message: any, prevMessage?: any): boolean {
|
||||
if (!prevMessage) {
|
||||
// First message, show it.
|
||||
return true;
|
||||
} else if (message.pending) {
|
||||
// If pending, it has no date, not show.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if day has changed.
|
||||
return !moment(message.timecreated).isSame(prevMessage.timecreated, 'day');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles delete state.
|
||||
*/
|
||||
toggleDelete(): void {
|
||||
this.showDelete = !this.showDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unset again, just in case.
|
||||
this.unsetPolling();
|
||||
this.syncObserver && this.syncObserver.off();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>{{ 'addon.messages.messages' | translate }}</ion-title>
|
||||
<ion-buttons end></ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<core-split-view>
|
||||
<ion-content>
|
||||
<core-tabs>
|
||||
<core-tab [title]="'addon.messages.messages' | translate" icon="chatbubbles">
|
||||
<ng-template>
|
||||
<addon-messages-discussions></addon-messages-discussions>
|
||||
</ng-template>
|
||||
</core-tab>
|
||||
<core-tab [title]="'addon.messages.contacts' | translate" icon="person">
|
||||
<ng-template>
|
||||
<addon-messages-contacts></addon-messages-contacts>
|
||||
</ng-template>
|
||||
</core-tab>
|
||||
</core-tabs>
|
||||
</ion-content>
|
||||
</core-split-view>
|
|
@ -0,0 +1,33 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonMessagesIndexPage } from './index';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { AddonMessagesComponentsModule } from '../../components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonMessagesIndexPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
AddonMessagesComponentsModule,
|
||||
IonicPageModule.forChild(AddonMessagesIndexPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonMessagesIndexPageModule {}
|
|
@ -0,0 +1,71 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { IonicPage } from 'ionic-angular';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonMessagesProvider } from '../../providers/messages';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
|
||||
/**
|
||||
* Page that displays the messages index page.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-messages-index' })
|
||||
@Component({
|
||||
selector: 'page-addon-messages-index',
|
||||
templateUrl: 'index.html',
|
||||
})
|
||||
export class AddonMessagesIndexPage implements OnDestroy {
|
||||
@ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent;
|
||||
|
||||
protected loadSplitViewObserver: any;
|
||||
protected siteId: string;
|
||||
|
||||
constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider,
|
||||
private messagesProvider: AddonMessagesProvider) {
|
||||
|
||||
this.siteId = sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Update split view or navigate.
|
||||
this.loadSplitViewObserver = eventsProvider.on(AddonMessagesProvider.SPLIT_VIEW_LOAD_EVENT, (data) => {
|
||||
if (data.discussion && (this.splitviewCtrl.isOn() || !data.onlyWithSplitView)) {
|
||||
this.gotoDiscussion(data.discussion, data.message);
|
||||
}
|
||||
}, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a particular discussion.
|
||||
*
|
||||
* @param {number} discussionUserId Discussion Id to load.
|
||||
* @param {number} [messageId] Message to scroll after loading the discussion. Used when searching.
|
||||
*/
|
||||
gotoDiscussion(discussionUserId: number, messageId?: number): void {
|
||||
const params = {
|
||||
userId: discussionUserId
|
||||
};
|
||||
if (messageId) {
|
||||
params['message'] = messageId;
|
||||
}
|
||||
this.splitviewCtrl.push('AddonMessagesDiscussionPage', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.loadSplitViewObserver && this.loadSplitViewObserver.off();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>{{ 'addon.messages.messagepreferences' | translate }}</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher [enabled]="preferencesLoaded" (ionRefresh)="refreshPreferences($event)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="preferencesLoaded">
|
||||
<!-- Block non contacts. -->
|
||||
<ion-card>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.messages.blocknoncontacts' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="blockNonContactsState" (ionChange)="blockNonContacts(blockNonContactsState)"></ion-toggle>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<ng-container *ngIf="preferences">
|
||||
<div *ngFor="let component of preferences.components">
|
||||
<ion-card list *ngFor="let notification of component.notifications">
|
||||
<ion-item-divider color="light" text-wrap>
|
||||
<ion-row no-padding>
|
||||
<ion-col no-padding>{{ notification.displayname }}</ion-col>
|
||||
<ion-col col-2 text-center no-padding class="hidden-phone">{{ 'core.settings.loggedin' | translate }}</ion-col>
|
||||
<ion-col col-2 text-center no-padding class="hidden-phone">{{ 'core.settings.loggedoff' | translate }}</ion-col>
|
||||
</ion-row>
|
||||
</ion-item-divider>
|
||||
<ng-container *ngFor="let processor of notification.processors">
|
||||
<!-- Tablet view -->
|
||||
<ion-row text-wrap class="hidden-phone" align-items-center>
|
||||
<ion-col margin-horizontal>{{ processor.displayname }}</ion-col>
|
||||
<ion-col col-2 text-center *ngFor="let state of ['loggedin', 'loggedoff']">
|
||||
<!-- If notifications not disabled, show toggle. -->
|
||||
<ion-spinner [hidden]="preferences.disableall || !(notification.updating && notification.updating[state])"></ion-spinner>
|
||||
<ion-toggle *ngIf="!preferences.disableall" [(ngModel)]="processor[state].checked" (ionChange)="changePreference(notification, state, processor)" [disabled]="processor.locked || (notification.updating && notification.updating[state])">
|
||||
</ion-toggle>
|
||||
<!-- If notifications are disabled, show "Disabled" instead of toggle. -->
|
||||
<span *ngIf="preferences.disableall">{{ 'core.settings.disabled' | translate }}</span>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<!-- Phone view -->
|
||||
<ion-list-header text-wrap class="hidden-tablet">{{ processor.displayname }}</ion-list-header>
|
||||
<!-- If notifications not disabled, show toggles. If notifications are disabled, show "Disabled" instead of toggle. -->
|
||||
<ion-item *ngFor="let state of ['loggedin', 'loggedoff']" text-wrap class="hidden-tablet">
|
||||
<ion-label>{{ 'core.settings.' + state | translate }}</ion-label>
|
||||
<ion-spinner item-end *ngIf="!preferences.disableall && (notification.updating && notification.updating[state])"></ion-spinner>
|
||||
<ion-toggle item-end *ngIf="!preferences.disableall" [(ngModel)]="processor[state].checked" (ionChange)="changePreference(notification, state, processor)" [disabled]="processor.locked || (notification.updating && notification.updating[state])">
|
||||
</ion-toggle>
|
||||
<ion-note item-end *ngIf="preferences.disableall">{{ 'core.settings.disabled' | translate }}</ion-note>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ion-card>
|
||||
</div>
|
||||
</ng-container>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,35 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonMessagesSettingsPage } from './settings';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
import { AddonMessagesComponentsModule } from '../../components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonMessagesSettingsPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
AddonMessagesComponentsModule,
|
||||
IonicPageModule.forChild(AddonMessagesSettingsPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonMessagesSettingsPageModule {}
|
|
@ -0,0 +1,10 @@
|
|||
page-addon-messages-settings {
|
||||
.list-header {
|
||||
margin-bottom: 0;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { IonicPage } from 'ionic-angular';
|
||||
import { AddonMessagesProvider } from '../../providers/messages';
|
||||
import { CoreUserProvider } from '@core/user/providers/user';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
|
||||
/**
|
||||
* Page that displays the messages settings page.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-messages-settings' })
|
||||
@Component({
|
||||
selector: 'page-addon-messages-settings',
|
||||
templateUrl: 'settings.html',
|
||||
})
|
||||
export class AddonMessagesSettingsPage implements OnDestroy {
|
||||
protected updateTimeout: any;
|
||||
|
||||
preferences: any;
|
||||
preferencesLoaded: boolean;
|
||||
blockNonContactsState = false;
|
||||
|
||||
constructor(private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider,
|
||||
private userProvider: CoreUserProvider) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs when the page has loaded. This event only happens once per page being created.
|
||||
* If a page leaves but is cached, then this event will not fire again on a subsequent viewing.
|
||||
* Setup code for the page.
|
||||
*/
|
||||
ionViewDidLoad(): void {
|
||||
this.fetchPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches preference data.
|
||||
*
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
protected fetchPreferences(): Promise<any> {
|
||||
return this.messagesProvider.getMessagePreferences().then((preferences) => {
|
||||
this.preferences = preferences;
|
||||
this.blockNonContactsState = preferences.blocknoncontacts;
|
||||
}).catch((message) => {
|
||||
this.domUtils.showErrorModal(message);
|
||||
}).finally(() => {
|
||||
this.preferencesLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update preferences. The purpose is to store the updated data, it won't be reflected in the view.
|
||||
*/
|
||||
protected updatePreferences(): void {
|
||||
this.messagesProvider.invalidateMessagePreferences().finally(() => {
|
||||
this.fetchPreferences();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update preferences after a certain time. The purpose is to store the updated data, it won't be reflected in the view.
|
||||
*/
|
||||
protected updatePreferencesAfterDelay(): void {
|
||||
// Cancel pending updates.
|
||||
clearTimeout(this.updateTimeout);
|
||||
|
||||
this.updateTimeout = setTimeout(() => {
|
||||
this.updateTimeout = null;
|
||||
this.updatePreferences();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block non contacts.
|
||||
*
|
||||
* @param {boolean} block If it should be blocked or not.
|
||||
*/
|
||||
blockNonContacts(block: boolean): void {
|
||||
const modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
this.userProvider.updateUserPreference('message_blocknoncontacts', block ? 1 : 0).then(() => {
|
||||
// Update the preferences since they were modified.
|
||||
this.updatePreferencesAfterDelay();
|
||||
}).catch((message) => {
|
||||
// Show error and revert change.
|
||||
this.domUtils.showErrorModal(message);
|
||||
this.blockNonContactsState = !this.blockNonContactsState;
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the value of a certain preference.
|
||||
*
|
||||
* @param {any} notification Notification object.
|
||||
* @param {string} state State name, ['loggedin', 'loggedoff'].
|
||||
* @param {any} processor Notification processor.
|
||||
*/
|
||||
changePreference(notification: any, state: string, processor: any): void {
|
||||
const processorState = processor[state],
|
||||
preferenceName = notification.preferencekey + '_' + processorState.name,
|
||||
valueArray = [];
|
||||
let value = 'none';
|
||||
|
||||
notification.processors.forEach((processor) => {
|
||||
if (processor[state].checked) {
|
||||
valueArray.push(processor.name);
|
||||
}
|
||||
});
|
||||
|
||||
if (value.length > 0) {
|
||||
value = valueArray.join(',');
|
||||
}
|
||||
|
||||
if (!notification.updating) {
|
||||
notification.updating = {};
|
||||
}
|
||||
|
||||
notification.updating[state] = true;
|
||||
this.userProvider.updateUserPreference(preferenceName, value).then(() => {
|
||||
// Update the preferences since they were modified.
|
||||
this.updatePreferencesAfterDelay();
|
||||
}).catch((message) => {
|
||||
// Show error and revert change.
|
||||
this.domUtils.showErrorModal(message);
|
||||
processorState.checked = !processorState.checked;
|
||||
}).finally(() => {
|
||||
notification.updating[state] = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the list of preferences.
|
||||
*
|
||||
* @param {any} refresher Refresher.
|
||||
*/
|
||||
refreshEvent(refresher: any): void {
|
||||
this.messagesProvider.invalidateMessagePreferences().finally(() => {
|
||||
this.fetchPreferences().finally(() => {
|
||||
refresher.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// If there is a pending action to update preferences, execute it right now.
|
||||
if (this.updateTimeout) {
|
||||
clearTimeout(this.updateTimeout);
|
||||
this.updatePreferences();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { AddonMessagesProvider } from './messages';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
|
||||
/**
|
||||
* Content links handler for a discussion.
|
||||
* Match message index URL with params id, user1 or user2.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesDiscussionLinkHandler extends CoreContentLinksHandlerBase {
|
||||
name = 'AddonMessagesDiscussionLinkHandler';
|
||||
pattern = /\/message\/index\.php.*([\?\&](id|user1|user2)=\d+)/;
|
||||
|
||||
constructor(private linkHelper: CoreContentLinksHelperProvider, private messagesProvider: AddonMessagesProvider,
|
||||
private sitesProvider: CoreSitesProvider) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
*
|
||||
* @param {string[]} siteIds List of sites the URL belongs to.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
return [{
|
||||
action: (siteId, navCtrl?): void => {
|
||||
const stateParams = {
|
||||
userId: parseInt(params.id || params.user2, 10)
|
||||
};
|
||||
// Always use redirect to make it the new history root (to avoid "loops" in history).
|
||||
this.linkHelper.goInSite(navCtrl, 'AddonMessagesDiscussionPage', stateParams, siteId);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||
* If not defined, defaults to true.
|
||||
*
|
||||
* @param {string} siteId The site ID.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
|
||||
*/
|
||||
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
|
||||
return this.messagesProvider.isPluginEnabled(siteId).then((enabled) => {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof params.id == 'undefined' && typeof params.user2 == 'undefined') {
|
||||
// Other user not defined, cannot treat the URL.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof params.user1 != 'undefined') {
|
||||
// Check if user1 is the current user, since the app only supports current user.
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return parseInt(params.user1, 10) == site.getUserId();
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { AddonMessagesProvider } from './messages';
|
||||
|
||||
/**
|
||||
* Content links handler for messaging index.
|
||||
* Match message index URL without params id, user1 or user2.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesIndexLinkHandler extends CoreContentLinksHandlerBase {
|
||||
name = 'AddonMessagesIndexLinkHandler';
|
||||
pattern = /\/message\/index\.php((?![\?\&](id|user1|user2)=\d+).)*$/;
|
||||
|
||||
constructor(private linkHelper: CoreContentLinksHelperProvider, private messagesProvider: AddonMessagesProvider) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
*
|
||||
* @param {string[]} siteIds List of sites the URL belongs to.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
return [{
|
||||
action: (siteId, navCtrl?): void => {
|
||||
// Always use redirect to make it the new history root (to avoid "loops" in history).
|
||||
this.linkHelper.goInSite(navCtrl, 'AddonMessagesIndexPage', undefined, siteId);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||
* If not defined, defaults to true.
|
||||
*
|
||||
* @param {string} siteId The site ID.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
|
||||
*/
|
||||
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
|
||||
return this.messagesProvider.isPluginEnabled(siteId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { AddonMessagesProvider } from './messages';
|
||||
import { CoreMainMenuDelegate, CoreMainMenuHandler, CoreMainMenuHandlerToDisplay } from '@core/mainmenu/providers/delegate';
|
||||
import { CoreCronHandler } from '@providers/cron';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { AddonPushNotificationsProvider } from '@addon/pushnotifications/providers/pushnotifications';
|
||||
import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate';
|
||||
|
||||
/**
|
||||
* Handler to inject an option into main menu.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCronHandler {
|
||||
name = 'AddonMessages';
|
||||
priority = 800;
|
||||
protected badge = '';
|
||||
protected loading = true;
|
||||
|
||||
constructor(private messagesProvider: AddonMessagesProvider, private sitesProvider: CoreSitesProvider,
|
||||
private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider,
|
||||
private localNotificationsProvider: CoreLocalNotificationsProvider, private textUtils: CoreTextUtilsProvider,
|
||||
private pushNotificationsProvider: AddonPushNotificationsProvider, utils: CoreUtilsProvider,
|
||||
pushNotificationsDelegate: AddonPushNotificationsDelegate) {
|
||||
|
||||
eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => {
|
||||
this.updateBadge(data.siteId);
|
||||
});
|
||||
|
||||
eventsProvider.on(AddonMessagesProvider.READ_CRON_EVENT, (data) => {
|
||||
this.updateBadge(data.siteId);
|
||||
});
|
||||
|
||||
// Reset info on logout.
|
||||
eventsProvider.on(CoreEventsProvider.LOGOUT, (data) => {
|
||||
this.badge = '';
|
||||
this.loading = true;
|
||||
});
|
||||
|
||||
// If a message push notification is received, refresh the count.
|
||||
pushNotificationsDelegate.on('receive').subscribe((notification) => {
|
||||
// New message received. If it's from current site, refresh the data.
|
||||
if (utils.isFalseOrZero(notification.notif) && this.sitesProvider.isCurrentSite(notification.site)) {
|
||||
this.updateBadge(notification.site);
|
||||
}
|
||||
});
|
||||
|
||||
// Register Badge counter.
|
||||
pushNotificationsDelegate.registerCounterHandler('AddonMessages');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean} Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return this.messagesProvider.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return {CoreMainMenuHandlerToDisplay} Data needed to render the handler.
|
||||
*/
|
||||
getDisplayData(): CoreMainMenuHandlerToDisplay {
|
||||
if (this.loading) {
|
||||
this.updateBadge();
|
||||
}
|
||||
|
||||
return {
|
||||
icon: 'chatbubbles',
|
||||
title: 'addon.messages.messages',
|
||||
page: 'AddonMessagesIndexPage',
|
||||
class: 'addon-messages-handler',
|
||||
showBadge: true, // Do not check isMessageCountEnabled because we'll use fallback it not enabled.
|
||||
badge: this.badge,
|
||||
loading: this.loading
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an update for the badge number and loading status. Mandatory if showBadge is enabled.
|
||||
*
|
||||
* @param {string} siteId Site ID or current Site if undefined.
|
||||
*/
|
||||
updateBadge(siteId?: string): void {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
if (!siteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messagesProvider.getUnreadConversationsCount(undefined, siteId).then((unread) => {
|
||||
// Leave badge enter if there is a 0+ or a 0.
|
||||
this.badge = parseInt(unread, 10) > 0 ? unread : '';
|
||||
// Update badge.
|
||||
this.pushNotificationsProvider.updateAddonCounter('AddonMessages', unread, siteId);
|
||||
}).catch(() => {
|
||||
this.badge = '';
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
this.eventsProvider.trigger(CoreMainMenuDelegate.UPDATE_BADGE_EVENT, {
|
||||
name: this.name,
|
||||
badge: this.badge
|
||||
}, siteId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the process.
|
||||
* Receives the ID of the site affected, undefined for all sites.
|
||||
*
|
||||
* @param {string} [siteId] ID of the site affected, undefined for all sites.
|
||||
* @return {Promise<any>} Promise resolved when done, rejected if failure.
|
||||
*/
|
||||
execute(siteId?: string): Promise<any> {
|
||||
if (this.sitesProvider.isCurrentSite(siteId)) {
|
||||
this.eventsProvider.trigger(AddonMessagesProvider.READ_CRON_EVENT, undefined, siteId);
|
||||
}
|
||||
|
||||
if (this.appProvider.isDesktop() && this.localNotificationsProvider.isAvailable()) {
|
||||
// @todo
|
||||
/*$mmEmulatorHelper.checkNewNotifications(
|
||||
AddonMessagesProvider.PUSH_SIMULATION_COMPONENT, this.fetchMessages, this.getTitleAndText, siteId);*/
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time between consecutive executions.
|
||||
*
|
||||
* @return {number} Time between consecutive executions (in ms).
|
||||
*/
|
||||
getInterval(): number {
|
||||
return this.appProvider.isDesktop() ? 60000 : 600000; // 1 or 10 minutes.
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether it's a synchronization process or not.
|
||||
*
|
||||
* @return {boolean} True if is a sync process, false otherwise.
|
||||
*/
|
||||
isSync(): boolean {
|
||||
// This is done to use only wifi if using the fallback function.
|
||||
// In desktop it is always sync, since it fetches messages to see if there's a new one.
|
||||
return !this.messagesProvider.isMessageCountEnabled() || this.appProvider.isDesktop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the process should be executed during a manual sync.
|
||||
*
|
||||
* @return {boolean} True if is a manual sync process, false otherwise.
|
||||
*/
|
||||
canManualSync(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest unread received messages from a site.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. Default current.
|
||||
* @return {Promise<any>} Promise resolved with the notifications.
|
||||
*/
|
||||
protected fetchMessages(siteId?: string): Promise<any> {
|
||||
return this.messagesProvider.getUnreadReceivedMessages(true, false, true, siteId).then((response) => {
|
||||
return response.messages;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a message, return the title and the text for the message.
|
||||
*
|
||||
* @param {any} message Message.
|
||||
* @return {Promise<any>} Promise resolved with an object with title and text.
|
||||
*/
|
||||
protected getTitleAndText(message: any): Promise<any> {
|
||||
const data = {
|
||||
title: message.userfromfullname,
|
||||
};
|
||||
|
||||
return this.textUtils.formatText(message.text, true, true).catch(() => {
|
||||
return message.text;
|
||||
}).then((formattedText) => {
|
||||
data['text'] = formattedText;
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
|
||||
/**
|
||||
* Service to handle Offline messages.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesOfflineProvider {
|
||||
|
||||
protected logger;
|
||||
|
||||
// Variables for database.
|
||||
protected MESSAGES_TABLE = 'addon_messages_offline_messages';
|
||||
protected tablesSchema = [
|
||||
{
|
||||
name: this.MESSAGES_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'touserid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'useridfrom',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'smallmessage',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'timecreated',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'deviceoffline', // If message was stored because device was offline.
|
||||
type: 'INTEGER'
|
||||
}
|
||||
],
|
||||
primaryKeys: ['touserid', 'smallmessage', 'timecreated']
|
||||
}
|
||||
];
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider) {
|
||||
this.logger = logger.getInstance('AddonMessagesOfflineProvider');
|
||||
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a message.
|
||||
*
|
||||
* @param {number} toUserId User ID to send the message to.
|
||||
* @param {string} message The message.
|
||||
* @param {number} timeCreated The time the message was created.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
deleteMessage(toUserId: number, message: string, timeCreated: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().deleteRecords(this.MESSAGES_TABLE, {
|
||||
touserid: toUserId,
|
||||
smallmessage: message,
|
||||
timecreated: timeCreated
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all messages where deviceoffline is set to 1.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with messages.
|
||||
*/
|
||||
getAllDeviceOfflineMessages(siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getRecords(this.MESSAGES_TABLE, {deviceoffline: 1});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get offline messages to send to a certain user.
|
||||
*
|
||||
* @param {number} toUserId User ID to get messages to.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with messages.
|
||||
*/
|
||||
getMessages(toUserId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getRecords(this.MESSAGES_TABLE, {touserid: toUserId});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all offline messages.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with messages.
|
||||
*/
|
||||
getAllMessages(siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getAllRecords(this.MESSAGES_TABLE);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are offline messages to send to a certain user.
|
||||
*
|
||||
* @param {number} toUserId User ID to check.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with boolean: true if has offline messages, false otherwise.
|
||||
*/
|
||||
hasMessages(toUserId: number, siteId?: string): Promise<any> {
|
||||
return this.getMessages(toUserId, siteId).then((messages) => {
|
||||
return !!messages.length;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a message to be sent later.
|
||||
*
|
||||
* @param {number} toUserId User ID recipient of the message.
|
||||
* @param {string} message The message to send.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
saveMessage(toUserId: number, message: string, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const entry = {
|
||||
touserid: toUserId,
|
||||
useridfrom: site.getUserId(),
|
||||
smallmessage: message,
|
||||
timecreated: new Date().getTime(),
|
||||
deviceoffline: this.appProvider.isOnline() ? 0 : 1
|
||||
};
|
||||
|
||||
return site.getDb().insertOrUpdateRecord(this.MESSAGES_TABLE, entry, {
|
||||
touserid: toUserId,
|
||||
smallmessage: message,
|
||||
timecreated: entry.timecreated
|
||||
}).then(() => {
|
||||
return entry;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set deviceoffline for a group of messages.
|
||||
*
|
||||
* @param {any} messages Messages to update. Should be the same entry as retrieved from the DB.
|
||||
* @param {boolean} value Value to set.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
setMessagesDeviceOffline(messages: any, value: boolean, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const db = site.getDb(),
|
||||
promises = [],
|
||||
data = { deviceoffline: value ? 1 : 0 };
|
||||
|
||||
messages.forEach((message) => {
|
||||
promises.push(db.insertOrUpdateRecord(this.MESSAGES_TABLE, data, {
|
||||
touserid: message.touserid,
|
||||
smallmessage: message.smallmessage,
|
||||
timecreated: message.timecreated
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,54 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { AddonMessagesProvider } from './messages';
|
||||
import { CoreSettingsHandler, CoreSettingsHandlerData } from '@core/settings/providers/delegate';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
|
||||
/**
|
||||
* Message settings handler.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesSettingsHandler implements CoreSettingsHandler {
|
||||
name = 'AddonMessages';
|
||||
priority = 600;
|
||||
|
||||
constructor(private messagesProvider: AddonMessagesProvider, private sitesProvider: CoreSitesProvider) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean | Promise<boolean>} Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return this.messagesProvider.isMessagePreferencesEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return {CoreSettingsHandlerData} Data needed to render the handler.
|
||||
*/
|
||||
getDisplayData(): CoreSettingsHandlerData {
|
||||
return {
|
||||
icon: 'chatbubbles',
|
||||
title: 'addon.messages.messagepreferences',
|
||||
page: 'AddonMessagesSettingsPage',
|
||||
class: 'addon-messages-settings-handler'
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCronHandler } from '@providers/cron';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonMessagesSyncProvider } from './sync';
|
||||
|
||||
/**
|
||||
* Synchronization cron handler.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesSyncCronHandler implements CoreCronHandler {
|
||||
name = 'AddonMessagesSyncCronHandler';
|
||||
|
||||
constructor(private sitesProvider: CoreSitesProvider, private messagesSync: AddonMessagesSyncProvider) {}
|
||||
|
||||
/**
|
||||
* Execute the process.
|
||||
* Receives the ID of the site affected, undefined for all sites.
|
||||
*
|
||||
* @param {string} [siteId] ID of the site affected, undefined for all sites.
|
||||
* @return {Promise<any>} Promise resolved when done, rejected if failure.
|
||||
*/
|
||||
execute(siteId?: string): Promise<any> {
|
||||
return this.messagesSync.syncAllDiscussions(siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time between consecutive executions.
|
||||
*
|
||||
* @return {number} Time between consecutive executions (in ms).
|
||||
*/
|
||||
getInterval(): number {
|
||||
return 300000; // 5 minutes.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { AddonMessagesOfflineProvider } from './messages-offline';
|
||||
import { AddonMessagesProvider } from './messages';
|
||||
import { CoreUserProvider } from '@core/user/providers/user';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreSyncProvider } from '@providers/sync';
|
||||
|
||||
/**
|
||||
* Service to sync messages.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
|
||||
|
||||
static AUTO_SYNCED = 'addon_messages_autom_synced';
|
||||
|
||||
constructor(protected sitesProvider: CoreSitesProvider, protected loggerProvider: CoreLoggerProvider,
|
||||
protected appProvider: CoreAppProvider, private messagesOffline: AddonMessagesOfflineProvider,
|
||||
private eventsProvider: CoreEventsProvider, private messagesProvider: AddonMessagesProvider,
|
||||
private userProvider: CoreUserProvider, private translate: TranslateService, private utils: CoreUtilsProvider,
|
||||
syncProvider: CoreSyncProvider) {
|
||||
super('AddonMessagesSync', sitesProvider, loggerProvider, appProvider, syncProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to synchronize all the discussions in a certain site or in all sites.
|
||||
*
|
||||
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||
* @param {boolean} [onlyDeviceOffline=false] True to only sync discussions that failed because device was offline,
|
||||
* false to sync all.
|
||||
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
syncAllDiscussions(siteId?: string, onlyDeviceOffline: boolean = false): Promise<any> {
|
||||
const syncFunctionLog = 'all discussions' + (onlyDeviceOffline ? ' (Only offline)' : '');
|
||||
|
||||
return this.syncOnSites(syncFunctionLog, this.syncAllDiscussionsFunc.bind(this), [onlyDeviceOffline], siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all messages pending to be sent in the site.
|
||||
* @param {boolean} [onlyDeviceOffline=false] True to only sync discussions that failed because device was offline,
|
||||
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||
* @param {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
protected syncAllDiscussionsFunc(onlyDeviceOffline: boolean = false, siteId?: string): Promise<any> {
|
||||
const promise = onlyDeviceOffline ?
|
||||
this.messagesOffline.getAllDeviceOfflineMessages(siteId) :
|
||||
this.messagesOffline.getAllMessages(siteId);
|
||||
|
||||
return promise.then((messages) => {
|
||||
const userIds = [],
|
||||
promises = [];
|
||||
|
||||
// Get all the discussions to be synced.
|
||||
messages.forEach((message) => {
|
||||
if (userIds.indexOf(message.touserid) == -1) {
|
||||
userIds.push(message.touserid);
|
||||
}
|
||||
});
|
||||
|
||||
// Sync all discussions.
|
||||
userIds.forEach((userId) => {
|
||||
promises.push(this.syncDiscussion(userId, siteId).then((warnings) => {
|
||||
if (typeof warnings != 'undefined') {
|
||||
// Sync successful, send event.
|
||||
this.eventsProvider.trigger(AddonMessagesSyncProvider.AUTO_SYNCED, {
|
||||
userid: userId,
|
||||
warnings: warnings
|
||||
}, siteId);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize a discussion.
|
||||
*
|
||||
* @param {number} userId User ID of the discussion.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||
*/
|
||||
syncDiscussion(userId: number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (this.isSyncing(userId, siteId)) {
|
||||
// There's already a sync ongoing for this SCORM, return the promise.
|
||||
return this.getOngoingSync(userId, siteId);
|
||||
}
|
||||
|
||||
const warnings = [];
|
||||
|
||||
this.logger.debug(`Try to sync discussion with user '${userId}'`);
|
||||
|
||||
// Get offline messages to be sent.
|
||||
const syncPromise = this.messagesOffline.getMessages(userId, siteId).then((messages) => {
|
||||
if (!messages.length) {
|
||||
// Nothing to sync.
|
||||
return [];
|
||||
} else if (!this.appProvider.isOnline()) {
|
||||
// Cannot sync in offline. Mark messages as device offline.
|
||||
this.messagesOffline.setMessagesDeviceOffline(messages, true);
|
||||
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
let promise: Promise<any> = Promise.resolve();
|
||||
const errors = [];
|
||||
|
||||
// Order message by timecreated.
|
||||
messages = this.messagesProvider.sortMessages(messages);
|
||||
|
||||
// Send the messages.
|
||||
// We don't use AddonMessagesProvider#sendMessagesOnline because there's a problem with display order.
|
||||
// @todo Use AddonMessagesProvider#sendMessagesOnline once the display order is fixed.
|
||||
messages.forEach((message, index) => {
|
||||
// Chain message sending. If 1 message fails to be sent we'll stop sending.
|
||||
promise = promise.then(() => {
|
||||
return this.messagesProvider.sendMessageOnline(userId, message.smallmessage, siteId).catch((error) => {
|
||||
if (this.utils.isWebServiceError(error)) {
|
||||
// Error returned by WS. Store the error to show a warning but keep sending messages.
|
||||
if (errors.indexOf(error) == -1) {
|
||||
errors.push(error);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Error sending, stop execution.
|
||||
if (this.appProvider.isOnline()) {
|
||||
// App is online, unmark deviceoffline if marked.
|
||||
this.messagesOffline.setMessagesDeviceOffline(messages, false);
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
}).then(() => {
|
||||
// Message was sent, delete it from local DB.
|
||||
return this.messagesOffline.deleteMessage(userId, message.smallmessage, message.timecreated, siteId);
|
||||
}).then(() => {
|
||||
// All done. Wait 1 second to ensure timecreated of messages is different.
|
||||
if (index < messages.length - 1) {
|
||||
return setTimeout(() => {return; }, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return promise.then(() => {
|
||||
return errors;
|
||||
});
|
||||
}).then((errors) => {
|
||||
if (errors && errors.length) {
|
||||
// At least an error occurred, get user full name and add errors to warnings array.
|
||||
return this.userProvider.getProfile(userId, undefined, true).catch(() => {
|
||||
// Ignore errors.
|
||||
return {};
|
||||
}).then((user) => {
|
||||
errors.forEach((error) => {
|
||||
warnings.push(this.translate.instant('addon.messages.warningmessagenotsent', {
|
||||
user: user.fullname ? user.fullname : userId,
|
||||
error: error
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
}).then(() => {
|
||||
// All done, return the warnings.
|
||||
return warnings;
|
||||
});
|
||||
|
||||
return this.addOngoingSync(userId, syncPromise, siteId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@core/user/providers/user-delegate';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { AddonMessagesProvider } from './messages';
|
||||
import { AddonMessagesBlockContactUserHandler } from './user-block-contact-handler';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Profile add/remove contact handler.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandler, OnDestroy {
|
||||
/**
|
||||
* Update handler information event.
|
||||
* @type {string}
|
||||
*/
|
||||
static UPDATED_EVENT = 'AddonMessagesAddContactUserHandler_updated_event';
|
||||
|
||||
name = 'AddonMessages:addContact';
|
||||
priority = 800;
|
||||
type = CoreUserDelegate.TYPE_ACTION;
|
||||
|
||||
protected disabled = false;
|
||||
protected updateObs: any;
|
||||
|
||||
constructor(private linkHelper: CoreContentLinksHelperProvider, protected sitesProvider: CoreSitesProvider,
|
||||
private messagesProvider: AddonMessagesProvider, protected eventsProvider: CoreEventsProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private translate: TranslateService) {
|
||||
|
||||
this.updateObs = eventsProvider.on(AddonMessagesBlockContactUserHandler.UPDATED_EVENT, (data) => {
|
||||
this.checkButton(data.userId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handler is enabled.
|
||||
*
|
||||
* @return {Promise<boolean>} Promise resolved with true if enabled, rejected or resolved with false otherwise.
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return this.messagesProvider.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handler is enabled for this user in this context.
|
||||
*
|
||||
* @param {any} user User to check.
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {any} [navOptions] Course navigation options for current user. See $mmCourses#getUserNavigationOptions.
|
||||
* @param {any} [admOptions] Course admin options for current user. See $mmCourses#getUserAdministrationOptions.
|
||||
* @return {boolean|Promise<boolean>} Promise resolved with true if enabled, resolved with false otherwise.
|
||||
*/
|
||||
isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise<boolean> {
|
||||
return user.id != this.sitesProvider.getCurrentSiteUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return {CoreUserProfileHandlerData} Data needed to render the handler.
|
||||
*/
|
||||
getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData {
|
||||
this.checkButton(user.id);
|
||||
|
||||
return {
|
||||
icon: '',
|
||||
title: '',
|
||||
spinner: false,
|
||||
class: '',
|
||||
action: (event, navCtrl, user, courseId): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
this.disabled = true;
|
||||
this.updateButton({spinner: true});
|
||||
|
||||
this.messagesProvider.isContact(user.id).then((isContact) => {
|
||||
if (isContact) {
|
||||
const template = this.translate.instant('addon.messages.removecontactconfirm'),
|
||||
title = this.translate.instant('addon.messages.removecontact');
|
||||
|
||||
return this.domUtils.showConfirm(template, title, title).then(() => {
|
||||
return this.messagesProvider.removeContact(user.id);
|
||||
}, () => {
|
||||
// Ignore on cancel.
|
||||
});
|
||||
} else {
|
||||
return this.messagesProvider.addContact(user.id);
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
}).finally(() => {
|
||||
this.eventsProvider.trigger(AddonMessagesAddContactUserHandler.UPDATED_EVENT, {userId: user.id});
|
||||
this.checkButton(user.id).finally(() => {
|
||||
this.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Button with avalaible data.
|
||||
* @param {number} userId User Id to update.
|
||||
* @return {Promise<void>} Promise resolved when done.
|
||||
*/
|
||||
protected checkButton(userId: number): Promise<void> {
|
||||
this.updateButton({spinner: true});
|
||||
|
||||
return this.messagesProvider.isContact(userId).then((isContact) => {
|
||||
if (isContact) {
|
||||
this.updateButton({
|
||||
title: 'addon.messages.removecontact',
|
||||
class: 'addon-messages-removecontact-handler',
|
||||
icon: 'remove',
|
||||
hidden: false,
|
||||
spinner: false
|
||||
});
|
||||
} else {
|
||||
this.updateButton({
|
||||
title: 'addon.messages.addcontact',
|
||||
class: 'addon-messages-addcontact-handler',
|
||||
icon: 'add',
|
||||
hidden: false,
|
||||
spinner: false
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
// This fails for some reason, let's just hide the button.
|
||||
this.updateButton({hidden: true});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the event to update the handler information.
|
||||
* @param {any} data Data that should be updated.
|
||||
*/
|
||||
protected updateButton(data: any): void {
|
||||
// This fails for some reason, let's just hide the button.
|
||||
this.eventsProvider.trigger(CoreUserDelegate.UPDATE_HANDLER_EVENT, { handler: this.name, data: data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroyed method.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.updateObs && this.updateObs.off();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@core/user/providers/user-delegate';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { AddonMessagesProvider } from './messages';
|
||||
import { AddonMessagesAddContactUserHandler } from './user-add-contact-handler';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Profile block/unblock contact handler.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesBlockContactUserHandler implements CoreUserProfileHandler, OnDestroy {
|
||||
/**
|
||||
* Update handler information event.
|
||||
* @type {string}
|
||||
*/
|
||||
static UPDATED_EVENT = 'AddonMessagesBlockContactUserHandler_updated_event';
|
||||
|
||||
name = 'AddonMessages:blockContact';
|
||||
priority = 600;
|
||||
type = CoreUserDelegate.TYPE_ACTION;
|
||||
|
||||
protected disabled = false;
|
||||
protected updateObs: any;
|
||||
|
||||
constructor(private linkHelper: CoreContentLinksHelperProvider, protected sitesProvider: CoreSitesProvider,
|
||||
private messagesProvider: AddonMessagesProvider, protected eventsProvider: CoreEventsProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private translate: TranslateService) {
|
||||
|
||||
this.updateObs = eventsProvider.on(AddonMessagesAddContactUserHandler.UPDATED_EVENT, (data) => {
|
||||
this.checkButton(data.userId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handler is enabled.
|
||||
*
|
||||
* @return {Promise<boolean>} Promise resolved with true if enabled, rejected or resolved with false otherwise.
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return this.messagesProvider.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handler is enabled for this user in this context.
|
||||
*
|
||||
* @param {any} user User to check.
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {any} [navOptions] Course navigation options for current user. See $mmCourses#getUserNavigationOptions.
|
||||
* @param {any} [admOptions] Course admin options for current user. See $mmCourses#getUserAdministrationOptions.
|
||||
* @return {boolean|Promise<boolean>} Promise resolved with true if enabled, resolved with false otherwise.
|
||||
*/
|
||||
isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise<boolean> {
|
||||
return user.id != this.sitesProvider.getCurrentSiteUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return {CoreUserProfileHandlerData} Data needed to render the handler.
|
||||
*/
|
||||
getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData {
|
||||
|
||||
this.checkButton(user.id);
|
||||
|
||||
return {
|
||||
icon: '',
|
||||
title: '',
|
||||
spinner: false,
|
||||
class: '',
|
||||
action: (event, navCtrl, user, courseId): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
this.disabled = true;
|
||||
this.updateButton({spinner: true});
|
||||
|
||||
this.messagesProvider.isBlocked(user.id).then((isBlocked) => {
|
||||
if (isBlocked) {
|
||||
return this.messagesProvider.unblockContact(user.id);
|
||||
} else {
|
||||
const template = this.translate.instant('addon.messages.blockcontactconfirm'),
|
||||
title = this.translate.instant('addon.messages.blockcontact');
|
||||
|
||||
return this.domUtils.showConfirm(template, title, title).then(() => {
|
||||
return this.messagesProvider.blockContact(user.id);
|
||||
}, () => {
|
||||
// Ignore on cancel.
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.error', true);
|
||||
}).finally(() => {
|
||||
this.eventsProvider.trigger(AddonMessagesBlockContactUserHandler.UPDATED_EVENT, {userId: user.id});
|
||||
this.checkButton(user.id).finally(() => {
|
||||
this.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Button with avalaible data.
|
||||
* @param {number} userId User Id to update.
|
||||
* @return {Promise<void>} Promise resolved when done.
|
||||
*/
|
||||
protected checkButton(userId: number): Promise<void> {
|
||||
this.updateButton({spinner: true});
|
||||
|
||||
return this.messagesProvider.isBlocked(userId).then((isBlocked) => {
|
||||
if (isBlocked) {
|
||||
this.updateButton({
|
||||
title: 'addon.messages.unblockcontact',
|
||||
class: 'addon-messages-unblockcontact-handler',
|
||||
icon: 'checkmark-circle',
|
||||
hidden: false,
|
||||
spinner: false
|
||||
});
|
||||
} else {
|
||||
this.updateButton({
|
||||
title: 'addon.messages.blockcontact',
|
||||
class: 'addon-messages-blockcontact-handler',
|
||||
icon: 'close-circle',
|
||||
hidden: false,
|
||||
spinner: false
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
// This fails for some reason, let's just hide the button.
|
||||
this.updateButton({hidden: true});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the event to update the handler information.
|
||||
* @param {any} data Data that should be updated.
|
||||
*/
|
||||
protected updateButton(data: any): void {
|
||||
// This fails for some reason, let's just hide the button.
|
||||
this.eventsProvider.trigger(CoreUserDelegate.UPDATE_HANDLER_EVENT, { handler: this.name, data: data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroyed method.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.updateObs && this.updateObs.off();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@core/user/providers/user-delegate';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { AddonMessagesProvider } from './messages';
|
||||
|
||||
/**
|
||||
* Profile send message handler.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonMessagesSendMessageUserHandler implements CoreUserProfileHandler {
|
||||
name = 'AddonMessages:sendMessage';
|
||||
priority = 1000;
|
||||
type = CoreUserDelegate.TYPE_COMMUNICATION;
|
||||
|
||||
constructor(private linkHelper: CoreContentLinksHelperProvider, protected sitesProvider: CoreSitesProvider,
|
||||
private messagesProvider: AddonMessagesProvider) { }
|
||||
|
||||
/**
|
||||
* Check if handler is enabled.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved with true if enabled, rejected or resolved with false otherwise.
|
||||
*/
|
||||
isEnabled(): Promise<any> {
|
||||
return this.messagesProvider.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handler is enabled for this user in this context.
|
||||
*
|
||||
* @param {any} user User to check.
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {any} [navOptions] Course navigation options for current user. See $mmCourses#getUserNavigationOptions.
|
||||
* @param {any} [admOptions] Course admin options for current user. See $mmCourses#getUserAdministrationOptions.
|
||||
* @return {boolean|Promise<boolean>} Promise resolved with true if enabled, resolved with false otherwise.
|
||||
*/
|
||||
isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise<boolean> {
|
||||
return user.id != this.sitesProvider.getCurrentSiteUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return {CoreUserProfileHandlerData} Data needed to render the handler.
|
||||
*/
|
||||
getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData {
|
||||
return {
|
||||
icon: 'send',
|
||||
title: 'addon.messages.message',
|
||||
class: 'addon-messages-send-message-handler',
|
||||
action: (event, navCtrl, user, courseId): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const pageParams = {
|
||||
showKeyboard: true,
|
||||
userId: user.id
|
||||
};
|
||||
// Always use redirect to make it the new history root (to avoid "loops" in history).
|
||||
this.linkHelper.goInSite(navCtrl, 'AddonMessagesDiscussionPage', pageParams);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -18,9 +18,9 @@ import { AddonModBookProvider } from './providers/book';
|
|||
import { AddonModBookModuleHandler } from './providers/module-handler';
|
||||
import { AddonModBookLinkHandler } from './providers/link-handler';
|
||||
import { AddonModBookPrefetchHandler } from './providers/prefetch-handler';
|
||||
import { CoreCourseModuleDelegate } from '../../../core/course/providers/module-delegate';
|
||||
import { CoreContentLinksDelegate } from '../../../core/contentlinks/providers/delegate';
|
||||
import { CoreCourseModulePrefetchDelegate } from '../../../core/course/providers/module-prefetch-delegate';
|
||||
import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
|
||||
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
|
||||
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -16,9 +16,9 @@ import { NgModule } from '@angular/core';
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreComponentsModule } from '../../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../../directives/directives.module';
|
||||
import { CoreCourseComponentsModule } from '../../../../core/course/components/components.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
||||
import { AddonModBookIndexComponent } from './index/index';
|
||||
import { AddonModBookTocPopoverComponent } from './toc-popover/toc-popover';
|
||||
import { AddonModBookNavigationArrowsComponent } from './navigation-arrows/navigation-arrows';
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, Optional } from '@angular/core';
|
||||
import { NavParams, NavController, Content, PopoverController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '../../../../../providers/app';
|
||||
import { CoreDomUtilsProvider } from '../../../../../providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '../../../../../providers/utils/text';
|
||||
import { CoreCourseProvider } from '../../../../../core/course/providers/course';
|
||||
import { CoreCourseHelperProvider } from '../../../../../core/course/providers/helper';
|
||||
import { CoreCourseModuleMainComponent } from '../../../../../core/course/providers/module-delegate';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||
import { CoreCourseModuleMainComponent } from '@core/course/providers/module-delegate';
|
||||
import { AddonModBookProvider, AddonModBookContentsMap, AddonModBookTocChapter } from '../../providers/book';
|
||||
import { AddonModBookPrefetchHandler } from '../../providers/prefetch-handler';
|
||||
import { AddonModBookTocPopoverComponent } from '../../components/toc-popover/toc-popover';
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreDirectivesModule } from '../../../../../directives/directives.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
import { AddonModBookComponentsModule } from '../../components/components.module';
|
||||
import { AddonModBookIndexPage } from './index';
|
||||
|
||||
|
|
|
@ -14,14 +14,14 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
import { CoreFileProvider } from '../../../../providers/file';
|
||||
import { CoreFilepoolProvider } from '../../../../providers/filepool';
|
||||
import { CoreLoggerProvider } from '../../../../providers/logger';
|
||||
import { CoreSitesProvider } from '../../../../providers/sites';
|
||||
import { CoreDomUtilsProvider } from '../../../../providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||
import { CoreCourseProvider } from '../../../../core/course/providers/course';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
|
||||
/**
|
||||
* A book chapter inside the toc list.
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksModuleIndexHandler } from '../../../../core/contentlinks/classes/module-index-handler';
|
||||
import { CoreCourseHelperProvider } from '../../../../core/course/providers/helper';
|
||||
import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler';
|
||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||
|
||||
/**
|
||||
* Handler to treat links to book.
|
||||
|
|
|
@ -16,8 +16,8 @@ import { Injectable } from '@angular/core';
|
|||
import { NavController, NavOptions } from 'ionic-angular';
|
||||
import { AddonModBookProvider } from './book';
|
||||
import { AddonModBookIndexComponent } from '../components/index/index';
|
||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '../../../../core/course/providers/module-delegate';
|
||||
import { CoreCourseProvider } from '../../../../core/course/providers/course';
|
||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
|
||||
/**
|
||||
* Handler to support book modules.
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { CoreCourseModulePrefetchHandlerBase } from '../../../../core/course/classes/module-prefetch-handler';
|
||||
import { CoreCourseModulePrefetchHandlerBase } from '@core/course/classes/module-prefetch-handler';
|
||||
import { AddonModBookProvider } from './book';
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { AddonModLabelModuleHandler } from './providers/module-handler';
|
||||
import { AddonModLabelLinkHandler } from './providers/link-handler';
|
||||
import { CoreCourseModuleDelegate } from '../../../core/course/providers/module-delegate';
|
||||
import { CoreContentLinksDelegate } from '../../../core/contentlinks/providers/delegate';
|
||||
import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
|
||||
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksModuleIndexHandler } from '../../../../core/contentlinks/classes/module-index-handler';
|
||||
import { CoreCourseHelperProvider } from '../../../../core/course/providers/helper';
|
||||
import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler';
|
||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||
|
||||
/**
|
||||
* Handler to treat links to label.
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '../../../../core/course/providers/module-delegate';
|
||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
|
||||
|
||||
/**
|
||||
* Handler to support label modules.
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Service to handle push notifications actions to perform when clicked and received.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonPushNotificationsDelegate {
|
||||
|
||||
protected logger;
|
||||
protected observables: { [s: string]: Subject<any> } = {};
|
||||
protected counterHandlers: { [s: string]: string } = {};
|
||||
|
||||
constructor(loggerProvider: CoreLoggerProvider) {
|
||||
this.logger = loggerProvider.getInstance('AddonPushNotificationsDelegate');
|
||||
this.observables['click'] = new Subject<any>();
|
||||
this.observables['receive'] = new Subject<any>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when a push notification is clicked. Sends notification to handlers.
|
||||
*
|
||||
* @param {any} notification Notification clicked.
|
||||
*/
|
||||
clicked(notification: any): void {
|
||||
this.observables['click'].next(notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when a push notification is received in foreground (cannot tell when it's received in background).
|
||||
* Sends notification to all handlers.
|
||||
*
|
||||
* @param {any} notification Notification received.
|
||||
*/
|
||||
received(notification: any): void {
|
||||
this.observables['receive'].next(notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a push notifications observable for click and receive notification event.
|
||||
* When a notification is clicked or received, the observable will receive a notification to treat.
|
||||
* let observer = pushNotificationsDelegate.on('click').subscribe((notification) => {
|
||||
* ...
|
||||
* observer.unsuscribe();
|
||||
*
|
||||
* @param {string} eventName Only click and receive are permitted.
|
||||
* @return {Subject<any>} Observer to subscribe.
|
||||
*/
|
||||
on(eventName: string): Subject<any> {
|
||||
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}'.`);
|
||||
|
||||
return new Subject<any>();
|
||||
}
|
||||
|
||||
return this.observables[eventName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a push notifications handler for update badge counter.
|
||||
*
|
||||
* @param {string} name Handler's name.
|
||||
*/
|
||||
registerCounterHandler(name: string): void {
|
||||
if (typeof this.counterHandlers[name] == 'undefined') {
|
||||
this.logger.debug(`Registered handler '${name}' as badge counter handler.`);
|
||||
this.counterHandlers[name] = name;
|
||||
} else {
|
||||
this.logger.log(`Handler '${name}' as badge counter handler already registered.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a counter handler is present.
|
||||
*
|
||||
* @param {string} name Handler's name.
|
||||
* @return {boolean} If handler name is present.
|
||||
*/
|
||||
isCounterHandlerRegistered(name: string): boolean {
|
||||
return typeof this.counterHandlers[name] != 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all counter badge handlers.
|
||||
*
|
||||
* @return {any} with all the handler names.
|
||||
*/
|
||||
getCounterHandlers(): any {
|
||||
return this.counterHandlers;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,409 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Platform } from 'ionic-angular';
|
||||
import { Badge } from '@ionic-native/badge';
|
||||
import { Push, PushObject, PushOptions } from '@ionic-native/push';
|
||||
import { Device } from '@ionic-native/device';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { AddonPushNotificationsDelegate } from './delegate';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreConfigProvider } from '@providers/config';
|
||||
import { CoreConfigConstants } from '.././../../configconstants';
|
||||
|
||||
/**
|
||||
* Service to handle push notifications.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonPushNotificationsProvider {
|
||||
|
||||
protected logger;
|
||||
protected pushID: string;
|
||||
protected appDB: any;
|
||||
static COMPONENT = 'AddonPushNotificationsProvider';
|
||||
|
||||
// Variables for database.
|
||||
protected BADGE_TABLE = 'addon_pushnotifications_badge';
|
||||
protected tablesSchema = [
|
||||
{
|
||||
name: this.BADGE_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'siteid',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'addon',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'number',
|
||||
type: 'INTEGER'
|
||||
}
|
||||
],
|
||||
primaryKeys: ['siteid', 'addon']
|
||||
}
|
||||
];
|
||||
|
||||
constructor(logger: CoreLoggerProvider, protected appProvider: CoreAppProvider, private platform: Platform,
|
||||
protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider,
|
||||
private badge: Badge, private localNotificationsProvider: CoreLocalNotificationsProvider,
|
||||
private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private push: Push,
|
||||
private configProvider: CoreConfigProvider, private device: Device) {
|
||||
this.logger = logger.getInstance('AddonPushNotificationsProvider');
|
||||
this.appDB = appProvider.getDB();
|
||||
this.appDB.createTablesFromSchema(this.tablesSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all badge records for a given site.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
cleanSiteCounters(siteId: string): Promise<any> {
|
||||
return this.appDB.deleteRecords(this.BADGE_TABLE, {siteid: siteId} ).finally(() => {
|
||||
this.updateAppCounter();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns options for push notifications based on device.
|
||||
*
|
||||
* @return {Promise<PushOptions>} Promise with the push options resolved when done.
|
||||
*/
|
||||
protected getOptions(): Promise<PushOptions> {
|
||||
// @todo: CoreSettingsProvider.NOTIFICATION_SOUND
|
||||
return this.configProvider.get('CoreSettingsProvider.NOTIFICATION_SOUND', true).then((soundEnabled) => {
|
||||
return {
|
||||
android: {
|
||||
senderID: CoreConfigConstants.gcmpn,
|
||||
sound: !!soundEnabled
|
||||
},
|
||||
ios: {
|
||||
alert: 'true',
|
||||
badge: true,
|
||||
sound: !!soundEnabled
|
||||
},
|
||||
windows: {
|
||||
sound: !!soundEnabled
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pushID for this device.
|
||||
*
|
||||
* @return {string} Push ID.
|
||||
*/
|
||||
getPushId(): string {
|
||||
return this.pushID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Sitebadge counter from the database.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the site.
|
||||
*/
|
||||
getSiteCounter(siteId: string): Promise<any> {
|
||||
return this.getAddonBadge(siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when a push notification is clicked. Redirect the user to the right state.
|
||||
*
|
||||
* @param {any} notification Notification.
|
||||
*/
|
||||
notificationClicked(notification: any): void {
|
||||
this.platform.ready().then(() => {
|
||||
this.pushNotificationsDelegate.clicked(notification);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when we receive a Notification from APNS or a message notification from GCM.
|
||||
* The app can be in foreground or background,
|
||||
* if we are in background this code is executed when we open the app clicking in the notification bar.
|
||||
*
|
||||
* @param {any} notification Notification received.
|
||||
*/
|
||||
onMessageReceived(notification: any): void {
|
||||
const data = notification ? notification.additionalData : {};
|
||||
|
||||
this.sitesProvider.getSite(data.site).then(() => {
|
||||
if (this.utils.isTrueOrOne(data.foreground)) {
|
||||
// If the app is in foreground when the notification is received, it's not shown. Let's show it ourselves.
|
||||
if (this.localNotificationsProvider.isAvailable()) {
|
||||
const localNotif = {
|
||||
id: 1,
|
||||
at: new Date(),
|
||||
data: {
|
||||
notif: data.notif,
|
||||
site: data.site
|
||||
},
|
||||
title: '',
|
||||
text: ''
|
||||
},
|
||||
promises = [];
|
||||
|
||||
// Apply formatText to title and message.
|
||||
promises.push(this.textUtils.formatText(notification.title, true, true).then((formattedTitle) => {
|
||||
localNotif.title = formattedTitle;
|
||||
}).catch(() => {
|
||||
localNotif.title = notification.title;
|
||||
}));
|
||||
|
||||
promises.push(this.textUtils.formatText(notification.message, true, true).then((formattedMessage) => {
|
||||
localNotif.text = formattedMessage;
|
||||
}).catch(() => {
|
||||
localNotif.text = notification.message;
|
||||
}));
|
||||
|
||||
Promise.all(promises).then(() => {
|
||||
this.localNotificationsProvider.schedule(localNotif, AddonPushNotificationsProvider.COMPONENT, data.site);
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger a notification received event.
|
||||
this.platform.ready().then(() => {
|
||||
data.title = notification.title;
|
||||
data.message = notification.message;
|
||||
this.pushNotificationsDelegate.received(data);
|
||||
});
|
||||
} else {
|
||||
// The notification was clicked.
|
||||
// For compatibility with old push plugin implementation we'll merge all the notification data in a single object.
|
||||
data.title = notification.title;
|
||||
data.message = notification.message;
|
||||
this.notificationClicked(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a device from a certain Moodle site.
|
||||
*
|
||||
* @param {any} site Site to unregister from.
|
||||
* @return {Promise<any>} Promise resolved when device is unregistered.
|
||||
*/
|
||||
unregisterDeviceOnMoodle(site: any): Promise<any> {
|
||||
if (!site || !this.appProvider.isMobile()) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
this.logger.debug(`Unregister device on Moodle: '${site.id}'`);
|
||||
|
||||
const data = {
|
||||
appid: CoreConfigConstants.app_id,
|
||||
uuid: this.device.uuid
|
||||
};
|
||||
|
||||
return site.write('core_user_remove_user_device', data).then((response) => {
|
||||
if (!response || !response.removed) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Counter for an addon. It will update the refered siteId counter and the total badge.
|
||||
* It will return the updated addon counter.
|
||||
*
|
||||
* @param {string} addon Registered addon name to set the badge number.
|
||||
* @param {number} value The number to be stored.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the addon on the site.
|
||||
*/
|
||||
updateAddonCounter(addon: string, value: number, siteId?: string): Promise<any> {
|
||||
if (this.pushNotificationsDelegate.isCounterHandlerRegistered(addon)) {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.saveAddonBadge(value, siteId, addon).then(() => {
|
||||
return this.updateSiteCounter(siteId).then(() => {
|
||||
return value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update total badge counter of the app.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the site.
|
||||
*/
|
||||
updateAppCounter(): Promise<any> {
|
||||
return this.sitesProvider.getSitesIds().then((sites) => {
|
||||
const promises = [];
|
||||
sites.forEach((siteId) => {
|
||||
promises.push(this.getAddonBadge(siteId));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then((counters) => {
|
||||
const total = counters.reduce((previous, counter) => {
|
||||
// The app badge counter does not support strings, so parse to int before.
|
||||
return previous + parseInt(counter, 10);
|
||||
}, 0);
|
||||
|
||||
// Set the app badge.
|
||||
return this.badge.set(total).then(() => {
|
||||
return total;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update counter for a site using the stored addon data. It will update the total badge application number.
|
||||
* It will return the updated site counter.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the site.
|
||||
*/
|
||||
updateSiteCounter(siteId: string): Promise<any> {
|
||||
const addons = this.pushNotificationsDelegate.getCounterHandlers(),
|
||||
promises = [];
|
||||
|
||||
for (const x in addons) {
|
||||
promises.push(this.getAddonBadge(siteId, addons[x]));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then((counters) => {
|
||||
let plus = false,
|
||||
total = counters.reduce((previous, counter) => {
|
||||
// Check if there is a plus sign at the end of the counter.
|
||||
if (counter != parseInt(counter, 10)) {
|
||||
plus = true;
|
||||
counter = parseInt(counter, 10);
|
||||
}
|
||||
|
||||
return previous + counter;
|
||||
}, 0);
|
||||
|
||||
total = plus && total > 0 ? total + '+' : total;
|
||||
|
||||
// Save the counter on site.
|
||||
return this.saveAddonBadge(total, siteId);
|
||||
}).then((siteTotal) => {
|
||||
return this.updateAppCounter().then(() => {
|
||||
return siteTotal;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a device in Apple APNS or Google GCM.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when the device is registered.
|
||||
*/
|
||||
registerDevice(): Promise<any> {
|
||||
try {
|
||||
// Check if sound is enabled for notifications.
|
||||
return this.getOptions().then((options) => {
|
||||
const pushObject: PushObject = this.push.init(options);
|
||||
|
||||
pushObject.on('notification').subscribe((notification: any) => {
|
||||
this.logger.log('Received a notification', notification);
|
||||
this.onMessageReceived(notification);
|
||||
});
|
||||
|
||||
pushObject.on('registration').subscribe((registrationId: any) => {
|
||||
this.pushID = registrationId;
|
||||
this.registerDeviceOnMoodle().catch((error) => {
|
||||
this.logger.warn('Can\'t register device', error);
|
||||
});
|
||||
});
|
||||
|
||||
pushObject.on('error').subscribe((error: any) => {
|
||||
this.logger.warn('Error with Push plugin', error);
|
||||
});
|
||||
});
|
||||
} catch (ex) {
|
||||
// Ignore errors.
|
||||
this.logger.warn(ex);
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a device on current Moodle site.
|
||||
*
|
||||
* @return {Promise<any>} Promise resolved when device is registered.
|
||||
*/
|
||||
registerDeviceOnMoodle(): Promise<any> {
|
||||
this.logger.debug('Register device on Moodle.');
|
||||
|
||||
if (!this.sitesProvider.isLoggedIn() || !this.pushID || !this.appProvider.isMobile()) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
const data = {
|
||||
appid: CoreConfigConstants.app_id,
|
||||
name: this.device.manufacturer || '',
|
||||
model: this.device.model,
|
||||
platform: this.device.platform,
|
||||
version: this.device.version,
|
||||
pushid: this.pushID,
|
||||
uuid: this.device.uuid
|
||||
};
|
||||
|
||||
return this.sitesProvider.getCurrentSite().write('core_user_add_user_device', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the addon/site badge counter from the database.
|
||||
*
|
||||
* @param {string} siteId Site ID.
|
||||
* @param {string} [addon='site'] Registered addon name. If not defined it will store the site total.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the addon or site or 0 if none.
|
||||
*/
|
||||
protected getAddonBadge(siteId?: string, addon: string = 'site'): Promise<any> {
|
||||
return this.appDB.getRecord(this.BADGE_TABLE, {siteid: siteId, addon: addon}).then((entry) => {
|
||||
return (entry && entry.number) || 0;
|
||||
}).catch(() => {
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the addon/site badgecounter on the database.
|
||||
*
|
||||
* @param {number} value The number to be stored.
|
||||
* @param {string} [siteId] Site ID. If not defined, use current site.
|
||||
* @param {string} [addon='site'] Registered addon name. If not defined it will store the site total.
|
||||
* @return {Promise<any>} Promise resolved with the stored badge counter for the addon or site.
|
||||
*/
|
||||
protected saveAddonBadge(value: number, siteId?: string, addon: string = 'site'): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const entry = {
|
||||
siteid: siteId,
|
||||
addon: addon,
|
||||
number: value
|
||||
};
|
||||
|
||||
return this.appDB.insertOrUpdateRecord(this.BADGE_TABLE, entry, {siteid: siteId, addon: addon}).then(() => {
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Platform } from 'ionic-angular';
|
||||
import { AddonPushNotificationsProvider } from './providers/pushnotifications';
|
||||
import { AddonPushNotificationsDelegate } from './providers/delegate';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
imports: [
|
||||
],
|
||||
providers: [
|
||||
AddonPushNotificationsProvider,
|
||||
AddonPushNotificationsDelegate
|
||||
]
|
||||
})
|
||||
export class AddonPushNotificationsModule {
|
||||
constructor(platform: Platform, pushNotificationsProvider: AddonPushNotificationsProvider, eventsProvider: CoreEventsProvider,
|
||||
localNotificationsProvider: CoreLocalNotificationsProvider, loggerProvider: CoreLoggerProvider) {
|
||||
|
||||
const logger = loggerProvider.getInstance('AddonPushNotificationsModule');
|
||||
|
||||
// Register device on GCM or APNS server.
|
||||
platform.ready().then(() => {
|
||||
pushNotificationsProvider.registerDevice();
|
||||
});
|
||||
|
||||
eventsProvider.on(CoreEventsProvider.NOTIFICATION_SOUND_CHANGED, () => {
|
||||
// Notification sound has changed, register the device again to update the sound setting.
|
||||
pushNotificationsProvider.registerDevice();
|
||||
});
|
||||
|
||||
// Register device on Moodle site when login.
|
||||
eventsProvider.on(CoreEventsProvider.LOGIN, () => {
|
||||
pushNotificationsProvider.registerDeviceOnMoodle().catch((error) => {
|
||||
logger.warn('Can\'t register device', error);
|
||||
});
|
||||
});
|
||||
|
||||
eventsProvider.on(CoreEventsProvider.SITE_DELETED, (site) => {
|
||||
pushNotificationsProvider.unregisterDeviceOnMoodle(site).catch((error) => {
|
||||
logger.warn('Can\'t unregister device', error);
|
||||
});
|
||||
pushNotificationsProvider.cleanSiteCounters(site.id);
|
||||
});
|
||||
|
||||
// Listen for local notification clicks (generated by the app).
|
||||
localNotificationsProvider.registerClick(AddonPushNotificationsProvider.COMPONENT,
|
||||
pushNotificationsProvider.notificationClicked);
|
||||
}
|
||||
}
|
|
@ -16,9 +16,9 @@ import { NgModule } from '@angular/core';
|
|||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldCheckboxHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { CoreUserProfileFieldDelegate } from '@core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldCheckboxComponent } from './component/checkbox';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to render a checkbox user profile field.
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { CoreTimeUtilsProvider } from '../../../../providers/utils/time';
|
||||
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to render a datetime user profile field.
|
||||
|
|
|
@ -16,10 +16,10 @@ import { NgModule } from '@angular/core';
|
|||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldDatetimeHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { CoreUserProfileFieldDelegate } from '@core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldDatetimeComponent } from './component/datetime';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
import { CorePipesModule } from '../../../pipes/pipes.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CorePipesModule } from '@pipes';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -16,10 +16,10 @@ import { NgModule } from '@angular/core';
|
|||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldMenuHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { CoreUserProfileFieldDelegate } from '@core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldMenuComponent } from './component/menu';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../directives/directives.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to render a text user profile field.
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
|
|||
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
|
||||
'../../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextComponent } from '../component/text';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
|
||||
/**
|
||||
* Text user profile field handlers.
|
||||
|
|
|
@ -16,10 +16,10 @@ import { NgModule } from '@angular/core';
|
|||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldTextHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { CoreUserProfileFieldDelegate } from '@core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextComponent } from './component/text';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../directives/directives.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
|
|||
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from
|
||||
'../../../../core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextareaComponent } from '../component/textarea';
|
||||
import { CoreTextUtilsProvider } from '../../../../providers/utils/text';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
|
||||
/**
|
||||
* Textarea user profile field handlers.
|
||||
|
|
|
@ -16,10 +16,10 @@ import { NgModule } from '@angular/core';
|
|||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { AddonUserProfileFieldTextareaHandler } from './providers/handler';
|
||||
import { CoreUserProfileFieldDelegate } from '../../../core/user/providers/user-profile-field-delegate';
|
||||
import { CoreUserProfileFieldDelegate } from '@core/user/providers/user-profile-field-delegate';
|
||||
import { AddonUserProfileFieldTextareaComponent } from './component/textarea';
|
||||
import { CoreComponentsModule } from '../../../components/components.module';
|
||||
import { CoreDirectivesModule } from '../../../directives/directives.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
|
@ -16,10 +16,10 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { Platform } from 'ionic-angular';
|
||||
import { StatusBar } from '@ionic-native/status-bar';
|
||||
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||
import { CoreAppProvider } from '../providers/app';
|
||||
import { CoreEventsProvider } from '../providers/events';
|
||||
import { CoreLoggerProvider } from '../providers/logger';
|
||||
import { CoreLoginHelperProvider } from '../core/login/providers/helper';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'app.html'
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicApp, IonicModule, Platform } from 'ionic-angular';
|
||||
import { HttpModule } from '@angular/http';
|
||||
|
@ -22,53 +23,56 @@ import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
|||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
|
||||
import { MoodleMobileApp } from './app.component';
|
||||
import { CoreInterceptor } from '../classes/interceptor';
|
||||
import { CoreLoggerProvider } from '../providers/logger';
|
||||
import { CoreDbProvider } from '../providers/db';
|
||||
import { CoreAppProvider } from '../providers/app';
|
||||
import { CoreConfigProvider } from '../providers/config';
|
||||
import { CoreLangProvider } from '../providers/lang';
|
||||
import { CoreTextUtilsProvider } from '../providers/utils/text';
|
||||
import { CoreDomUtilsProvider } from '../providers/utils/dom';
|
||||
import { CoreTimeUtilsProvider } from '../providers/utils/time';
|
||||
import { CoreUrlUtilsProvider } from '../providers/utils/url';
|
||||
import { CoreUtilsProvider } from '../providers/utils/utils';
|
||||
import { CoreMimetypeUtilsProvider } from '../providers/utils/mimetype';
|
||||
import { CoreInitDelegate } from '../providers/init';
|
||||
import { CoreFileProvider } from '../providers/file';
|
||||
import { CoreWSProvider } from '../providers/ws';
|
||||
import { CoreEventsProvider } from '../providers/events';
|
||||
import { CoreSitesFactoryProvider } from '../providers/sites-factory';
|
||||
import { CoreSitesProvider } from '../providers/sites';
|
||||
import { CoreLocalNotificationsProvider } from '../providers/local-notifications';
|
||||
import { CoreGroupsProvider } from '../providers/groups';
|
||||
import { CoreCronDelegate } from '../providers/cron';
|
||||
import { CoreFileSessionProvider } from '../providers/file-session';
|
||||
import { CoreFilepoolProvider } from '../providers/filepool';
|
||||
import { CoreUpdateManagerProvider } from '../providers/update-manager';
|
||||
import { CorePluginFileDelegate } from '../providers/plugin-file-delegate';
|
||||
import { CoreSyncProvider } from '../providers/sync';
|
||||
import { CoreInterceptor } from '@classes/interceptor';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDbProvider } from '@providers/db';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreConfigProvider } from '@providers/config';
|
||||
import { CoreLangProvider } from '@providers/lang';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||
import { CoreInitDelegate } from '@providers/init';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreWSProvider } from '@providers/ws';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesFactoryProvider } from '@providers/sites-factory';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
|
||||
import { CoreGroupsProvider } from '@providers/groups';
|
||||
import { CoreCronDelegate } from '@providers/cron';
|
||||
import { CoreFileSessionProvider } from '@providers/file-session';
|
||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||
import { CoreUpdateManagerProvider } from '@providers/update-manager';
|
||||
import { CorePluginFileDelegate } from '@providers/plugin-file-delegate';
|
||||
import { CoreSyncProvider } from '@providers/sync';
|
||||
|
||||
// Core modules.
|
||||
import { CoreComponentsModule } from '../components/components.module';
|
||||
import { CoreEmulatorModule } from '../core/emulator/emulator.module';
|
||||
import { CoreLoginModule } from '../core/login/login.module';
|
||||
import { CoreMainMenuModule } from '../core/mainmenu/mainmenu.module';
|
||||
import { CoreCoursesModule } from '../core/courses/courses.module';
|
||||
import { CoreFileUploaderModule } from '../core/fileuploader/fileuploader.module';
|
||||
import { CoreSharedFilesModule } from '../core/sharedfiles/sharedfiles.module';
|
||||
import { CoreCourseModule } from '../core/course/course.module';
|
||||
import { CoreSiteHomeModule } from '../core/sitehome/sitehome.module';
|
||||
import { CoreContentLinksModule } from '../core/contentlinks/contentlinks.module';
|
||||
import { CoreUserModule } from '../core/user/user.module';
|
||||
import { CoreGradesModule } from '../core/grades/grades.module';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreEmulatorModule } from '@core/emulator/emulator.module';
|
||||
import { CoreLoginModule } from '@core/login/login.module';
|
||||
import { CoreMainMenuModule } from '@core/mainmenu/mainmenu.module';
|
||||
import { CoreCoursesModule } from '@core/courses/courses.module';
|
||||
import { CoreFileUploaderModule } from '@core/fileuploader/fileuploader.module';
|
||||
import { CoreSharedFilesModule } from '@core/sharedfiles/sharedfiles.module';
|
||||
import { CoreCourseModule } from '@core/course/course.module';
|
||||
import { CoreSiteHomeModule } from '@core/sitehome/sitehome.module';
|
||||
import { CoreContentLinksModule } from '@core/contentlinks/contentlinks.module';
|
||||
import { CoreUserModule } from '@core/user/user.module';
|
||||
import { CoreGradesModule } from '@core/grades/grades.module';
|
||||
import { CoreSettingsModule } from '@core/settings/settings.module';
|
||||
|
||||
// Addon modules.
|
||||
import { AddonCalendarModule } from '../addon/calendar/calendar.module';
|
||||
import { AddonUserProfileFieldModule } from '../addon/userprofilefield/userprofilefield.module';
|
||||
import { AddonFilesModule } from '../addon/files/files.module';
|
||||
import { AddonModBookModule } from '../addon/mod/book/book.module';
|
||||
import { AddonModLabelModule } from '../addon/mod/label/label.module';
|
||||
import { AddonCalendarModule } from '@addon/calendar/calendar.module';
|
||||
import { AddonUserProfileFieldModule } from '@addon/userprofilefield/userprofilefield.module';
|
||||
import { AddonFilesModule } from '@addon/files/files.module';
|
||||
import { AddonModBookModule } from '@addon/mod/book/book.module';
|
||||
import { AddonModLabelModule } from '@addon/mod/label/label.module';
|
||||
import { AddonMessagesModule } from '@addon/messages/messages.module';
|
||||
import { AddonPushNotificationsModule } from '@addon/pushnotifications/pushnotifications.module';
|
||||
|
||||
// For translate loader. AoT requires an exported function for factories.
|
||||
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
||||
|
@ -81,6 +85,7 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
|||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.
|
||||
HttpModule,
|
||||
IonicModule.forRoot(MoodleMobileApp, {
|
||||
|
@ -105,11 +110,14 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
|||
CoreContentLinksModule,
|
||||
CoreUserModule,
|
||||
CoreGradesModule,
|
||||
CoreSettingsModule,
|
||||
AddonCalendarModule,
|
||||
AddonUserProfileFieldModule,
|
||||
AddonFilesModule,
|
||||
AddonModBookModule,
|
||||
AddonModLabelModule
|
||||
AddonModLabelModule,
|
||||
AddonMessagesModule,
|
||||
AddonPushNotificationsModule
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
// for the .md, .ios, or .wp mode classes. The mode class is
|
||||
// automatically applied to the <body> element in the app.
|
||||
|
||||
|
||||
// Alignment
|
||||
// -------------------------
|
||||
|
||||
|
@ -51,7 +50,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
@include media-breakpoint-down(sm) {
|
||||
.hidden-phone {
|
||||
display: none !important;
|
||||
opacity: 0 !important;
|
||||
|
@ -236,6 +235,18 @@ core-format-text[ng-reflect-max-height], *[core-format-text][ng-reflect-max-heig
|
|||
}
|
||||
}
|
||||
|
||||
core-format-text[singleLine="true"], *[core-format-text][singleLine="true"],
|
||||
core-format-text[ng-reflect-single-line="true"], *[core-format-text][ng-reflect-single-line="true"] {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.core-media-adapt-width {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@ -432,10 +443,52 @@ ion-toast.core-toast-alert .toast-wrapper{
|
|||
background: $red-dark;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
resize: none;
|
||||
|
||||
&[core-auto-rows] {
|
||||
height: auto;
|
||||
line-height: 18px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
&:not([core-auto-rows]) {
|
||||
height: 200px;
|
||||
min-height: $core-rte-min-height;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar .core-bar-button-image {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: $core-toolbar-button-image-width - 1;
|
||||
max-height: $core-toolbar-button-image-width - 1;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
// Footer with auto height.
|
||||
.footer.footer-adjustable {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
// Message cards
|
||||
@each $color-name, $color-base, $color-contrast in get-colors($colors) {
|
||||
.core-#{$color-name}-card {
|
||||
@extend ion-card;
|
||||
border-bottom: 3px solid $color-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.core-circle:before {
|
||||
content: ' \25CF';
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
@each $color-name, $color-base, $color-contrast in get-colors($colors) {
|
||||
.core-#{$color-name}-circle:before {
|
||||
@extend .core-circle:before;
|
||||
color: $color-base;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { trigger, style, transition, animate, keyframes } from '@angular/animations';
|
||||
|
||||
export const coreShowHideAnimation = trigger('coreShowHideAnimation', [
|
||||
transition(':enter', [
|
||||
style({opacity: 0}),
|
||||
animate('500ms ease-in-out', style({opacity: 1}))
|
||||
]),
|
||||
transition(':leave', [
|
||||
style({opacity: 1}),
|
||||
animate('500ms ease-in-out', style({opacity: 0}))
|
||||
])
|
||||
]);
|
||||
|
||||
export const coreSlideInOut = trigger('coreSlideInOut', [
|
||||
// Enter animation.
|
||||
transition('void => fromLeft', [
|
||||
style({transform: 'translateX(0)', opacity: 1}),
|
||||
animate(300, keyframes([
|
||||
style({opacity: 0, transform: 'translateX(-100%)', offset: 0}),
|
||||
style({opacity: 1, transform: 'translateX(5%)', offset: 0.7}),
|
||||
style({opacity: 1, transform: 'translateX(0)', offset: 1.0})
|
||||
]))
|
||||
]),
|
||||
// Leave animation.
|
||||
transition('fromLeft => void', [
|
||||
style({transform: 'translateX(-100%)', opacity: 0}),
|
||||
animate(300, keyframes([
|
||||
style({opacity: 1, transform: 'translateX(0)', offset: 0}),
|
||||
style({opacity: 1, transform: 'translateX(5%)', offset: 0.3}),
|
||||
style({opacity: 0, transform: 'translateX(-100%)', offset: 1.0})
|
||||
]))
|
||||
]),
|
||||
// Enter animation.
|
||||
transition('void => fromRight', [
|
||||
style({transform: 'translateX(0)', opacity: 1}),
|
||||
animate(300, keyframes([
|
||||
style({opacity: 0, transform: 'translateX(100%)', offset: 0}),
|
||||
style({opacity: 1, transform: 'translateX(-5%)', offset: 0.7}),
|
||||
style({opacity: 1, transform: 'translateX(0)', offset: 1.0})
|
||||
]))
|
||||
]),
|
||||
// Leave animation.
|
||||
transition('fromRight => void', [
|
||||
style({transform: 'translateX(-100%)', opacity: 0}),
|
||||
animate(300, keyframes([
|
||||
style({opacity: 1, transform: 'translateX(0)', offset: 0}),
|
||||
style({opacity: 1, transform: 'translateX(-5%)', offset: 0.3}),
|
||||
style({opacity: 0, transform: 'translateX(100%)', offset: 1.0})
|
||||
]))
|
||||
])
|
||||
]);
|
|
@ -12,13 +12,22 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreSitesProvider } from '../providers/sites';
|
||||
import { CoreSyncProvider } from '../providers/sync';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreSyncProvider } from '@providers/sync';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
|
||||
/**
|
||||
* Base class to create sync providers. It provides some common functions.
|
||||
*/
|
||||
export class CoreSyncBaseProvider {
|
||||
|
||||
/**
|
||||
* Logger instance get from CoreLoggerProvider.
|
||||
* @type {any}
|
||||
*/
|
||||
protected logger;
|
||||
|
||||
/**
|
||||
* Component of the sync provider.
|
||||
* @type {string}
|
||||
|
@ -34,17 +43,21 @@ export class CoreSyncBaseProvider {
|
|||
// Store sync promises.
|
||||
protected syncPromises: { [siteId: string]: { [uniqueId: string]: Promise<any> } } = {};
|
||||
|
||||
constructor(private sitesProvider: CoreSitesProvider) { }
|
||||
constructor(component: string, protected sitesProvider: CoreSitesProvider, protected loggerProvider: CoreLoggerProvider,
|
||||
protected appProvider: CoreAppProvider, protected syncProvider: CoreSyncProvider) {
|
||||
this.logger = this.loggerProvider.getInstance(component);
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an ongoing sync to the syncPromises list. On finish the promise will be removed.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string | number} id Unique sync identifier per component.
|
||||
* @param {Promise<any>} promise The promise of the sync to add.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} The sync promise.
|
||||
*/
|
||||
addOngoingSync(id: number, promise: Promise<any>, siteId?: string): Promise<any> {
|
||||
addOngoingSync(id: string | number, promise: Promise<any>, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const uniqueId = this.getUniqueSyncId(id);
|
||||
|
@ -63,11 +76,11 @@ export class CoreSyncBaseProvider {
|
|||
/**
|
||||
* If there's an ongoing sync for a certain identifier return it.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string | number} id Unique sync identifier per component.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise of the current sync or undefined if there isn't any.
|
||||
*/
|
||||
getOngoingSync(id: number, siteId?: string): Promise<any> {
|
||||
getOngoingSync(id: string | number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (this.isSyncing(id, siteId)) {
|
||||
|
@ -81,59 +94,55 @@ export class CoreSyncBaseProvider {
|
|||
/**
|
||||
* Get the synchronization time. Returns 0 if no time stored.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string | number} id Unique sync identifier per component.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<number>} Promise resolved with the time.
|
||||
*/
|
||||
getSyncTime(id: number, siteId?: string): Promise<number> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.getRecord(CoreSyncProvider.SYNC_TABLE, { component: this.component, id: id }).then((entry) => {
|
||||
return entry.time;
|
||||
}).catch(() => {
|
||||
return 0;
|
||||
});
|
||||
getSyncTime(id: string | number, siteId?: string): Promise<number> {
|
||||
return this.syncProvider.getSyncRecord(this.component, id, siteId).then((entry) => {
|
||||
return entry.time;
|
||||
}).catch(() => {
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the synchronization warnings of an instance.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string | number} id Unique sync identifier per component.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<string[]>} Promise resolved with the warnings.
|
||||
*/
|
||||
getSyncWarnings(id: number, siteId?: string): Promise<string[]> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.getRecord(CoreSyncProvider.SYNC_TABLE, { component: this.component, id: id }).then((entry) => {
|
||||
try {
|
||||
return JSON.parse(entry.warnings);
|
||||
} catch (ex) {
|
||||
return [];
|
||||
}
|
||||
}).catch(() => {
|
||||
getSyncWarnings(id: string | number, siteId?: string): Promise<string[]> {
|
||||
return this.syncProvider.getSyncRecord(this.component, id, siteId).then((entry) => {
|
||||
try {
|
||||
return JSON.parse(entry.warnings);
|
||||
} catch (ex) {
|
||||
return [];
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a unique identifier from component and id.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string | number} id Unique sync identifier per component.
|
||||
* @return {string} Unique identifier from component and id.
|
||||
*/
|
||||
protected getUniqueSyncId(id: number): string {
|
||||
protected getUniqueSyncId(id: string | number): string {
|
||||
return this.component + '#' + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a there's an ongoing syncronization for the given id.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string | number} id Unique sync identifier per component.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {boolean} Whether it's synchronizing.
|
||||
*/
|
||||
isSyncing(id: number, siteId?: string): boolean {
|
||||
isSyncing(id: string | number, siteId?: string): boolean {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const uniqueId = this.getUniqueSyncId(id);
|
||||
|
@ -144,11 +153,11 @@ export class CoreSyncBaseProvider {
|
|||
/**
|
||||
* Check if a sync is needed: if a certain time has passed since the last time.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string} id Unique sync identifier per component.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: whether sync is needed.
|
||||
*/
|
||||
isSyncNeeded(id: number, siteId?: string): Promise<boolean> {
|
||||
isSyncNeeded(id: string, siteId?: string): Promise<boolean> {
|
||||
return this.getSyncTime(id, siteId).then((time) => {
|
||||
return Date.now() - this.syncInterval >= time;
|
||||
});
|
||||
|
@ -157,33 +166,67 @@ export class CoreSyncBaseProvider {
|
|||
/**
|
||||
* Set the synchronization time.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string | number} id Unique sync identifier per component.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @param {number} [time] Time to set. If not defined, current time.
|
||||
* @return {Promise<any>} Promise resolved when the time is set.
|
||||
*/
|
||||
setSyncTime(id: number, siteId?: string, time?: number): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
time = typeof time != 'undefined' ? time : Date.now();
|
||||
setSyncTime(id: string | number, siteId?: string, time?: number): Promise<any> {
|
||||
time = typeof time != 'undefined' ? time : Date.now();
|
||||
|
||||
return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, { time: time }, { component: this.component, id: id });
|
||||
});
|
||||
return this.syncProvider.insertOrUpdateSyncRecord(this.component, id, { time: time }, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the synchronization warnings.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string} id Unique sync identifier per component.
|
||||
* @param {string[]} warnings Warnings to set.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
setSyncWarnings(id: number, warnings: string[], siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
warnings = warnings || [];
|
||||
setSyncWarnings(id: string, warnings: string[], siteId?: string): Promise<any> {
|
||||
const warningsText = JSON.stringify(warnings || []);
|
||||
|
||||
return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, { warnings: JSON.stringify(warnings) },
|
||||
{ component: this.component, id: id });
|
||||
return this.syncProvider.insertOrUpdateSyncRecord(this.component, id, { warnings: warningsText }, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a sync function on selected sites.
|
||||
*
|
||||
* @param {string} syncFunctionLog Log message to explain the sync function purpose.
|
||||
* @param {Function} syncFunction Sync function to execute.
|
||||
* @param {any[]} [params] Array that defines the params that admit the funcion.
|
||||
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||
* @return {Promise<any>} Resolved with siteIds selected. Rejected if offline.
|
||||
*/
|
||||
syncOnSites(syncFunctionLog: string, syncFunction: Function, params?: any[], siteId?: string): Promise<any> {
|
||||
if (!this.appProvider.isOnline()) {
|
||||
this.logger.debug(`Cannot sync '${syncFunctionLog}' because device is offline.`);
|
||||
|
||||
return Promise.reject(null);
|
||||
}
|
||||
|
||||
let promise;
|
||||
if (!siteId) {
|
||||
// No site ID defined, sync all sites.
|
||||
this.logger.debug(`Try to sync '${syncFunctionLog}' in all sites.`);
|
||||
promise = this.sitesProvider.getSitesIds();
|
||||
} else {
|
||||
this.logger.debug(`Try to sync '${syncFunctionLog}' in site '${siteId}'.`);
|
||||
promise = Promise.resolve([siteId]);
|
||||
}
|
||||
|
||||
params = params || [];
|
||||
|
||||
return promise.then((siteIds) => {
|
||||
const sitePromises = [];
|
||||
siteIds.forEach((siteId) => {
|
||||
// Execute function for every site selected.
|
||||
sitePromises.push(syncFunction.apply(syncFunction, [siteId].concat(params)));
|
||||
});
|
||||
|
||||
return Promise.all(sitePromises);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -191,11 +234,11 @@ export class CoreSyncBaseProvider {
|
|||
* If there's an ongoing sync for a certain identifier, wait for it to end.
|
||||
* If there's no sync ongoing the promise will be resolved right away.
|
||||
*
|
||||
* @param {number} id Unique sync identifier per component.
|
||||
* @param {string | number} id Unique sync identifier per component.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when there's no sync going on for the identifier.
|
||||
*/
|
||||
waitForSync(id: number, siteId?: string): Promise<any> {
|
||||
waitForSync(id: string | number, siteId?: string): Promise<any> {
|
||||
const promise = this.getOngoingSync(id, siteId);
|
||||
if (promise) {
|
||||
return promise.catch(() => {
|
||||
|
@ -204,5 +247,5 @@ export class CoreSyncBaseProvider {
|
|||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '../providers/logger';
|
||||
import { CoreSitesProvider } from '../providers/sites';
|
||||
import { CoreEventsProvider } from '../providers/events';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
|
||||
export interface CoreDelegateHandler {
|
||||
/**
|
||||
|
|
|
@ -16,18 +16,18 @@ import { Injector } from '@angular/core';
|
|||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { SQLiteDB } from './sqlitedb';
|
||||
import { CoreAppProvider } from '../providers/app';
|
||||
import { CoreDbProvider } from '../providers/db';
|
||||
import { CoreEventsProvider } from '../providers/events';
|
||||
import { CoreFileProvider } from '../providers/file';
|
||||
import { CoreLoggerProvider } from '../providers/logger';
|
||||
import { CoreWSProvider, CoreWSPreSets, CoreWSFileUploadOptions } from '../providers/ws';
|
||||
import { CoreDomUtilsProvider } from '../providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '../providers/utils/text';
|
||||
import { CoreTimeUtilsProvider } from '../providers/utils/time';
|
||||
import { CoreUrlUtilsProvider } from '../providers/utils/url';
|
||||
import { CoreUtilsProvider } from '../providers/utils/utils';
|
||||
import { CoreConstants } from '../core/constants';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreDbProvider } from '@providers/db';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreWSProvider, CoreWSPreSets, CoreWSFileUploadOptions } from '@providers/ws';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreConfigConstants } from '../configconstants';
|
||||
import { Md5 } from 'ts-md5/dist/md5';
|
||||
import { InAppBrowserObject } from '@ionic-native/in-app-browser';
|
||||
|
@ -531,7 +531,7 @@ export class CoreSite {
|
|||
} else {
|
||||
this.logger.error(`WS function '${method}' is not available, even in compatibility mode.`);
|
||||
|
||||
return Promise.reject(this.wsProvider.createFakeWSError('core.wsfunctionnotavailable', true));
|
||||
return Promise.reject(this.utils.createFakeWSError('core.wsfunctionnotavailable', true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -560,7 +560,7 @@ export class CoreSite {
|
|||
data = this.wsProvider.convertValuesToString(data, wsPreSets.cleanUnicode);
|
||||
} catch (e) {
|
||||
// Empty cleaned text found.
|
||||
return Promise.reject(this.wsProvider.createFakeWSError('core.unicodenotsupportedcleanerror', true));
|
||||
return Promise.reject(this.utils.createFakeWSError('core.unicodenotsupportedcleanerror', true));
|
||||
}
|
||||
|
||||
return this.getFromCache(method, data, preSets).catch(() => {
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreDirectivesModule } from '../directives/directives.module';
|
||||
import { CorePipesModule } from '../pipes/pipes.module';
|
||||
import { CoreDirectivesModule } from '@directives';
|
||||
import { CorePipesModule } from '@pipes';
|
||||
import { CoreLoadingComponent } from './loading/loading';
|
||||
import { CoreMarkRequiredComponent } from './mark-required/mark-required';
|
||||
import { CoreInputErrorsComponent } from './input-errors/input-errors';
|
||||
|
@ -39,6 +39,7 @@ import { CoreTabComponent } from './tabs/tab';
|
|||
import { CoreRichTextEditorComponent } from './rich-text-editor/rich-text-editor';
|
||||
import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
|
||||
import { CoreDynamicComponent } from './dynamic-component/dynamic-component';
|
||||
import { CoreSendMessageFormComponent } from './send-message-form/send-message-form';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -63,7 +64,8 @@ import { CoreDynamicComponent } from './dynamic-component/dynamic-component';
|
|||
CoreTabComponent,
|
||||
CoreRichTextEditorComponent,
|
||||
CoreNavBarButtonsComponent,
|
||||
CoreDynamicComponent
|
||||
CoreDynamicComponent,
|
||||
CoreSendMessageFormComponent
|
||||
],
|
||||
entryComponents: [
|
||||
CoreContextMenuPopoverComponent,
|
||||
|
@ -95,7 +97,8 @@ import { CoreDynamicComponent } from './dynamic-component/dynamic-component';
|
|||
CoreTabComponent,
|
||||
CoreRichTextEditorComponent,
|
||||
CoreNavBarButtonsComponent,
|
||||
CoreDynamicComponent
|
||||
CoreDynamicComponent,
|
||||
CoreSendMessageFormComponent
|
||||
]
|
||||
})
|
||||
export class CoreComponentsModule {}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { NavParams, ViewController } from 'ionic-angular';
|
||||
import { CoreContextMenuItemComponent } from './context-menu-item';
|
||||
import { CoreLoggerProvider } from '../../providers/logger';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
|
||||
/**
|
||||
* Component to display a list of items received by param in a popover.
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { Component, Input, OnInit, OnDestroy, ElementRef } from '@angular/core';
|
||||
import { PopoverController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreContextMenuItemComponent } from './context-menu-item';
|
||||
import { CoreContextMenuPopoverComponent } from './context-menu-popover';
|
||||
import { Subject } from 'rxjs';
|
||||
|
@ -34,6 +34,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
|||
hideMenu: boolean;
|
||||
ariaLabel: string;
|
||||
protected items: CoreContextMenuItemComponent[] = [];
|
||||
protected itemsMovedToParent: CoreContextMenuItemComponent[] = [];
|
||||
protected itemsChangedStream: Subject<void>; // Stream to update the hideMenu boolean when items change.
|
||||
protected instanceId: string;
|
||||
protected parentContextMenu: CoreContextMenuComponent;
|
||||
|
@ -74,7 +75,11 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
|||
if (this.parentContextMenu) {
|
||||
// All items were moved to the "parent" menu. Add the item in there.
|
||||
this.parentContextMenu.addItem(item);
|
||||
} else {
|
||||
|
||||
if (this.itemsMovedToParent.indexOf(item) == -1) {
|
||||
this.itemsMovedToParent.push(item);
|
||||
}
|
||||
} else if (this.items.indexOf(item) == -1) {
|
||||
this.items.push(item);
|
||||
this.itemsChanged();
|
||||
}
|
||||
|
@ -103,7 +108,9 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
|||
|
||||
// Add all the items to the other menu.
|
||||
for (let i = 0; i < this.items.length; i++) {
|
||||
contextMenu.addItem(this.items[i]);
|
||||
const item = this.items[i];
|
||||
contextMenu.addItem(item);
|
||||
this.itemsMovedToParent.push(item);
|
||||
}
|
||||
|
||||
// Remove all items from the current menu.
|
||||
|
@ -120,6 +127,11 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
|||
if (this.parentContextMenu) {
|
||||
// All items were moved to the "parent" menu. Remove the item from there.
|
||||
this.parentContextMenu.removeItem(item);
|
||||
|
||||
const index = this.itemsMovedToParent.indexOf(item);
|
||||
if (index >= 0) {
|
||||
this.itemsMovedToParent.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
const index = this.items.indexOf(item);
|
||||
if (index >= 0) {
|
||||
|
@ -129,6 +141,28 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the items that were merged to a parent context menu.
|
||||
*/
|
||||
removeMergedItems(): void {
|
||||
if (this.parentContextMenu) {
|
||||
for (let i = 0; i < this.itemsMovedToParent.length; i++) {
|
||||
this.parentContextMenu.removeItem(this.itemsMovedToParent[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the items that were merged to a parent context menu.
|
||||
*/
|
||||
restoreMergedItems(): void {
|
||||
if (this.parentContextMenu) {
|
||||
for (let i = 0; i < this.itemsMovedToParent.length; i++) {
|
||||
this.parentContextMenu.addItem(this.itemsMovedToParent[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the context menu.
|
||||
*
|
||||
|
@ -146,5 +180,6 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
|||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.domUtils.removeInstanceById(this.instanceId);
|
||||
this.removeMergedItems();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
Component, Input, ViewChild, OnInit, OnChanges, DoCheck, ViewContainerRef, ComponentFactoryResolver,
|
||||
KeyValueDiffers, SimpleChange
|
||||
} from '@angular/core';
|
||||
import { CoreLoggerProvider } from '../../providers/logger';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
|
||||
/**
|
||||
* Component to create another component dynamically.
|
||||
|
|
|
@ -14,15 +14,15 @@
|
|||
|
||||
import { Component, Input, Output, OnInit, OnDestroy, EventEmitter } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '../../providers/app';
|
||||
import { CoreEventsProvider } from '../../providers/events';
|
||||
import { CoreFileProvider } from '../../providers/file';
|
||||
import { CoreFilepoolProvider } from '../../providers/filepool';
|
||||
import { CoreSitesProvider } from '../../providers/sites';
|
||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
||||
import { CoreMimetypeUtilsProvider } from '../../providers/utils/mimetype';
|
||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
|
||||
import { CoreConstants } from '../../core/constants';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
|
||||
/**
|
||||
* Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button
|
||||
|
|
|
@ -15,13 +15,13 @@
|
|||
import { Component, Input, OnInit, ViewChild, ElementRef } from '@angular/core';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { Platform } from 'ionic-angular';
|
||||
import { CoreFileProvider } from '../../providers/file';
|
||||
import { CoreLoggerProvider } from '../../providers/logger';
|
||||
import { CoreSitesProvider } from '../../providers/sites';
|
||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '../../providers/utils/text';
|
||||
import { CoreUrlUtilsProvider } from '../../providers/utils/url';
|
||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
/**
|
||||
*/
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<!-- @TODO: Add show hide animation -->
|
||||
<div class="core-loading-container" *ngIf="!hideUntil">
|
||||
<div [@coreShowHideAnimation] class="core-loading-container" *ngIf="!hideUntil">
|
||||
<span class="core-loading-spinner">
|
||||
<ion-spinner></ion-spinner>
|
||||
<p class="core-loading-message" *ngIf="message">{{message}}</p>
|
||||
</span>
|
||||
</div>
|
||||
<ng-content class="core-loading-content" *ngIf="hideUntil">
|
||||
<ng-content [@coreShowHideAnimation] class="core-loading-content" *ngIf="hideUntil">
|
||||
</ng-content>
|
|
@ -13,6 +13,7 @@ core-loading {
|
|||
&.core-loading-noheight .core-loading-content {
|
||||
height: auto;
|
||||
}
|
||||
@include core-transition(core-show-animation);
|
||||
}
|
||||
|
||||
.scroll-content > core-loading > .core-loading-container,
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { coreShowHideAnimation } from '@classes/animations';
|
||||
|
||||
/**
|
||||
* Component to show a loading spinner and message while data is being loaded.
|
||||
|
@ -37,7 +38,8 @@ import { TranslateService } from '@ngx-translate/core';
|
|||
*/
|
||||
@Component({
|
||||
selector: 'core-loading',
|
||||
templateUrl: 'loading.html'
|
||||
templateUrl: 'loading.html',
|
||||
animations: [coreShowHideAnimation]
|
||||
})
|
||||
export class CoreLoadingComponent implements OnInit {
|
||||
@Input() hideUntil: boolean; // Determine when should the contents be shown.
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
|
||||
import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreFileProvider } from '../../providers/file';
|
||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
||||
import { CoreMimetypeUtilsProvider } from '../../providers/utils/mimetype';
|
||||
import { CoreTextUtilsProvider } from '../../providers/utils/text';
|
||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import * as moment from 'moment';
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
import { Component, Input, OnInit, AfterViewInit, ElementRef } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreTextUtilsProvider } from '../../providers/utils/text';
|
||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Directive to add a red asterisk for required input fields.
|
||||
|
|
|
@ -12,10 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, OnInit, ContentChildren, ElementRef, QueryList } from '@angular/core';
|
||||
import { Component, Input, OnInit, OnDestroy, ContentChildren, ElementRef, QueryList } from '@angular/core';
|
||||
import { Button } from 'ionic-angular';
|
||||
import { CoreLoggerProvider } from '../../providers/logger';
|
||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreContextMenuComponent } from '../context-menu/context-menu';
|
||||
|
||||
/**
|
||||
* Component to add buttons to the app's header without having to place them inside the header itself. This is meant for
|
||||
|
@ -26,10 +27,12 @@ import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
|||
*
|
||||
* You can use the [hidden] input to hide all the inner buttons if a certain condition is met.
|
||||
*
|
||||
* IMPORTANT: Do not use *ngIf in the buttons inside this component, it can cause problems. Please use [hidden] instead.
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* <core-navbar-buttons end>
|
||||
* <button ion-button icon-only *ngIf="buttonShown" [attr.aria-label]="Do something" (click)="action()">
|
||||
* <button ion-button icon-only [hidden]="!buttonShown" [attr.aria-label]="Do something" (click)="action()">
|
||||
* <ion-icon name="funnel"></ion-icon>
|
||||
* </button>
|
||||
* </core-navbar-buttons>
|
||||
|
@ -38,37 +41,35 @@ import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
|||
selector: 'core-navbar-buttons',
|
||||
template: '<ng-content></ng-content>'
|
||||
})
|
||||
export class CoreNavBarButtonsComponent implements OnInit {
|
||||
export class CoreNavBarButtonsComponent implements OnInit, OnDestroy {
|
||||
|
||||
protected BUTTON_HIDDEN_CLASS = 'core-navbar-button-hidden';
|
||||
|
||||
// If the hidden input is true, hide all buttons.
|
||||
@Input('hidden') set hidden(value: boolean) {
|
||||
this._hidden = value;
|
||||
if (this._buttons) {
|
||||
this._buttons.forEach((button: Button) => {
|
||||
this.showHideButton(button);
|
||||
});
|
||||
}
|
||||
this.showHideAllElements();
|
||||
}
|
||||
|
||||
// Get all the buttons inside this directive.
|
||||
// Get all the ion-buttons inside this directive and apply the role bar-button.
|
||||
@ContentChildren(Button) set buttons(buttons: QueryList<Button>) {
|
||||
this._buttons = buttons;
|
||||
buttons.forEach((button: Button) => {
|
||||
button.setRole('bar-button');
|
||||
this.showHideButton(button);
|
||||
});
|
||||
}
|
||||
|
||||
protected element: HTMLElement;
|
||||
protected _buttons: QueryList<Button>;
|
||||
protected _hidden: boolean;
|
||||
protected forceHidden = false;
|
||||
protected logger: any;
|
||||
protected movedChildren: Node[];
|
||||
protected instanceId: string;
|
||||
protected mergedContextMenu: CoreContextMenuComponent;
|
||||
|
||||
constructor(element: ElementRef, logger: CoreLoggerProvider, private domUtils: CoreDomUtilsProvider) {
|
||||
this.element = element.nativeElement;
|
||||
this.logger = logger.getInstance('CoreNavBarButtonsComponent');
|
||||
this.instanceId = this.domUtils.storeInstanceByElement(this.element, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,7 +92,9 @@ export class CoreNavBarButtonsComponent implements OnInit {
|
|||
if (buttonsContainer) {
|
||||
this.mergeContextMenus(buttonsContainer);
|
||||
|
||||
this.domUtils.moveChildren(this.element, buttonsContainer);
|
||||
this.movedChildren = this.domUtils.moveChildren(this.element, buttonsContainer);
|
||||
this.showHideAllElements();
|
||||
|
||||
} else {
|
||||
this.logger.warn('The header was found, but it didn\'t have the right ion-buttons.', selector);
|
||||
}
|
||||
|
@ -102,6 +105,17 @@ export class CoreNavBarButtonsComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Force or unforce hiding all buttons. If this is true, it will override the "hidden" input.
|
||||
*
|
||||
* @param {boolean} value The value to set.
|
||||
*/
|
||||
forceHide(value: boolean): void {
|
||||
this.forceHidden = value;
|
||||
|
||||
this.showHideAllElements();
|
||||
}
|
||||
|
||||
/**
|
||||
* If both button containers have a context menu, merge them into a single one.
|
||||
*
|
||||
|
@ -124,7 +138,9 @@ export class CoreNavBarButtonsComponent implements OnInit {
|
|||
secondaryContextMenuInstance = this.domUtils.getInstanceByElement(secondaryContextMenu);
|
||||
|
||||
if (mainContextMenuInstance && secondaryContextMenuInstance) {
|
||||
secondaryContextMenuInstance.mergeContextMenus(mainContextMenuInstance);
|
||||
this.mergedContextMenu = secondaryContextMenuInstance;
|
||||
|
||||
this.mergedContextMenu.mergeContextMenus(mainContextMenuInstance);
|
||||
|
||||
// Remove the empty context menu from the DOM.
|
||||
secondaryContextMenu.parentElement.removeChild(secondaryContextMenu);
|
||||
|
@ -151,7 +167,7 @@ export class CoreNavBarButtonsComponent implements OnInit {
|
|||
if (parentPage) {
|
||||
// Check if the page has a header. If it doesn't, search the next parent page.
|
||||
const header = this.searchHeaderInPage(parentPage);
|
||||
if (header) {
|
||||
if (header && getComputedStyle(header, null).display != 'none') {
|
||||
return Promise.resolve(header);
|
||||
}
|
||||
}
|
||||
|
@ -188,15 +204,61 @@ export class CoreNavBarButtonsComponent implements OnInit {
|
|||
}
|
||||
|
||||
/**
|
||||
* Show or hide a button.
|
||||
*
|
||||
* @param {Button} button Button to show or hide.
|
||||
* Show or hide all the elements.
|
||||
*/
|
||||
protected showHideButton(button: Button): void {
|
||||
if (this._hidden) {
|
||||
button.getNativeElement().classList.add(this.BUTTON_HIDDEN_CLASS);
|
||||
} else {
|
||||
button.getNativeElement().classList.remove(this.BUTTON_HIDDEN_CLASS);
|
||||
protected showHideAllElements(): void {
|
||||
// Show or hide all moved children.
|
||||
if (this.movedChildren) {
|
||||
this.movedChildren.forEach((child: Node) => {
|
||||
this.showHideElement(child);
|
||||
});
|
||||
}
|
||||
|
||||
// Show or hide all the context menu items that were merged to another context menu.
|
||||
if (this.mergedContextMenu) {
|
||||
if (this.forceHidden || this._hidden) {
|
||||
this.mergedContextMenu.removeMergedItems();
|
||||
} else {
|
||||
this.mergedContextMenu.restoreMergedItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide an element.
|
||||
*
|
||||
* @param {Node} element Element to show or hide.
|
||||
*/
|
||||
protected showHideElement(element: Node): void {
|
||||
// Check if it's an HTML Element
|
||||
if (element instanceof Element) {
|
||||
if (this.forceHidden || this._hidden) {
|
||||
element.classList.add(this.BUTTON_HIDDEN_CLASS);
|
||||
} else {
|
||||
element.classList.remove(this.BUTTON_HIDDEN_CLASS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.domUtils.removeInstanceById(this.instanceId);
|
||||
|
||||
// This component was destroyed, remove all the buttons that were moved.
|
||||
// The buttons can be moved outside of the current page, that's why we need to manually destroy them.
|
||||
// There's no need to destroy context menu items that were merged because they weren't moved from their DOM position.
|
||||
if (this.movedChildren) {
|
||||
this.movedChildren.forEach((child) => {
|
||||
if (child.parentElement) {
|
||||
child.parentElement.removeChild(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.mergedContextMenu) {
|
||||
this.mergedContextMenu.removeMergedItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
|
||||
import { TextInput } from 'ionic-angular';
|
||||
import { CoreDomUtilsProvider } from '../../providers/utils/dom';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Keyboard } from '@ionic-native/keyboard';
|
||||
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
<ion-card>
|
||||
<form #f="ngForm" (ngSubmit)="submitForm(f.value.search)">
|
||||
<form #f="ngForm" (ngSubmit)="submitForm()">
|
||||
<ion-item>
|
||||
<ion-input type="text" name="search" ngModel [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus"></ion-input>
|
||||
<button item-end ion-button clear icon-only type="submit" class="button-small" [attr.aria-label]="searchLabel" [disabled]="!f.value.search || (f.value.search.length < lengthCheck)">
|
||||
<ion-input type="text" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled"></ion-input>
|
||||
<button item-end ion-button clear icon-only type="submit" class="button-small" [attr.aria-label]="searchLabel" [disabled]="!searchText || (searchText.length < lengthCheck)" [disabled]="disabled">
|
||||
<ion-icon name="search"></ion-icon>
|
||||
</button>
|
||||
<button *ngIf="showClear" item-end ion-button clear icon-only class="button-small" [attr.aria-label]="'core.clearsearch' | translate" [disabled]="!searched" (click)="clearForm()" [disabled]="disabled">
|
||||
<ion-icon name="close"></ion-icon>
|
||||
</button>
|
||||
</ion-item>
|
||||
</form>
|
||||
</ion-card>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Component to display a "search box".
|
||||
|
@ -31,23 +31,30 @@ import { CoreUtilsProvider } from '../../providers/utils/utils';
|
|||
templateUrl: 'search-box.html'
|
||||
})
|
||||
export class CoreSearchBoxComponent implements OnInit {
|
||||
@Input() initialValue? = ''; // Initial value for search text.
|
||||
@Input() searchLabel?: string; // Label to be used on action button.
|
||||
@Input() placeholder?: string; // Placeholder text for search text input.
|
||||
@Input() autocorrect? = 'on'; // Enables/disable Autocorrection on search text input.
|
||||
@Input() spellcheck?: string | boolean = true; // Enables/disable Spellchecker on search text input.
|
||||
@Input() autoFocus?: string | boolean; // Enables/disable Autofocus when entering view.
|
||||
@Input() lengthCheck? = 3; // Check value length before submit. If 0, any string will be submitted.
|
||||
@Input() showClear? = true; // Show/hide clear button.
|
||||
@Input() disabled? = false; // Disables the input text.
|
||||
@Output() onSubmit: EventEmitter<string>; // Send data when submitting the search form.
|
||||
@Output() onClear?: EventEmitter<void>; // Send event when clearing the search form.
|
||||
|
||||
searched = false;
|
||||
searchText = '';
|
||||
|
||||
constructor(private translate: TranslateService, private utils: CoreUtilsProvider) {
|
||||
this.onSubmit = new EventEmitter();
|
||||
this.onSubmit = new EventEmitter<string>();
|
||||
this.onClear = new EventEmitter<void>();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.searchLabel = this.searchLabel || this.translate.instant('core.search');
|
||||
this.placeholder = this.placeholder || this.translate.instant('core.search');
|
||||
this.spellcheck = this.utils.isTrueOrOne(this.spellcheck);
|
||||
this.showClear = this.utils.isTrueOrOne(this.showClear);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,11 +63,21 @@ export class CoreSearchBoxComponent implements OnInit {
|
|||
* @param {string} value Entered value.
|
||||
*/
|
||||
submitForm(value: string): void {
|
||||
if (value.length < this.lengthCheck) {
|
||||
if (this.searchText.length < this.lengthCheck) {
|
||||
// The view should handle this case, but we check it here too just in case.
|
||||
return;
|
||||
}
|
||||
|
||||
this.onSubmit.emit(value);
|
||||
this.searched = true;
|
||||
this.onSubmit.emit(this.searchText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submitted.
|
||||
*/
|
||||
clearForm(): void {
|
||||
this.searched = false;
|
||||
this.searchText = '';
|
||||
this.onClear.emit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<form (ngSubmit)="submitForm($event)">
|
||||
<textarea class="core-send-message-input" [core-auto-focus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows [(ngModel)]="message" name="message" (onResize)="textareaResized()"></textarea>
|
||||
<ion-buttons end>
|
||||
<button ion-button icon-only clear="true" type="submit" [disabled]="!message" [attr.aria-label]="'core.send' | translate">
|
||||
<ion-icon name="send" color="dark"></ion-icon>
|
||||
</button>
|
||||
</ion-buttons>
|
||||
</form>
|
|
@ -0,0 +1,36 @@
|
|||
$core-send-message-input-background: $gray;
|
||||
$core-send-message-input-color: $black;
|
||||
|
||||
core-send-message-form {
|
||||
background: $white;
|
||||
|
||||
form {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
flex-shrink: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.core-send-message-input {
|
||||
@include appearance(none);
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
font-family: inherit;
|
||||
align-self: self-start;
|
||||
background: $core-send-message-input-background;
|
||||
color: $core-send-message-input-color;
|
||||
border-radius: 5px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.core-send-message-button {
|
||||
@include margin(0);
|
||||
@include padding(0);
|
||||
display: none;
|
||||
min-height: 0;
|
||||
align-self: self-end;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
|
||||
/**
|
||||
* Component to display a "send message form".
|
||||
*
|
||||
* @description
|
||||
* This component will display a standalone send message form in order to have a better UX.
|
||||
*
|
||||
* Example usage:
|
||||
* <core-send-message-form (onSubmit)="sendMessage($event)" [placeholder]="'core.messages.newmessage' | translate"
|
||||
* [show-keyboard]="showKeyboard"></core-send-message-form>
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-send-message-form',
|
||||
templateUrl: 'send-message-form.html'
|
||||
})
|
||||
export class CoreSendMessageFormComponent implements OnInit {
|
||||
@Input() placeholder = ''; // Placeholder for the input area.
|
||||
@Input() showKeyboard = false; // If keyboard is shown or not.
|
||||
@Output() onSubmit: EventEmitter<string>; // Send data when submitting the message form.
|
||||
@Output() onResize: EventEmitter<void>; // Emit when resizing the textarea.
|
||||
|
||||
message: string;
|
||||
|
||||
constructor(private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider) {
|
||||
this.onSubmit = new EventEmitter();
|
||||
this.onResize = new EventEmitter();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.showKeyboard = this.utils.isTrueOrOne(this.showKeyboard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submitted.
|
||||
* @param {any} $event Form submit
|
||||
*/
|
||||
submitForm($event: any): void {
|
||||
let value = this.message.trim();
|
||||
$event.target.reset();
|
||||
|
||||
// Focus again on textarea.
|
||||
$event.target[0].focus();
|
||||
|
||||
if (!value) {
|
||||
// Silent error.
|
||||
return;
|
||||
}
|
||||
|
||||
value = this.textUtils.replaceNewLines(value, '<br>');
|
||||
this.onSubmit.emit(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Textarea resized.
|
||||
*/
|
||||
textareaResized(): void {
|
||||
this.onResize.emit();
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, AfterViewInit, Input, ElementRef } from '@angular/core';
|
||||
import { CoreUtilsProvider } from '../../providers/utils/utils';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Component to allow showing and hiding a password. The affected input MUST have a name to identify it.
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
|
||||
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreSitesProvider } from '../../providers/sites';
|
||||
import { CoreTextUtilsProvider } from '../../providers/utils/text';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
|
||||
/**
|
||||
* Component to display a site selector. It will display a select with the list of sites. If the selected site changes,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue