MOBILE-2353 wiki: Implement index page and component

main
Dani Palou 2018-05-24 12:33:53 +02:00
parent 4949f9307a
commit d61eae4f66
15 changed files with 1485 additions and 0 deletions

View File

@ -0,0 +1,49 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
import { AddonModWikiIndexComponent } from './index/index';
import { AddonModWikiSubwikiPickerComponent } from './subwiki-picker/subwiki-picker';
@NgModule({
declarations: [
AddonModWikiIndexComponent,
AddonModWikiSubwikiPickerComponent
],
imports: [
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CoreCourseComponentsModule
],
providers: [
],
exports: [
AddonModWikiIndexComponent,
AddonModWikiSubwikiPickerComponent
],
entryComponents: [
AddonModWikiIndexComponent,
AddonModWikiSubwikiPickerComponent
]
})
export class AddonModWikiComponentsModule {}

View File

@ -0,0 +1,74 @@
<!-- Buttons to add to the header. -->
<core-navbar-buttons end>
<!-- Select subwiki. -->
<button ion-button icon-only *ngIf="subwikiData.count > 1" (click)="showSubwikiPicker($event)" [attr.aria-label]="'addon.mod_wiki.subwiki' | translate">
<ion-icon name="person"></ion-icon>
</button>
<!-- Go to "home". -->
<button ion-button icon-only *ngIf="showHomeButton" (click)="goToWikiHome()" [attr.aria-label]="'addon.mod_wiki.gowikihome' | translate">
<ion-icon name="home"></ion-icon>
</button>
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" [iconAction]="'open'"></core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></core-context-menu-item>
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline && !pageIsOffline" [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="loaded && isOnline && (hasOffline || pageIsOffline)" [priority]="600" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="canEdit && isOnline" [priority]="590" [content]="'core.edit' | translate" iconAction="create" (action)="goToEditPage()"></core-context-menu-item>
<core-context-menu-item *ngIf="canEdit" [priority]="580" [content]="'addon.mod_wiki.createpage' | translate" iconAction="add" (action)="goToNewPage()"></core-context-menu-item>
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch()" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="size" [priority]="400" [content]="size" [iconDescription]="'cube'" (action)="removeFiles()" [iconAction]="'trash'"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
<!-- Content. -->
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-tabs [selectedIndex]="selectedTab">
<!-- Page contents. -->
<core-tab [title]="'addon.mod_wiki.viewpage' | translate" icon="document">
<ng-template>
<div padding>
<core-course-module-description *ngIf="isMainPage" [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
<!-- Wiki has something offline. -->
<div class="core-warning-card" icon-start *ngIf="pageIsOffline || hasOffline">
<ion-icon name="warning"></ion-icon>
<span *ngIf="pageIsOffline">{{ 'core.hasdatatosync' | translate:{$a: pageStr} }}</span>
<span *ngIf="!pageIsOffline">{{ 'core.hasdatatosync' | translate:{$a: moduleName} }}</span>
</div>
<!-- Page warning. -->
<div class="core-warning-card" icon-start *ngIf="pageWarning">
<ion-icon name="warning"></ion-icon>
{{ pageWarning }}
</div>
<article [ngClass]="{'addon-mod_wiki-noedit': !canEdit}" class="addon-mod_wiki-page-content">
<core-format-text *ngIf="pageContent" [component]="component" [componentId]="componentId" [text]="pageContent"></core-format-text>
<core-empty-box *ngIf="!pageContent" icon="document" [message]="'addon.mod_wiki.nocontent' | translate"></core-empty-box>
</article>
</div>
</ng-template>
</core-tab>
<!-- Map. -->
<core-tab [title]="'addon.mod_wiki.map' | translate" icon="map">
<ng-template>
<ion-list>
<ng-container *ngFor="let letter of map">
<ion-item-divider color="light" *ngIf="letter.label">
{{ letter.label }}
</ion-item-divider>
<a ion-item text-wrap *ngFor="let page of letter.pages" (click)="goToPage(page)">
{{ page.title }}
<ion-note *ngIf="!page.id" item-end color="danger">{{ 'core.offline' | translate }}</ion-note>
</a>
</ng-container>
</ion-list>
</ng-template>
</core-tab>
</core-tabs>
</core-loading>

View File

@ -0,0 +1,50 @@
$addon-mod-wiki-toc-level-padding: 12px !default;
$addon-mod-wiki-newentry-link-color: $red !default;
$addon-mod-wiki-toc-title-color: $gray-darker !default;
$addon-mod-wiki-toc-border-color: $gray-dark !default;
$addon-mod-wiki-toc-background-color: $gray-light !default;
addon-mod-wiki-index {
background-color: $white;
.core-tabs-content-container, .addon-mod_wiki-page-content {
background-color: $white;
}
.wiki-toc {
border: 1px solid $addon-mod-wiki-toc-border-color;
background: $addon-mod-wiki-toc-background-color;
margin: 16px;
padding: 8px;
}
.wiki-toc-title {
color: $addon-mod-wiki-toc-title-color;
font-size: 1.1em;
font-variant: small-caps;
text-align: center;
}
.wiki-toc-section {
padding: 0;
margin: 2px 8px;
}
.wiki-toc-section-2 {
padding-left: $addon-mod-wiki-toc-level-padding;
}
.wiki-toc-section-3 {
padding-left: $addon-mod-wiki-toc-level-padding * 2;
}
.wiki_newentry {
color: $addon-mod-wiki-newentry-link-color;
font-style: italic;
}
/* Hide edit section links */
.addon-mod_wiki-noedit a.wiki_edit_section {
display: none;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,11 @@
<ion-list>
<ng-container *ngFor="let group of subwikis">
<ion-item-divider *ngIf="group.label" color="light">
{{ group.label }}
</ion-item-divider>
<a ion-item text-wrap *ngFor="let subwiki of group.subwikis;" (click)="openSubwiki(subwiki)" [attr.disabled]="!subwiki.canedit && subwiki.id <= 0 ? true : null" [ngClass]="{'addon-mod_wiki-subwiki-selected': isSubwikiSelected(subwiki)}" detail-none>
{{ subwiki.name }}
<ion-icon *ngIf="isSubwikiSelected(subwiki)" name="checkmark" item-end></ion-icon>
</a>
</ng-container>
</ion-list>

View File

@ -0,0 +1,14 @@
addon-mod-wiki-subwiki-picker {
.item-divider, .item-divider .label {
font-weight: bold;
}
.item.addon-mod_wiki-subwiki-selected {
background-color: $gray-light;
.icon {
font-size: 24px;
}
}
}

View File

@ -0,0 +1,67 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { NavParams, ViewController } from 'ionic-angular';
/**
* Component to display the a list of subwikis in a wiki.
*/
@Component({
selector: 'addon-mod-wiki-subwiki-picker',
templateUrl: 'subwiki-picker.html'
})
export class AddonModWikiSubwikiPickerComponent {
subwikis: any[];
currentSubwiki: any;
constructor(navParams: NavParams, private viewCtrl: ViewController) {
this.subwikis = navParams.get('subwikis');
this.currentSubwiki = navParams.get('currentSubwiki');
}
/**
* Checks if the given subwiki is the one currently selected.
*
* @param {any} subwiki Subwiki to check.
* @return {boolean} Whether it's the selected subwiki.
*/
protected isSubwikiSelected(subwiki: any): boolean {
const subwikiId = parseInt(subwiki.id, 10) || 0;
if (subwikiId > 0 && this.currentSubwiki.id > 0) {
return subwikiId == this.currentSubwiki.id;
}
const userId = parseInt(subwiki.userid, 10) || 0,
groupId = parseInt(subwiki.groupid, 10) || 0;
return userId == this.currentSubwiki.userid && groupId == this.currentSubwiki.groupid;
}
/**
* Function called when a subwiki is clicked.
*
* @param {any} subwiki The subwiki to open.
*/
openSubwiki(subwiki: any): void {
// Check if the subwiki is disabled.
if (subwiki.id > 0 || subwiki.canedit) {
// Check if it isn't current subwiki.
if (subwiki != this.currentSubwiki) {
this.viewCtrl.dismiss(subwiki);
}
}
}
}

View File

@ -0,0 +1,16 @@
<ion-header>
<ion-navbar>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-buttons end>
<!-- The buttons defined by the component will be added in here. -->
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="wikiComponent.loaded" (ionRefresh)="wikiComponent.doRefresh($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<addon-mod-wiki-index [module]="module" [courseId]="courseId" [action]="action" [pageId]="pageId" [pageTitle]="pageTitle" [wikiId]="wikiId" [subwikiId]="subwikiId" [userId]="userId" [groupId]="groupId" (dataRetrieved)="updateData($event)"></addon-mod-wiki-index>
</ion-content>

View File

@ -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 { CoreDirectivesModule } from '@directives/directives.module';
import { AddonModWikiComponentsModule } from '../../components/components.module';
import { AddonModWikiIndexPage } from './index';
@NgModule({
declarations: [
AddonModWikiIndexPage,
],
imports: [
CoreDirectivesModule,
AddonModWikiComponentsModule,
IonicPageModule.forChild(AddonModWikiIndexPage),
TranslateModule.forChild()
],
})
export class AddonModWikiIndexPageModule {}

View File

@ -0,0 +1,83 @@
// (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, ViewChild } from '@angular/core';
import { IonicPage, NavParams } from 'ionic-angular';
import { AddonModWikiIndexComponent } from '../../components/index/index';
/**
* Page that displays a wiki page.
*/
@IonicPage({ segment: 'addon-mod-wiki-index' })
@Component({
selector: 'page-addon-mod-wiki-index',
templateUrl: 'index.html',
})
export class AddonModWikiIndexPage {
@ViewChild(AddonModWikiIndexComponent) wikiComponent: AddonModWikiIndexComponent;
title: string;
module: any;
courseId: number;
action: string;
pageId: number;
pageTitle: string;
wikiId: number;
subwikiId: number;
userId: number;
groupId: number;
constructor(navParams: NavParams) {
this.module = navParams.get('module') || {};
this.courseId = navParams.get('courseId');
this.action = navParams.get('action') || 'page';
this.pageId = navParams.get('pageId');
this.pageTitle = navParams.get('pageTitle');
this.wikiId = navParams.get('wikiId');
this.subwikiId = navParams.get('subwikiId');
this.userId = navParams.get('userId');
this.groupId = navParams.get('groupId');
this.title = this.pageTitle || this.module.name;
}
/**
* Update some data based on the data received.
*
* @param {any} data The data received.
*/
updateData(data: any): void {
if (typeof data == 'string') {
// We received the title to display.
this.title = data;
} else {
// We received a wiki instance.
this.title = this.pageTitle || data.title || this.title;
}
}
/**
* User entered the page.
*/
ionViewDidEnter(): void {
this.wikiComponent.ionViewDidEnter();
}
/**
* User left the page.
*/
ionViewDidLeave(): void {
this.wikiComponent.ionViewDidLeave();
}
}

View File

@ -95,6 +95,7 @@ export interface AddonModWikiSyncWikiResult {
export class AddonModWikiSyncProvider extends CoreSyncBaseProvider {
static AUTO_SYNCED = 'addon_mod_wiki_autom_synced';
static MANUAL_SYNCED = 'addon_mod_wiki_manual_synced';
static SYNC_TIME = 300000;
protected componentTranslate: string;

View File

@ -60,6 +60,7 @@ export interface AddonModWikiSubwikiListData {
@Injectable()
export class AddonModWikiProvider {
static COMPONENT = 'mmaModWiki';
static PAGE_CREATED_EVENT = 'addon_mod_wiki_page_created';
protected ROOT_CACHE_KEY = 'mmaModWiki:';
protected logger;

View File

@ -16,6 +16,7 @@ import { NgModule } from '@angular/core';
import { CoreCronDelegate } from '@providers/cron';
import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { AddonModWikiComponentsModule } from './components/components.module';
import { AddonModWikiProvider } from './providers/wiki';
import { AddonModWikiOfflineProvider } from './providers/wiki-offline';
import { AddonModWikiSyncProvider } from './providers/wiki-sync';
@ -27,6 +28,7 @@ import { AddonModWikiSyncCronHandler } from './providers/sync-cron-handler';
declarations: [
],
imports: [
AddonModWikiComponentsModule
],
providers: [
AddonModWikiProvider,

View File

@ -93,6 +93,7 @@ import { AddonModScormModule } from '@addon/mod/scorm/scorm.module';
import { AddonModUrlModule } from '@addon/mod/url/url.module';
import { AddonModSurveyModule } from '@addon/mod/survey/survey.module';
import { AddonModImscpModule } from '@addon/mod/imscp/imscp.module';
import { AddonModWikiModule } from '@addon/mod/wiki/wiki.module';
import { AddonMessageOutputModule } from '@addon/messageoutput/messageoutput.module';
import { AddonMessageOutputAirnotifierModule } from '@addon/messageoutput/airnotifier/airnotifier.module';
import { AddonMessagesModule } from '@addon/messages/messages.module';
@ -195,6 +196,7 @@ export const CORE_PROVIDERS: any[] = [
AddonModUrlModule,
AddonModSurveyModule,
AddonModImscpModule,
AddonModWikiModule,
AddonMessageOutputModule,
AddonMessageOutputAirnotifierModule,
AddonMessagesModule,

View File

@ -127,6 +127,25 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
});
}
/**
* Show loading and perform the load content function.
*
* @param {boolean} [sync=false] If the fetch needs syncing.
* @param {boolean} [showErrors=false] Wether to show errors to the user or hide them.
* @return {Promise<any>} Resolved when done.
*/
protected showLoadingAndFetch(sync: boolean = false, showErrors: boolean = false): Promise<any> {
this.refreshIcon = 'spinner';
this.syncIcon = 'spinner';
this.loaded = false;
this.content && this.content.scrollToTop();
return this.loadContent(false, sync, showErrors).finally(() => {
this.refreshIcon = 'refresh';
this.syncIcon = 'sync';
});
}
/**
* Show loading and perform the refresh content function.
*