diff --git a/src/app/app.scss b/src/app/app.scss index 34fa86dbd..81f002012 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -119,3 +119,8 @@ ion-icon.icon-accessory { padding-bottom: 10px; font-style: italic; } + +.content { + // Set bgcolor in content instead of overriding $background-color because that variable is applied to a lot of places. + background-color: $gray-light; +} diff --git a/src/core/login/pages/sites/sites.html b/src/core/login/pages/sites/sites.html new file mode 100644 index 000000000..138e2158b --- /dev/null +++ b/src/core/login/pages/sites/sites.html @@ -0,0 +1,30 @@ + + + {{ 'mm.settings.sites' | translate }} + + + + + + + + + + + + {{ 'mm.core.pictureof' | translate:{$a: site.fullname} }} + +

{{site.fullName}}

+

+

{{site.siteUrl}}

+ {{site.badge}} + +
+
+
diff --git a/src/core/login/pages/sites/sites.module.ts b/src/core/login/pages/sites/sites.module.ts new file mode 100644 index 000000000..52f9e6601 --- /dev/null +++ b/src/core/login/pages/sites/sites.module.ts @@ -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 { CoreLoginSitesPage } from './sites'; +import { CoreLoginModule } from '../../login.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; + +@NgModule({ + declarations: [ + CoreLoginSitesPage, + ], + imports: [ + CoreDirectivesModule, + CoreLoginModule, + IonicPageModule.forChild(CoreLoginSitesPage), + TranslateModule.forChild() + ], +}) +export class CoreLoginSitesPageModule {} diff --git a/src/core/login/pages/sites/sites.scss b/src/core/login/pages/sites/sites.scss new file mode 100644 index 000000000..7bb74215b --- /dev/null +++ b/src/core/login/pages/sites/sites.scss @@ -0,0 +1,3 @@ +page-core-login-sites { + +} diff --git a/src/core/login/pages/sites/sites.ts b/src/core/login/pages/sites/sites.ts new file mode 100644 index 000000000..cbbbc498d --- /dev/null +++ b/src/core/login/pages/sites/sites.ts @@ -0,0 +1,150 @@ +// (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 { IonicPage, NavController } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreLoggerProvider } from '../../../../providers/logger'; +import { CoreSitesProvider, CoreSiteBasicInfo } from '../../../../providers/sites'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; +import { CoreLoginHelperProvider } from '../../providers/helper'; + +/** + * Page that displays the list of stored sites. + */ +@IonicPage() +@Component({ + selector: 'page-core-login-sites', + templateUrl: 'sites.html', +}) +export class CoreLoginSitesPage { + sites: CoreSiteBasicInfo[]; + showDelete: boolean; + protected logger; + + constructor(private navCtrl: NavController, private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider, + private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, + private translate: TranslateService, logger: CoreLoggerProvider) { + this.logger = logger.getInstance('CoreLoginSitesPage'); + } + + /** + * View loaded. + */ + ionViewDidLoad() { + this.sitesProvider.getSites().then((sites) => { + // Remove protocol from the url to show more url text. + sites = sites.map((site) => { + site.siteUrl = site.siteUrl.replace(/^https?:\/\//, ''); + site.badge = 10; + // @todo: Implement it once push notifications addon is implemented. + // if ($mmaPushNotifications) { + // $mmaPushNotifications.getSiteCounter(site.id).then(function(number) { + // site.badge = number; + // }); + // } + return site; + }); + + // Sort sites by url and fullname. + this.sites = sites.sort((a, b) => { + // First compare by site url without the protocol. + let compareA = a.siteUrl.toLowerCase(), + compareB = b.siteUrl.toLowerCase(), + compare = compareA.localeCompare(compareB); + + if (compare !== 0) { + return compare; + } + + // If site url is the same, use fullname instead. + compareA = a.fullName.toLowerCase().trim(); + compareB = b.fullName.toLowerCase().trim(); + return compareA.localeCompare(compareB); + }); + + this.showDelete = false; + }).catch(() => { + // Shouldn't happen. + }); + } + + /** + * Go to the page to add a site. + */ + add() : void { + this.loginHelper.goToAddSite(this.navCtrl, false); + } + + /** + * Delete a site. + * + * @param {Event} e Click event. + * @param {number} index Position of the site. + */ + deleteSite(e: Event, index: number) : void { + e.stopPropagation(); + + let site = this.sites[index], + siteName = site.siteName; + + this.textUtils.formatText(siteName).then((siteName) => { + this.domUtils.showConfirm(this.translate.instant('mm.login.confirmdeletesite', {sitename: siteName})).then(() => { + this.sitesProvider.deleteSite(site.id).then(() => { + this.sites.splice(index, 1); + this.showDelete = false; + + // If there are no sites left, go to add site. + this.sitesProvider.hasNoSites().then(() => { + this.loginHelper.goToAddSite(this.navCtrl, true); + }); + }).catch((error) => { + this.logger.error('Error deleting site ' + site.id, error); + this.domUtils.showErrorModalDefault(error, 'Delete site failed.'); + this.domUtils.showErrorModal('mm.login.errordeletesite', true); + }); + }).catch(() => { + // User cancelled, nothing to do. + }); + }); + } + + /** + * Login in a site. + * + * @param {string} siteId The site ID. + */ + login(siteId: string) : void { + let modal = this.domUtils.showModalLoading(); + + this.sitesProvider.loadSite(siteId).then(() => { + if (!this.loginHelper.isSiteLoggedOut()) { + return this.loginHelper.goToSiteInitialPage(this.navCtrl, true); + } + }).catch((error) => { + this.logger.error('Error loading site ' + siteId, error); + this.domUtils.showErrorModalDefault(error, 'Error loading site.'); + }).finally(() => { + modal.dismiss(); + }); + } + + /** + * Toggle delete. + */ + toggleDelete() : void { + this.showDelete = !this.showDelete; + } +} diff --git a/src/directives/external-content.ts b/src/directives/external-content.ts index e89d550e5..a91ff93ce 100644 --- a/src/directives/external-content.ts +++ b/src/directives/external-content.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Directive, Input, OnInit, ElementRef } from '@angular/core'; +import { Directive, Input, AfterViewInit, ElementRef } from '@angular/core'; import { Platform } from 'ionic-angular'; import { CoreAppProvider } from '../providers/app'; import { CoreLoggerProvider } from '../providers/logger'; @@ -32,7 +32,7 @@ import { CoreUrlUtilsProvider } from '../providers/utils/url'; @Directive({ selector: '[core-external-content]' }) -export class CoreExternalContentDirective implements OnInit { +export class CoreExternalContentDirective implements AfterViewInit { @Input() siteId?: string; // Site ID to use. @Input() component?: string; // Component to link the file to. @Input() componentId?: string|number; // Component ID to use in conjunction with the component. @@ -49,9 +49,9 @@ export class CoreExternalContentDirective implements OnInit { } /** - * Function executed when the component is initialized. + * View has been initialized */ - ngOnInit() { + ngAfterViewInit() { let currentSite = this.sitesProvider.getCurrentSite(), siteId = this.siteId || (currentSite && currentSite.getId()), targetAttr, diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 5c84bf3b7..bcedffd61 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -88,7 +88,7 @@ export class CoreFormatTextDirective implements OnInit { extContent.componentId = this.componentId; extContent.siteId = this.siteId; - extContent.ngOnInit(); + extContent.ngAfterViewInit(); } /** diff --git a/src/lang/en.json b/src/lang/en.json index 415978003..3b9f8ba52 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -1,5 +1,6 @@ { "accounts": "Accounts", + "add": "Add", "allparticipants": "All participants", "android": "Android", "areyousure": "Are you sure?", diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index 1dfb5166b..da1208c79 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -1435,7 +1435,14 @@ export class CoreFilepoolProvider { protected getFileUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string|number, mode = 'url', timemodified = 0, checkSize = true, downloadUnknown?: boolean, options: any = {}) : Promise { let fileId, - revision; + revision, + addToQueue = (fileUrl) => { + // Add the file to queue if needed and ignore errors. + this.addToQueueIfNeeded(siteId, fileUrl, component, componentId, timemodified, checkSize, + downloadUnknown, options).catch(() => { + // Ignore errors. + }); + }; return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { fileUrl = fixedUrl; @@ -1447,14 +1454,12 @@ export class CoreFilepoolProvider { if (typeof entry === 'undefined') { // We do not have the file, add it to the queue, and return real URL. - this.addToQueueIfNeeded( - siteId, fileUrl, component, componentId, timemodified, checkSize, downloadUnknown, options); + addToQueue(fileUrl); response = fileUrl; } else if (this.isFileOutdated(entry, revision, timemodified) && this.appProvider.isOnline()) { // The file is outdated, we add to the queue and return real URL. - this.addToQueueIfNeeded( - siteId, fileUrl, component, componentId, timemodified, checkSize, downloadUnknown, options); + addToQueue(fileUrl); response = fileUrl; } else { // We found the file entry, now look for the file on disk. @@ -1471,8 +1476,7 @@ export class CoreFilepoolProvider { // We could not retrieve the file, delete the entries associated with that ID. this.logger.debug('File ' + fileId + ' not found on disk'); this.removeFileById(siteId, fileId); - this.addToQueueIfNeeded( - siteId, fileUrl, component, componentId, timemodified, checkSize, downloadUnknown, options); + addToQueue(fileUrl); if (this.appProvider.isOnline()) { // We still have a chance to serve the right content. @@ -1486,8 +1490,7 @@ export class CoreFilepoolProvider { return response; }, () => { // We do not have the file in store yet. Add to queue and return the fixed URL. - this.addToQueueIfNeeded( - siteId, fileUrl, component, componentId, timemodified, checkSize, downloadUnknown, options); + addToQueue(fileUrl); return fileUrl; }); }); diff --git a/src/providers/sites.ts b/src/providers/sites.ts index bdbb7007b..7ad1545c6 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -47,6 +47,7 @@ export interface CoreSiteBasicInfo { fullName: string; siteName: string; avatar: string; + badge?: number; }; /* @@ -750,17 +751,23 @@ export class CoreSitesProvider { * @param {String[]} [ids] IDs of the sites to get. If not defined, return all sites. * @return {Promise} Promise resolved when the sites are retrieved. */ - getSites(ids: string[]) : Promise { + getSites(ids?: string[]) : Promise { return this.appDB.getAllRecords(this.SITES_TABLE).then((sites) => { let formattedSites = []; sites.forEach((site) => { if (!ids || ids.indexOf(site.id) > -1) { + // Try to parse info. + let siteInfo = site.info; + try { + siteInfo = siteInfo ? JSON.parse(siteInfo) : siteInfo; + } catch(ex) {} + const basicInfo: CoreSiteBasicInfo = { id: site.id, siteUrl: site.siteUrl, - fullName: site.info.fullname, - siteName: site.info.sitename, - avatar: site.info.userpictureurl + fullName: siteInfo && siteInfo.fullname, + siteName: siteInfo && siteInfo.sitename, + avatar: siteInfo && siteInfo.userpictureurl }; formattedSites.push(basicInfo); }