commit
4a70a972e2
|
@ -3,7 +3,7 @@
|
||||||
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
||||||
</ion-navbar>
|
</ion-navbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content [class.has-fab]="showUpload && root != 'site' && !path">
|
||||||
<ion-refresher [enabled]="filesLoaded && (showPrivateFiles || showSiteFiles)" (ionRefresh)="refreshData($event)">
|
<ion-refresher [enabled]="filesLoaded && (showPrivateFiles || showSiteFiles)" (ionRefresh)="refreshData($event)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
|
|
@ -95,5 +95,10 @@
|
||||||
<a (click)="searchReset()">{{ 'addon.mod_data.resetsettings' | translate}}</a>
|
<a (click)="searchReset()">{{ 'addon.mod_data.resetsettings' | translate}}</a>
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
|
|
||||||
|
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
||||||
|
<ion-fab bottom right *ngIf="canAdd">
|
||||||
|
<button ion-fab (click)="gotoAddEntries($event)" [attr.aria-label]="'addon.mod_data.addentries' | translate">
|
||||||
|
<ion-icon name="add"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-fab>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
<core-split-view>
|
<core-split-view>
|
||||||
<ion-content>
|
<ion-content [class.has-fab]="forum && forum.cancreatediscussions">
|
||||||
<ion-refresher [enabled]="loaded" (ionRefresh)="doRefresh($event)">
|
<ion-refresher [enabled]="loaded" (ionRefresh)="doRefresh($event)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
@ -25,11 +25,6 @@
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
<ng-container *ngIf="forum && discussions.length > 0">
|
<ng-container *ngIf="forum && discussions.length > 0">
|
||||||
<div padding-horizontal margin-vertical *ngIf="forum.cancreatediscussions">
|
|
||||||
<button ion-button block (click)="openNewDiscussion()">
|
|
||||||
{{addDiscussionText}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<ion-card *ngFor="let discussion of offlineDiscussions" (click)="openNewDiscussion(discussion.timecreated)" [class.addon-forum-discussion-selected]="discussion.timecreated == -selectedDiscussion">
|
<ion-card *ngFor="let discussion of offlineDiscussions" (click)="openNewDiscussion(discussion.timecreated)" [class.addon-forum-discussion-selected]="discussion.timecreated == -selectedDiscussion">
|
||||||
<ion-item text-wrap>
|
<ion-item text-wrap>
|
||||||
<ion-avatar item-start core-user-link [userId]="discussion.userid" [courseId]="courseId">
|
<ion-avatar item-start core-user-link [userId]="discussion.userid" [courseId]="courseId">
|
||||||
|
@ -97,5 +92,11 @@
|
||||||
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
<ion-infinite-scroll-content></ion-infinite-scroll-content>
|
||||||
</ion-infinite-scroll>
|
</ion-infinite-scroll>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
||||||
|
<ion-fab bottom right *ngIf="forum && forum.cancreatediscussions">
|
||||||
|
<button ion-fab (click)="openNewDiscussion()" [attr.aria-label]="addDiscussionText">
|
||||||
|
<ion-icon name="add"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-fab>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</core-split-view>
|
</core-split-view>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
|
||||||
<core-tabs [selectedIndex]="selectedTab">
|
<core-tabs [hideUntil]="loaded" [selectedIndex]="selectedTab">
|
||||||
<!-- Page contents. -->
|
<!-- Page contents. -->
|
||||||
<core-tab [title]="'addon.mod_wiki.viewpage' | translate" icon="document">
|
<core-tab [title]="'addon.mod_wiki.viewpage' | translate" icon="document">
|
||||||
<ng-template>
|
<ng-template>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { NgModule, COMPILER_OPTIONS } from '@angular/core';
|
import { NgModule, COMPILER_OPTIONS } from '@angular/core';
|
||||||
import { IonicApp, IonicModule, Platform, Content, ScrollEvent } from 'ionic-angular';
|
import { IonicApp, IonicModule, Platform, Content, ScrollEvent, Config } from 'ionic-angular';
|
||||||
import { assert } from 'ionic-angular/util/util';
|
import { assert } from 'ionic-angular/util/util';
|
||||||
import { HttpModule } from '@angular/http';
|
import { HttpModule } from '@angular/http';
|
||||||
import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
|
@ -26,6 +26,7 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||||
|
|
||||||
import { MoodleMobileApp } from './app.component';
|
import { MoodleMobileApp } from './app.component';
|
||||||
import { CoreInterceptor } from '@classes/interceptor';
|
import { CoreInterceptor } from '@classes/interceptor';
|
||||||
|
import { CorePageTransition } from '@classes/page-transition';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreDbProvider } from '@providers/db';
|
import { CoreDbProvider } from '@providers/db';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
@ -154,7 +155,7 @@ export const CORE_PROVIDERS: any[] = [
|
||||||
HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.
|
HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content.
|
||||||
HttpModule,
|
HttpModule,
|
||||||
IonicModule.forRoot(MoodleMobileApp, {
|
IonicModule.forRoot(MoodleMobileApp, {
|
||||||
pageTransition: 'ios-transition'
|
pageTransition: 'core-page-transition'
|
||||||
}),
|
}),
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
|
@ -257,7 +258,7 @@ export const CORE_PROVIDERS: any[] = [
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
constructor(platform: Platform, initDelegate: CoreInitDelegate, updateManager: CoreUpdateManagerProvider,
|
constructor(platform: Platform, initDelegate: CoreInitDelegate, updateManager: CoreUpdateManagerProvider, config: Config,
|
||||||
sitesProvider: CoreSitesProvider, fileProvider: CoreFileProvider) {
|
sitesProvider: CoreSitesProvider, fileProvider: CoreFileProvider) {
|
||||||
// Register a handler for platform ready.
|
// Register a handler for platform ready.
|
||||||
initDelegate.registerProcess({
|
initDelegate.registerProcess({
|
||||||
|
@ -289,6 +290,9 @@ export class AppModule {
|
||||||
// Execute the init processes.
|
// Execute the init processes.
|
||||||
initDelegate.executeInitProcesses();
|
initDelegate.executeInitProcesses();
|
||||||
|
|
||||||
|
// Set transition animation.
|
||||||
|
config.setTransition('core-page-transition', CorePageTransition);
|
||||||
|
|
||||||
// Decorate ion-content.
|
// Decorate ion-content.
|
||||||
this.decorateIonContent();
|
this.decorateIonContent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,7 +370,13 @@ ion-col ion-select {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-button-select {
|
||||||
|
ion-icon:last-child {
|
||||||
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// File uploader.
|
// File uploader.
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
@ -677,6 +683,16 @@ canvas[core-chart] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-fab .scroll-content{
|
||||||
|
padding-bottom: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-content ion-fab {
|
||||||
|
position: fixed;
|
||||||
|
margin-bottom: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// For some reason, in iOS the pages don't inherit the background-color from ion-app, set it explicitly.
|
// For some reason, in iOS the pages don't inherit the background-color from ion-app, set it explicitly.
|
||||||
.ion-page {
|
.ion-page {
|
||||||
background-color: $background-color;
|
background-color: $background-color;
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
// (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 { Animation } from 'ionic-angular/animations/animation';
|
||||||
|
import { isPresent } from 'ionic-angular/util/util';
|
||||||
|
import { PageTransition } from 'ionic-angular/transitions/page-transition';
|
||||||
|
|
||||||
|
const DURATION = 500;
|
||||||
|
const EASING = 'cubic-bezier(0.36,0.66,0.04,1)';
|
||||||
|
const OPACITY = 'opacity';
|
||||||
|
const TRANSFORM = 'transform';
|
||||||
|
const TRANSLATEX = 'translateX';
|
||||||
|
const CENTER = '0%';
|
||||||
|
const OFF_OPACITY = 0.8;
|
||||||
|
const SHOW_BACK_BTN_CSS = 'show-back-button';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class overrides the default transition to avoid glitches with new tabs and split view.
|
||||||
|
* Is based on IOSTransition class but it has some changes:
|
||||||
|
* - The animation is done to the full page not header, footer and content separetely.
|
||||||
|
* - On the Navbar only the back button is animated (title and other buttons will be done as a whole). Otherwise back button won't
|
||||||
|
* appear.
|
||||||
|
*/
|
||||||
|
export class CorePageTransition extends PageTransition {
|
||||||
|
init(): void {
|
||||||
|
super.init();
|
||||||
|
const plt = this.plt;
|
||||||
|
const OFF_RIGHT = plt.isRTL ? '-99.5%' : '99.5%';
|
||||||
|
const OFF_LEFT = plt.isRTL ? '33%' : '-33%';
|
||||||
|
const enteringView = this.enteringView;
|
||||||
|
const leavingView = this.leavingView;
|
||||||
|
const opts = this.opts;
|
||||||
|
this.duration(isPresent(opts.duration) ? opts.duration : DURATION);
|
||||||
|
this.easing(isPresent(opts.easing) ? opts.easing : EASING);
|
||||||
|
const backDirection = (opts.direction === 'back');
|
||||||
|
const enteringHasNavbar = (enteringView && enteringView.hasNavbar());
|
||||||
|
const leavingHasNavbar = (leavingView && leavingView.hasNavbar());
|
||||||
|
if (enteringView) {
|
||||||
|
// Get the native element for the entering page.
|
||||||
|
const enteringPageEle = enteringView.pageRef().nativeElement;
|
||||||
|
// Entering content.
|
||||||
|
const enteringContent = new Animation(plt, enteringPageEle);
|
||||||
|
this.add(enteringContent);
|
||||||
|
if (backDirection) {
|
||||||
|
// Entering content, back direction.
|
||||||
|
enteringContent
|
||||||
|
.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true)
|
||||||
|
.fromTo(OPACITY, OFF_OPACITY, 1, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Entering content, forward direction.
|
||||||
|
enteringContent
|
||||||
|
.beforeClearStyles([OPACITY])
|
||||||
|
.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
|
||||||
|
}
|
||||||
|
if (enteringHasNavbar) {
|
||||||
|
// Entering page has a navbar.
|
||||||
|
const enteringNavbarEle = enteringPageEle.querySelector('ion-navbar');
|
||||||
|
const enteringNavBar = new Animation(plt, enteringNavbarEle);
|
||||||
|
this.add(enteringNavBar);
|
||||||
|
const enteringBackButton = new Animation(plt, enteringNavbarEle.querySelector('.back-button'));
|
||||||
|
enteringNavBar
|
||||||
|
.add(enteringBackButton);
|
||||||
|
// Set properties depending on direction.
|
||||||
|
if (backDirection) {
|
||||||
|
// Entering navbar, back direction.
|
||||||
|
if (enteringView.enableBack()) {
|
||||||
|
// Back direction, entering page has a back button.
|
||||||
|
enteringBackButton
|
||||||
|
.beforeAddClass(SHOW_BACK_BTN_CSS)
|
||||||
|
.fromTo(OPACITY, 0.01, 1, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Entering navbar, forward direction.
|
||||||
|
if (enteringView.enableBack()) {
|
||||||
|
// Forward direction, entering page has a back button.
|
||||||
|
enteringBackButton
|
||||||
|
.beforeAddClass(SHOW_BACK_BTN_CSS)
|
||||||
|
.fromTo(OPACITY, 0.01, 1, true);
|
||||||
|
const enteringBackBtnText = new Animation(plt, enteringNavbarEle.querySelector('.back-button-text'));
|
||||||
|
enteringBackBtnText.fromTo(TRANSLATEX, (plt.isRTL ? '-100px' : '100px'), '0px');
|
||||||
|
enteringNavBar.add(enteringBackBtnText);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Setup leaving view.
|
||||||
|
if (leavingView && leavingView.pageRef()) {
|
||||||
|
// Leaving content.
|
||||||
|
const leavingPageEle = leavingView.pageRef().nativeElement;
|
||||||
|
const leavingContent = new Animation(plt, leavingPageEle);
|
||||||
|
this.add(leavingContent);
|
||||||
|
if (backDirection) {
|
||||||
|
// Leaving content, back direction.
|
||||||
|
leavingContent
|
||||||
|
.beforeClearStyles([OPACITY])
|
||||||
|
.fromTo(TRANSLATEX, CENTER, (plt.isRTL ? '-100%' : '100%'));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Leaving content, forward direction.
|
||||||
|
leavingContent
|
||||||
|
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
|
||||||
|
.fromTo(OPACITY, 1, OFF_OPACITY)
|
||||||
|
.afterClearStyles([TRANSFORM, OPACITY]);
|
||||||
|
}
|
||||||
|
if (leavingHasNavbar) {
|
||||||
|
// Leaving page has a navbar.
|
||||||
|
const leavingNavbarEle = leavingPageEle.querySelector('ion-navbar');
|
||||||
|
const leavingNavBar = new Animation(plt, leavingNavbarEle);
|
||||||
|
const leavingBackButton = new Animation(plt, leavingNavbarEle.querySelector('.back-button'));
|
||||||
|
leavingNavBar
|
||||||
|
.add(leavingBackButton);
|
||||||
|
this.add(leavingNavBar);
|
||||||
|
// Fade out leaving navbar items.
|
||||||
|
leavingBackButton.fromTo(OPACITY, 0.99, 0);
|
||||||
|
if (backDirection) {
|
||||||
|
const leavingBackBtnText = new Animation(plt, leavingNavbarEle.querySelector('.back-button-text'));
|
||||||
|
leavingBackBtnText.fromTo(TRANSLATEX, CENTER, (plt.isRTL ? -300 : 300) + 'px');
|
||||||
|
leavingNavBar.add(leavingBackBtnText);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Leaving navbar, forward direction.
|
||||||
|
leavingBackButton.afterClearStyles([OPACITY]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ core-empty-box {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.core-empty-box-inline {
|
&.core-empty-box-inline {
|
||||||
|
|
|
@ -5,18 +5,18 @@
|
||||||
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand -->
|
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand -->
|
||||||
<div #decorate class="core-rte-toolbar">
|
<div #decorate class="core-rte-toolbar">
|
||||||
<div class="core-rte-buttons">
|
<div class="core-rte-buttons">
|
||||||
<button data-command="bold"><strong>B</strong></button>
|
<button data-command="bold"><core-icon name="fa-bold"></core-icon></button>
|
||||||
<button data-command="italic"><i>I</i></button>
|
<button data-command="italic"><core-icon name="fa-italic"></core-icon></button>
|
||||||
<button data-command="underline"><u>U</u></button>
|
<button data-command="underline"><core-icon name="fa-underline"></core-icon></button>
|
||||||
<button data-command="formatBlock|<p>">Normal</button>
|
<button data-command="strikeThrough"><core-icon name="fa-strikethrough"></core-icon></button>
|
||||||
<button data-command="formatBlock|<h1>">H1</button>
|
<button data-command="formatBlock|<p>"><core-icon name="fa-paragraph"></core-icon></button>
|
||||||
<button data-command="formatBlock|<h2>">H2</button>
|
<button data-command="formatBlock|<h1>"><core-icon name="fa-header"></core-icon>1</button>
|
||||||
<button data-command="formatBlock|<h3>">H3</button>
|
<button data-command="formatBlock|<h2>"><core-icon name="fa-header"></core-icon>2</button>
|
||||||
<button data-command="formatBlock|<pre>"><pre></button>
|
<button data-command="formatBlock|<h3>"><core-icon name="fa-header"></core-icon>3</button>
|
||||||
<button data-command="insertOrderedList"><ion-icon name="list" md="ios-list"></ion-icon></button>
|
<button data-command="insertUnorderedList"><core-icon name="fa-list-ul"></core-icon></button>
|
||||||
<button data-command="insertUnorderedList">1,2,3</button>
|
<button data-command="insertOrderedList"><core-icon name="fa-list-ol"></core-icon></button>
|
||||||
<button data-command="removeFormat"><ion-icon name="brush"></ion-icon></button>
|
<button data-command="removeFormat"><core-icon name="fa-eraser"></core-icon></button>
|
||||||
<button (click)="toggleEditor($event)"><ion-icon name="eye-off"></ion-icon> {{ 'core.viewcode' | translate }}</button>
|
<button (click)="toggleEditor($event)"><core-icon name="fa-code"></core-icon> {{ 'core.viewcode' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
<ion-textarea #textarea class="core-textarea" [placeholder]="placeholder" [attr.name]="name" ngControl="control" (ionChange)="onChange($event)"></ion-textarea>
|
<ion-textarea #textarea class="core-textarea" [placeholder]="placeholder" [attr.name]="name" ngControl="control" (ionChange)="onChange($event)"></ion-textarea>
|
||||||
<div class="core-rte-toolbar">
|
<div class="core-rte-toolbar">
|
||||||
<div #decorate class="core-rte-buttons">
|
<div #decorate class="core-rte-buttons">
|
||||||
<button tappable (click)="toggleEditor($event)"><ion-icon name="eye"></ion-icon> {{ 'core.vieweditor' | translate }}</button>
|
<button tappable (click)="toggleEditor($event)"><core-icon name="fa-pencil-square-o"></core-icon> {{ 'core.vieweditor' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -39,6 +39,11 @@ core-rich-text-editor {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make empty elements selectable (to move the cursor).
|
||||||
|
*:empty:after {
|
||||||
|
content: '\200B';
|
||||||
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style-type: disc;
|
list-style-type: disc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
this.editorElement.onkeyup = this.onChange.bind(this);
|
this.editorElement.onkeyup = this.onChange.bind(this);
|
||||||
this.editorElement.onpaste = this.onChange.bind(this);
|
this.editorElement.onpaste = this.onChange.bind(this);
|
||||||
this.editorElement.oninput = this.onChange.bind(this);
|
this.editorElement.oninput = this.onChange.bind(this);
|
||||||
|
this.editorElement.onkeydown = this.moveCursor.bind(this);
|
||||||
|
|
||||||
// Listen for changes on the control to update the editor (if it is updated from outside of this component).
|
// Listen for changes on the control to update the editor (if it is updated from outside of this component).
|
||||||
this.valueChangeSubscription = this.control.valueChanges.subscribe((param) => {
|
this.valueChangeSubscription = this.control.valueChanges.subscribe((param) => {
|
||||||
|
@ -116,17 +117,28 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use paragraph on enter.
|
||||||
|
document.execCommand('DefaultParagraphSeparator', false, 'p');
|
||||||
|
|
||||||
this.treatExternalContent();
|
this.treatExternalContent();
|
||||||
|
|
||||||
this.resizeFunction = this.maximizeEditorSize.bind(this);
|
this.resizeFunction = this.maximizeEditorSize.bind(this);
|
||||||
window.addEventListener('resize', this.resizeFunction);
|
window.addEventListener('resize', this.resizeFunction);
|
||||||
setTimeout(this.resizeFunction, 1000);
|
|
||||||
|
let i = 0;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const height = this.maximizeEditorSize();
|
||||||
|
if (i >= 5 || height != 0) {
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}, 750);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resize editor to maximize the space occupied.
|
* Resize editor to maximize the space occupied.
|
||||||
*/
|
*/
|
||||||
protected maximizeEditorSize(): void {
|
protected maximizeEditorSize(): number {
|
||||||
this.content.resize();
|
this.content.resize();
|
||||||
const contentVisibleHeight = this.content.contentHeight;
|
const contentVisibleHeight = this.content.contentHeight;
|
||||||
|
|
||||||
|
@ -138,7 +150,11 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
} else {
|
} else {
|
||||||
this.element.style.height = '';
|
this.element.style.height = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return contentVisibleHeight - height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -195,6 +211,132 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
this.contentChanged.emit(this.control.value);
|
this.contentChanged.emit(this.control.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On key down function to move the cursor.
|
||||||
|
* https://stackoverflow.com/questions/6249095/how-to-set-caretcursor-position-in-contenteditable-element-div
|
||||||
|
*
|
||||||
|
* @param {Event} $event The event.
|
||||||
|
*/
|
||||||
|
moveCursor($event: Event): void {
|
||||||
|
if (!this.rteEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($event['key'] != 'ArrowLeft' && $event['key'] != 'ArrowRight') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event.preventDefault();
|
||||||
|
$event.stopPropagation();
|
||||||
|
|
||||||
|
const move = $event['key'] == 'ArrowLeft' ? -1 : +1,
|
||||||
|
cursor = this.getCurrentCursorPosition(this.editorElement);
|
||||||
|
|
||||||
|
this.setCurrentCursorPosition(this.editorElement, cursor + move);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of chars from the beggining where is placed the cursor.
|
||||||
|
*
|
||||||
|
* @param {Node} parent Parent where to get the position from.
|
||||||
|
* @return {number} Position in chars.
|
||||||
|
*/
|
||||||
|
protected getCurrentCursorPosition(parent: Node): number {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
|
||||||
|
let charCount = -1,
|
||||||
|
node;
|
||||||
|
|
||||||
|
if (selection.focusNode) {
|
||||||
|
if (parent.contains(selection.focusNode)) {
|
||||||
|
node = selection.focusNode;
|
||||||
|
charCount = selection.focusOffset;
|
||||||
|
|
||||||
|
while (node) {
|
||||||
|
if (node.isSameNode(parent)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.previousSibling) {
|
||||||
|
node = node.previousSibling;
|
||||||
|
charCount += node.textContent.length;
|
||||||
|
} else {
|
||||||
|
node = node.parentNode;
|
||||||
|
if (node === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return charCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the caret position on the character number.
|
||||||
|
*
|
||||||
|
* @param {Node} parent Parent where to set the position.
|
||||||
|
* @param {number} [chars] Number of chars where to place the caret. If not defined it will go to the end.
|
||||||
|
*/
|
||||||
|
protected setCurrentCursorPosition(parent: Node, chars?: number): void {
|
||||||
|
/**
|
||||||
|
* Loops round all the child text nodes within the supplied node and sets a range from the start of the initial node to
|
||||||
|
* the characters.
|
||||||
|
*
|
||||||
|
* @param {Node} node Node where to start.
|
||||||
|
* @param {Range} range Previous calculated range.
|
||||||
|
* @param {any} chars Object with counting of characters (input-output param).
|
||||||
|
* @return {Range} Selection range.
|
||||||
|
*/
|
||||||
|
const setRange = (node: Node, range: Range, chars: any): Range => {
|
||||||
|
if (chars.count === 0) {
|
||||||
|
range.setEnd(node, 0);
|
||||||
|
} else if (node && chars.count > 0) {
|
||||||
|
if (node.hasChildNodes()) {
|
||||||
|
// Navigate through children.
|
||||||
|
for (let lp = 0; lp < node.childNodes.length; lp++) {
|
||||||
|
range = setRange(node.childNodes[lp], range, chars);
|
||||||
|
|
||||||
|
if (chars.count === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (node.textContent.length < chars.count) {
|
||||||
|
// Jump this node.
|
||||||
|
// @todo: empty nodes will be omitted.
|
||||||
|
chars.count -= node.textContent.length;
|
||||||
|
} else {
|
||||||
|
// The cursor will be placed in this element.
|
||||||
|
range.setEnd(node, chars.count);
|
||||||
|
chars.count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return range;
|
||||||
|
};
|
||||||
|
|
||||||
|
let range = document.createRange();
|
||||||
|
if (typeof chars === 'undefined') {
|
||||||
|
// Select all so it will go to the end.
|
||||||
|
range.selectNode(parent);
|
||||||
|
range.selectNodeContents(parent);
|
||||||
|
} else if (chars < 0 || chars > parent.textContent.length) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
range.selectNode(parent);
|
||||||
|
range.setStart(parent, 0);
|
||||||
|
range = setRange(parent, range, {count: chars});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (range) {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
range.collapse(false);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle from rte editor to textarea syncing values.
|
* Toggle from rte editor to textarea syncing values.
|
||||||
*
|
*
|
||||||
|
@ -204,7 +346,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
$event.preventDefault();
|
$event.preventDefault();
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
|
|
||||||
if (this.isNullOrWhiteSpace(this.control.value)) {
|
const isNull = this.isNullOrWhiteSpace(this.control.value);
|
||||||
|
if (isNull) {
|
||||||
this.clearText();
|
this.clearText();
|
||||||
} else {
|
} else {
|
||||||
this.editorElement.innerHTML = this.control.value;
|
this.editorElement.innerHTML = this.control.value;
|
||||||
|
@ -217,14 +360,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.rteEnabled) {
|
if (this.rteEnabled) {
|
||||||
this.editorElement.focus();
|
this.editorElement.focus();
|
||||||
|
this.setCurrentCursorPosition(this.editorElement.firstChild);
|
||||||
const range = document.createRange();
|
|
||||||
range.selectNodeContents(this.editorElement);
|
|
||||||
range.collapse(false);
|
|
||||||
|
|
||||||
const sel = window.getSelection();
|
|
||||||
sel.removeAllRanges();
|
|
||||||
sel.addRange(range);
|
|
||||||
} else {
|
} else {
|
||||||
this.textarea.setFocus();
|
this.textarea.setFocus();
|
||||||
}
|
}
|
||||||
|
@ -279,8 +415,15 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
clearText(): void {
|
clearText(): void {
|
||||||
this.editorElement.innerHTML = '<p></p>';
|
this.editorElement.innerHTML = '<p></p>';
|
||||||
this.textarea.value = '';
|
this.textarea.value = '';
|
||||||
|
|
||||||
// Don't emit event so our valueChanges doesn't get notified by this change.
|
// Don't emit event so our valueChanges doesn't get notified by this change.
|
||||||
this.control.setValue(null, {emitEvent: false});
|
this.control.setValue(null, {emitEvent: false});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.rteEnabled) {
|
||||||
|
this.setCurrentCursorPosition(this.editorElement);
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,14 +1,28 @@
|
||||||
<core-loading [hideUntil]="hideUntil" class="core-loading-center">
|
<core-loading [hideUntil]="hideUntil" class="core-loading-center">
|
||||||
<div class="core-tabs-bar" #topTabs [hidden]="!tabs || tabs.length < 2">
|
<div class="core-tabs-bar" #topTabs [hidden]="!tabs || tabs.length < 2">
|
||||||
<ng-container *ngFor="let tab of tabs; let idx = index">
|
<ion-row>
|
||||||
<a *ngIf="tab.show" [attr.aria-selected]="selected == idx" (click)="selectTab(idx)">
|
<ion-col class="col-with-arrow" (click)="slidePrev()" no-padding col-1>
|
||||||
<core-icon *ngIf="tab.icon" [name]="tab.icon"></core-icon>
|
<ion-icon *ngIf="showPrevButton" name="arrow-back"></ion-icon>
|
||||||
<span *ngIf="tab.title">{{ tab.title }}</span>
|
</ion-col>
|
||||||
<ion-badge *ngIf="tab.badge" [color]="tab.badgeStyle" class="tab-badge">{{tab.badge}}</ion-badge>
|
<ion-col no-padding col-10>
|
||||||
</a>
|
<ion-slides (ionSlideDidChange)="slideChanged()" [slidesPerView]="slidesShown">
|
||||||
</ng-container>
|
<ng-container *ngFor="let tab of tabs; let idx = index">
|
||||||
|
<ion-slide *ngIf="tab.show">
|
||||||
|
<a [attr.aria-selected]="selected == idx" (click)="selectTab(idx)" class="tab-slide">
|
||||||
|
<core-icon *ngIf="tab.icon" [name]="tab.icon"></core-icon>
|
||||||
|
<span *ngIf="tab.title">{{ tab.title }}</span>
|
||||||
|
<ion-badge *ngIf="tab.badge" [color]="tab.badgeStyle" class="tab-badge">{{tab.badge}}</ion-badge>
|
||||||
|
</a>
|
||||||
|
</ion-slide>
|
||||||
|
</ng-container>
|
||||||
|
</ion-slides>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col class="col-with-arrow" (click)="slideNext()" no-padding col-1>
|
||||||
|
<ion-icon *ngIf="showNextButton" name="arrow-forward"></ion-icon>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
</div>
|
</div>
|
||||||
<div class="core-tabs-content-container" #originalTabs>
|
<div class="core-tabs-content-container" #originalTabs>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -6,7 +6,11 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: $core-top-tabs-background;
|
background: $core-top-tabs-background;
|
||||||
|
|
||||||
> a {
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.tab-slide {
|
||||||
@extend .tab-button;
|
@extend .tab-button;
|
||||||
|
|
||||||
background: $core-top-tabs-background;
|
background: $core-top-tabs-background;
|
||||||
|
@ -20,9 +24,25 @@
|
||||||
border-bottom: 2px solid $core-top-tabs-color-active !important;
|
border-bottom: 2px solid $core-top-tabs-color-active !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-col {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 1.6rem;
|
||||||
|
|
||||||
|
&.col-with-arrow {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.md .core-tabs-bar > a {
|
.md .core-tabs-bar a.tab-slide {
|
||||||
// @extend .tabs-md .tab-button;
|
// @extend .tabs-md .tab-button;
|
||||||
min-height: $tabs-md-tab-min-height;
|
min-height: $tabs-md-tab-min-height;
|
||||||
|
|
||||||
|
@ -30,17 +50,17 @@
|
||||||
color: $tabs-md-tab-text-color;
|
color: $tabs-md-tab-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ios .core-tabs-bar > a {
|
.ios .core-tabs-bar a.tab-slide {
|
||||||
// @extend .tabs-ios .tab-button;
|
// @extend .tabs-ios .tab-button;
|
||||||
max-width: $tabs-ios-tab-max-width;
|
max-width: $tabs-ios-tab-max-width;
|
||||||
min-height: $tabs-ios-tab-min-height;
|
min-height: $tabs-ios-tab-min-height;
|
||||||
|
|
||||||
font-size: $tabs-ios-tab-font-size;
|
font-size: $tabs-ios-tab-font-size + 4;
|
||||||
font-weight: $tabs-ios-tab-font-weight;
|
font-weight: $tabs-ios-tab-font-weight;
|
||||||
color: $tabs-ios-tab-text-color;
|
color: $tabs-ios-tab-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wp .core-tabs-bar > a {
|
.wp .core-tabs-bar a.tab-slide {
|
||||||
//@extend .tabs-wp .tab-button;
|
//@extend .tabs-wp .tab-button;
|
||||||
@include border-radius(0);
|
@include border-radius(0);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
SimpleChange
|
SimpleChange
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CoreTabComponent } from './tab';
|
import { CoreTabComponent } from './tab';
|
||||||
import { Content } from 'ionic-angular';
|
import { Content, Slides } from 'ionic-angular';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component displays some tabs that usually share data between them.
|
* This component displays some tabs that usually share data between them.
|
||||||
|
@ -48,9 +48,16 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
||||||
@Output() ionChange: EventEmitter<CoreTabComponent> = new EventEmitter<CoreTabComponent>(); // Emitted when the tab changes.
|
@Output() ionChange: EventEmitter<CoreTabComponent> = new EventEmitter<CoreTabComponent>(); // Emitted when the tab changes.
|
||||||
@ViewChild('originalTabs') originalTabsRef: ElementRef;
|
@ViewChild('originalTabs') originalTabsRef: ElementRef;
|
||||||
@ViewChild('topTabs') topTabs: ElementRef;
|
@ViewChild('topTabs') topTabs: ElementRef;
|
||||||
|
@ViewChild(Slides) slides: Slides;
|
||||||
|
|
||||||
tabs: CoreTabComponent[] = []; // List of tabs.
|
tabs: CoreTabComponent[] = []; // List of tabs.
|
||||||
selected: number; // Selected tab number.
|
selected: number; // Selected tab number.
|
||||||
|
showPrevButton: boolean;
|
||||||
|
showNextButton: boolean;
|
||||||
|
maxSlides = 3;
|
||||||
|
slidesShown = this.maxSlides;
|
||||||
|
numTabsShown = 0;
|
||||||
|
|
||||||
protected originalTabsContainer: HTMLElement; // The container of the original tabs. It will include each tab's content.
|
protected originalTabsContainer: HTMLElement; // The container of the original tabs. It will include each tab's content.
|
||||||
protected initialized = false;
|
protected initialized = false;
|
||||||
protected afterViewInitTriggered = false;
|
protected afterViewInitTriggered = false;
|
||||||
|
@ -77,10 +84,16 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
||||||
*/
|
*/
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.afterViewInitTriggered = true;
|
this.afterViewInitTriggered = true;
|
||||||
|
|
||||||
if (!this.initialized && this.hideUntil) {
|
if (!this.initialized && this.hideUntil) {
|
||||||
// Tabs should be shown, initialize them.
|
// Tabs should be shown, initialize them.
|
||||||
this.initializeTabs();
|
this.initializeTabs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
this.calculateMaxSlides();
|
||||||
|
this.updateSlides();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,6 +120,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
||||||
if (this.getIndex(tab) == -1) {
|
if (this.getIndex(tab) == -1) {
|
||||||
this.tabs.push(tab);
|
this.tabs.push(tab);
|
||||||
this.sortTabs();
|
this.sortTabs();
|
||||||
|
this.updateSlides();
|
||||||
|
|
||||||
if (this.initialized && this.tabs.length > 1 && this.tabBarHeight == 0) {
|
if (this.initialized && this.tabs.length > 1 && this.tabBarHeight == 0) {
|
||||||
// Calculate the tabBarHeight again now that there is more than 1 tab and the bar will be seen.
|
// Calculate the tabBarHeight again now that there is more than 1 tab and the bar will be seen.
|
||||||
|
@ -190,9 +204,72 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check which arrows should be shown
|
||||||
|
this.calculateMaxSlides();
|
||||||
|
this.updateSlides();
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method executed when the slides are changed.
|
||||||
|
*/
|
||||||
|
slideChanged(): void {
|
||||||
|
const currentIndex = this.slides.getActiveIndex();
|
||||||
|
if (this.slidesShown >= this.numTabsShown) {
|
||||||
|
this.showPrevButton = false;
|
||||||
|
this.showNextButton = false;
|
||||||
|
} else if (typeof currentIndex !== 'undefined') {
|
||||||
|
this.showPrevButton = currentIndex > 0;
|
||||||
|
this.showNextButton = currentIndex < this.numTabsShown - this.slidesShown;
|
||||||
|
} else {
|
||||||
|
this.showPrevButton = false;
|
||||||
|
this.showNextButton = this.numTabsShown > this.slidesShown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update slides.
|
||||||
|
*/
|
||||||
|
protected updateSlides(): void {
|
||||||
|
this.numTabsShown = this.tabs.reduce((prev: number, current: any) => {
|
||||||
|
return current.show ? prev + 1 : prev;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
this.slidesShown = Math.min(this.maxSlides, this.numTabsShown);
|
||||||
|
this.slides.update();
|
||||||
|
this.slides.resize();
|
||||||
|
|
||||||
|
this.slideChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected calculateMaxSlides(): void {
|
||||||
|
if (this.slides && this.slides.renderedWidth) {
|
||||||
|
this.maxSlides = Math.floor(this.slides.renderedWidth / 120);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.maxSlides = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that shows the next slide.
|
||||||
|
*/
|
||||||
|
slideNext(): void {
|
||||||
|
if (this.showNextButton) {
|
||||||
|
this.slides.slideNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that shows the previous slide.
|
||||||
|
*/
|
||||||
|
slidePrev(): void {
|
||||||
|
if (this.showPrevButton) {
|
||||||
|
this.slides.slidePrev();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show or hide the tabs. This is used when the user is scrolling inside a tab.
|
* Show or hide the tabs. This is used when the user is scrolling inside a tab.
|
||||||
*
|
*
|
||||||
|
@ -221,6 +298,8 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
||||||
removeTab(tab: CoreTabComponent): void {
|
removeTab(tab: CoreTabComponent): void {
|
||||||
const index = this.getIndex(tab);
|
const index = this.getIndex(tab);
|
||||||
this.tabs.splice(index, 1);
|
this.tabs.splice(index, 1);
|
||||||
|
|
||||||
|
this.updateSlides();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -252,6 +331,10 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges {
|
||||||
currentTab.unselectTab();
|
currentTab.unselectTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.selected) {
|
||||||
|
this.slides.slideTo(index);
|
||||||
|
}
|
||||||
|
|
||||||
this.selected = index;
|
this.selected = index;
|
||||||
newTab.selectTab();
|
newTab.selectTab();
|
||||||
this.ionChange.emit(newTab);
|
this.ionChange.emit(newTab);
|
||||||
|
|
|
@ -16,10 +16,10 @@
|
||||||
<!-- Section selector. -->
|
<!-- Section selector. -->
|
||||||
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
|
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
|
||||||
<div text-wrap *ngIf="displaySectionSelector && sections && sections.length" no-padding class="clearfix">
|
<div text-wrap *ngIf="displaySectionSelector && sections && sections.length" no-padding class="clearfix">
|
||||||
<!-- @todo: How to display availabilityinfo and not visible messages? -->
|
<button float-start ion-button (click)="showSectionSelector($event)" clear class="core-button-select">
|
||||||
<ion-select [ngModel]="selectedSection" (ngModelChange)="sectionChanged($event)" [compareWith]="compareSections" [selectOptions]="selectOptions" float-start interface="popover">
|
{{selectedSection && (selectedSection.formattedName || selectedSection.name) || 'core.course.sections' | translate }}
|
||||||
<ion-option *ngFor="let section of sections" [value]="section">{{section.formattedName || section.name}}</ion-option>
|
<ion-icon name="arrow-dropdown" ios="md-arrow-dropdown"></ion-icon>
|
||||||
</ion-select>
|
</button>
|
||||||
<!-- Section download. -->
|
<!-- Section download. -->
|
||||||
<ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: selectedSection}"></ng-container>
|
<ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: selectedSection}"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,4 +22,8 @@ core-course-format {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.core-section-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
import {
|
import {
|
||||||
Component, Input, OnInit, OnChanges, OnDestroy, SimpleChange, Output, EventEmitter, ViewChildren, QueryList, Injector
|
Component, Input, OnInit, OnChanges, OnDestroy, SimpleChange, Output, EventEmitter, ViewChildren, QueryList, Injector
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { Content } from 'ionic-angular';
|
import { Content, ModalController } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
@ -72,7 +72,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
constructor(private cfDelegate: CoreCourseFormatDelegate, translate: TranslateService, private injector: Injector,
|
constructor(private cfDelegate: CoreCourseFormatDelegate, translate: TranslateService, private injector: Injector,
|
||||||
private courseHelper: CoreCourseHelperProvider, private domUtils: CoreDomUtilsProvider,
|
private courseHelper: CoreCourseHelperProvider, private domUtils: CoreDomUtilsProvider,
|
||||||
eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private content: Content,
|
eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private content: Content,
|
||||||
prefetchDelegate: CoreCourseModulePrefetchDelegate) {
|
prefetchDelegate: CoreCourseModulePrefetchDelegate, private modalCtrl: ModalController) {
|
||||||
|
|
||||||
this.selectOptions.title = translate.instant('core.course.sections');
|
this.selectOptions.title = translate.instant('core.course.sections');
|
||||||
this.completionChanged = new EventEmitter();
|
this.completionChanged = new EventEmitter();
|
||||||
|
@ -221,6 +221,20 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the section selector modal.
|
||||||
|
*/
|
||||||
|
showSectionSelector(): void {
|
||||||
|
const modal = this.modalCtrl.create('CoreCourseSectionSelectorPage',
|
||||||
|
{sections: this.sections, selected: this.selectedSection});
|
||||||
|
modal.onDidDismiss((newSection) => {
|
||||||
|
if (newSection) {
|
||||||
|
this.sectionChanged(newSection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
modal.present();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called when selected section changes.
|
* Function called when selected section changes.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<ion-card *ngIf="description">
|
<ion-card *ngIf="description">
|
||||||
<ion-item text-wrap>
|
<ion-item text-wrap>
|
||||||
<core-format-text [text]="description" [component]="component" [componentId]="componentId" [maxHeight]="showFull && showFull !== 'false' ? 0 : 120" fullOnClick="true"></core-format-text>
|
<core-format-text [text]="description" [component]="component" [componentId]="componentId" [maxHeight]="showFull && showFull !== 'false' ? 0 : 120" fullOnClick="true"></core-format-text>
|
||||||
<ion-note *ngIf="note" item-end>{{ note }}</ion-note>
|
</ion-item>
|
||||||
|
<ion-item text-wrap *ngIf="note">
|
||||||
|
<ion-note item-end>{{ note }}</ion-note>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
</ion-card>
|
|
@ -11,6 +11,7 @@
|
||||||
"contents": "Contents",
|
"contents": "Contents",
|
||||||
"couldnotloadsectioncontent": "Could not load the section content. Please try again later.",
|
"couldnotloadsectioncontent": "Could not load the section content. Please try again later.",
|
||||||
"couldnotloadsections": "Could not load the sections. Please try again later.",
|
"couldnotloadsections": "Could not load the sections. Please try again later.",
|
||||||
|
"coursesummary": "Course summary",
|
||||||
"downloadcourse": "Download course",
|
"downloadcourse": "Download course",
|
||||||
"errordownloadingcourse": "Error downloading course.",
|
"errordownloadingcourse": "Error downloading course.",
|
||||||
"errordownloadingsection": "Error downloading section.",
|
"errordownloadingsection": "Error downloading section.",
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title>{{ 'core.course.sections' | 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>
|
||||||
|
<ng-container *ngFor="let section of sections">
|
||||||
|
<ion-item *ngIf="sectionHasContent(section)" text-wrap (click)="selectSection(section)" [class.core-primary-item]="selected.id == section.id" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
|
||||||
|
<h2><core-format-text [text]="section.formattedName || section.name"></core-format-text></h2>
|
||||||
|
<ion-badge color="secondary" *ngIf="section.visible === 0">{{ 'core.course.nocontentavailable' | translate }}</ion-badge>
|
||||||
|
<ion-badge color="secondary" *ngIf="section.availabilityinfo"><core-format-text [text]=" section.availabilityinfo"></core-format-text></ion-badge>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
</ion-content>
|
|
@ -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 { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CoreCourseSectionSelectorPage } from './section-selector';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
CoreCourseSectionSelectorPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
IonicPageModule.forChild(CoreCourseSectionSelectorPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreCourseSectionSelectorPageModule {}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// (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, NavParams, ViewController } from 'ionic-angular';
|
||||||
|
import { CoreCourseHelperProvider } from '../../providers/helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays course section selector.
|
||||||
|
*/
|
||||||
|
@IonicPage({ segment: 'core-course-section-selector' })
|
||||||
|
@Component({
|
||||||
|
selector: 'page-core-course-section-selector',
|
||||||
|
templateUrl: 'section-selector.html',
|
||||||
|
})
|
||||||
|
export class CoreCourseSectionSelectorPage {
|
||||||
|
|
||||||
|
sections: any;
|
||||||
|
selected: number;
|
||||||
|
sectionHasContent: any;
|
||||||
|
|
||||||
|
constructor(navParams: NavParams, courseHelper: CoreCourseHelperProvider, private viewCtrl: ViewController) {
|
||||||
|
this.sections = navParams.get('sections');
|
||||||
|
this.selected = navParams.get('selected');
|
||||||
|
|
||||||
|
this.sectionHasContent = courseHelper.sectionHasContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the modal.
|
||||||
|
*/
|
||||||
|
closeModal(): void {
|
||||||
|
this.viewCtrl.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a section.
|
||||||
|
*
|
||||||
|
* @param {any} section Selected section object.
|
||||||
|
*/
|
||||||
|
selectSection(section: any): void {
|
||||||
|
if (!(section.visible === 0 || section.uservisible === false)) {
|
||||||
|
this.viewCtrl.dismiss(section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item *ngIf="displayEnableDownload" [priority]="2000" [content]="'core.settings.enabledownloadsection' | translate" (action)="toggleDownload()" [iconAction]="downloadEnabledIcon"></core-context-menu-item>
|
<core-context-menu-item *ngIf="displayEnableDownload" [priority]="2000" [content]="'core.settings.enabledownloadsection' | translate" (action)="toggleDownload()" [iconAction]="downloadEnabledIcon"></core-context-menu-item>
|
||||||
<core-context-menu-item [hidden]="!downloadCourseEnabled" [priority]="1900" [content]="prefetchCourseData.title | translate" (action)="prefetchCourse()" [iconAction]="prefetchCourseData.prefetchCourseIcon" [closeOnClick]="false"></core-context-menu-item>
|
<core-context-menu-item [hidden]="!downloadCourseEnabled" [priority]="1900" [content]="prefetchCourseData.title | translate" (action)="prefetchCourse()" [iconAction]="prefetchCourseData.prefetchCourseIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||||
|
<core-context-menu-item [priority]="1800" [content]="'core.course.coursesummary' | translate" (action)="openCourseSummary()" iconAction="fa-graduation-cap"></core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
|
|
|
@ -356,6 +356,13 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
this.prefetchCourseData.title = statusData.title;
|
this.prefetchCourseData.title = statusData.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the course summary
|
||||||
|
*/
|
||||||
|
openCourseSummary(): void {
|
||||||
|
this.navCtrl.push('CoreCoursesCoursePreviewPage', {course: this.course, avoidOpenCourse: true});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page destroyed.
|
* Page destroyed.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { Component, Input, OnInit, Optional } from '@angular/core';
|
||||||
import { NavController } from 'ionic-angular';
|
import { NavController } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreCoursesProvider } from '../../providers/courses';
|
import { CoreCoursesProvider } from '../../providers/courses';
|
||||||
|
import { CoreCourseFormatDelegate } from '@core/course/providers/format-delegate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This directive is meant to display an item for a list of courses.
|
* This directive is meant to display an item for a list of courses.
|
||||||
|
@ -32,7 +33,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit {
|
||||||
@Input() course: any; // The course to render.
|
@Input() course: any; // The course to render.
|
||||||
|
|
||||||
constructor(@Optional() private navCtrl: NavController, private translate: TranslateService,
|
constructor(@Optional() private navCtrl: NavController, private translate: TranslateService,
|
||||||
private coursesProvider: CoreCoursesProvider) {
|
private coursesProvider: CoreCoursesProvider, private courseFormatDelegate: CoreCourseFormatDelegate) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,6 +81,10 @@ export class CoreCoursesCourseListItemComponent implements OnInit {
|
||||||
* @param {any} course The course to open.
|
* @param {any} course The course to open.
|
||||||
*/
|
*/
|
||||||
openCourse(course: any): void {
|
openCourse(course: any): void {
|
||||||
this.navCtrl.push('CoreCoursesCoursePreviewPage', {course: course});
|
if (course.isEnrolled) {
|
||||||
|
this.courseFormatDelegate.openCourse(this.navCtrl, course);
|
||||||
|
} else {
|
||||||
|
this.navCtrl.push('CoreCoursesCoursePreviewPage', {course: course});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div *ngIf="course.imageThumb" (click)="openCourse()" class="core-course-thumb">
|
<div *ngIf="course.imageThumb" (click)="openCourse()" class="core-course-thumb">
|
||||||
<img [src]="course.imageThumb" core-external-content alt=""/>
|
<img [src]="course.imageThumb" core-external-content alt=""/>
|
||||||
</div>
|
</div>
|
||||||
<a ion-item text-wrap (click)="openCourse()" [title]="course.fullname" [attr.detail-none]="!canAccessCourse">
|
<a ion-item text-wrap (click)="openCourse()" [title]="course.fullname" [attr.detail-none]=" avoidOpenCourse || !canAccessCourse">
|
||||||
<core-icon name="fa-graduation-cap" fixed-width item-start></core-icon>
|
<core-icon name="fa-graduation-cap" fixed-width item-start></core-icon>
|
||||||
<h2><core-format-text [text]="course.fullname"></core-format-text></h2>
|
<h2><core-format-text [text]="course.fullname"></core-format-text></h2>
|
||||||
<p *ngIf="course.categoryname"><core-format-text [text]="course.categoryname"></core-format-text></p>
|
<p *ngIf="course.categoryname"><core-format-text [text]="course.categoryname"></core-format-text></p>
|
||||||
|
@ -26,7 +26,12 @@
|
||||||
|
|
||||||
<ng-container text-wrap *ngIf="course.contacts && course.contacts.length">
|
<ng-container text-wrap *ngIf="course.contacts && course.contacts.length">
|
||||||
<ion-item-divider color="light">{{ 'core.teachers' | translate }}</ion-item-divider>
|
<ion-item-divider color="light">{{ 'core.teachers' | translate }}</ion-item-divider>
|
||||||
<a ion-item text-wrap *ngFor="let contact of course.contacts" core-user-link userId="{{contact.id}}" courseId="{{isEnrolled ? course.id : null}}" [attr.aria-label]="'core.viewprofile' | translate">{{contact.fullname}}</a>
|
<a ion-item text-wrap *ngFor="let contact of course.contacts" core-user-link userId="{{contact.id}}" courseId="{{isEnrolled ? course.id : null}}" [attr.aria-label]="'core.viewprofile' | translate">
|
||||||
|
<ion-avatar item-start>
|
||||||
|
<img [src]="contact.userpictureurl" onError="this.src='assets/img/user-avatar.png'" core-external-content [alt]="'core.pictureof' | translate:{$a: contact.userfullname}" role="presentation">
|
||||||
|
</ion-avatar>
|
||||||
|
<h2>{{contact.fullname}}</h2>
|
||||||
|
</a>
|
||||||
<ion-item-divider color="light"></ion-item-divider>
|
<ion-item-divider color="light"></ion-item-divider>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<core-file *ngFor="let file of course.overviewfiles" [file]="file" [component]="component" [componentId]="course.id"></core-file>
|
<core-file *ngFor="let file of course.overviewfiles" [file]="file" [component]="component" [componentId]="course.id"></core-file>
|
||||||
|
@ -49,7 +54,7 @@
|
||||||
<ion-spinner *ngIf="prefetchCourseData.prefetchCourseIcon == 'spinner'" item-start></ion-spinner>
|
<ion-spinner *ngIf="prefetchCourseData.prefetchCourseIcon == 'spinner'" item-start></ion-spinner>
|
||||||
<h2>{{ 'core.course.downloadcourse' | translate }}</h2>
|
<h2>{{ 'core.course.downloadcourse' | translate }}</h2>
|
||||||
</a>
|
</a>
|
||||||
<a ion-item (click)="openCourse()" [title]="course.fullname" *ngIf="canAccessCourse">
|
<a ion-item (click)="openCourse()" [title]="course.fullname" *ngIf="!avoidOpenCourse && canAccessCourse">
|
||||||
<ion-icon name="briefcase" item-start></ion-icon>
|
<ion-icon name="briefcase" item-start></ion-icon>
|
||||||
<h2>{{ 'core.course.contents' | translate }}</h2>
|
<h2>{{ 'core.course.contents' | translate }}</h2>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { CoreCoursesProvider } from '../../providers/courses';
|
||||||
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
|
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
|
||||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
import { CoreCourseFormatDelegate } from '@core/course/providers/format-delegate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
||||||
|
@ -41,6 +42,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
||||||
selfEnrolInstances: any[] = [];
|
selfEnrolInstances: any[] = [];
|
||||||
paypalEnabled: boolean;
|
paypalEnabled: boolean;
|
||||||
dataLoaded: boolean;
|
dataLoaded: boolean;
|
||||||
|
avoidOpenCourse = false;
|
||||||
prefetchCourseData = {
|
prefetchCourseData = {
|
||||||
prefetchCourseIcon: 'spinner',
|
prefetchCourseIcon: 'spinner',
|
||||||
title: 'core.course.downloadcourse'
|
title: 'core.course.downloadcourse'
|
||||||
|
@ -67,9 +69,10 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
||||||
private coursesProvider: CoreCoursesProvider, private platform: Platform, private modalCtrl: ModalController,
|
private coursesProvider: CoreCoursesProvider, private platform: Platform, private modalCtrl: ModalController,
|
||||||
private translate: TranslateService, private eventsProvider: CoreEventsProvider,
|
private translate: TranslateService, private eventsProvider: CoreEventsProvider,
|
||||||
private courseOptionsDelegate: CoreCourseOptionsDelegate, private courseHelper: CoreCourseHelperProvider,
|
private courseOptionsDelegate: CoreCourseOptionsDelegate, private courseHelper: CoreCourseHelperProvider,
|
||||||
private courseProvider: CoreCourseProvider) {
|
private courseProvider: CoreCourseProvider, private courseFormatDelegate: CoreCourseFormatDelegate) {
|
||||||
|
|
||||||
this.course = navParams.get('course');
|
this.course = navParams.get('course');
|
||||||
|
this.avoidOpenCourse = navParams.get('avoidOpenCourse');
|
||||||
this.isMobile = appProvider.isMobile();
|
this.isMobile = appProvider.isMobile();
|
||||||
this.isDesktop = appProvider.isDesktop();
|
this.isDesktop = appProvider.isDesktop();
|
||||||
this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite();
|
this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite();
|
||||||
|
@ -224,11 +227,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// The user is not an admin/manager. Check if we can provide guest access to the course.
|
// The user is not an admin/manager. Check if we can provide guest access to the course.
|
||||||
return this.canAccessAsGuest().then((passwordRequired) => {
|
return this.canAccessAsGuest().then((passwordRequired) => {
|
||||||
if (!passwordRequired) {
|
this.canAccessCourse = !passwordRequired;
|
||||||
this.canAccessCourse = true;
|
|
||||||
} else {
|
|
||||||
this.canAccessCourse = false;
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.canAccessCourse = false;
|
this.canAccessCourse = false;
|
||||||
});
|
});
|
||||||
|
@ -242,12 +241,12 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy {
|
||||||
* Open the course.
|
* Open the course.
|
||||||
*/
|
*/
|
||||||
openCourse(): void {
|
openCourse(): void {
|
||||||
if (!this.canAccessCourse) {
|
if (!this.canAccessCourse || this.avoidOpenCourse) {
|
||||||
// Course cannot be opened.
|
// Course cannot be opened or we are avoiding opening because we accessed from inside a course.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.navCtrl.push('CoreCourseSectionPage', { course: this.course });
|
this.courseFormatDelegate.openCourse(this.navCtrl, this.course);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
page-core-login-init {
|
page-core-login-init {
|
||||||
.core-bglogo {
|
.scroll-content {
|
||||||
background-color: $core-color-init-screen; /* Change this to add a bg image or change color */
|
background-color: $core-color-init-screen; /* Change this to add a bg image or change color */
|
||||||
background: -webkit-radial-gradient($core-color-init-screen-alt, $core-color-init-screen);
|
background: -webkit-radial-gradient($core-color-init-screen-alt, $core-color-init-screen);
|
||||||
background: radial-gradient($core-color-init-screen-alt, $core-color-init-screen);
|
background: radial-gradient($core-color-init-screen-alt, $core-color-init-screen);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
|
}
|
||||||
|
.core-bglogo {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
|
@ -26,9 +26,8 @@
|
||||||
<!-- Pick the site from a list of fixed sites. -->
|
<!-- Pick the site from a list of fixed sites. -->
|
||||||
<div *ngIf="fixedSites" text-wrap>
|
<div *ngIf="fixedSites" text-wrap>
|
||||||
<!-- Display them using a select. -->
|
<!-- Display them using a select. -->
|
||||||
<ion-item *ngIf="!displayAsButtons">
|
<ion-item *ngIf="!displayAsButtons" margin-vertical>
|
||||||
<!-- @todo: Display label and select in different lines. -->
|
<ion-label stacked for="siteSelect">{{ 'core.login.selectsite' | translate }}</ion-label>
|
||||||
<ion-label for="siteSelect">{{ 'core.login.selectsite' | translate }}</ion-label>
|
|
||||||
<ion-select formControlName="siteUrl" name="url" placeholder="{{ 'core.login.siteaddress' | translate }}" interface="popover">
|
<ion-select formControlName="siteUrl" name="url" placeholder="{{ 'core.login.siteaddress' | translate }}" interface="popover">
|
||||||
<ion-option *ngFor="let site of fixedSites" [value]="site.url">{{site.name}}</ion-option>
|
<ion-option *ngFor="let site of fixedSites" [value]="site.url">{{site.name}}</ion-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
|
@ -37,7 +36,7 @@
|
||||||
<!-- Display them using buttons. -->
|
<!-- Display them using buttons. -->
|
||||||
<div *ngIf="displayAsButtons">
|
<div *ngIf="displayAsButtons">
|
||||||
<p class="padding no-padding-bottom">{{ 'core.login.selectsite' | translate }}</p>
|
<p class="padding no-padding-bottom">{{ 'core.login.selectsite' | translate }}</p>
|
||||||
<a *ngFor="let site of fixedSites" ion-button block (click)="connect(site.url)" title="{{site.name}}">{{site.name}}</a>
|
<a *ngFor="let site of fixedSites" ion-button block (click)="connect(site.url)" title="{{site.name}}" margin-bottom>{{site.name}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-navbar>
|
</ion-navbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content class="has-fab">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item (click)="login(site.id)" *ngFor="let site of sites; let idx = index">
|
<ion-item (click)="login(site.id)" *ngFor="let site of sites; let idx = index">
|
||||||
<ion-avatar item-start>
|
<ion-avatar item-start>
|
||||||
|
|
|
@ -22,7 +22,10 @@
|
||||||
"@components/*": ["components/*"],
|
"@components/*": ["components/*"],
|
||||||
"@directives/*": ["directives/*"],
|
"@directives/*": ["directives/*"],
|
||||||
"@pipes/*": ["pipes/*"]
|
"@pipes/*": ["pipes/*"]
|
||||||
}
|
},
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts"
|
"src/**/*.ts"
|
||||||
|
|
Loading…
Reference in New Issue