commit
eb9ba11d39
|
@ -46,19 +46,6 @@ ion-app.app-root {
|
|||
text-transform: none;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.core-center-view .scroll-content {
|
||||
display: flex!important;
|
||||
align-content: center !important;
|
||||
align-items: center !important;
|
||||
> * {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.hidden-phone {
|
||||
display: none !important;
|
||||
|
|
|
@ -1332,7 +1332,9 @@
|
|||
"core.block.blocks": "Blocks",
|
||||
"core.browser": "Browser",
|
||||
"core.cancel": "Cancel",
|
||||
"core.cannotconnect": "<strong>Cannot connect</strong>: Verify that you have correctly typed your site address.",
|
||||
"core.cannotconnect": "Cannot connect",
|
||||
"core.cannotconnecttrouble": "We're having trouble connecting to your site.",
|
||||
"core.cannotconnectverify": "<strong>Please check the address is correct.</strong>",
|
||||
"core.cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.",
|
||||
"core.captureaudio": "Record audio",
|
||||
"core.capturedimage": "Taken picture.",
|
||||
|
@ -1759,8 +1761,6 @@
|
|||
"core.login.policyagreement": "Site policy agreement",
|
||||
"core.login.policyagreementclick": "Link to site policy agreement",
|
||||
"core.login.potentialidps": "Log in using your account on:",
|
||||
"core.login.problemconnectingerror": "We're having trouble connecting to",
|
||||
"core.login.problemconnectingerrorcontinue": "Double check you've entered the address correctly and try again.",
|
||||
"core.login.profileinvaliddata": "Invalid value",
|
||||
"core.login.recaptchachallengeimage": "reCAPTCHA challenge image",
|
||||
"core.login.recaptchaexpired": "Verification expired. Answer the security question again.",
|
||||
|
@ -1774,7 +1774,7 @@
|
|||
"core.login.selectacountry": "Select a country",
|
||||
"core.login.selectsite": "Please select your site:",
|
||||
"core.login.signupplugindisabled": "{{$a}} is not enabled.",
|
||||
"core.login.siteaddress": "Your site address",
|
||||
"core.login.siteaddress": "Your site",
|
||||
"core.login.sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects, this could be the issue that's preventing the app from connecting to your site.",
|
||||
"core.login.siteinmaintenance": "Your site is in maintenance mode",
|
||||
"core.login.sitepolicynotagreederror": "Site policy not agreed.",
|
||||
|
@ -1788,7 +1788,8 @@
|
|||
"core.login.usernamerequired": "Username required",
|
||||
"core.login.usernotaddederror": "User not added - error",
|
||||
"core.login.visitchangepassword": "Do you want to visit the site to change the password?",
|
||||
"core.login.webservicesnotenabled": "Web services are not enabled in your site. Please contact your site administrator if you think they should be enabled.",
|
||||
"core.login.webservicesnotenabled": "Your host site may not have enabled Web services. Please contact your administrator for help.",
|
||||
"core.login.yourenteredsite": "Connect to your site",
|
||||
"core.lostconnection": "Your authentication token is invalid or has expired. You will have to reconnect to the site.",
|
||||
"core.mainmenu.changesite": "Change site",
|
||||
"core.mainmenu.help": "Help",
|
||||
|
|
|
@ -73,8 +73,6 @@
|
|||
"policyagreement": "Site policy agreement",
|
||||
"policyagreementclick": "Link to site policy agreement",
|
||||
"potentialidps": "Log in using your account on:",
|
||||
"problemconnectingerror": "We're having trouble connecting to",
|
||||
"problemconnectingerrorcontinue": "Double check you've entered the address correctly and try again.",
|
||||
"profileinvaliddata": "Invalid value",
|
||||
"recaptchachallengeimage": "reCAPTCHA challenge image",
|
||||
"recaptchaexpired": "Verification expired. Answer the security question again.",
|
||||
|
@ -88,7 +86,7 @@
|
|||
"selectacountry": "Select a country",
|
||||
"selectsite": "Please select your site:",
|
||||
"signupplugindisabled": "{{$a}} is not enabled.",
|
||||
"siteaddress": "Your site address",
|
||||
"siteaddress": "Your site",
|
||||
"sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects, this could be the issue that's preventing the app from connecting to your site.",
|
||||
"siteinmaintenance": "Your site is in maintenance mode",
|
||||
"sitepolicynotagreederror": "Site policy not agreed.",
|
||||
|
@ -102,5 +100,6 @@
|
|||
"usernamerequired": "Username required",
|
||||
"usernotaddederror": "User not added - error",
|
||||
"visitchangepassword": "Do you want to visit the site to change the password?",
|
||||
"webservicesnotenabled": "Web services are not enabled in your site. Please contact your site administrator if you think they should be enabled."
|
||||
"yourenteredsite": "Connect to your site",
|
||||
"webservicesnotenabled": "Your host site may not have enabled Web services. Please contact your administrator for help."
|
||||
}
|
|
@ -1,11 +1,28 @@
|
|||
$core-login-page-background-color: $white !default;
|
||||
$core-login-page-text-color: $text-color !default;
|
||||
$core-login-button-outline: false !default;
|
||||
$core-login-loading-color: false !default;
|
||||
$core-login-item-inner-background-color: $white !default;
|
||||
$core-login-item-background-color: $white !default;
|
||||
|
||||
// Dark.
|
||||
$core-dark-login-page-background-color: $black !default;
|
||||
$core-dark-login-page-text-color: $core-dark-text-color !default;
|
||||
$core-dark-login-item-inner-background-color: $core-dark-login-page-background-color !default;
|
||||
$core-dark-login-item-background-color: $core-dark-login-page-background-color !default;
|
||||
$core-dark-login-button-outline: $core-login-button-outline !default;
|
||||
$core-dark-login-loading-color: $core-dark-text-color !default;
|
||||
|
||||
ion-app.app-root page-core-login-credentials,
|
||||
ion-app.app-root page-core-login-reconnect,
|
||||
ion-app.app-root page-core-login-site {
|
||||
.scroll-content {
|
||||
background: $core-login-page-background-color;
|
||||
color: $core-login-page-text-color;
|
||||
|
||||
@include darkmode() {
|
||||
background: $core-dark-login-page-background-color;
|
||||
color: $core-dark-login-page-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,35 +30,18 @@ ion-app.app-root page-core-login-site {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
img.login-logo {
|
||||
width: 90%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.box {
|
||||
padding: 16px;
|
||||
margin: 8px;
|
||||
background: $core-login-box-background-color;
|
||||
border: 1px solid $core-login-box-background-border;
|
||||
color: $core-login-box-text-color;
|
||||
|
||||
@include darkmode() {
|
||||
background: $core-dark-login-box-background-color;
|
||||
border-color: $core-dark-login-box-background-border;
|
||||
color: $core-dark-login-box-text-color;
|
||||
}
|
||||
|
||||
.item {
|
||||
@include darkmode() {
|
||||
background: $core-dark-login-box-background-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.core-sitename, .core-siteurl {
|
||||
@if $core-fixed-url { display: none; }
|
||||
}
|
||||
|
||||
.core-sitename + .core-siteurl {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.core-sitename {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
@if $core-login-button-outline {
|
||||
.button-md.button-default-md, .button-ios.button-default-ios {
|
||||
@extend .button-md-light;
|
||||
|
@ -77,4 +77,27 @@ ion-app.app-root page-core-login-site {
|
|||
.item-input {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
ion-list.core-login-forgotten-password {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
a.item {
|
||||
background: transparent;
|
||||
text-decoration: underline;
|
||||
|
||||
@include darkmode() {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.core-login-site-logo {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
img {
|
||||
width: 90%;
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding class="core-center-view">
|
||||
<ion-content padding>
|
||||
<ion-list>
|
||||
<ion-item text-wrap *ngIf="!changingPassword">
|
||||
<h2>{{ 'core.login.forcepasswordchangenotice' | translate }}</h2>
|
||||
|
|
|
@ -9,52 +9,54 @@
|
|||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content class="core-center-view">
|
||||
<ion-content padding>
|
||||
<core-loading [hideUntil]="pageLoaded">
|
||||
<div class="box">
|
||||
<div text-wrap text-center margin-bottom>
|
||||
<div text-wrap text-center margin-bottom>
|
||||
<div class="core-login-site-logo">
|
||||
<!-- Show site logo or a default image. -->
|
||||
<img *ngIf="logoUrl" [src]="logoUrl" role="presentation">
|
||||
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" class="login-logo" role="presentation">
|
||||
|
||||
<!-- If no sitename show big siteurl. -->
|
||||
<p *ngIf="!siteName" padding class="item-heading core-siteurl">{{siteUrl}}</p>
|
||||
<!-- If sitename, show big sitename and small siteurl. -->
|
||||
<p *ngIf="siteName" padding class="item-heading core-sitename"><core-format-text [text]="siteName" [filter]="false"></core-format-text></p>
|
||||
<p *ngIf="siteName" class="core-siteurl">{{siteUrl}}</p>
|
||||
</div>
|
||||
<form ion-list [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #credentialsForm>
|
||||
<ion-item *ngIf="siteChecked && !isBrowserSSO">
|
||||
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}" formControlName="username" autocapitalize="none" autocorrect="off"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="siteChecked && !isBrowserSSO" margin-bottom>
|
||||
<core-show-password item-content [name]="'password'">
|
||||
<ion-input class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" formControlName="password" core-show-password [clearOnEdit]="false"></ion-input>
|
||||
</core-show-password>
|
||||
</ion-item>
|
||||
<button ion-button block [disabled]="siteChecked && !isBrowserSSO && !credForm.valid">{{ 'core.login.loginbutton' | translate }}</button>
|
||||
</form>
|
||||
|
||||
<!-- Forgotten password button. -->
|
||||
<div *ngIf="showForgottenPassword" padding-top class="core-login-forgotten-password">
|
||||
<button ion-button block text-wrap color="light" (click)="forgottenPassword()">{{ 'core.login.forgotten' | translate }}</button>
|
||||
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" role="presentation">
|
||||
</div>
|
||||
|
||||
<ion-list *ngIf="identityProviders && identityProviders.length" padding-top class="core-login-identity-providers">
|
||||
<ion-list-header text-wrap>{{ 'core.login.potentialidps' | translate }}</ion-list-header>
|
||||
<button ion-item *ngFor="let provider of identityProviders" text-wrap class="core-oauth-icon" (click)="oauthClicked(provider)" title="{{provider.name}}">
|
||||
<img [src]="provider.iconurl" alt="" width="32" height="32" item-start>
|
||||
{{provider.name}}
|
||||
</button>
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="canSignup" padding-top class="core-login-sign-up">
|
||||
<ion-list-header text-wrap>{{ 'core.login.firsttime' | translate }}</ion-list-header>
|
||||
<ion-item no-lines text-wrap *ngIf="authInstructions">
|
||||
<p><core-format-text [text]="authInstructions" [filter]="false"></core-format-text></p>
|
||||
</ion-item>
|
||||
<button ion-button block (click)="signup()">{{ 'core.login.startsignup' | translate }}</button>
|
||||
</ion-list>
|
||||
<h3 *ngIf="siteName" padding class="core-sitename"><core-format-text [text]="siteName" [filter]="false"></core-format-text></h3>
|
||||
<p class="core-siteurl">{{siteUrl}}</p>
|
||||
</div>
|
||||
|
||||
<form ion-list [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #credentialsForm>
|
||||
<ion-item *ngIf="siteChecked && !isBrowserSSO">
|
||||
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}" formControlName="username" autocapitalize="none" autocorrect="off"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="siteChecked && !isBrowserSSO" margin-bottom>
|
||||
<core-show-password item-content [name]="'password'">
|
||||
<ion-input class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" formControlName="password" core-show-password [clearOnEdit]="false"></ion-input>
|
||||
</core-show-password>
|
||||
</ion-item>
|
||||
<div padding>
|
||||
<button ion-button block [disabled]="siteChecked && !isBrowserSSO && !credForm.valid" class="core-login-login-button">{{ 'core.login.loginbutton' | translate }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Forgotten password button. -->
|
||||
<ion-list no-lines *ngIf="showForgottenPassword" class="core-login-forgotten-password">
|
||||
<a ion-item text-center text-wrap (click)="forgottenPassword()" detail-none>
|
||||
{{ 'core.login.forgotten' | translate }}
|
||||
</a>
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="identityProviders && identityProviders.length" padding-top class="core-login-identity-providers">
|
||||
<ion-item text-wrap no-lines><h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3></ion-item>
|
||||
<button ion-item *ngFor="let provider of identityProviders" text-wrap class="core-oauth-icon" (click)="oauthClicked(provider)" title="{{provider.name}}">
|
||||
<img [src]="provider.iconurl" alt="" width="32" height="32" item-start>
|
||||
{{provider.name}}
|
||||
</button>
|
||||
</ion-list>
|
||||
|
||||
<ion-list *ngIf="canSignup" padding-top class="core-login-sign-up">
|
||||
<ion-item text-wrap no-lines><h3 class="item-heading">{{ 'core.login.firsttime' | translate }}</h3></ion-item>
|
||||
<ion-item no-lines text-wrap *ngIf="authInstructions">
|
||||
<p><core-format-text [text]="authInstructions" [filter]="false"></core-format-text></p>
|
||||
</ion-item>
|
||||
<button ion-button block color="light" (click)="signup()">{{ 'core.login.startsignup' | translate }}</button>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
ion-app.app-root page-core-login-credentials {
|
||||
.item-input {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
|
@ -65,6 +65,8 @@ export class CoreLoginCredentialsPage {
|
|||
private eventsProvider: CoreEventsProvider) {
|
||||
|
||||
this.siteUrl = navParams.get('siteUrl');
|
||||
this.siteName = navParams.get('siteName') || null;
|
||||
this.logoUrl = navParams.get('logoUrl') || null;
|
||||
this.siteConfig = navParams.get('siteConfig');
|
||||
this.urlToOpen = navParams.get('urlToOpen');
|
||||
|
||||
|
@ -170,8 +172,6 @@ export class CoreLoginCredentialsPage {
|
|||
this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, { config: this.siteConfig });
|
||||
}
|
||||
} else {
|
||||
this.siteName = null;
|
||||
this.logoUrl = null;
|
||||
this.authInstructions = null;
|
||||
this.canSignup = false;
|
||||
this.identityProviders = [];
|
||||
|
|
|
@ -3,72 +3,69 @@
|
|||
<ion-title>{{ 'core.login.reconnect' | translate }}</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content class="core-center-view">
|
||||
<div class="box">
|
||||
<div *ngIf="site" text-wrap text-center margin-bottom [ngClass]="{'item-avatar-center': showSiteAvatar}">
|
||||
<ng-container *ngIf="showSiteAvatar">
|
||||
<ion-avatar>
|
||||
<!-- Show user avatar. -->
|
||||
<img [src]="site.avatar" class="avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullname} }}" role="presentation" onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
</ng-container>
|
||||
<ion-content padding>
|
||||
<div *ngIf="site" text-wrap text-center margin-bottom [ngClass]="{'item-avatar-center': showSiteAvatar}">
|
||||
<ng-container *ngIf="showSiteAvatar">
|
||||
<ion-avatar>
|
||||
<!-- Show user avatar. -->
|
||||
<img [src]="site.avatar" class="avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullname} }}" role="presentation" onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!showSiteAvatar">
|
||||
<!-- Show site logo or a default image. -->
|
||||
<img *ngIf="logoUrl" [src]="logoUrl" core-external-content [siteId]="site.id" role="presentation">
|
||||
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" class="login-logo" role="presentation">
|
||||
</ng-container>
|
||||
|
||||
<!-- If no sitename show big siteurl. -->
|
||||
<p *ngIf="!siteName" class="item-heading core-siteurl">{{siteUrl}}</p>
|
||||
<!-- If sitename, show big sitename and small siteurl. -->
|
||||
<p *ngIf="siteName" class="item-heading core-sitename"><core-format-text [text]="siteName" [filter]="false"></core-format-text></p>
|
||||
<p *ngIf="siteName" class="core-siteurl">{{siteUrl}}</p>
|
||||
|
||||
<p *ngIf="!isLoggedOut">
|
||||
<ion-icon padding name="alert"></ion-icon> {{ 'core.login.reconnectdescription' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<form ion-list *ngIf="!isOAuth" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #reconnectForm>
|
||||
<ion-item text-wrap class="core-username">
|
||||
<p>{{username}}</p>
|
||||
</ion-item>
|
||||
<ion-item margin-bottom>
|
||||
<core-show-password item-content [name]="'password'">
|
||||
<ion-input class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"></ion-input>
|
||||
</core-show-password>
|
||||
</ion-item>
|
||||
<ion-grid no-padding>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<a ion-button block color="light" (click)="cancel($event)">{{ 'core.login.cancel' | translate }}</a>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<button ion-button block [disabled]="!credForm.valid">{{ 'core.login.loginbutton' | translate }}</button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</form>
|
||||
|
||||
<!-- Forgotten password button. -->
|
||||
<div *ngIf="showForgottenPassword && !isOAuth" padding-top class="core-login-forgotten-password">
|
||||
<button ion-button block text-wrap color="light" (click)="forgottenPassword()">{{ 'core.login.forgotten' | translate }}</button>
|
||||
<div class="core-login-site-logo" *ngIf="!showSiteAvatar">
|
||||
<!-- Show site logo or a default image. -->
|
||||
<img *ngIf="logoUrl" [src]="logoUrl" core-external-content [siteId]="siteId" role="presentation">
|
||||
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" role="presentation">
|
||||
</div>
|
||||
|
||||
<!-- Identity providers. -->
|
||||
<ion-list *ngIf="identityProviders && identityProviders.length" padding-top class="core-login-identity-providers">
|
||||
<ion-list-header text-wrap>{{ 'core.login.potentialidps' | translate }}</ion-list-header>
|
||||
<button ion-item *ngFor="let provider of identityProviders" text-wrap class="core-oauth-icon" (click)="oauthClicked(provider)" title="{{provider.name}}">
|
||||
<img [src]="provider.iconurl" alt="" width="32" height="32" item-start>
|
||||
{{provider.name}}
|
||||
</button>
|
||||
</ion-list>
|
||||
<h3 *ngIf="siteName" padding class="core-sitename"><core-format-text [text]="siteName" [filter]="false"></core-format-text></h3>
|
||||
<p class="core-siteurl">{{siteUrl}}</p>
|
||||
|
||||
<!-- If OAuth, display cancel button since the form isn't displayed. -->
|
||||
<ion-list *ngIf="isOAuth">
|
||||
<ion-item>
|
||||
<a ion-button block color="light" (click)="cancel($event)">{{ 'core.login.cancel' | translate }}</a>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<p *ngIf="!isLoggedOut" class="core-login-reconnect-warning">
|
||||
<ion-icon padding name="alert"></ion-icon> {{ 'core.login.reconnectdescription' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<form ion-list *ngIf="!isOAuth" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #reconnectForm>
|
||||
<ion-item text-wrap class="core-username">
|
||||
<p>{{username}}</p>
|
||||
</ion-item>
|
||||
<ion-item margin-bottom>
|
||||
<core-show-password item-content [name]="'password'">
|
||||
<ion-input class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [clearOnEdit]="false"></ion-input>
|
||||
</core-show-password>
|
||||
</ion-item>
|
||||
<ion-grid padding>
|
||||
<ion-row>
|
||||
<ion-col>
|
||||
<a ion-button block color="light" (click)="cancel($event)">{{ 'core.login.cancel' | translate }}</a>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<button ion-button block [disabled]="!credForm.valid">{{ 'core.login.loginbutton' | translate }}</button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</form>
|
||||
|
||||
<!-- Forgotten password button. -->
|
||||
<ion-list no-lines *ngIf="showForgottenPassword && !isOAuth" class="core-login-forgotten-password">
|
||||
<a ion-item text-center text-wrap (click)="forgottenPassword()" detail-none>
|
||||
{{ 'core.login.forgotten' | translate }}
|
||||
</a>
|
||||
</ion-list>
|
||||
|
||||
<!-- Identity providers. -->
|
||||
<ion-list *ngIf="identityProviders && identityProviders.length" padding-top class="core-login-identity-providers">
|
||||
<ion-item text-wrap><h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3></ion-item>
|
||||
<button ion-item *ngFor="let provider of identityProviders" text-wrap class="core-oauth-icon" (click)="oauthClicked(provider)" title="{{provider.name}}">
|
||||
<img [src]="provider.iconurl" alt="" width="32" height="32" item-start>
|
||||
{{provider.name}}
|
||||
</button>
|
||||
</ion-list>
|
||||
|
||||
<!-- If OAuth, display cancel button since the form isn't displayed. -->
|
||||
<ion-list *ngIf="isOAuth">
|
||||
<ion-item>
|
||||
<a ion-button block color="light" (click)="cancel($event)">{{ 'core.login.cancel' | translate }}</a>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
|
|
@ -30,7 +30,7 @@ ion-app.app-root page-core-login-reconnect {
|
|||
}
|
||||
}
|
||||
|
||||
.item-input {
|
||||
margin-bottom: 20px;
|
||||
.core-login-reconnect-warning {
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
<ion-header>
|
||||
<ion-navbar core-back-button>
|
||||
<ion-title>{{ 'core.error' | translate }}</ion-title>
|
||||
|
||||
<ion-buttons end>
|
||||
<button ion-button icon-only (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||
<ion-icon name="close"></ion-icon>
|
||||
</button>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<h3>{{ 'core.whoops' | translate }}</h3>
|
||||
<p>{{ 'core.login.problemconnectingerror' | translate }}</p>
|
||||
<p padding>{{siteUrl}}</p>
|
||||
<p>{{ 'core.login.problemconnectingerrorcontinue' | translate }}</p>
|
||||
<button ion-button block (click)="closeModal()">{{ 'core.tryagain' | translate }}</button>
|
||||
<h3>{{ 'core.login.stillcantconnect' | translate }}</h3>
|
||||
<p>{{ 'core.login.contactyouradministrator' | translate }}</p>
|
||||
<p *ngIf="issue">
|
||||
{{ 'core.login.contactyouradministratorissue' | translate:{$a: ''} }}
|
||||
</p>
|
||||
<p *ngIf="issue" margin-bottom>
|
||||
<core-format-text [text]="issue" [filter]="false"></core-format-text>
|
||||
</p>
|
||||
</ion-content>
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { CoreLoginSiteErrorPage } from './site-error';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreLoginSiteErrorPage
|
||||
],
|
||||
imports: [
|
||||
CoreDirectivesModule,
|
||||
IonicPageModule.forChild(CoreLoginSiteErrorPage),
|
||||
TranslateModule.forChild()
|
||||
]
|
||||
})
|
||||
export class CoreLoginSiteErrorPageModule {}
|
|
@ -1,3 +0,0 @@
|
|||
page-core-login-site-error button.button.button-block {
|
||||
margin-bottom: 3rem;
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { IonicPage, ViewController, NavParams } from 'ionic-angular';
|
||||
|
||||
/**
|
||||
* Component that displays an error when trying to connect to a site.
|
||||
*/
|
||||
@IonicPage({ segment: 'core-login-site-error' })
|
||||
@Component({
|
||||
selector: 'page-core-login-site-error',
|
||||
templateUrl: 'site-error.html',
|
||||
})
|
||||
export class CoreLoginSiteErrorPage {
|
||||
siteUrl: string;
|
||||
issue: string;
|
||||
|
||||
constructor(private viewCtrl: ViewController, params: NavParams) {
|
||||
this.siteUrl = params.get('siteUrl');
|
||||
this.issue = params.get('issue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Close modal.
|
||||
*/
|
||||
closeModal(): void {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
}
|
|
@ -9,9 +9,9 @@
|
|||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content class="core-center-view">
|
||||
<div class="box">
|
||||
<div text-center padding>
|
||||
<ion-content padding>
|
||||
<div>
|
||||
<div text-center padding margin-bottom [class.hidden]="hasSites" class="core-login-site-logo">
|
||||
<img src="assets/img/login_logo.png" class="avatar-full login-logo" role="presentation">
|
||||
</div>
|
||||
<form ion-list [formGroup]="siteForm" (ngSubmit)="connect($event, siteForm.value.siteUrl)" *ngIf="!fixedSites || fixedDisplay == 'select'" #siteFormEl>
|
||||
|
@ -19,19 +19,36 @@
|
|||
<ng-container *ngIf="!fixedSites">
|
||||
<ion-item>
|
||||
<ion-label stacked><h2>{{ 'core.login.siteaddress' | translate }}</h2></ion-label>
|
||||
<ion-input type="url" name="url" placeholder="https://campus.example.edu" formControlName="siteUrl" [core-auto-focus]="showKeyboard"></ion-input>
|
||||
<ion-input type="url" name="url" placeholder="https://campus.example.edu" formControlName="siteUrl" [core-auto-focus]="showKeyboard" (ionChange)="searchSite($event, siteForm.value.siteUrl)"></ion-input>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
|
||||
<ion-list *ngIf="!fixedSites" [class.hidden]="!hasSites" class="core-login-site-list" [class.dimmed]="loadingSites">
|
||||
<div *ngIf="loadingSites" class="core-login-site-list-loading"><ion-spinner></ion-spinner></div>
|
||||
<ion-item no-lines class="core-login-site-list-title" *ngIf="onlyWrittenSite"><h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2></ion-item>
|
||||
<ion-item *ngFor="let site of sites" (click)="connect($event, site.url, site)" [title]="site.name" detail-push [class.core-login-entered-site]="!site.fromWS">
|
||||
<ion-thumbnail item-start>
|
||||
<core-icon name="fa-pencil" *ngIf="!site.imageurl && !site.fromWS"></core-icon>
|
||||
<img [src]="site.imageurl" *ngIf="site.imageurl">
|
||||
<img src="assets/icon/icon.png" *ngIf="!site.imageurl && site.fromWS" class="core-login-default-icon">
|
||||
</ion-thumbnail>
|
||||
<h2 text-wrap>{{site.name}}<ng-container *ngIf="site.alias"> ({{site.alias}})</ng-container></h2>
|
||||
<p>{{site.noProtocolUrl}}</p>
|
||||
<p *ngIf="site.country || site.city" text-wrap><ng-container *ngIf="site.city">{{site.city}} - </ng-container>{{site.country}}</p>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<div *ngIf="!fixedSites && !hasSites && loadingSites" class="core-login-site-nolist-loading"><ion-spinner></ion-spinner></div>
|
||||
|
||||
<!-- Pick the site from a list of fixed sites. -->
|
||||
<ion-item *ngIf="fixedSites && fixedDisplay == 'select'" margin-vertical text-wrap>
|
||||
<ion-label stacked for="siteSelect">{{ 'core.login.selectsite' | translate }}</ion-label>
|
||||
<ion-select formControlName="siteUrl" name="url" placeholder="{{ 'core.login.siteaddress' | translate }}" interface="action-sheet">
|
||||
<ion-option *ngFor="let site of fixedSites" [value]="site.url">{{site.name}}</ion-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<button ion-button block [disabled]="!siteForm.valid">{{ 'core.login.connect' | translate }}</button>
|
||||
</form>
|
||||
|
||||
<!-- Pick the site from a list of fixed sites. -->
|
||||
<ion-list *ngIf="fixedSites && (fixedDisplay == 'list' || fixedDisplay == 'listnourl')">
|
||||
<ion-item no-lines><h2 class="item-heading">{{ 'core.login.selectsite' | translate }}</h2></ion-item>
|
||||
|
@ -48,33 +65,9 @@
|
|||
<a *ngFor="let site of fixedSites" ion-button block (click)="connect($event, site.url)" [title]="site.name" margin-bottom>{{site.name}}</a>
|
||||
</div>
|
||||
|
||||
<!-- Error. -->
|
||||
<div padding-top *ngIf="error" >
|
||||
<ion-card class="core-site-error">
|
||||
<ion-card-header>
|
||||
{{ 'core.whoops' | translate }}
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<p><core-format-text [text]="error.message" [filter]="false"></core-format-text></p>
|
||||
<ng-container *ngIf="error.url">
|
||||
<p>{{ 'core.login.problemconnectingerror' | translate }}</p>
|
||||
<p padding><a [href]="error.fullUrl" core-link>{{ error.url }}</a></p>
|
||||
<p><strong>{{ 'core.login.problemconnectingerrorcontinue' | translate }}</strong></p>
|
||||
</ng-container>
|
||||
</ion-card-content>
|
||||
<ion-card-header>
|
||||
{{ 'core.login.stillcantconnect' | translate }}
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<p>{{ 'core.login.contactyouradministrator' | translate }}</p>
|
||||
<p>{{ 'core.whoissiteadmin' | translate }}</p>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</div>
|
||||
|
||||
<!-- Help. -->
|
||||
<ion-list no-lines>
|
||||
<a ion-item text-center class="core-login-need-help" (click)="showHelp()" detail-none>
|
||||
<ion-list no-lines margin-top>
|
||||
<a ion-item text-center text-wrap class="core-login-need-help" (click)="showHelp()" detail-none>
|
||||
{{ 'core.needhelp' | translate }}
|
||||
</a>
|
||||
</ion-list>
|
||||
|
|
|
@ -11,23 +11,108 @@ ion-app.app-root page-core-login-site {
|
|||
}
|
||||
}
|
||||
|
||||
.core-site-error {
|
||||
background: $red-light;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
width: 100%;
|
||||
user-select: text;
|
||||
|
||||
p, ion-card-header {
|
||||
color: $red-dark;
|
||||
user-select: text;
|
||||
}
|
||||
ion-card-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.core-login-need-help {
|
||||
.core-login-need-help.item {
|
||||
background: transparent;
|
||||
text-decoration: underline;
|
||||
|
||||
@include darkmode() {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.core-login-site-connect {
|
||||
margin-top: 1.4rem;
|
||||
}
|
||||
|
||||
.item ion-thumbnail {
|
||||
min-width: 50px;
|
||||
min-height: 50px;
|
||||
border-radius: 20%;
|
||||
box-shadow: 0 0 4px #eee;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
ion-icon {
|
||||
margin: 0 auto;
|
||||
font-size: 40px;
|
||||
line-height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.core-login-site-logo,
|
||||
.core-login-site-list {
|
||||
transition-delay: 0s;
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: all 0.7s ease-in-out;
|
||||
max-height: 9999px;
|
||||
|
||||
&.hidden {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.core-login-site-list.dimmed {
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.core-login-site-list-loading {
|
||||
position: absolute;
|
||||
@include position(0, 0, 0, 0);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
z-index: 1;
|
||||
ion-spinner {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.core-login-site-nolist-loading {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item.core-login-site-list-title {
|
||||
ion-label, ion-label h2.item-heading {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.scroll-content > * {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
.core-login-site-logo {
|
||||
margin-top: 20%;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.core-login-entered-site {
|
||||
background-color: $gray-lighter;
|
||||
ion-thumbnail {
|
||||
box-shadow: 0 0 4px #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.core-login-default-icon {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,36 +13,26 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavController, ModalController, NavParams } from 'ionic-angular';
|
||||
import { IonicPage, NavController, ModalController, AlertController, NavParams } from 'ionic-angular';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider, CoreSiteCheckResponse } from '@providers/sites';
|
||||
import { CoreSitesProvider, CoreSiteCheckResponse, CoreLoginSiteInfo } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||
import { CoreConfigConstants } from '../../../../configconstants';
|
||||
import { CoreLoginHelperProvider } from '../../providers/helper';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FormBuilder, FormGroup, ValidatorFn, AbstractControl } from '@angular/forms';
|
||||
import { CoreUrl } from '@singletons/url';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Data about an error when connecting to a site.
|
||||
* Extended data for UI implementation.
|
||||
*/
|
||||
type CoreLoginSiteError = {
|
||||
/**
|
||||
* The error message that ocurred.
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* URL the user entered.
|
||||
*/
|
||||
url?: string;
|
||||
|
||||
/**
|
||||
* URL the user entered with protocol added if needed.
|
||||
*/
|
||||
fullUrl?: string;
|
||||
type CoreLoginSiteInfoExtended = CoreLoginSiteInfo & {
|
||||
fromWS?: boolean; // If the site came from the WS call.
|
||||
noProtocolUrl?: string; // Url wihtout protocol.
|
||||
country?: string; // Based on countrycode.
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -58,12 +48,16 @@ export class CoreLoginSitePage {
|
|||
@ViewChild('siteFormEl') formElement: ElementRef;
|
||||
|
||||
siteForm: FormGroup;
|
||||
fixedSites: any[];
|
||||
filteredSites: any[];
|
||||
fixedSites: CoreLoginSiteInfo[];
|
||||
filteredSites: CoreLoginSiteInfo[];
|
||||
fixedDisplay = 'buttons';
|
||||
showKeyboard = false;
|
||||
filter = '';
|
||||
error: CoreLoginSiteError;
|
||||
sites: CoreLoginSiteInfoExtended[] = [];
|
||||
hasSites = false;
|
||||
loadingSites = false;
|
||||
onlyWrittenSite = false;
|
||||
searchFnc: Function;
|
||||
|
||||
constructor(navParams: NavParams,
|
||||
protected navCtrl: NavController,
|
||||
|
@ -72,10 +66,12 @@ export class CoreLoginSitePage {
|
|||
protected sitesProvider: CoreSitesProvider,
|
||||
protected loginHelper: CoreLoginHelperProvider,
|
||||
protected modalCtrl: ModalController,
|
||||
protected alertCtrl: AlertController,
|
||||
protected urlUtils: CoreUrlUtilsProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected translate: TranslateService,
|
||||
protected urlUtils: CoreUrlUtilsProvider) {
|
||||
protected utils: CoreUtilsProvider) {
|
||||
|
||||
this.showKeyboard = !!navParams.get('showKeyboard');
|
||||
|
||||
|
@ -94,8 +90,44 @@ export class CoreLoginSitePage {
|
|||
}
|
||||
|
||||
this.siteForm = fb.group({
|
||||
siteUrl: [url, Validators.required]
|
||||
siteUrl: [url, this.moodleUrlValidator()]
|
||||
});
|
||||
|
||||
this.searchFnc = this.utils.debounce(async (search: string, isValid: boolean = false) => {
|
||||
search = search.trim();
|
||||
|
||||
if (search.length >= 3) {
|
||||
this.onlyWrittenSite = false;
|
||||
|
||||
// Update the sites list.
|
||||
this.sites = await this.sitesProvider.findSites(search);
|
||||
|
||||
// UI tweaks.
|
||||
this.sites.forEach((site) => {
|
||||
site.noProtocolUrl = CoreUrl.removeProtocol(site.url);
|
||||
site.fromWS = true;
|
||||
site.country = this.utils.getCountryName(site.countrycode);
|
||||
});
|
||||
|
||||
// If it's a valid URL, add it.
|
||||
if (isValid) {
|
||||
this.onlyWrittenSite = !!this.sites.length;
|
||||
this.sites.unshift({
|
||||
url: search,
|
||||
fromWS: false,
|
||||
name: this.translate.instant('core.login.yourenteredsite'),
|
||||
noProtocolUrl: CoreUrl.removeProtocol(search),
|
||||
});
|
||||
}
|
||||
|
||||
this.hasSites = !!this.sites.length;
|
||||
} else {
|
||||
// Not reseting the array to allow animation to be displayed.
|
||||
this.hasSites = false;
|
||||
}
|
||||
|
||||
this.loadingSites = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,8 +135,9 @@ export class CoreLoginSitePage {
|
|||
*
|
||||
* @param e Event.
|
||||
* @param url The URL to connect to.
|
||||
* @param foundSite The site clicked, if any, from the found sites list.
|
||||
*/
|
||||
connect(e: Event, url: string): void {
|
||||
connect(e: Event, url: string, foundSite?: CoreLoginSiteInfoExtended): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -130,8 +163,6 @@ export class CoreLoginSitePage {
|
|||
return;
|
||||
}
|
||||
|
||||
this.hideLoginIssue();
|
||||
|
||||
const modal = this.domUtils.showModalLoading(),
|
||||
siteData = this.sitesProvider.getDemoSiteData(url);
|
||||
|
||||
|
@ -174,7 +205,7 @@ export class CoreLoginSitePage {
|
|||
|
||||
return Promise.reject(error);
|
||||
})
|
||||
.then((result) => this.login(result))
|
||||
.then((result) => this.login(result, foundSite))
|
||||
.catch((error) => this.showLoginIssue(url, error))
|
||||
.finally(() => modal.dismiss());
|
||||
}
|
||||
|
@ -204,13 +235,6 @@ export class CoreLoginSitePage {
|
|||
modal.present();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the login error.
|
||||
*/
|
||||
protected hideLoginIssue(): void {
|
||||
this.error = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error that aims people to solve the issue.
|
||||
*
|
||||
|
@ -218,13 +242,60 @@ export class CoreLoginSitePage {
|
|||
* @param error Error to display.
|
||||
*/
|
||||
protected showLoginIssue(url: string, error: any): void {
|
||||
this.error = {
|
||||
url: url,
|
||||
message: this.domUtils.getErrorMessage(error),
|
||||
};
|
||||
error = this.domUtils.getErrorMessage(error);
|
||||
|
||||
if (error == this.translate.instant('core.cannotconnecttrouble')) {
|
||||
const found = this.sites.find((site) => site.fromWS && site.url == url);
|
||||
|
||||
if (!found) {
|
||||
error += ' ' + this.translate.instant('core.cannotconnectverify');
|
||||
}
|
||||
}
|
||||
|
||||
let message = '<p>' + error + '</p>';
|
||||
if (url) {
|
||||
this.error.fullUrl = this.urlUtils.isAbsoluteURL(url) ? url : 'https://' + url;
|
||||
const fullUrl = this.urlUtils.isAbsoluteURL(url) ? url : 'https://' + url;
|
||||
message += '<p padding><a href="' + fullUrl + '" core-link>' + url + '</a></p>';
|
||||
}
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
text: this.translate.instant('core.needhelp'),
|
||||
handler: (): void => {
|
||||
this.showHelp();
|
||||
}
|
||||
},
|
||||
{
|
||||
text: this.translate.instant('core.tryagain'),
|
||||
role: 'cancel'
|
||||
}
|
||||
];
|
||||
|
||||
this.domUtils.showAlertWithButtons(this.translate.instant('core.cannotconnect'), message, buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a site on the backend.
|
||||
*
|
||||
* @param e Event.
|
||||
* @param search Text to search.
|
||||
*/
|
||||
searchSite(e: Event, search: string): void {
|
||||
this.loadingSites = true;
|
||||
|
||||
this.searchFnc(search.trim(), this.siteForm.valid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the demo data for a certain "name" if it is a demo site.
|
||||
*
|
||||
* @param name Name of the site to check.
|
||||
* @return Site data if it's a demo site, undefined otherwise.
|
||||
*/
|
||||
getDemoSiteData(name: string): any {
|
||||
const demoSites = CoreConfigConstants.demo_sites;
|
||||
if (typeof demoSites != 'undefined' && typeof demoSites[name] != 'undefined') {
|
||||
return demoSites[name];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,10 +303,11 @@ export class CoreLoginSitePage {
|
|||
* Process login to a site.
|
||||
*
|
||||
* @param response Response obtained from the site check request.
|
||||
* @param foundSite The site clicked, if any, from the found sites list.
|
||||
*
|
||||
* @return Promise resolved after logging in.
|
||||
*/
|
||||
protected async login(response: CoreSiteCheckResponse): Promise<void> {
|
||||
protected async login(response: CoreSiteCheckResponse, foundSite?: CoreLoginSiteInfoExtended): Promise<void> {
|
||||
return this.sitesProvider.checkRequiredMinimumVersion(response.config).then(() => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement, true);
|
||||
|
@ -249,11 +321,39 @@ export class CoreLoginSitePage {
|
|||
this.loginHelper.confirmAndOpenBrowserForSSOLogin(
|
||||
response.siteUrl, response.code, response.service, response.config && response.config.launchurl);
|
||||
} else {
|
||||
this.navCtrl.push('CoreLoginCredentialsPage', { siteUrl: response.siteUrl, siteConfig: response.config });
|
||||
const pageParams = { siteUrl: response.siteUrl, siteConfig: response.config };
|
||||
if (foundSite) {
|
||||
pageParams['siteName'] = foundSite.name;
|
||||
pageParams['logoUrl'] = foundSite.imageurl;
|
||||
}
|
||||
|
||||
this.navCtrl.push('CoreLoginCredentialsPage', pageParams);
|
||||
}
|
||||
}).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Url.
|
||||
*
|
||||
* @return {ValidatorFn} Validation results.
|
||||
*/
|
||||
protected moodleUrlValidator(): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} | null => {
|
||||
const value = control.value.trim();
|
||||
let valid = value.length >= 3 && CoreUrl.isValidMoodleUrl(value);
|
||||
|
||||
if (!valid) {
|
||||
const demo = !!this.getDemoSiteData(value);
|
||||
|
||||
if (demo) {
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
return valid ? null : {siteUrl: {value: control.value}};
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
"back": "Back",
|
||||
"browser": "Browser",
|
||||
"cancel": "Cancel",
|
||||
"cannotconnect": "<strong>Cannot connect</strong>: Verify that you have correctly typed your site address.",
|
||||
"cannotconnect": "Cannot connect",
|
||||
"cannotconnecttrouble": "We're having trouble connecting to your site.",
|
||||
"cannotconnectverify": "<strong>Please check the address is correct.</strong>",
|
||||
"cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.",
|
||||
"captureaudio": "Record audio",
|
||||
"capturedimage": "Taken picture.",
|
||||
|
|
|
@ -165,6 +165,41 @@ export interface CoreSiteSchema {
|
|||
migrate?(db: SQLiteDB, oldVersion: number, siteId: string): Promise<any> | void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data about sites to be listed.
|
||||
*/
|
||||
export interface CoreLoginSiteInfo {
|
||||
/**
|
||||
* Site name.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Site alias.
|
||||
*/
|
||||
alias?: string;
|
||||
|
||||
/**
|
||||
* URL of the site.
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* Image URL of the site.
|
||||
*/
|
||||
imageurl?: string;
|
||||
|
||||
/**
|
||||
* City of the site.
|
||||
*/
|
||||
city?: string;
|
||||
|
||||
/**
|
||||
* Countrycode of the site.
|
||||
*/
|
||||
countrycode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered site schema.
|
||||
*/
|
||||
|
@ -367,10 +402,17 @@ export class CoreSitesProvider {
|
|||
]
|
||||
};
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private http: HttpClient, private sitesFactory: CoreSitesFactoryProvider,
|
||||
private appProvider: CoreAppProvider, private translate: TranslateService, private urlUtils: CoreUrlUtilsProvider,
|
||||
private eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider,
|
||||
private utils: CoreUtilsProvider, private injector: Injector, private wsProvider: CoreWSProvider,
|
||||
constructor(logger: CoreLoggerProvider,
|
||||
protected http: HttpClient,
|
||||
protected sitesFactory: CoreSitesFactoryProvider,
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected translate: TranslateService,
|
||||
protected urlUtils: CoreUrlUtilsProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
protected utils: CoreUtilsProvider,
|
||||
protected injector: Injector,
|
||||
protected wsProvider: CoreWSProvider,
|
||||
protected domUtils: CoreDomUtilsProvider) {
|
||||
this.logger = logger.getInstance('CoreSitesProvider');
|
||||
|
||||
|
@ -431,7 +473,7 @@ export class CoreSitesProvider {
|
|||
} else if (this.textUtils.getErrorMessageFromError(secondError)) {
|
||||
return Promise.reject(secondError);
|
||||
} else {
|
||||
return this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION});
|
||||
return this.translate.instant('core.cannotconnecttrouble');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -523,8 +565,7 @@ export class CoreSitesProvider {
|
|||
error.error = this.translate.instant('core.login.sitehasredirect');
|
||||
} else {
|
||||
// We can't be sure if there is a redirect or not. Display cannot connect error.
|
||||
error.error = this.translate.instant('core.cannotconnect',
|
||||
{$a: CoreSite.MINIMUM_MOODLE_VERSION});
|
||||
error.error = this.translate.instant('core.cannotconnecttrouble');
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
|
@ -569,7 +610,7 @@ export class CoreSitesProvider {
|
|||
return this.http.post(siteUrl + '/login/token.php', {}).timeout(this.wsProvider.getRequestTimeout()).toPromise()
|
||||
.catch(() => {
|
||||
// Default error messages are kinda bad, return our own message.
|
||||
return Promise.reject({error: this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION})});
|
||||
return Promise.reject({error: this.translate.instant('core.cannotconnecttrouble')});
|
||||
}).then((data: any) => {
|
||||
|
||||
if (data === null) {
|
||||
|
@ -616,7 +657,7 @@ export class CoreSitesProvider {
|
|||
|
||||
return promise.then((data: any): any => {
|
||||
if (typeof data == 'undefined') {
|
||||
return Promise.reject(this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION}));
|
||||
return Promise.reject(this.translate.instant('core.cannotconnecttrouble'));
|
||||
} else {
|
||||
if (typeof data.token != 'undefined') {
|
||||
return { token: data.token, siteUrl: siteUrl, privateToken: data.privatetoken };
|
||||
|
@ -648,7 +689,7 @@ export class CoreSitesProvider {
|
|||
}
|
||||
}
|
||||
}, () => {
|
||||
return Promise.reject(this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION}));
|
||||
return Promise.reject(this.translate.instant('core.cannotconnecttrouble'));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1931,6 +1972,16 @@ export class CoreSitesProvider {
|
|||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns site info found on the backend.
|
||||
*
|
||||
* @param search Searched text.
|
||||
* @return Site info list.
|
||||
*/
|
||||
async findSites(search: string): Promise<CoreLoginSiteInfo[]> {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export class CoreSites extends makeSingleton(CoreSitesProvider) {}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import { Injectable, SimpleChange, ElementRef } from '@angular/core';
|
||||
import {
|
||||
LoadingController, Loading, ToastController, Toast, AlertController, Alert, Platform, Content, PopoverController,
|
||||
ModalController,
|
||||
ModalController, AlertButton
|
||||
} from 'ionic-angular';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
@ -1138,65 +1138,76 @@ export class CoreDomUtilsProvider {
|
|||
* @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
||||
* @return Promise resolved with the alert modal.
|
||||
*/
|
||||
showAlert(title: string, message: string, buttonText?: string, autocloseTime?: number): Promise<CoreAlert> {
|
||||
async showAlert(title: string, message: string, buttonText?: string, autocloseTime?: number): Promise<CoreAlert> {
|
||||
const buttons = [buttonText || this.translate.instant('core.ok')];
|
||||
|
||||
return this.showAlertWithButtons(title, message, buttons, autocloseTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an alert modal with some buttons.
|
||||
*
|
||||
* @param title Title to show.
|
||||
* @param message Message to show.
|
||||
* @param buttons Buttons objects or texts.
|
||||
* @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
||||
* @return Promise resolved with the alert modal.
|
||||
*/
|
||||
async showAlertWithButtons(title: string, message: string, buttons: (string | AlertButton)[], autocloseTime?: number):
|
||||
Promise<CoreAlert> {
|
||||
const hasHTMLTags = this.textUtils.hasHTMLTags(message);
|
||||
let promise;
|
||||
|
||||
if (hasHTMLTags) {
|
||||
// Format the text.
|
||||
promise = this.textUtils.formatText(message);
|
||||
} else {
|
||||
promise = Promise.resolve(message);
|
||||
message = await this.textUtils.formatText(message);
|
||||
}
|
||||
|
||||
return promise.then((message) => {
|
||||
const alertId = <string> Md5.hashAsciiStr((title || '') + '#' + (message || ''));
|
||||
const alertId = <string> Md5.hashAsciiStr((title || '') + '#' + (message || ''));
|
||||
|
||||
if (this.displayedAlerts[alertId]) {
|
||||
// There's already an alert with the same message and title. Return it.
|
||||
return this.displayedAlerts[alertId];
|
||||
}
|
||||
if (this.displayedAlerts[alertId]) {
|
||||
// There's already an alert with the same message and title. Return it.
|
||||
return this.displayedAlerts[alertId];
|
||||
}
|
||||
|
||||
const alert: CoreAlert = <any> this.alertCtrl.create({
|
||||
title: title,
|
||||
message: message,
|
||||
buttons: [buttonText || this.translate.instant('core.ok')]
|
||||
});
|
||||
|
||||
alert.present().then(() => {
|
||||
if (hasHTMLTags) {
|
||||
// Treat all anchors so they don't override the app.
|
||||
const alertMessageEl: HTMLElement = alert.pageRef().nativeElement.querySelector('.alert-message');
|
||||
this.treatAnchors(alertMessageEl);
|
||||
}
|
||||
});
|
||||
|
||||
// Store the alert and remove it when dismissed.
|
||||
this.displayedAlerts[alertId] = alert;
|
||||
|
||||
// Define the observables to extend the Alert class. This will allow several callbacks instead of just one.
|
||||
alert.didDismiss = new Subject();
|
||||
alert.willDismiss = new Subject();
|
||||
|
||||
// Set the callbacks to trigger an observable event.
|
||||
alert.onDidDismiss((data: any, role: string) => {
|
||||
delete this.displayedAlerts[alertId];
|
||||
|
||||
alert.didDismiss.next({data: data, role: role});
|
||||
});
|
||||
|
||||
alert.onWillDismiss((data: any, role: string) => {
|
||||
alert.willDismiss.next({data: data, role: role});
|
||||
});
|
||||
|
||||
if (autocloseTime > 0) {
|
||||
setTimeout(() => {
|
||||
alert.dismiss();
|
||||
}, autocloseTime);
|
||||
}
|
||||
|
||||
return alert;
|
||||
const alert: CoreAlert = <any> this.alertCtrl.create({
|
||||
title: title,
|
||||
message: message,
|
||||
buttons: buttons,
|
||||
});
|
||||
|
||||
alert.present().then(() => {
|
||||
if (hasHTMLTags) {
|
||||
// Treat all anchors so they don't override the app.
|
||||
const alertMessageEl: HTMLElement = alert.pageRef().nativeElement.querySelector('.alert-message');
|
||||
this.treatAnchors(alertMessageEl);
|
||||
}
|
||||
});
|
||||
|
||||
// Store the alert and remove it when dismissed.
|
||||
this.displayedAlerts[alertId] = alert;
|
||||
|
||||
// Define the observables to extend the Alert class. This will allow several callbacks instead of just one.
|
||||
alert.didDismiss = new Subject();
|
||||
alert.willDismiss = new Subject();
|
||||
|
||||
// Set the callbacks to trigger an observable event.
|
||||
alert.onDidDismiss((data: any, role: string) => {
|
||||
delete this.displayedAlerts[alertId];
|
||||
|
||||
alert.didDismiss.next({data: data, role: role});
|
||||
});
|
||||
|
||||
alert.onWillDismiss((data: any, role: string) => {
|
||||
alert.willDismiss.next({data: data, role: role});
|
||||
});
|
||||
|
||||
if (autocloseTime > 0) {
|
||||
setTimeout(() => {
|
||||
alert.dismiss();
|
||||
}, autocloseTime);
|
||||
}
|
||||
|
||||
return alert;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -225,7 +225,7 @@ export class CoreUrlUtilsProvider {
|
|||
url = 'https://' + url;
|
||||
}
|
||||
|
||||
// http allways in lowercase.
|
||||
// http always in lowercase.
|
||||
url = url.replace(/^http/i, 'http');
|
||||
url = url.replace(/^https/i, 'https');
|
||||
|
||||
|
|
|
@ -119,4 +119,36 @@ export class CoreUrl {
|
|||
return urlParts && urlParts.domain ? urlParts.domain : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pattern to check if the URL is a valid Moodle Url.
|
||||
*
|
||||
* @return {RegExp} Desired RegExp.
|
||||
*/
|
||||
static getValidMoodleUrlPattern(): RegExp {
|
||||
// Regular expression based on RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B.
|
||||
// Improved to not admit spaces.
|
||||
return new RegExp(/^(([^:/?# ]+):)?(\/\/([^/?# ]*))?([^?# ]*)(\?([^#]*))?(#(.*))?$/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given url is valid for the app to connect.
|
||||
*
|
||||
* @param {string} url Url to check.
|
||||
* @return {boolean} True if valid, false otherwise.
|
||||
*/
|
||||
static isValidMoodleUrl(url: string): boolean {
|
||||
const patt = CoreUrl.getValidMoodleUrlPattern();
|
||||
|
||||
return patt.test(url.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes protocol from the url.
|
||||
*
|
||||
* @param url Site url.
|
||||
* @return Url without protocol.
|
||||
*/
|
||||
static removeProtocol(url: string): string {
|
||||
return url.replace(/^[a-zA-Z]+:\/\//i, '');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,16 +6,6 @@ $core-dark-item-bg-color: $gray-darker !default;
|
|||
$core-dark-item-divider-bg-color: $gray-dark !default;
|
||||
$core-dark-background-color: $black !default;
|
||||
|
||||
// Login.
|
||||
$core-dark-login-page-background-color: radial-gradient(white, $gray-dark) !default;
|
||||
$core-dark-login-box-background-color: $black !default;
|
||||
$core-dark-login-box-background-border: $core-login-box-background-border !default;
|
||||
$core-dark-login-box-text-color: $core-dark-text-color !default;
|
||||
$core-dark-login-item-inner-background-color: $core-dark-login-box-background-color !default;
|
||||
$core-dark-login-item-background-color: $core-dark-login-box-background-color !default;
|
||||
$core-dark-login-button-outline: $core-login-button-outline !default;
|
||||
$core-dark-login-loading-color: $core-dark-text-color !default;
|
||||
|
||||
ion-app.app-root {
|
||||
@include darkmode() {
|
||||
ion-action-sheet .action-sheet-container .action-sheet-group .action-sheet-button {
|
||||
|
|
|
@ -184,16 +184,6 @@ $core-button-outline-background-color: $white !default;
|
|||
|
||||
$core-network-message-height: 16px !default;
|
||||
|
||||
// Login.
|
||||
$core-login-page-background-color: radial-gradient(white, $gray-light) !default;
|
||||
$core-login-box-background-color: $white !default;
|
||||
$core-login-box-background-border: $gray !default;
|
||||
$core-login-box-text-color: $text-color !default;
|
||||
$core-login-button-outline: false !default;
|
||||
$core-login-loading-color: false !default;
|
||||
$core-login-item-inner-background-color: $white !default;
|
||||
$core-login-item-background-color: $white !default;
|
||||
|
||||
$core-action-sheet-color: $core-color !default;
|
||||
$core-action-sheet-cancel-color: $danger !default;
|
||||
$core-dark-action-sheet-cancel-color: $danger-dark !default;
|
||||
|
|
Loading…
Reference in New Issue