commit
a4b1dd0c73
|
@ -19,6 +19,5 @@
|
|||
|
||||
<!-- Edit -->
|
||||
<ion-item text-wrap *ngIf="edit && loaded">
|
||||
<!-- @todo: [component]="component" [componentId]="assign.cmid" -->
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name" name="assignfeedbackcomments_editor"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name" name="assignfeedbackcomments_editor" [component]="component" [componentId]="assign.cmid"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
|
|
@ -329,7 +329,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy {
|
|||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = false;
|
||||
this.isDestroyed = true;
|
||||
|
||||
// Unblock the assignment.
|
||||
if (this.assign) {
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
<p>{{ 'core.numwords' | translate: {'$a': words + ' / ' + configs.wordlimit} }}</p>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<!-- @todo: [component]="component" [componentId]="assign.cmid" -->
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name" name="onlinetext_editor_text" (contentChanged)="onChange($event)"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name" name="onlinetext_editor_text" (contentChanged)="onChange($event)" [component]="component" [componentId]="assign.cmid"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
|
|
@ -38,9 +38,7 @@
|
|||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label stacked>{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.message' | translate" [name]="'mod_forum_reply_' + post.id"></core-rich-text-editor>
|
||||
<!-- @todo: Attributes that were passed to RTE in Ionic 1 but now they aren't supported yet:
|
||||
[component]="component" [componentId]="componentId" -->
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.message' | translate" [name]="'mod_forum_reply_' + post.id" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
<core-attachments *ngIf="forum.id && forum.maxattachments > 0" [files]="replyData.files" [maxSize]="forum.maxbytes" [maxSubmissions]="forum.maxattachments" [component]="component" [componentId]="forum.cmid" [allowOffline]="true"></core-attachments>
|
||||
<ion-grid>
|
||||
|
|
|
@ -19,9 +19,7 @@
|
|||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label stacked>{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.message' | translate" name="addon_mod_forum_new_discussion"></core-rich-text-editor>
|
||||
<!-- @todo: Attributes that were passed to RTE in Ionic 1 but now they aren't supported yet:
|
||||
[component]="component" [componentId]="forum.cmid" -->
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.message' | translate" name="addon_mod_forum_new_discussion" [component]="component" [componentId]="forum.cmid"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="showGroups">
|
||||
<ion-label id="addon-mod-forum-groupslabel">{{ 'addon.mod_forum.group' | translate }}</ion-label>
|
||||
|
|
|
@ -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 {}
|
|
@ -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 || pageIsOffline)" [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 class="addon-mod_wiki-page-content">
|
||||
<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}">
|
||||
<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>
|
|
@ -0,0 +1,51 @@
|
|||
$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
|
@ -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>
|
|
@ -0,0 +1,16 @@
|
|||
addon-mod-wiki-subwiki-picker {
|
||||
|
||||
.item-divider, .item-divider .label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item.addon-mod_wiki-subwiki-selected {
|
||||
background-color: $gray-light;
|
||||
color: $core-color;
|
||||
|
||||
.icon {
|
||||
color: $core-color;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"cannoteditpage": "You can not edit this page.",
|
||||
"createpage": "Create page",
|
||||
"editingpage": "Editing this page '{{$a}}'",
|
||||
"errorloadingpage": "An error occurred while loading the page.",
|
||||
"errornowikiavailable": "This wiki does not have any content yet.",
|
||||
"gowikihome": "Go to the wiki first page",
|
||||
"map": "Map",
|
||||
"newpagehdr": "New page",
|
||||
"newpagetitle": "New page title",
|
||||
"nocontent": "There is no content for this page",
|
||||
"notingroup": "Not in group",
|
||||
"page": "Page",
|
||||
"pageexists": "This page already exists.",
|
||||
"pagename": "Page name",
|
||||
"subwiki": "Sub-wiki",
|
||||
"titleshouldnotbeempty": "The title should not be empty",
|
||||
"viewpage": "View page",
|
||||
"wikipage": "Wiki page",
|
||||
"wrongversionlock": "Another user has edited this page while you were editing and your content is obsolete."
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
||||
|
||||
<ion-buttons end>
|
||||
<button ion-button clear (click)="save()" [attr.aria-label]="'core.save' | translate">
|
||||
{{ 'core.save' | translate }}
|
||||
</button>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<form ion-list [formGroup]="pageForm">
|
||||
<ion-item text-wrap *ngIf="canEditTitle" class="item-title">
|
||||
<ion-input name="title" type="text" [placeholder]="'addon.mod_wiki.newpagetitle' | translate" [formControlName]="'title'"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<core-rich-text-editor item-content [control]="contentControl" [placeholder]="'core.content' | translate" name="wiki_page_content" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
||||
<ion-badge color="danger" *ngIf="wrongVersionLock" item-end>{{ 'addon.mod_wiki.wrongversionlock' | translate }}</ion-badge> <!-- @todo: Check this. -->
|
||||
</form>
|
||||
</core-loading>
|
||||
</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 { AddonModWikiEditPage } from './edit';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonModWikiEditPage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
IonicPageModule.forChild(AddonModWikiEditPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonModWikiEditPageModule {}
|
|
@ -0,0 +1,555 @@
|
|||
// (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, OnInit, OnDestroy } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { FormControl, FormGroup, FormBuilder } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreSyncProvider } from '@providers/sync';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||
import { AddonModWikiProvider } from '../../providers/wiki';
|
||||
import { AddonModWikiOfflineProvider } from '../../providers/wiki-offline';
|
||||
import { AddonModWikiSyncProvider, AddonModWikiSyncSubwikiResult } from '../../providers/wiki-sync';
|
||||
|
||||
/**
|
||||
* Page that allows adding or editing a wiki page.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-mod-wiki-edit' })
|
||||
@Component({
|
||||
selector: 'page-addon-mod-wiki-edit',
|
||||
templateUrl: 'edit.html',
|
||||
})
|
||||
export class AddonModWikiEditPage implements OnInit, OnDestroy {
|
||||
|
||||
title: string; // Title to display.
|
||||
pageForm: FormGroup; // The form group.
|
||||
contentControl: FormControl; // The FormControl for the page content.
|
||||
canEditTitle: boolean; // Whether title can be edited.
|
||||
loaded: boolean; // Whether the data has been loaded.
|
||||
component = AddonModWikiProvider.COMPONENT; // Component to link the files to.
|
||||
componentId: number; // Component ID to link the files to.
|
||||
wrongVersionLock: boolean; // Whether the page lock doesn't match the initial one.
|
||||
|
||||
protected module: any; // Wiki module instance.
|
||||
protected courseId: number; // Course the wiki belongs to.
|
||||
protected subwikiId: number; // Subwiki ID the page belongs to.
|
||||
protected initialSubwikiId: number; // Same as subwikiId, but it won't be updated, it'll always be the value received.
|
||||
protected wikiId: number; // Wiki ID the page belongs to.
|
||||
protected pageId: number; // The page ID (if editing a page).
|
||||
protected section: string; // The section being edited.
|
||||
protected groupId: number; // The group the subwiki belongs to.
|
||||
protected userId: number; // The user the subwiki belongs to.
|
||||
protected blockId: string; // ID to block the subwiki.
|
||||
protected editing: boolean; // Whether the user is editing a page (true) or creating a new one (false).
|
||||
protected editOffline: boolean; // Whether the user is editing an offline page.
|
||||
protected rteEnabled: boolean; // Whether rich text editor is enabled.
|
||||
protected subwikiFiles: any[]; // List of files of the subwiki.
|
||||
protected originalContent: string; // The original page content.
|
||||
protected version: number; // Page version.
|
||||
protected renewLockInterval: any; // An interval to renew the lock every certain time.
|
||||
protected forceLeave = false; // To allow leaving the page without checking for changes.
|
||||
protected isDestroyed = false; // Whether the page has been destroyed.
|
||||
protected pageParamsToLoad: any; // Params of the page to load when this page is closed.
|
||||
|
||||
constructor(navParams: NavParams, fb: FormBuilder, protected navCtrl: NavController, protected sitesProvider: CoreSitesProvider,
|
||||
protected syncProvider: CoreSyncProvider, protected domUtils: CoreDomUtilsProvider,
|
||||
protected translate: TranslateService, protected courseProvider: CoreCourseProvider,
|
||||
protected eventsProvider: CoreEventsProvider, protected wikiProvider: AddonModWikiProvider,
|
||||
protected wikiOffline: AddonModWikiOfflineProvider, protected wikiSync: AddonModWikiSyncProvider,
|
||||
protected textUtils: CoreTextUtilsProvider, protected courseHelper: CoreCourseHelperProvider) {
|
||||
|
||||
this.module = navParams.get('module') || {};
|
||||
this.courseId = navParams.get('courseId');
|
||||
this.subwikiId = navParams.get('subwikiId');
|
||||
this.wikiId = navParams.get('wikiId');
|
||||
this.pageId = navParams.get('pageId');
|
||||
this.section = navParams.get('section');
|
||||
this.groupId = navParams.get('groupId');
|
||||
this.userId = navParams.get('userId');
|
||||
|
||||
let pageTitle = navParams.get('pageTitle');
|
||||
pageTitle = pageTitle ? pageTitle.replace(/\+/g, ' ') : '';
|
||||
|
||||
this.initialSubwikiId = this.subwikiId;
|
||||
this.componentId = this.module.id;
|
||||
this.canEditTitle = !pageTitle;
|
||||
this.title = pageTitle ? this.translate.instant('addon.mod_wiki.editingpage', {$a: pageTitle}) :
|
||||
this.translate.instant('addon.mod_wiki.newpagehdr');
|
||||
this.blockId = this.wikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId);
|
||||
|
||||
// Create the form group and its controls.
|
||||
this.contentControl = fb.control('');
|
||||
this.pageForm = fb.group({
|
||||
title: pageTitle
|
||||
});
|
||||
this.pageForm.addControl('text', this.contentControl);
|
||||
|
||||
// Block the wiki so it cannot be synced.
|
||||
this.syncProvider.blockOperation(this.component, this.blockId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being initialized.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.fetchWikiPageData().then((success) => {
|
||||
if (success && this.blockId && !this.isDestroyed) {
|
||||
// Block the subwiki now that we have blockId for sure.
|
||||
const newBlockId = this.wikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId);
|
||||
if (newBlockId != this.blockId) {
|
||||
this.syncProvider.unblockOperation(this.component, this.blockId);
|
||||
this.blockId = newBlockId;
|
||||
this.syncProvider.blockOperation(this.component, this.blockId);
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to get wiki page data.
|
||||
*
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: whether it was successful.
|
||||
*/
|
||||
protected fetchWikiPageData(): Promise<boolean> {
|
||||
let promise,
|
||||
canEdit = false;
|
||||
|
||||
if (this.pageId) {
|
||||
// Editing a page that already exists.
|
||||
this.canEditTitle = false;
|
||||
this.editing = true;
|
||||
this.editOffline = false; // Cannot edit pages in offline.
|
||||
|
||||
// Get page contents to obtain title and editing permission
|
||||
promise = this.wikiProvider.getPageContents(this.pageId).then((pageContents) => {
|
||||
this.pageForm.controls.title.setValue(pageContents.title); // Set the title in the form group.
|
||||
this.wikiId = pageContents.wikiid;
|
||||
this.subwikiId = pageContents.subwikiid;
|
||||
this.title = this.translate.instant('addon.mod_wiki.editingpage', {$a: pageContents.title});
|
||||
this.groupId = pageContents.groupid;
|
||||
this.userId = pageContents.userid;
|
||||
canEdit = pageContents.caneditpage;
|
||||
|
||||
// Wait for sync to be over (if any).
|
||||
return this.wikiSync.waitForSync(this.blockId);
|
||||
}).then(() => {
|
||||
// Check if rich text editor is enabled.
|
||||
return this.domUtils.isRichTextEditorEnabled();
|
||||
}).then((enabled) => {
|
||||
this.rteEnabled = enabled;
|
||||
|
||||
if (enabled) {
|
||||
// Get subwiki files, needed to replace URLs for rich text editor.
|
||||
return this.wikiProvider.getSubwikiFiles(this.wikiId, this.groupId, this.userId);
|
||||
}
|
||||
}).then((files) => {
|
||||
this.subwikiFiles = files;
|
||||
|
||||
// Get editable text of the page/section.
|
||||
return this.wikiProvider.getPageForEditing(this.pageId, this.section);
|
||||
}).then((editContents) => {
|
||||
// Get the original page contents, treating file URLs if needed.
|
||||
const content = this.rteEnabled ? this.textUtils.replacePluginfileUrls(editContents.content, this.subwikiFiles) :
|
||||
editContents.content;
|
||||
|
||||
this.contentControl.setValue(content);
|
||||
this.originalContent = content;
|
||||
this.version = editContents.version;
|
||||
|
||||
if (canEdit) {
|
||||
// Renew the lock every certain time.
|
||||
this.renewLockInterval = setInterval(() => {
|
||||
this.renewLock();
|
||||
}, AddonModWikiProvider.RENEW_LOCK_TIME);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const pageTitle = this.pageForm.controls.title.value;
|
||||
|
||||
// New page. Wait for sync to be over (if any).
|
||||
promise = this.wikiSync.waitForSync(this.blockId);
|
||||
|
||||
if (pageTitle) {
|
||||
// Title is set, it could be editing an offline page or creating a new page using an edit link.
|
||||
promise = promise.then((result: AddonModWikiSyncSubwikiResult) => {
|
||||
|
||||
// First of all, verify if this page was created in the current sync.
|
||||
if (result) {
|
||||
const page = result.created.find((page) => {
|
||||
return page.title == pageTitle;
|
||||
});
|
||||
|
||||
if (page && page.pageId > 0) {
|
||||
// Page was created, now it exists in the site.
|
||||
this.pageId = page.pageId;
|
||||
|
||||
return this.fetchWikiPageData();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there's already some offline data for this page.
|
||||
return this.wikiOffline.getNewPage(pageTitle, this.subwikiId, this.wikiId, this.userId, this.groupId);
|
||||
}).then((page) => {
|
||||
// Load offline content.
|
||||
this.contentControl.setValue(page.cachedcontent);
|
||||
this.originalContent = page.cachedcontent;
|
||||
this.editOffline = true;
|
||||
}).catch(() => {
|
||||
// No offline data found.
|
||||
this.editOffline = false;
|
||||
});
|
||||
} else {
|
||||
this.editOffline = false;
|
||||
}
|
||||
|
||||
promise.then(() => {
|
||||
this.editing = false;
|
||||
canEdit = !!this.blockId; // If no blockId, the user cannot edit the page.
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
return true;
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error getting wiki data.');
|
||||
|
||||
// Go back.
|
||||
this.forceLeavePage();
|
||||
|
||||
return false;
|
||||
}).finally(() => {
|
||||
if (!canEdit) {
|
||||
// Cannot edit, show alert and go back.
|
||||
this.domUtils.showAlert(this.translate.instant('core.notice'),
|
||||
this.translate.instant('addon.mod_wiki.cannoteditpage'));
|
||||
this.forceLeavePage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Force leaving the page, without checking for changes.
|
||||
*/
|
||||
protected forceLeavePage(): void {
|
||||
this.forceLeave = true;
|
||||
this.navCtrl.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a new offline page.
|
||||
*
|
||||
* @param {string} title Page title.
|
||||
*/
|
||||
protected goToNewOfflinePage(title: string): void {
|
||||
if (this.courseId && (this.module.id || this.wikiId)) {
|
||||
// We have enough data to navigate to the page.
|
||||
if (!this.editOffline || this.previousViewPageIsDifferentOffline(title)) {
|
||||
this.pageParamsToLoad = {
|
||||
module: this.module,
|
||||
courseId: this.courseId,
|
||||
pageId: null,
|
||||
pageTitle: title,
|
||||
wikiId: this.wikiId,
|
||||
subwikiId: this.subwikiId,
|
||||
userId: this.userId,
|
||||
groupId: this.groupId
|
||||
};
|
||||
}
|
||||
} else {
|
||||
this.domUtils.showAlert(this.translate.instant('core.success'), this.translate.instant('core.datastoredoffline'));
|
||||
}
|
||||
|
||||
this.forceLeavePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we need to navigate to a new state.
|
||||
*
|
||||
* @param {string} title Page title.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected gotoPage(title: string): Promise<any> {
|
||||
return this.retrieveModuleInfo(this.wikiId).then(() => {
|
||||
let openPage = false;
|
||||
|
||||
// Not the firstpage.
|
||||
if (this.initialSubwikiId) {
|
||||
if (!this.editing && this.editOffline && this.previousViewPageIsDifferentOffline(title)) {
|
||||
// The user submitted an offline page that isn't loaded in the back view, open it.
|
||||
openPage = true;
|
||||
} else if (!this.editOffline && this.previousViewIsDifferentPageOnline()) {
|
||||
// The user submitted an offline page that isn't loaded in the back view, open it.
|
||||
openPage = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (openPage) {
|
||||
// Setting that will do the app navigate to the page.
|
||||
this.pageParamsToLoad = {
|
||||
module: this.module,
|
||||
courseId: this.courseId,
|
||||
pageId: this.pageId,
|
||||
pageTitle: title,
|
||||
wikiId: this.wikiId,
|
||||
subwikiId: this.subwikiId,
|
||||
userId: this.userId,
|
||||
groupId: this.groupId
|
||||
};
|
||||
}
|
||||
|
||||
this.forceLeavePage();
|
||||
}).catch(() => {
|
||||
// Go back if it fails.
|
||||
this.forceLeavePage();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data has changed.
|
||||
*
|
||||
* @return {boolean} Whether data has changed.
|
||||
*/
|
||||
protected hasDataChanged(): boolean {
|
||||
const values = this.pageForm.value;
|
||||
|
||||
return !(this.originalContent == values.text || (!this.editing && !values.text && !values.title));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can leave the page or not.
|
||||
*
|
||||
* @return {boolean|Promise<void>} Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
if (this.forceLeave) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if data has changed.
|
||||
if (this.hasDataChanged()) {
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* View left.
|
||||
*/
|
||||
ionViewDidLeave(): void {
|
||||
if (this.pageParamsToLoad) {
|
||||
// Go to the page we've just created/edited.
|
||||
this.navCtrl.push('AddonModWikiIndexPage', this.pageParamsToLoad);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In case we are NOT editing an offline page, check if the page loaded in previous view is different than this view.
|
||||
*
|
||||
* @return {boolean} Whether previous view wiki page is different than current page.
|
||||
*/
|
||||
protected previousViewIsDifferentPageOnline(): boolean {
|
||||
// We cannot precisely detect when the state is the same but this is close to it.
|
||||
const previousView = this.navCtrl.getPrevious();
|
||||
|
||||
return !this.editing || previousView.component.name != 'AddonModWikiIndexPage' ||
|
||||
previousView.data.module.id != this.module.id || previousView.data.pageId != this.pageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* In case we're editing an offline page, check if the page loaded in previous view is different than this view.
|
||||
*
|
||||
* @param {string} title The current page title.
|
||||
* @return {boolean} Whether previous view wiki page is different than current page.
|
||||
*/
|
||||
protected previousViewPageIsDifferentOffline(title: string): boolean {
|
||||
// We cannot precisely detect when the state is the same but this is close to it.
|
||||
const previousView = this.navCtrl.getPrevious();
|
||||
|
||||
if (previousView.component.name != 'AddonModWikiIndexPage' || previousView.data.module.id != this.module.id ||
|
||||
previousView.data.wikiId != this.wikiId || previousView.data.pageTitle != title) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check subwiki using subwiki or user and group.
|
||||
const previousSubwikiId = parseInt(previousView.data.subwikiId, 10) || 0;
|
||||
if (previousSubwikiId > 0 && this.subwikiId > 0) {
|
||||
return previousSubwikiId != this.subwikiId;
|
||||
}
|
||||
|
||||
const previousUserId = parseInt(previousView.data.userId, 10) || 0,
|
||||
previousGroupId = parseInt(previousView.data.groupId, 10) || 0;
|
||||
|
||||
return this.userId != previousUserId || this.groupId != previousGroupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the data.
|
||||
*/
|
||||
save(): void {
|
||||
const values = this.pageForm.value,
|
||||
title = values.title,
|
||||
modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
let promise,
|
||||
text = values.text;
|
||||
|
||||
if (this.rteEnabled) {
|
||||
text = this.textUtils.restorePluginfileUrls(text, this.subwikiFiles);
|
||||
} else {
|
||||
text = this.textUtils.formatHtmlLines(text);
|
||||
}
|
||||
|
||||
if (this.editing) {
|
||||
// Edit existing page.
|
||||
promise = this.wikiProvider.editPage(this.pageId, text, this.section).then(() => {
|
||||
// Invalidate page since it changed.
|
||||
return this.wikiProvider.invalidatePage(this.pageId).then(() => {
|
||||
return this.gotoPage(title);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Creating a new page.
|
||||
if (!title) {
|
||||
// Title is mandatory, stop.
|
||||
this.domUtils.showAlert(this.translate.instant('core.notice'),
|
||||
this.translate.instant('addon.mod_wiki.titleshouldnotbeempty'));
|
||||
modal.dismiss();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.editOffline) {
|
||||
// Check if the user has an offline page with the same title.
|
||||
promise = this.wikiOffline.getNewPage(title, this.subwikiId, this.wikiId, this.userId, this.groupId).then(() => {
|
||||
// There's a page with same name, reject with error message.
|
||||
return Promise.reject(this.translate.instant('addon.mod_wiki.pageexists'));
|
||||
}, () => {
|
||||
// Not found, page can be sent.
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
promise = promise.then(() => {
|
||||
// Try to send the page.
|
||||
let wikiId = this.wikiId || (this.module && this.module.instance);
|
||||
|
||||
return this.wikiProvider.newPage(title, text, this.subwikiId, wikiId, this.userId, this.groupId).then((id) => {
|
||||
if (id > 0) {
|
||||
// Page was created, get its data and go to the page.
|
||||
this.pageId = id;
|
||||
|
||||
return this.wikiProvider.getPageContents(this.pageId).then((pageContents) => {
|
||||
const promises = [];
|
||||
|
||||
wikiId = parseInt(pageContents.wikiid, 10);
|
||||
if (!this.subwikiId) {
|
||||
// Subwiki was not created, invalidate subwikis as well.
|
||||
promises.push(this.wikiProvider.invalidateSubwikis(wikiId));
|
||||
}
|
||||
|
||||
this.subwikiId = parseInt(pageContents.subwikiid, 10);
|
||||
this.userId = parseInt(pageContents.userid, 10);
|
||||
this.groupId = parseInt(pageContents.groupid, 10);
|
||||
|
||||
// Invalidate subwiki pages since there are new.
|
||||
promises.push(this.wikiProvider.invalidateSubwikiPages(wikiId));
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return this.gotoPage(title);
|
||||
});
|
||||
}).finally(() => {
|
||||
// Notify page created.
|
||||
this.eventsProvider.trigger(AddonModWikiProvider.PAGE_CREATED_EVENT, {
|
||||
pageId: this.pageId,
|
||||
subwikiId: this.subwikiId,
|
||||
pageTitle: title,
|
||||
siteId: this.sitesProvider.getCurrentSiteId()
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Page stored in offline. Go to see the offline page.
|
||||
this.goToNewOfflinePage(title);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return promise.catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'Error saving wiki data.');
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renew lock and control versions.
|
||||
*/
|
||||
protected renewLock(): void {
|
||||
this.wikiProvider.getPageForEditing(this.pageId, this.section, true).then((response) => {
|
||||
if (response.version && this.version != response.version) {
|
||||
this.wrongVersionLock = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch module information to redirect when needed.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected retrieveModuleInfo(wikiId: number): Promise<any> {
|
||||
if (this.module.id && this.courseId) {
|
||||
// We have enough data.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const promise = this.module.id ? Promise.resolve(this.module) :
|
||||
this.courseProvider.getModuleBasicInfoByInstance(wikiId, 'wiki');
|
||||
|
||||
return promise.then((mod) => {
|
||||
this.module = mod;
|
||||
this.componentId = this.module.id;
|
||||
|
||||
if (!this.courseId && this.module.course) {
|
||||
this.courseId = this.module.course;
|
||||
} else if (!this.courseId) {
|
||||
return this.courseHelper.getModuleCourseIdByInstance(wikiId, 'wiki').then((course) => {
|
||||
this.courseId = course;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.isDestroyed = true;
|
||||
clearInterval(this.renewLockInterval);
|
||||
|
||||
// Unblock the subwiki.
|
||||
if (this.blockId) {
|
||||
this.syncProvider.unblockOperation(this.component, this.blockId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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 {}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NavController, ViewController } from 'ionic-angular';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||
import { AddonModWikiProvider } from './wiki';
|
||||
|
||||
/**
|
||||
* Handler to treat links to create a wiki page.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModWikiCreateLinkHandler extends CoreContentLinksHandlerBase {
|
||||
name = 'AddonModWikiCreateLinkHandler';
|
||||
featureName = 'CoreCourseModuleDelegate_AddonModWiki';
|
||||
pattern = /\/mod\/wiki\/create\.php.*([\&\?]swid=\d+)/;
|
||||
|
||||
constructor(protected domUtils: CoreDomUtilsProvider, protected wikiProvider: AddonModWikiProvider,
|
||||
protected courseHelper: CoreCourseHelperProvider, protected linkHelper: CoreContentLinksHelperProvider,
|
||||
protected courseProvider: CoreCourseProvider) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current view is a wiki page of the same wiki.
|
||||
*
|
||||
* @param {ViewController} activeView Active view.
|
||||
* @param {number} subwikiId Subwiki ID to check.
|
||||
* @param {string} siteId Site ID.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: whether current view belongs to the same wiki.
|
||||
*/
|
||||
protected currentStateIsSameWiki(activeView: ViewController, subwikiId: number, siteId: string): Promise<boolean> {
|
||||
|
||||
if (activeView && activeView.component.name == 'AddonModWikiIndexPage') {
|
||||
if (activeView.data.subwikiId == subwikiId) {
|
||||
// Same subwiki, so it's same wiki.
|
||||
return Promise.resolve(true);
|
||||
|
||||
} else if (activeView.data.pageId) {
|
||||
// Get the page contents to check the subwiki.
|
||||
return this.wikiProvider.getPageContents(activeView.data.pageId, false, false, siteId).then((page) => {
|
||||
return page.subwikiid == subwikiId;
|
||||
}).catch(() => {
|
||||
// Not found, return false.
|
||||
return false;
|
||||
});
|
||||
|
||||
} else if (activeView.data.wikiId) {
|
||||
// Check if the subwiki belongs to this wiki.
|
||||
return this.wikiProvider.wikiHasSubwiki(activeView.data.wikiId, subwikiId, false, false, siteId);
|
||||
|
||||
} else if (activeView.data.courseId && activeView.data.module) {
|
||||
const moduleId = activeView.data.module && activeView.data.module.id;
|
||||
if (moduleId) {
|
||||
// Get the wiki.
|
||||
return this.wikiProvider.getWiki(activeView.data.courseId, moduleId, false, siteId).then((wiki) => {
|
||||
// Check if the subwiki belongs to this wiki.
|
||||
return this.wikiProvider.wikiHasSubwiki(wiki.id, subwikiId, false, false, siteId);
|
||||
}).catch(() => {
|
||||
// Not found, return false.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
*
|
||||
* @param {string[]} siteIds List of sites the URL belongs to.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
|
||||
courseId = courseId || params.courseid || params.cid;
|
||||
|
||||
return [{
|
||||
action: (siteId, navCtrl?: NavController): void => {
|
||||
const modal = this.domUtils.showModalLoading(),
|
||||
subwikiId = parseInt(params.swid, 10),
|
||||
activeView = navCtrl && navCtrl.getActive();
|
||||
|
||||
// Check if the link is inside the same wiki.
|
||||
this.currentStateIsSameWiki(activeView, subwikiId, siteId).then((isSameWiki) => {
|
||||
if (isSameWiki) {
|
||||
// User is seeing the wiki, we can get the module from the wiki params.
|
||||
if (activeView && activeView.data.module && activeView.data.module.id) {
|
||||
// We already have it in the params.
|
||||
return activeView.data.module;
|
||||
} else if (activeView && activeView.data.wikiId) {
|
||||
return this.courseProvider.getModuleBasicInfoByInstance(activeView.data.wikiId, 'wiki', siteId)
|
||||
.catch(() => {
|
||||
// Not found.
|
||||
});
|
||||
}
|
||||
}
|
||||
}).then((module) => {
|
||||
// Return the params.
|
||||
const pageParams = {
|
||||
module: module,
|
||||
courseId: courseId || (module && module.course) || (activeView && activeView.data.courseId),
|
||||
pageTitle: params.title,
|
||||
subwikiId: subwikiId
|
||||
};
|
||||
|
||||
this.linkHelper.goInSite(navCtrl, 'AddonModWikiEditPage', pageParams, siteId);
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
|
||||
/**
|
||||
* Handler to treat links to edit a wiki page.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModWikiEditLinkHandler extends CoreContentLinksHandlerBase {
|
||||
name = 'AddonModWikiEditLinkHandler';
|
||||
featureName = 'CoreCourseModuleDelegate_AddonModWiki';
|
||||
pattern = /\/mod\/wiki\/edit\.php.*([\&\?]pageid=\d+)/;
|
||||
|
||||
constructor(protected linkHelper: CoreContentLinksHelperProvider, protected textUtils: CoreTextUtilsProvider) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
*
|
||||
* @param {string[]} siteIds List of sites the URL belongs to.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
|
||||
courseId = courseId || params.courseid || params.cid;
|
||||
|
||||
return [{
|
||||
action: (siteId, navCtrl?): void => {
|
||||
|
||||
let section = '';
|
||||
if (typeof params.section != 'undefined') {
|
||||
section = this.textUtils.decodeURIComponent(params.section.replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
const pageParams = {
|
||||
courseId: courseId,
|
||||
section: section,
|
||||
pageId: parseInt(params.pageid, 10)
|
||||
};
|
||||
|
||||
this.linkHelper.goInSite(navCtrl, 'AddonModWikiEditPage', pageParams, siteId);
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler';
|
||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||
|
||||
/**
|
||||
* Handler to treat links to wiki index.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModWikiIndexLinkHandler extends CoreContentLinksModuleIndexHandler {
|
||||
name = 'AddonModWikiIndexLinkHandler';
|
||||
|
||||
constructor(courseHelper: CoreCourseHelperProvider) {
|
||||
super(courseHelper, 'AddonModWiki', 'wiki');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NavController, NavOptions } from 'ionic-angular';
|
||||
import { AddonModWikiIndexComponent } from '../components/index/index';
|
||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
|
||||
/**
|
||||
* Handler to support wiki modules.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModWikiModuleHandler implements CoreCourseModuleHandler {
|
||||
name = 'AddonModWiki';
|
||||
modName = 'wiki';
|
||||
|
||||
constructor(private courseProvider: CoreCourseProvider) { }
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean} Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data required to display the module in the course contents view.
|
||||
*
|
||||
* @param {any} module The module object.
|
||||
* @param {number} courseId The course ID.
|
||||
* @param {number} sectionId The section ID.
|
||||
* @return {CoreCourseModuleHandlerData} Data to render the module.
|
||||
*/
|
||||
getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
|
||||
return {
|
||||
icon: this.courseProvider.getModuleIconSrc('wiki'),
|
||||
title: module.name,
|
||||
class: 'addon-mod_wiki-handler',
|
||||
showDownloadButton: true,
|
||||
action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void {
|
||||
navCtrl.push('AddonModWikiIndexPage', {module: module, courseId: courseId}, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the component to render the module. This is needed to support singleactivity course format.
|
||||
* The component returned must implement CoreCourseModuleMainComponent.
|
||||
*
|
||||
* @param {any} course The course object.
|
||||
* @param {any} module The module object.
|
||||
* @return {any} The component to use, undefined if not found.
|
||||
*/
|
||||
getMainComponent(course: any, module: any): any {
|
||||
return AddonModWikiIndexComponent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
||||
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
|
||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||
import { AddonModWikiProvider } from './wiki';
|
||||
|
||||
/**
|
||||
* Handler to treat links to a wiki page or the wiki map.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModWikiPageOrMapLinkHandler extends CoreContentLinksHandlerBase {
|
||||
name = 'AddonModWikiPageOrMapLinkHandler';
|
||||
featureName = 'CoreCourseModuleDelegate_AddonModWiki';
|
||||
pattern = /\/mod\/wiki\/(view|map)\.php.*([\&\?]pageid=\d+)/;
|
||||
|
||||
constructor(protected domUtils: CoreDomUtilsProvider, protected wikiProvider: AddonModWikiProvider,
|
||||
protected courseHelper: CoreCourseHelperProvider, protected linkHelper: CoreContentLinksHelperProvider) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of actions for a link (url).
|
||||
*
|
||||
* @param {string[]} siteIds List of sites the URL belongs to.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {CoreContentLinksAction[]|Promise<CoreContentLinksAction[]>} List of (or promise resolved with list of) actions.
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: any, courseId?: number):
|
||||
CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
|
||||
courseId = courseId || params.courseid || params.cid;
|
||||
|
||||
return [{
|
||||
action: (siteId, navCtrl?): void => {
|
||||
const modal = this.domUtils.showModalLoading(),
|
||||
pageId = parseInt(params.pageid, 10),
|
||||
action = url.indexOf('mod/wiki/map.php') != -1 ? 'map' : 'page';
|
||||
|
||||
// Get the page data to obtain wikiId, subwikiId, etc.
|
||||
this.wikiProvider.getPageContents(pageId, false, false, siteId).then((page) => {
|
||||
let promise;
|
||||
if (courseId) {
|
||||
promise = Promise.resolve(courseId);
|
||||
} else {
|
||||
promise = this.courseHelper.getModuleCourseIdByInstance(page.wikiid, 'wiki', siteId);
|
||||
}
|
||||
|
||||
return promise.then((courseId) => {
|
||||
const pageParams = {
|
||||
courseId: courseId,
|
||||
pageId: page.id,
|
||||
pageTitle: page.title,
|
||||
wikiId: page.wikiid,
|
||||
subwikiId: page.subwikiid,
|
||||
action: action
|
||||
};
|
||||
|
||||
this.linkHelper.goInSite(navCtrl, 'AddonModWikiIndexPage', pageParams, siteId);
|
||||
});
|
||||
}).catch((error) => {
|
||||
|
||||
this.domUtils.showErrorModalDefault(error, 'addon.mod_wiki.errorloadingpage', true);
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled for a certain site (site + user) and a URL.
|
||||
* If not defined, defaults to true.
|
||||
*
|
||||
* @param {string} siteId The site ID.
|
||||
* @param {string} url The URL to treat.
|
||||
* @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1}
|
||||
* @param {number} [courseId] Course ID related to the URL. Optional but recommended.
|
||||
* @return {boolean|Promise<boolean>} Whether the handler is enabled for the URL and site.
|
||||
*/
|
||||
isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise<boolean> {
|
||||
const isMap = url.indexOf('mod/wiki/map.php') != -1;
|
||||
|
||||
if (params.id && !isMap) {
|
||||
// ID param is more prioritary than pageid in index page, it's a index URL.
|
||||
return false;
|
||||
} else if (isMap && typeof params.option != 'undefined' && params.option != 5) {
|
||||
// Map link but the option isn't "Page list", not supported.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||
import { CoreGroupsProvider } from '@providers/groups';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreCourseModulePrefetchHandlerBase } from '@core/course/classes/module-prefetch-handler';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||
import { CoreGradesHelperProvider } from '@core/grades/providers/helper';
|
||||
import { CoreUserProvider } from '@core/user/providers/user';
|
||||
import { AddonModWikiProvider } from './wiki';
|
||||
|
||||
/**
|
||||
* Handler to prefetch wikis.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModWikiPrefetchHandler extends CoreCourseModulePrefetchHandlerBase {
|
||||
name = 'AddonModWiki';
|
||||
modName = 'wiki';
|
||||
component = AddonModWikiProvider.COMPONENT;
|
||||
updatesNames = /^.*files$|^pages$/;
|
||||
|
||||
constructor(protected injector: Injector, protected wikiProvider: AddonModWikiProvider,
|
||||
protected textUtils: CoreTextUtilsProvider, protected courseProvider: CoreCourseProvider,
|
||||
protected courseHelper: CoreCourseHelperProvider, protected filepoolProvider: CoreFilepoolProvider,
|
||||
protected groupsProvider: CoreGroupsProvider, protected gradesHelper: CoreGradesHelperProvider,
|
||||
protected userProvider: CoreUserProvider) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the module.
|
||||
*
|
||||
* @param {any} module The module object returned by WS.
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {string} [dirPath] Path of the directory where to store all the content files. @see downloadOrPrefetch.
|
||||
* @return {Promise<any>} Promise resolved when all content is downloaded.
|
||||
*/
|
||||
download(module: any, courseId: number, dirPath?: string): Promise<any> {
|
||||
// Same implementation for download or prefetch.
|
||||
return this.prefetch(module, courseId, false, dirPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of pages that can be downloaded.
|
||||
*
|
||||
* @param {any} module The module object returned by WS.
|
||||
* @param {number} courseId The course ID.
|
||||
* @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache.
|
||||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} List of pages.
|
||||
*/
|
||||
protected getAllPages(module: any, courseId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string)
|
||||
: Promise<any[]> {
|
||||
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.wikiProvider.getWiki(courseId, module.id, offline, siteId).then((wiki) => {
|
||||
return this.wikiProvider.getWikiPageList(wiki, offline, ignoreCache, siteId);
|
||||
}).catch(() => {
|
||||
// Wiki not found, return empty list.
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the download size of a module.
|
||||
*
|
||||
* @param {any} module Module.
|
||||
* @param {Number} courseId Course ID the module belongs to.
|
||||
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
|
||||
* @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able
|
||||
* to calculate the total size.
|
||||
*/
|
||||
getDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> {
|
||||
const promises = [],
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
promises.push(this.getFiles(module, courseId, single, siteId).then((files) => {
|
||||
return this.utils.sumFileSizes(files);
|
||||
}));
|
||||
|
||||
promises.push(this.getAllPages(module, courseId, false, true, siteId).then((pages) => {
|
||||
let size = 0;
|
||||
|
||||
pages.forEach((page) => {
|
||||
if (page.contentsize) {
|
||||
size = size + page.contentsize;
|
||||
}
|
||||
});
|
||||
|
||||
return {size: size, total: true};
|
||||
}));
|
||||
|
||||
return Promise.all(promises).then((sizes) => {
|
||||
// Sum values in the array.
|
||||
return sizes.reduce((a, b) => {
|
||||
return {size: a.size + b.size, total: a.total && b.total};
|
||||
}, {size: 0, total: true});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of files. If not defined, we'll assume they're in module.contents.
|
||||
*
|
||||
* @param {any} module Module.
|
||||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with the list of files.
|
||||
*/
|
||||
getFiles(module: any, courseId: number, single?: boolean, siteId?: string): Promise<any[]> {
|
||||
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.wikiProvider.getWiki(courseId, module.id, false, siteId).then((wiki) => {
|
||||
const introFiles = this.getIntroFilesFromInstance(module, wiki);
|
||||
|
||||
return this.wikiProvider.getWikiFileList(wiki, false, false, siteId).then((files) => {
|
||||
return introFiles.concat(files);
|
||||
});
|
||||
}).catch(() => {
|
||||
// Wiki not found, return empty list.
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the prefetched content.
|
||||
*
|
||||
* @param {number} moduleId The module ID.
|
||||
* @param {number} courseId The course ID the module belongs to.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateContent(moduleId: number, courseId: number): Promise<any> {
|
||||
return this.wikiProvider.invalidateContent(moduleId, courseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch a module.
|
||||
*
|
||||
* @param {any} module Module.
|
||||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
|
||||
* @param {string} [dirPath] Path of the directory where to store all the content files. @see downloadOrPrefetch.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise<any> {
|
||||
// Get the download time of the package before starting the download (otherwise we'd always get current time).
|
||||
const siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.filepoolProvider.getPackageData(siteId, this.component, module.id).catch(() => {
|
||||
// No package data yet.
|
||||
}).then((data) => {
|
||||
const downloadTime = (data && data.downloadTime) || 0;
|
||||
|
||||
return this.prefetchPackage(module, courseId, single, this.prefetchWiki.bind(this), siteId, [downloadTime]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch a wiki.
|
||||
*
|
||||
* @param {any} module Module.
|
||||
* @param {number} courseId Course ID the module belongs to.
|
||||
* @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section.
|
||||
* @param {string} siteId Site ID.
|
||||
* @param {number} downloadTime The previous download time, 0 if no previous download.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected prefetchWiki(module: any, courseId: number, single: boolean, siteId: string, downloadTime: number): Promise<any> {
|
||||
const userId = this.sitesProvider.getCurrentSiteUserId();
|
||||
|
||||
// Get the list of pages.
|
||||
return this.getAllPages(module, courseId, false, true, siteId).then((pages) => {
|
||||
const promises = [];
|
||||
|
||||
pages.forEach((page) => {
|
||||
// Fetch page contents if it needs to be fetched.
|
||||
if (page.timemodified > downloadTime) {
|
||||
promises.push(this.wikiProvider.getPageContents(page.id, false, true, siteId));
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch group data.
|
||||
promises.push(this.groupsProvider.getActivityGroupMode(module.id, siteId).then((groupMode) => {
|
||||
if (groupMode === CoreGroupsProvider.SEPARATEGROUPS || groupMode === CoreGroupsProvider.VISIBLEGROUPS) {
|
||||
// Get the groups available for the user.
|
||||
return this.groupsProvider.getActivityAllowedGroups(module.id, userId, siteId);
|
||||
}
|
||||
}));
|
||||
|
||||
// Fetch info to provide wiki links.
|
||||
promises.push(this.wikiProvider.getWiki(courseId, module.id, false, siteId).then((wiki) => {
|
||||
return this.courseHelper.getModuleCourseIdByInstance(wiki.id, 'wiki', siteId);
|
||||
}));
|
||||
promises.push(this.courseProvider.getModuleBasicInfo(module.id, siteId));
|
||||
|
||||
// Get related page files and fetch them.
|
||||
promises.push(this.getFiles(module, courseId, single, siteId).then((files) => {
|
||||
return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id);
|
||||
}));
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCronHandler } from '@providers/cron';
|
||||
import { AddonModWikiSyncProvider } from './wiki-sync';
|
||||
|
||||
/**
|
||||
* Synchronization cron handler.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModWikiSyncCronHandler implements CoreCronHandler {
|
||||
name = 'AddonModWikiSyncCronHandler';
|
||||
|
||||
constructor(private wikiSync: AddonModWikiSyncProvider) {}
|
||||
|
||||
/**
|
||||
* Execute the process.
|
||||
* Receives the ID of the site affected, undefined for all sites.
|
||||
*
|
||||
* @param {string} [siteId] ID of the site affected, undefined for all sites.
|
||||
* @return {Promise<any>} Promise resolved when done, rejected if failure.
|
||||
*/
|
||||
execute(siteId?: string): Promise<any> {
|
||||
return this.wikiSync.syncAllWikis(siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time between consecutive executions.
|
||||
*
|
||||
* @return {number} Time between consecutive executions (in ms).
|
||||
*/
|
||||
getInterval(): number {
|
||||
return 600000; // 10 minutes.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
|
||||
/**
|
||||
* Service to handle offline wiki.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModWikiOfflineProvider {
|
||||
|
||||
protected logger;
|
||||
|
||||
// Variables for database.
|
||||
protected NEW_PAGES_TABLE = 'addon_mod_wiki_new_pages_store';
|
||||
protected tablesSchema = [
|
||||
{
|
||||
name: this.NEW_PAGES_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'wikiid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'subwikiid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'userid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'groupid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'cachedcontent',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'contentformat',
|
||||
type: 'TEXT'
|
||||
},
|
||||
{
|
||||
name: 'courseid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'timecreated',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'timemodified',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'caneditpage',
|
||||
type: 'INTEGER'
|
||||
}
|
||||
],
|
||||
primaryKeys: ['wikiid', 'subwikiid', 'userid', 'groupid', 'title']
|
||||
}
|
||||
];
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) {
|
||||
this.logger = logger.getInstance('AddonModWikiOfflineProvider');
|
||||
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to a positive number. If not a number or less than 0, 0 will be returned.
|
||||
*
|
||||
* @param {any} value Value to convert.
|
||||
* @return {number} Converted value.
|
||||
*/
|
||||
convertToPositiveNumber(value: any): number {
|
||||
value = parseInt(value, 10);
|
||||
|
||||
return value > 0 ? value : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a new page.
|
||||
*
|
||||
* @param {string} title Title of the page.
|
||||
* @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined.
|
||||
* @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [userId] User ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if deleted, rejected if failure.
|
||||
*/
|
||||
deleteNewPage(title: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string)
|
||||
: Promise<any> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
|
||||
subwikiId = this.convertToPositiveNumber(subwikiId);
|
||||
wikiId = this.convertToPositiveNumber(wikiId);
|
||||
userId = this.convertToPositiveNumber(userId);
|
||||
groupId = this.convertToPositiveNumber(groupId);
|
||||
|
||||
return site.getDb().deleteRecords(this.NEW_PAGES_TABLE, {
|
||||
subwikiid: subwikiId,
|
||||
wikiid: wikiId,
|
||||
userid: userId,
|
||||
groupid: groupId,
|
||||
title: title
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the stored new pages from all the wikis.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with pages.
|
||||
*/
|
||||
getAllNewPages(siteId?: string): Promise<any[]> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getAllRecords(this.NEW_PAGES_TABLE);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a stored new page.
|
||||
*
|
||||
* @param {string} title Title of the page.
|
||||
* @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined.
|
||||
* @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [userId] User ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with page.
|
||||
*/
|
||||
getNewPage(title: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string)
|
||||
: Promise<any> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
|
||||
subwikiId = this.convertToPositiveNumber(subwikiId);
|
||||
wikiId = this.convertToPositiveNumber(wikiId);
|
||||
userId = this.convertToPositiveNumber(userId);
|
||||
groupId = this.convertToPositiveNumber(groupId);
|
||||
|
||||
return site.getDb().getRecord(this.NEW_PAGES_TABLE, {
|
||||
subwikiid: subwikiId,
|
||||
wikiid: wikiId,
|
||||
userid: userId,
|
||||
groupid: groupId,
|
||||
title: title
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the stored new pages from a certain subwiki.
|
||||
*
|
||||
* @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined.
|
||||
* @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [userId] User ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with pages.
|
||||
*/
|
||||
getSubwikiNewPages(subwikiId?: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string): Promise<any[]> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
|
||||
subwikiId = this.convertToPositiveNumber(subwikiId);
|
||||
wikiId = this.convertToPositiveNumber(wikiId);
|
||||
userId = this.convertToPositiveNumber(userId);
|
||||
groupId = this.convertToPositiveNumber(groupId);
|
||||
|
||||
return site.getDb().getRecords(this.NEW_PAGES_TABLE, {
|
||||
subwikiid: subwikiId,
|
||||
wikiid: wikiId,
|
||||
userid: userId,
|
||||
groupid: groupId
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the stored new pages from a list of subwikis.
|
||||
*
|
||||
* @param {any[]} subwikis List of subwiki.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with pages.
|
||||
*/
|
||||
getSubwikisNewPages(subwikis: any[], siteId?: string): Promise<any[]> {
|
||||
const promises = [];
|
||||
let pages = [];
|
||||
|
||||
subwikis.forEach((subwiki) => {
|
||||
promises.push(this.getSubwikiNewPages(subwiki.id, subwiki.wikiid, subwiki.userid, subwiki.groupid, siteId)
|
||||
.then((subwikiPages) => {
|
||||
pages = pages.concat(subwikiPages);
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return pages;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new page to be sent later.
|
||||
*
|
||||
* @param {string} title Title of the page.
|
||||
* @param {string} content Content of the page.
|
||||
* @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined.
|
||||
* @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [userId] User ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
saveNewPage(title: string, content: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number,
|
||||
siteId?: string): Promise<any> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const now = new Date().getTime(),
|
||||
entry = {
|
||||
title: title,
|
||||
cachedcontent: content,
|
||||
subwikiid: this.convertToPositiveNumber(subwikiId),
|
||||
wikiid: this.convertToPositiveNumber(wikiId),
|
||||
userid: this.convertToPositiveNumber(userId),
|
||||
groupid: this.convertToPositiveNumber(groupId),
|
||||
contentformat: 'html',
|
||||
timecreated: now,
|
||||
timemodified: now,
|
||||
caneditpage: true
|
||||
};
|
||||
|
||||
return site.getDb().insertRecord(this.NEW_PAGES_TABLE, entry);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a list of subwikis have offline data stored.
|
||||
*
|
||||
* @param {any[]} subwikis List of subwikis.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return{Promise<boolean>} Promise resolved with boolean: whether it has offline data.
|
||||
*/
|
||||
subwikisHaveOfflineData(subwikis: any[], siteId?: string): Promise<boolean> {
|
||||
return this.getSubwikisNewPages(subwikis, siteId).then((pages) => {
|
||||
return !!pages.length;
|
||||
}).catch(() => {
|
||||
// Error, return false.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,388 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreGroupsProvider } from '@providers/groups';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreSyncProvider } from '@providers/sync';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||
import { AddonModWikiProvider } from './wiki';
|
||||
import { AddonModWikiOfflineProvider } from './wiki-offline';
|
||||
|
||||
/**
|
||||
* Data returned by a subwiki sync.
|
||||
*/
|
||||
export interface AddonModWikiSyncSubwikiResult {
|
||||
/**
|
||||
* List of warnings.
|
||||
* @type {string[]}
|
||||
*/
|
||||
warnings: string[];
|
||||
|
||||
/**
|
||||
* Whether data was updated in the site.
|
||||
* @type {boolean}
|
||||
*/
|
||||
updated: boolean;
|
||||
|
||||
/**
|
||||
* List of created pages.
|
||||
* @type {{pageId: number, title: string}}
|
||||
*/
|
||||
created: {pageId: number, title: string}[];
|
||||
|
||||
/**
|
||||
* List of discarded pages.
|
||||
* @type {{title: string, warning: string}}
|
||||
*/
|
||||
discarded: {title: string, warning: string}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data returned by a wiki sync.
|
||||
*/
|
||||
export interface AddonModWikiSyncWikiResult {
|
||||
/**
|
||||
* List of warnings.
|
||||
* @type {string[]}
|
||||
*/
|
||||
warnings: string[];
|
||||
|
||||
/**
|
||||
* Whether data was updated in the site.
|
||||
* @type {boolean}
|
||||
*/
|
||||
updated: boolean;
|
||||
|
||||
/**
|
||||
* List of subwikis.
|
||||
* @type {{[subwikiId: number]: {created: {pageId: number, title: string}, discarded: {title: string, warning: string}}}}
|
||||
*/
|
||||
subwikis: {[subwikiId: number]: {
|
||||
created: {pageId: number, title: string}[],
|
||||
discarded: {title: string, warning: string}[]
|
||||
}};
|
||||
|
||||
/**
|
||||
* Site ID.
|
||||
* @type {string}
|
||||
*/
|
||||
siteId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service to sync wikis.
|
||||
*/
|
||||
@Injectable()
|
||||
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;
|
||||
|
||||
constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider,
|
||||
syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService,
|
||||
courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider,
|
||||
private wikiProvider: AddonModWikiProvider, private wikiOfflineProvider: AddonModWikiOfflineProvider,
|
||||
private utils: CoreUtilsProvider, private groupsProvider: CoreGroupsProvider) {
|
||||
|
||||
super('AddonModWikiSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate);
|
||||
|
||||
this.componentTranslate = courseProvider.translateModuleName('wiki');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string to identify a subwiki. If it doesn't have a subwiki ID it will be identified by wiki ID, user ID and group ID.
|
||||
*
|
||||
* @param {number} subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined.
|
||||
* @param {number} [wikiId] Wiki ID. Optional, will be used to create the subwiki if subwiki ID not provided.
|
||||
* @param {number} [userId] User ID. Optional, will be used to create the subwiki if subwiki ID not provided.
|
||||
* @param {number} [groupId] Group ID. Optional, will be used to create the subwiki if subwiki ID not provided.
|
||||
* @return {string} Identifier.
|
||||
*/
|
||||
getSubwikiBlockId(subwikiId: number, wikiId?: number, userId?: number, groupId?: number): string {
|
||||
subwikiId = this.wikiOfflineProvider.convertToPositiveNumber(subwikiId);
|
||||
|
||||
if (subwikiId && subwikiId > 0) {
|
||||
return String(subwikiId);
|
||||
}
|
||||
|
||||
wikiId = this.wikiOfflineProvider.convertToPositiveNumber(wikiId);
|
||||
if (wikiId) {
|
||||
userId = this.wikiOfflineProvider.convertToPositiveNumber(userId);
|
||||
groupId = this.wikiOfflineProvider.convertToPositiveNumber(groupId);
|
||||
|
||||
return wikiId + ':' + userId + ':' + groupId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to synchronize all the wikis in a certain site or in all sites.
|
||||
*
|
||||
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
syncAllWikis(siteId?: string): Promise<any> {
|
||||
return this.syncOnSites('all wikis', this.syncAllWikisFunc.bind(this), [], siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all wikis on a site.
|
||||
*
|
||||
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||
* @param {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
protected syncAllWikisFunc(siteId?: string): Promise<any> {
|
||||
// Get all the pages created in offline.
|
||||
return this.wikiOfflineProvider.getAllNewPages(siteId).then((pages) => {
|
||||
const promises = [],
|
||||
subwikis = {};
|
||||
|
||||
// Get subwikis to sync.
|
||||
pages.forEach((page) => {
|
||||
const index = this.getSubwikiBlockId(page.subwikiid, page.wikiid, page.userid, page.groupid);
|
||||
subwikis[index] = page;
|
||||
});
|
||||
|
||||
// Sync all subwikis.
|
||||
for (const id in subwikis) {
|
||||
const subwiki = subwikis[id];
|
||||
|
||||
promises.push(this.syncSubwikiIfNeeded(subwiki.subwikiid, subwiki.wikiid, subwiki.userid, subwiki.groupid,
|
||||
siteId).then((result) => {
|
||||
|
||||
if (result && result.updated) {
|
||||
// Sync successful, send event.
|
||||
this.eventsProvider.trigger(AddonModWikiSyncProvider.AUTO_SYNCED, {
|
||||
siteId: siteId,
|
||||
subwikiId: subwiki.subwikiid,
|
||||
wikiId: subwiki.wikiid,
|
||||
userId: subwiki.userid,
|
||||
groupId: subwiki.groupid,
|
||||
created: result.created,
|
||||
discarded: result.discarded,
|
||||
warnings: result.warnings
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a subwiki only if a certain time has passed since the last time.
|
||||
*
|
||||
* @param {number} subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined.
|
||||
* @param {number} [wikiId] Wiki ID. Optional, will be used to create the subwiki if subwiki ID not provided.
|
||||
* @param {number} [userId] User ID. Optional, will be used to create the subwiki if subwiki ID not provided.
|
||||
* @param {number} [groupId] Group ID. Optional, will be used to create the subwiki if subwiki ID not provided.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<void|AddonModWikiSyncSubwikiResult>} Promise resolved when subwiki is synced or doesn't need to be synced.
|
||||
*/
|
||||
syncSubwikiIfNeeded(subwikiId: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string)
|
||||
: Promise<void | AddonModWikiSyncSubwikiResult> {
|
||||
|
||||
const blockId = this.getSubwikiBlockId(subwikiId, wikiId, userId, groupId);
|
||||
|
||||
return this.isSyncNeeded(blockId, siteId).then((needed) => {
|
||||
if (needed) {
|
||||
return this.syncSubwiki(subwikiId, wikiId, userId, groupId, siteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize a subwiki.
|
||||
*
|
||||
* @param {number} subwikiId Subwiki ID. If not defined, wikiId, userId and groupId should be defined.
|
||||
* @param {number} [wikiId] Wiki ID. Optional, will be used to create the subwiki if subwiki ID not provided.
|
||||
* @param {number} [userId] User ID. Optional, will be used to create the subwiki if subwiki ID not provided.
|
||||
* @param {number} [groupId] Group ID. Optional, will be used to create the subwiki if subwiki ID not provided.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<AddonModWikiSyncSubwikiResult>} Promise resolved if sync is successful, rejected otherwise.
|
||||
*/
|
||||
syncSubwiki(subwikiId: number, wikiId?: number, userId?: number, groupId?: number, siteId?: string)
|
||||
: Promise<AddonModWikiSyncSubwikiResult> {
|
||||
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const result: AddonModWikiSyncSubwikiResult = {
|
||||
warnings: [],
|
||||
updated: false,
|
||||
created: [],
|
||||
discarded: []
|
||||
},
|
||||
subwikiBlockId = this.getSubwikiBlockId(subwikiId, wikiId, userId, groupId);
|
||||
|
||||
if (this.isSyncing(subwikiBlockId, siteId)) {
|
||||
// There's already a sync ongoing for this subwiki, return the promise.
|
||||
return this.getOngoingSync(subwikiBlockId, siteId);
|
||||
}
|
||||
|
||||
// Verify that subwiki isn't blocked.
|
||||
if (this.syncProvider.isBlocked(AddonModWikiProvider.COMPONENT, subwikiBlockId, siteId)) {
|
||||
this.logger.debug('Cannot sync subwiki ' + subwikiBlockId + ' because it is blocked.');
|
||||
|
||||
return Promise.reject(this.translate.instant('core.errorsyncblocked', {$a: this.componentTranslate}));
|
||||
}
|
||||
|
||||
this.logger.debug('Try to sync subwiki ' + subwikiBlockId);
|
||||
|
||||
// Get offline responses to be sent.
|
||||
const syncPromise = this.wikiOfflineProvider.getSubwikiNewPages(subwikiId, wikiId, userId, groupId, siteId).catch(() => {
|
||||
// No offline data found, return empty array.
|
||||
return [];
|
||||
}).then((pages) => {
|
||||
if (!pages || !pages.length) {
|
||||
// Nothing to sync.
|
||||
return;
|
||||
} else if (!this.appProvider.isOnline()) {
|
||||
// Cannot sync in offline.
|
||||
return Promise.reject(this.translate.instant('core.networkerrormsg'));
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
|
||||
// Send the pages.
|
||||
pages.forEach((page) => {
|
||||
promises.push(this.wikiProvider.newPageOnline(page.title, page.cachedcontent, subwikiId, wikiId, userId, groupId,
|
||||
siteId).then((pageId) => {
|
||||
|
||||
result.updated = true;
|
||||
|
||||
// Add page to created pages array.
|
||||
result.created.push({
|
||||
pageId: pageId,
|
||||
title: page.title
|
||||
});
|
||||
|
||||
// Delete the local page.
|
||||
return this.wikiOfflineProvider.deleteNewPage(page.title, subwikiId, wikiId, userId, groupId, siteId);
|
||||
}).catch((error) => {
|
||||
if (this.utils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means that the page cannot be submitted. Delete it.
|
||||
return this.wikiOfflineProvider.deleteNewPage(page.title, subwikiId, wikiId, userId, groupId, siteId)
|
||||
.then(() => {
|
||||
|
||||
result.updated = true;
|
||||
|
||||
// Page deleted, add the page to discarded pages and add a warning.
|
||||
const warning = this.translate.instant('core.warningofflinedatadeleted', {
|
||||
component: this.translate.instant('addon.mod_wiki.wikipage'),
|
||||
name: page.title,
|
||||
error: error
|
||||
});
|
||||
|
||||
result.discarded.push({
|
||||
title: page.title,
|
||||
warning: warning
|
||||
});
|
||||
|
||||
result.warnings.push(warning);
|
||||
});
|
||||
} else {
|
||||
// Couldn't connect to server, reject.
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
}).then(() => {
|
||||
// Sync finished, set sync time.
|
||||
return this.setSyncTime(subwikiBlockId, siteId).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
}).then(() => {
|
||||
// All done, return the warnings.
|
||||
return result;
|
||||
});
|
||||
|
||||
return this.addOngoingSync(subwikiBlockId, syncPromise, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to synchronize a wiki.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {number} [courseId] Course ID.
|
||||
* @param {number} [cmId] Wiki course module ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<AddonModWikiSyncWikiResult>} Promise resolved if sync is successful, rejected otherwise.
|
||||
*/
|
||||
syncWiki(wikiId: number, courseId?: number, cmId?: number, siteId?: string): Promise<AddonModWikiSyncWikiResult> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Sync is done at subwiki level, get all the subwikis.
|
||||
return this.wikiProvider.getSubwikis(wikiId).then((subwikis) => {
|
||||
const promises = [],
|
||||
result: AddonModWikiSyncWikiResult = {
|
||||
warnings: [],
|
||||
updated: false,
|
||||
subwikis: {},
|
||||
siteId: siteId
|
||||
};
|
||||
|
||||
subwikis.forEach((subwiki) => {
|
||||
promises.push(this.syncSubwiki(subwiki.id, subwiki.wikiid, subwiki.userid, subwiki.groupid, siteId).then((data) => {
|
||||
if (data && data.updated) {
|
||||
result.warnings = result.warnings.concat(data.warnings);
|
||||
result.updated = true;
|
||||
result.subwikis[subwiki.id] = {
|
||||
created: data.created,
|
||||
discarded: data.discarded
|
||||
};
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
const promises = [];
|
||||
|
||||
if (result.updated) {
|
||||
// Something has changed, invalidate data.
|
||||
if (wikiId) {
|
||||
promises.push(this.wikiProvider.invalidateSubwikis(wikiId));
|
||||
promises.push(this.wikiProvider.invalidateSubwikiPages(wikiId));
|
||||
promises.push(this.wikiProvider.invalidateSubwikiFiles(wikiId));
|
||||
}
|
||||
if (courseId) {
|
||||
promises.push(this.wikiProvider.invalidateWikiData(courseId));
|
||||
}
|
||||
if (cmId) {
|
||||
promises.push(this.groupsProvider.invalidateActivityAllowedGroups(cmId));
|
||||
promises.push(this.groupsProvider.invalidateActivityGroupMode(cmId));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return result;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,848 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||
import { AddonModWikiOfflineProvider } from './wiki-offline';
|
||||
import { CoreSiteWSPreSets } from '@classes/site';
|
||||
|
||||
export interface AddonModWikiSubwikiListData {
|
||||
/**
|
||||
* Number of subwikis.
|
||||
* @type {number}
|
||||
*/
|
||||
count: number;
|
||||
|
||||
/**
|
||||
* Subwiki ID currently selected.
|
||||
* @type {number}
|
||||
*/
|
||||
subwikiSelected: number;
|
||||
|
||||
/**
|
||||
* User of the subwiki currently selected.
|
||||
* @type {number}
|
||||
*/
|
||||
userSelected: number;
|
||||
|
||||
/**
|
||||
* Group of the subwiki currently selected.
|
||||
* @type {number}
|
||||
*/
|
||||
groupSelected: number;
|
||||
|
||||
/**
|
||||
* List of subwikis.
|
||||
* @type {any[]}
|
||||
*/
|
||||
subwikis: any[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Service that provides some features for wikis.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModWikiProvider {
|
||||
static COMPONENT = 'mmaModWiki';
|
||||
static PAGE_CREATED_EVENT = 'addon_mod_wiki_page_created';
|
||||
static RENEW_LOCK_TIME = 30000; // Milliseconds.
|
||||
|
||||
protected ROOT_CACHE_KEY = 'mmaModWiki:';
|
||||
protected logger;
|
||||
protected subwikiListsCache: {[wikiId: number]: AddonModWikiSubwikiListData} = {};
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider,
|
||||
private filepoolProvider: CoreFilepoolProvider, private utils: CoreUtilsProvider, private translate: TranslateService,
|
||||
private wikiOffline: AddonModWikiOfflineProvider, eventsProvider: CoreEventsProvider) {
|
||||
this.logger = logger.getInstance('AddonModWikiProvider');
|
||||
|
||||
// Clear subwiki lists cache on logout.
|
||||
eventsProvider.on(CoreEventsProvider.LOGIN, () => {
|
||||
this.clearSubwikiList();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear subwiki list cache for a certain wiki or all of them.
|
||||
*
|
||||
* @param {number} [wikiId] wiki Id, if not provided all will be cleared.
|
||||
*/
|
||||
clearSubwikiList(wikiId?: number): void {
|
||||
if (typeof wikiId == 'undefined') {
|
||||
this.subwikiListsCache = {};
|
||||
} else {
|
||||
delete this.subwikiListsCache[wikiId];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save wiki contents on a page or section.
|
||||
*
|
||||
* @param {number} pageId Page ID.
|
||||
* @param {string} content content to be saved.
|
||||
* @param {string} [section] section to get.
|
||||
* @return {Promise<number>} Promise resolved with the page ID.
|
||||
*/
|
||||
editPage(pageId: number, content: string, section?: string, siteId?: string): Promise<number> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params: any = {
|
||||
pageid: pageId,
|
||||
content: content
|
||||
};
|
||||
|
||||
if (section) {
|
||||
params.section = section;
|
||||
}
|
||||
|
||||
return site.write('mod_wiki_edit_page', params).then((response) => {
|
||||
return response.pageid || Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a wiki page contents.
|
||||
*
|
||||
* @param {number} pageId Page ID.
|
||||
* @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache.
|
||||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the page data.
|
||||
*/
|
||||
getPageContents(pageId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
pageid: pageId
|
||||
},
|
||||
preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getPageContentsCacheKey(pageId)
|
||||
};
|
||||
|
||||
if (offline) {
|
||||
preSets.omitExpires = true;
|
||||
} else if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
return site.read('mod_wiki_get_page_contents', params, preSets).then((response) => {
|
||||
return response.page || Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for wiki Pages Contents WS calls.
|
||||
*
|
||||
* @param {number} pageId Wiki Page ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getPageContentsCacheKey(pageId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'page:' + pageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a wiki page contents for editing. It does not cache calls.
|
||||
*
|
||||
* @param {number} pageId Page ID.
|
||||
* @param {string} [section] Section to get.
|
||||
* @param {boolean} [lockOnly] Just renew lock and not return content.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with page contents.
|
||||
*/
|
||||
getPageForEditing(pageId: number, section?: string, lockOnly?: boolean, siteId?: string): Promise<any> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params: any = {
|
||||
pageid: pageId
|
||||
};
|
||||
|
||||
if (section) {
|
||||
params.section = section;
|
||||
}
|
||||
|
||||
// This parameter requires Moodle 3.2. It saves network usage.
|
||||
if (lockOnly && site.isVersionGreaterEqualThan('3.2')) {
|
||||
params.lockonly = 1;
|
||||
}
|
||||
|
||||
return site.write('mod_wiki_get_page_for_editing', params).then((response) => {
|
||||
return response.pagesection || Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of files from a specific subwiki.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {number} [groupId] Group to get files from.
|
||||
* @param {number} [userId] User to get files from.
|
||||
* @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache.
|
||||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with subwiki files.
|
||||
*/
|
||||
getSubwikiFiles(wikiId: number, groupId?: number, userId?: number, offline?: boolean, ignoreCache?: boolean, siteId?: string)
|
||||
: Promise<any[]> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
groupId = groupId || -1;
|
||||
userId = userId || 0;
|
||||
|
||||
const params = {
|
||||
wikiid: wikiId,
|
||||
groupid: groupId,
|
||||
userid: userId
|
||||
},
|
||||
preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getSubwikiFilesCacheKey(wikiId, groupId, userId)
|
||||
};
|
||||
|
||||
if (offline) {
|
||||
preSets.omitExpires = true;
|
||||
} else if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
return site.read('mod_wiki_get_subwiki_files', params, preSets).then((response) => {
|
||||
return response.files || Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for wiki Subwiki Files WS calls.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {number} groupId Group ID.
|
||||
* @param {number} userId User ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getSubwikiFilesCacheKey(wikiId: number, groupId: number, userId: number): string {
|
||||
return this.getSubwikiFilesCacheKeyPrefix(wikiId) + ':' + groupId + ':' + userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for all wiki Subwiki Files WS calls.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getSubwikiFilesCacheKeyPrefix(wikiId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'subwikifiles:' + wikiId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of subwikis and related data for a certain wiki from the cache.
|
||||
*
|
||||
* @param {number} wikiId wiki Id
|
||||
* @return {AddonModWikiSubwikiListData} Subwiki list and related data.
|
||||
*/
|
||||
getSubwikiList(wikiId: number): AddonModWikiSubwikiListData {
|
||||
return this.subwikiListsCache[wikiId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of Pages of a SubWiki.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {number} [groupId] Group to get pages from.
|
||||
* @param {number} [userId] User to get pages from.
|
||||
* @param {string} [sortBy=title] The attribute to sort the returned list.
|
||||
* @param {string} [sortDirection=ASC] Direction to sort the returned list (ASC | DESC).
|
||||
* @param {boolean} [includeContent] Whether the pages have to include its content. Default: false.
|
||||
* @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache.
|
||||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with wiki subwiki pages.
|
||||
*/
|
||||
getSubwikiPages(wikiId: number, groupId?: number, userId?: number, sortBy: string = 'title', sortDirection: string = 'ASC',
|
||||
includeContent?: boolean, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
groupId = groupId || -1;
|
||||
userId = userId || 0;
|
||||
sortBy = sortBy || 'title';
|
||||
sortDirection = sortDirection || 'ASC';
|
||||
includeContent = includeContent || false;
|
||||
|
||||
const params = {
|
||||
wikiid: wikiId,
|
||||
groupid: groupId,
|
||||
userid: userId,
|
||||
options: {
|
||||
sortby: sortBy,
|
||||
sortdirection: sortDirection,
|
||||
includecontent: includeContent ? 1 : 0
|
||||
}
|
||||
|
||||
},
|
||||
preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getSubwikiPagesCacheKey(wikiId, groupId, userId)
|
||||
};
|
||||
|
||||
if (offline) {
|
||||
preSets.omitExpires = true;
|
||||
} else if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
return site.read('mod_wiki_get_subwiki_pages', params, preSets).then((response) => {
|
||||
return response.pages || Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for wiki Subwiki Pages WS calls.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {number} groupId Group ID.
|
||||
* @param {number} userId User ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getSubwikiPagesCacheKey(wikiId: number, groupId: number, userId: number): string {
|
||||
return this.getSubwikiPagesCacheKeyPrefix(wikiId) + ':' + groupId + ':' + userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for all wiki Subwiki Pages WS calls.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getSubwikiPagesCacheKeyPrefix(wikiId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'subwikipages:' + wikiId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the subwikis of a wiki.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache.
|
||||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with subwikis.
|
||||
*/
|
||||
getSubwikis(wikiId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
wikiid: wikiId
|
||||
},
|
||||
preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getSubwikisCacheKey(wikiId)
|
||||
};
|
||||
|
||||
if (offline) {
|
||||
preSets.omitExpires = true;
|
||||
} else if (ignoreCache) {
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
return site.read('mod_wiki_get_subwikis', params, preSets).then((response) => {
|
||||
return response.subwikis || Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for get wiki subWikis WS calls.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getSubwikisCacheKey(wikiId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'subwikis:' + wikiId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a wiki by module ID.
|
||||
*
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {number} cmId Course module ID.
|
||||
* @param {boolean} [forceCache] Whether it should always return cached data.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the wiki is retrieved.
|
||||
*/
|
||||
getWiki(courseId: number, cmId: number, forceCache?: boolean, siteId?: string): Promise<any> {
|
||||
return this.getWikiByField(courseId, 'coursemodule', cmId, forceCache, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a wiki with key=value. If more than one is found, only the first will be returned.
|
||||
*
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {string} key Name of the property to check.
|
||||
* @param {any} value Value to search.
|
||||
* @param {boolean} [forceCache] Whether it should always return cached data.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the wiki is retrieved.
|
||||
*/
|
||||
protected getWikiByField(courseId: number, key: string, value: any, forceCache?: boolean, siteId?: string): Promise<any> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
courseids: [courseId]
|
||||
},
|
||||
preSets = {
|
||||
cacheKey: this.getWikiDataCacheKey(courseId)
|
||||
};
|
||||
|
||||
return site.read('mod_wiki_get_wikis_by_courses', params, preSets).then((response) => {
|
||||
if (response.wikis) {
|
||||
const currentWiki = response.wikis.find((wiki) => {
|
||||
return wiki[key] == value;
|
||||
});
|
||||
|
||||
if (currentWiki) {
|
||||
return currentWiki;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a wiki by wiki ID.
|
||||
*
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {number} id Wiki ID.
|
||||
* @param {boolean} [forceCache] Whether it should always return cached data.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the wiki is retrieved.
|
||||
*/
|
||||
getWikiById(courseId: number, id: number, forceCache?: boolean, siteId?: string): Promise<any> {
|
||||
return this.getWikiByField(courseId, 'id', id, forceCache, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for wiki data WS calls.
|
||||
*
|
||||
* @param {number} courseId Course ID.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getWikiDataCacheKey(courseId: number): string {
|
||||
return this.ROOT_CACHE_KEY + 'wiki:' + courseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of files to download for a wiki, using a format similar to module.contents from get_course_contents.
|
||||
*
|
||||
* @param {any} wiki Wiki.
|
||||
* @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache.
|
||||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with the list of files.
|
||||
*/
|
||||
getWikiFileList(wiki: any, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
let files = [];
|
||||
|
||||
return this.getSubwikis(wiki.id, offline, ignoreCache, siteId).then((subwikis) => {
|
||||
const promises = [];
|
||||
|
||||
subwikis.forEach((subwiki) => {
|
||||
promises.push(this.getSubwikiFiles(subwiki.wikiid, subwiki.groupid, subwiki.userid, offline, ignoreCache, siteId)
|
||||
.then((swFiles) => {
|
||||
files = files.concat(swFiles);
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return files;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of all pages for a Wiki.
|
||||
*
|
||||
* @param {any} wiki Wiki.
|
||||
* @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache.
|
||||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Page list.
|
||||
*/
|
||||
getWikiPageList(wiki: any, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any[]> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
let pages = [];
|
||||
|
||||
return this.getSubwikis(wiki.id, offline, ignoreCache, siteId).then((subwikis) => {
|
||||
const promises = [];
|
||||
|
||||
subwikis.forEach((subwiki) => {
|
||||
promises.push(this.getSubwikiPages(subwiki.wikiid, subwiki.groupid, subwiki.userid, undefined, undefined,
|
||||
undefined, offline, ignoreCache, siteId).then((subwikiPages) => {
|
||||
pages = pages.concat(subwikiPages);
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return pages;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the prefetched content except files.
|
||||
* To invalidate files, use invalidateFiles.
|
||||
*
|
||||
* @param {number} moduleId The module ID.
|
||||
* @param {number} courseId Course ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.getWiki(courseId, moduleId, false, siteId).then((wiki) => {
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.invalidateWikiData(courseId, siteId));
|
||||
promises.push(this.invalidateSubwikis(wiki.id, siteId));
|
||||
promises.push(this.invalidateSubwikiPages(wiki.id, siteId));
|
||||
promises.push(this.invalidateSubwikiFiles(wiki.id, siteId));
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the prefetched files.
|
||||
*
|
||||
* @param {number} moduleId The module ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the files are invalidated.
|
||||
*/
|
||||
invalidateFiles(moduleId: number, siteId?: string): Promise<any> {
|
||||
return this.filepoolProvider.invalidateFilesByComponent(siteId, AddonModWikiProvider.COMPONENT, moduleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates page content WS call for a certain page.
|
||||
*
|
||||
* @param {number} pageId Wiki Page ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidatePage(pageId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getPageContentsCacheKey(pageId));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all the subwiki files WS calls for a certain wiki.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateSubwikiFiles(wikiId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKeyStartingWith(this.getSubwikiFilesCacheKeyPrefix(wikiId));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all the subwiki pages WS calls for a certain wiki.
|
||||
*
|
||||
* @param {Number} wikiId Wiki ID.
|
||||
* @param {String} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateSubwikiPages(wikiId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKeyStartingWith(this.getSubwikiPagesCacheKeyPrefix(wikiId));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all the get subwikis WS calls for a certain wiki.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateSubwikis(wikiId: number, siteId?: string): Promise<any> {
|
||||
this.clearSubwikiList(wikiId);
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getSubwikisCacheKey(wikiId));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates wiki data.
|
||||
*
|
||||
* @param {Number} courseId Course ID.
|
||||
* @param {String} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateWikiData(courseId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getWikiDataCacheKey(courseId));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a page title is already used.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {number} subwikiId Subwiki ID.
|
||||
* @param {string} title Page title.
|
||||
* @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache.
|
||||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<boolean>} Promise resolved with true if used, resolved with false if not used or cannot determine.
|
||||
*/
|
||||
isTitleUsed(wikiId: number, subwikiId: number, title: string, offline?: boolean, ignoreCache?: boolean, siteId?: string)
|
||||
: Promise<boolean> {
|
||||
|
||||
// First get the subwiki.
|
||||
return this.getSubwikis(wikiId, offline, ignoreCache, siteId).then((subwikis) => {
|
||||
// Search the subwiki.
|
||||
const subwiki = subwikis.find((subwiki) => {
|
||||
return subwiki.id == subwikiId;
|
||||
});
|
||||
|
||||
return subwiki || Promise.reject(null);
|
||||
}).then((subwiki) => {
|
||||
// Now get all the pages of the subwiki.
|
||||
return this.getSubwikiPages(wikiId, subwiki.groupid, subwiki.userid, undefined, undefined, false, offline,
|
||||
ignoreCache, siteId);
|
||||
}).then((pages) => {
|
||||
// Check if there's any page with the same title.
|
||||
const page = pages.find((page) => {
|
||||
return page.title == title;
|
||||
});
|
||||
|
||||
return !!page;
|
||||
}).catch(() => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a wiki page as being viewed.
|
||||
*
|
||||
* @param {string} id Page ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the WS call is successful.
|
||||
*/
|
||||
logPageView(id: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
pageid: id
|
||||
};
|
||||
|
||||
return site.write('mod_wiki_view_page', params);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the wiki as being viewed.
|
||||
*
|
||||
* @param {number} id Wiki ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the WS call is successful.
|
||||
*/
|
||||
logView(id: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
wikiid: id
|
||||
};
|
||||
|
||||
return site.write('mod_wiki_view_wiki', params);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new page on a subwiki.
|
||||
*
|
||||
* @param {string} title Title to create the page.
|
||||
* @param {string} content Content to save on the page.
|
||||
* @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined.
|
||||
* @param {number} [wikiId] Wiki ID. Optional, will be used to create a new subwiki if subwikiId not supplied.
|
||||
* @param {number} [userId] User ID. Optional, will be used to create a new subwiki if subwikiId not supplied.
|
||||
* @param {number} [groupId] Group ID. Optional, will be used to create a new subwiki if subwikiId not supplied.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<number>} Promise resolved with page ID if page was created in server, -1 if stored in device.
|
||||
*/
|
||||
newPage(title: string, content: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number,
|
||||
siteId?: string): Promise<number> {
|
||||
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Convenience function to store a new page to be synchronized later.
|
||||
const storeOffline = (): Promise<number> => {
|
||||
let promise;
|
||||
|
||||
if (wikiId) {
|
||||
// We have wiki ID, check if there's already an online page with this title and subwiki.
|
||||
promise = this.isTitleUsed(wikiId, subwikiId, title, true, false, siteId).catch(() => {
|
||||
// Error, assume not used.
|
||||
return false;
|
||||
}).then((used) => {
|
||||
if (used) {
|
||||
return Promise.reject(this.translate.instant('addon.mod_wiki.pageexists'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
return this.wikiOffline.saveNewPage(title, content, subwikiId, wikiId, userId, groupId, siteId).then(() => {
|
||||
return -1;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (!this.appProvider.isOnline()) {
|
||||
// App is offline, store the action.
|
||||
return storeOffline();
|
||||
}
|
||||
|
||||
// Discard stored content for this page. If it exists it means the user is editing it.
|
||||
return this.wikiOffline.deleteNewPage(title, subwikiId, wikiId, userId, groupId, siteId).then(() => {
|
||||
// Try to create it in online.
|
||||
return this.newPageOnline(title, content, subwikiId, wikiId, userId, groupId, siteId).catch((error) => {
|
||||
if (this.utils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means that the page cannot be added.
|
||||
return Promise.reject(error);
|
||||
} else {
|
||||
// Couldn't connect to server, store in offline.
|
||||
return storeOffline();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new page on a subwiki. It will fail if offline or cannot connect.
|
||||
*
|
||||
* @param {string} title Title to create the page.
|
||||
* @param {string} content Content to save on the page.
|
||||
* @param {number} [subwikiId] Subwiki ID. If not defined, wikiId, userId and groupId should be defined.
|
||||
* @param {number} [wikiId] Wiki ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [userId] User ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {number} [groupId] Group ID. Optional, will be used create subwiki if not informed.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<number>} Promise resolved with the page ID if created, rejected otherwise.
|
||||
*/
|
||||
newPageOnline(title: string, content: string, subwikiId?: number, wikiId?: number, userId?: number, groupId?: number,
|
||||
siteId?: string): Promise<number> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params: any = {
|
||||
title: title,
|
||||
content: content,
|
||||
contentformat: 'html'
|
||||
};
|
||||
|
||||
subwikiId = this.wikiOffline.convertToPositiveNumber(subwikiId);
|
||||
wikiId = this.wikiOffline.convertToPositiveNumber(wikiId);
|
||||
|
||||
if (subwikiId && subwikiId > 0) {
|
||||
params.subwikiid = subwikiId;
|
||||
} else if (wikiId) {
|
||||
params.wikiid = wikiId;
|
||||
params.userid = this.wikiOffline.convertToPositiveNumber(userId);
|
||||
params.groupid = this.wikiOffline.convertToPositiveNumber(groupId);
|
||||
}
|
||||
|
||||
return site.write('mod_wiki_new_page', params).then((response) => {
|
||||
return response.pageid || Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save subwiki list for a wiki to the cache.
|
||||
*
|
||||
* @param {number} wikiId Wiki Id.
|
||||
* @param {any[]} subwikis List of subwikis.
|
||||
* @param {number} count Number of subwikis in the subwikis list.
|
||||
* @param {number} subwikiId Subwiki Id currently selected.
|
||||
* @param {number} userId User Id currently selected.
|
||||
* @param {number} groupId Group Id currently selected.
|
||||
*/
|
||||
setSubwikiList(wikiId: number, subwikis: any[], count: number, subwikiId: number, userId: number, groupId: number): void {
|
||||
this.subwikiListsCache[wikiId] = {
|
||||
count: count,
|
||||
subwikiSelected: subwikiId,
|
||||
userSelected: userId,
|
||||
groupSelected: groupId,
|
||||
subwikis: subwikis
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort an array of wiki pages by title.
|
||||
*
|
||||
* @param {any[]} pages Pages to sort.
|
||||
* @param {boolean} [desc] True to sort in descendent order, false to sort in ascendent order. Defaults to false.
|
||||
* @return {any[]} Sorted pages.
|
||||
*/
|
||||
sortPagesByTitle(pages: any[], desc?: boolean): any[] {
|
||||
return pages.sort((a, b) => {
|
||||
let result = a.title >= b.title ? 1 : -1;
|
||||
|
||||
if (desc) {
|
||||
result = -result;
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a wiki has a certain subwiki.
|
||||
*
|
||||
* @param {number} wikiId Wiki ID.
|
||||
* @param {number} subwikiId Subwiki ID to search.
|
||||
* @param {boolean} [offline] Whether it should return cached data. Has priority over ignoreCache.
|
||||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<boolean>} Promise resolved with true if it has subwiki, resolved with false otherwise.
|
||||
*/
|
||||
wikiHasSubwiki(wikiId: number, subwikiId: number, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise<boolean> {
|
||||
// Get the subwikis to check if any of them matches the one passed as param.
|
||||
return this.getSubwikis(wikiId, offline, ignoreCache, siteId).then((subwikis) => {
|
||||
const subwiki = subwikis.find((subwiki) => {
|
||||
return subwiki.id == subwikiId;
|
||||
});
|
||||
|
||||
return !!subwiki;
|
||||
}).catch(() => {
|
||||
// Not found, return false.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { 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 { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
|
||||
import { AddonModWikiComponentsModule } from './components/components.module';
|
||||
import { AddonModWikiProvider } from './providers/wiki';
|
||||
import { AddonModWikiOfflineProvider } from './providers/wiki-offline';
|
||||
import { AddonModWikiSyncProvider } from './providers/wiki-sync';
|
||||
import { AddonModWikiModuleHandler } from './providers/module-handler';
|
||||
import { AddonModWikiPrefetchHandler } from './providers/prefetch-handler';
|
||||
import { AddonModWikiSyncCronHandler } from './providers/sync-cron-handler';
|
||||
import { AddonModWikiIndexLinkHandler } from './providers/index-link-handler';
|
||||
import { AddonModWikiPageOrMapLinkHandler } from './providers/page-or-map-link-handler';
|
||||
import { AddonModWikiCreateLinkHandler } from './providers/create-link-handler';
|
||||
import { AddonModWikiEditLinkHandler } from './providers/edit-link-handler';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
imports: [
|
||||
AddonModWikiComponentsModule
|
||||
],
|
||||
providers: [
|
||||
AddonModWikiProvider,
|
||||
AddonModWikiOfflineProvider,
|
||||
AddonModWikiSyncProvider,
|
||||
AddonModWikiModuleHandler,
|
||||
AddonModWikiPrefetchHandler,
|
||||
AddonModWikiSyncCronHandler,
|
||||
AddonModWikiIndexLinkHandler,
|
||||
AddonModWikiPageOrMapLinkHandler,
|
||||
AddonModWikiCreateLinkHandler,
|
||||
AddonModWikiEditLinkHandler
|
||||
]
|
||||
})
|
||||
export class AddonModWikiModule {
|
||||
constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModWikiModuleHandler,
|
||||
prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModWikiPrefetchHandler,
|
||||
cronDelegate: CoreCronDelegate, syncHandler: AddonModWikiSyncCronHandler, linksDelegate: CoreContentLinksDelegate,
|
||||
indexHandler: AddonModWikiIndexLinkHandler, pageOrMapHandler: AddonModWikiPageOrMapLinkHandler,
|
||||
createHandler: AddonModWikiCreateLinkHandler, editHandler: AddonModWikiEditLinkHandler) {
|
||||
|
||||
moduleDelegate.registerHandler(moduleHandler);
|
||||
prefetchDelegate.registerHandler(prefetchHandler);
|
||||
cronDelegate.register(syncHandler);
|
||||
linksDelegate.registerHandler(indexHandler);
|
||||
linksDelegate.registerHandler(pageOrMapHandler);
|
||||
linksDelegate.registerHandler(createHandler);
|
||||
linksDelegate.registerHandler(editHandler);
|
||||
}
|
||||
}
|
|
@ -11,9 +11,7 @@
|
|||
<!-- Plain text textarea. -->
|
||||
<ion-textarea *ngIf="question.isPlainText" class="core-question-textarea" [ngClass]='{"core-monospaced": question.isMonospaced}' placeholder="{{ 'core.question.answer' | translate }}" [attr.name]="question.textarea.name" aria-multiline="true" [ngModel]="question.textarea.text"></ion-textarea>
|
||||
<!-- Rich text editor. -->
|
||||
<core-rich-text-editor item-content *ngIf="!question.isPlainText" placeholder="{{ 'core.question.answer' | translate }}" [control]="formControl" [name]="question.textarea.name"></core-rich-text-editor>
|
||||
<!-- @todo: Attributes that were passed to RTE in Ionic 1 but now they aren't supported yet:
|
||||
[component]="component" [componentId]="componentId" -->
|
||||
<core-rich-text-editor item-content *ngIf="!question.isPlainText" placeholder="{{ 'core.question.answer' | translate }}" [control]="formControl" [name]="question.textarea.name" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
||||
<!-- Draft files not supported. -->
|
||||
|
|
|
@ -94,6 +94,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';
|
||||
|
@ -197,6 +198,7 @@ export const CORE_PROVIDERS: any[] = [
|
|||
AddonModUrlModule,
|
||||
AddonModSurveyModule,
|
||||
AddonModImscpModule,
|
||||
AddonModWikiModule,
|
||||
AddonMessageOutputModule,
|
||||
AddonMessageOutputAirnotifierModule,
|
||||
AddonMessagesModule,
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
.img-responsive {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
/* height: auto; */
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.opacity-hide { opacity: 0; }
|
||||
|
|
|
@ -8,7 +8,6 @@ core-empty-box {
|
|||
display: table;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
clear: both;
|
||||
|
|
|
@ -3,26 +3,30 @@
|
|||
</div>
|
||||
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand -->
|
||||
<div #decorate class="formatOptions">
|
||||
<button data-command="bold"><strong>B</strong></button>
|
||||
<button data-command="italic"><i>I</i></button>
|
||||
<button data-command="underline"><u>U</u></button>
|
||||
<button data-command="formatBlock|<p>">Normal</button>
|
||||
<button data-command="formatBlock|<h1>">H1</button>
|
||||
<button data-command="formatBlock|<h2>">H2</button>
|
||||
<button data-command="formatBlock|<h3>">H3</button>
|
||||
<button data-command="formatBlock|<pre>">Pre</button>
|
||||
<button data-command="insertOrderedList">OL</button>
|
||||
<button data-command="insertUnorderedList">UL</button>
|
||||
<button data-command="removeFormat">Tx</button>
|
||||
<button (click)="toggleEditor($event)">Toggle Editor</button>
|
||||
<div #decorate class="core-rte-toolbar">
|
||||
<div class="core-rte-buttons">
|
||||
<button data-command="bold"><strong>B</strong></button>
|
||||
<button data-command="italic"><i>I</i></button>
|
||||
<button data-command="underline"><u>U</u></button>
|
||||
<button data-command="formatBlock|<p>">Normal</button>
|
||||
<button data-command="formatBlock|<h1>">H1</button>
|
||||
<button data-command="formatBlock|<h2>">H2</button>
|
||||
<button data-command="formatBlock|<h3>">H3</button>
|
||||
<button data-command="formatBlock|<pre>"><pre></button>
|
||||
<button data-command="insertOrderedList"><ion-icon name="list" md="ios-list"></ion-icon></button>
|
||||
<button data-command="insertUnorderedList">1,2,3</button>
|
||||
<button data-command="removeFormat"><ion-icon name="brush"></ion-icon></button>
|
||||
<button (click)="toggleEditor($event)"><ion-icon name="eye-off"></ion-icon> {{ 'core.viewcode' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div [hidden]="rteEnabled">
|
||||
<ion-textarea #textarea class="core-textarea" [placeholder]="placeholder" [attr.name]="name" ngControl="control" (ionChange)="onChange($event)"></ion-textarea>
|
||||
<div class="formatOptions">
|
||||
<button tappable (click)="toggleEditor($event)">Toggle Editor</button>
|
||||
<div class="core-rte-toolbar">
|
||||
<div #decorate class="core-rte-buttons">
|
||||
<button tappable (click)="toggleEditor($event)"><ion-icon name="eye"></ion-icon> {{ 'core.vieweditor' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ core-rich-text-editor {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.core-rte-editor, .core-textarea {
|
||||
padding: 2px;
|
||||
margin: 2px;
|
||||
|
@ -51,21 +50,33 @@ core-rich-text-editor {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
div.formatOptions {
|
||||
background: $gray-dark;
|
||||
margin: 5px 1px 15px 1px;
|
||||
div.core-rte-toolbar {
|
||||
background: $gray-darker;
|
||||
margin: 0px 1px 15px 1px;
|
||||
text-align: center;
|
||||
flex-grow: 0;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
button {
|
||||
background: $gray-dark;
|
||||
color: $white;
|
||||
font-size: 1.1em;
|
||||
height: 35px;
|
||||
min-width: 30px;
|
||||
padding-left: 1px;
|
||||
padding-right: 1px;
|
||||
|
||||
.core-rte-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
|
||||
button {
|
||||
background: $gray-darker;
|
||||
color: $white;
|
||||
font-size: 1.1em;
|
||||
height: 35px;
|
||||
min-width: 30px;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
border-right: 1px solid $gray-dark;
|
||||
border-bottom: 1px solid $gray-dark;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,16 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
|
||||
import { TextInput } from 'ionic-angular';
|
||||
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, AfterContentInit, OnDestroy, Optional }
|
||||
from '@angular/core';
|
||||
import { TextInput, Content } from 'ionic-angular';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Keyboard } from '@ionic-native/keyboard';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Directive to display a rich text editor if enabled.
|
||||
|
@ -36,25 +41,34 @@ import { Keyboard } from '@ionic-native/keyboard';
|
|||
selector: 'core-rich-text-editor',
|
||||
templateUrl: 'rich-text-editor.html'
|
||||
})
|
||||
export class CoreRichTextEditorComponent {
|
||||
export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy {
|
||||
// Based on: https://github.com/judgewest2000/Ionic3RichText/
|
||||
// @todo: Resize, images, anchor button, fullscreen...
|
||||
// @todo: Anchor button, fullscreen...
|
||||
|
||||
@Input() placeholder = ''; // Placeholder to set in textarea.
|
||||
@Input() control: FormControl; // Form control.
|
||||
@Input() name = 'core-rich-text-editor'; // Name to set to the textarea.
|
||||
@Input() component?: string; // The component to link the files to.
|
||||
@Input() componentId?: number; // An ID to use in conjunction with the component.
|
||||
@Output() contentChanged: EventEmitter<string>;
|
||||
|
||||
@ViewChild('editor') editor: ElementRef; // WYSIWYG editor.
|
||||
@ViewChild('textarea') textarea: TextInput; // Textarea editor.
|
||||
@ViewChild('decorate') decorate: ElementRef; // Buttons.
|
||||
|
||||
rteEnabled = false;
|
||||
uniqueId = `rte{Math.floor(Math.random() * 1000000)}`;
|
||||
editorElement: HTMLDivElement;
|
||||
protected element: HTMLDivElement;
|
||||
protected editorElement: HTMLDivElement;
|
||||
protected resizeFunction;
|
||||
|
||||
constructor(private domUtils: CoreDomUtilsProvider, private keyboard: Keyboard) {
|
||||
protected valueChangeSubscription: Subscription;
|
||||
|
||||
rteEnabled = false;
|
||||
|
||||
constructor(private domUtils: CoreDomUtilsProvider, private keyboard: Keyboard, private urlUtils: CoreUrlUtilsProvider,
|
||||
private sitesProvider: CoreSitesProvider, private filepoolProvider: CoreFilepoolProvider,
|
||||
@Optional() private content: Content, elementRef: ElementRef) {
|
||||
this.contentChanged = new EventEmitter<string>();
|
||||
this.element = elementRef.nativeElement as HTMLDivElement;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,13 +83,17 @@ export class CoreRichTextEditorComponent {
|
|||
this.editorElement = this.editor.nativeElement as HTMLDivElement;
|
||||
this.editorElement.innerHTML = this.control.value;
|
||||
this.textarea.value = this.control.value;
|
||||
this.control.setValue(this.control.value);
|
||||
|
||||
this.editorElement.onchange = this.onChange.bind(this);
|
||||
this.editorElement.onkeyup = this.onChange.bind(this);
|
||||
this.editorElement.onpaste = this.onChange.bind(this);
|
||||
this.editorElement.oninput = this.onChange.bind(this);
|
||||
|
||||
// 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.editorElement.innerHTML = param;
|
||||
});
|
||||
|
||||
// Setup button actions.
|
||||
const buttons = (this.decorate.nativeElement as HTMLDivElement).getElementsByTagName('button');
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
|
@ -97,6 +115,58 @@ export class CoreRichTextEditorComponent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.treatExternalContent();
|
||||
|
||||
this.resizeFunction = this.maximizeEditorSize.bind(this);
|
||||
window.addEventListener('resize', this.resizeFunction);
|
||||
setTimeout(this.resizeFunction, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize editor to maximize the space occupied.
|
||||
*/
|
||||
protected maximizeEditorSize(): void {
|
||||
this.content.resize();
|
||||
const contentVisibleHeight = this.content.contentHeight;
|
||||
|
||||
// Editor is ready, adjust Height if needed.
|
||||
if (contentVisibleHeight > 0) {
|
||||
const height = this.getSurroundingHeight(this.element);
|
||||
if (contentVisibleHeight > height) {
|
||||
this.element.style.height = this.domUtils.formatPixelsSize(contentVisibleHeight - height);
|
||||
} else {
|
||||
this.element.style.height = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the surrounding elements from the current to the top element.
|
||||
*
|
||||
* @param {any} element Directive DOM element to get surroundings elements from.
|
||||
* @return {number} Surrounding height in px.
|
||||
*/
|
||||
protected getSurroundingHeight(element: any): number {
|
||||
let height = 0;
|
||||
|
||||
while (element.parentNode && element.parentNode.tagName != 'ION-CONTENT') {
|
||||
const parent = element.parentNode;
|
||||
if (element.tagName && element.tagName != 'CORE-LOADING') {
|
||||
parent.childNodes.forEach((child) => {
|
||||
if (child.tagName && child != element) {
|
||||
height += this.domUtils.getElementHeight(child, false, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
element = parent;
|
||||
}
|
||||
|
||||
const cs = getComputedStyle(element);
|
||||
height += this.domUtils.getComputedStyleMeasure(cs, 'paddingTop') +
|
||||
this.domUtils.getComputedStyleMeasure(cs, 'paddingBottom');
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,14 +179,16 @@ export class CoreRichTextEditorComponent {
|
|||
if (this.isNullOrWhiteSpace(this.editorElement.innerText)) {
|
||||
this.clearText();
|
||||
} else {
|
||||
this.control.setValue(this.editorElement.innerHTML);
|
||||
// Don't emit event so our valueChanges doesn't get notified by this change.
|
||||
this.control.setValue(this.editorElement.innerHTML, {emitEvent: false});
|
||||
this.textarea.value = this.editorElement.innerHTML;
|
||||
}
|
||||
} else {
|
||||
if (this.isNullOrWhiteSpace(this.textarea.value)) {
|
||||
this.clearText();
|
||||
} else {
|
||||
this.control.setValue(this.textarea.value);
|
||||
// Don't emit event so our valueChanges doesn't get notified by this change.
|
||||
this.control.setValue(this.textarea.value, {emitEvent: false});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,6 +234,30 @@ export class CoreRichTextEditorComponent {
|
|||
}, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat elements that can contain external content.
|
||||
* We only search for images because the editor should receive unfiltered text, so the multimedia filter won't be applied.
|
||||
* Treating videos and audios in here is complex, so if a user manually adds one he won't be able to play it in the editor.
|
||||
*/
|
||||
protected treatExternalContent(): void {
|
||||
const elements = Array.from(this.editorElement.querySelectorAll('img')),
|
||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||
canDownloadFiles = this.sitesProvider.getCurrentSite().canDownloadFiles();
|
||||
elements.forEach((el) => {
|
||||
const url = el.src;
|
||||
|
||||
if (!url || !this.urlUtils.isDownloadableUrl(url) || (!canDownloadFiles && this.urlUtils.isPluginFileUrl(url))) {
|
||||
// Nothing to treat.
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's downloaded.
|
||||
return this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId).then((finalUrl) => {
|
||||
el.setAttribute('src', finalUrl);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if text is empty.
|
||||
* @param {string} value text
|
||||
|
@ -183,7 +279,8 @@ export class CoreRichTextEditorComponent {
|
|||
clearText(): void {
|
||||
this.editorElement.innerHTML = '<p></p>';
|
||||
this.textarea.value = '';
|
||||
this.control.setValue(null);
|
||||
// Don't emit event so our valueChanges doesn't get notified by this change.
|
||||
this.control.setValue(null, {emitEvent: false});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,4 +296,12 @@ export class CoreRichTextEditorComponent {
|
|||
$event.stopPropagation();
|
||||
document.execCommand(command, false, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.valueChangeSubscription && this.valueChangeSubscription.unsubscribe();
|
||||
window.removeEventListener('resize', this.resizeFunction);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -950,12 +950,10 @@ export class CoreLoginHelperProvider {
|
|||
* @param {any} error Error object containing errorcode and error message.
|
||||
*/
|
||||
treatUserTokenError(siteUrl: string, error: any): void {
|
||||
if (typeof error == 'string') {
|
||||
this.domUtils.showErrorModal(error);
|
||||
} else if (error.errorcode == 'forcepasswordchangenotice') {
|
||||
this.openChangePassword(siteUrl, error.error);
|
||||
if (error.errorcode == 'forcepasswordchangenotice') {
|
||||
this.openChangePassword(siteUrl, error.error || error.message || error.body || error.content);
|
||||
} else {
|
||||
this.domUtils.showErrorModal(error.error);
|
||||
this.domUtils.showErrorModal(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -228,6 +228,8 @@
|
|||
"usernotfullysetup": "User not fully set-up",
|
||||
"users": "Users",
|
||||
"view": "View",
|
||||
"viewcode": "View code",
|
||||
"vieweditor": "View editor",
|
||||
"viewprofile": "View profile",
|
||||
"warningofflinedatadeleted": "Offline data of {{component}} '{{name}}' has been deleted. {{error}}",
|
||||
"whatisyourage": "What is your age?",
|
||||
|
|
|
@ -365,13 +365,16 @@ export class CoreDomUtilsProvider {
|
|||
let surround = 0;
|
||||
|
||||
if (usePadding) {
|
||||
surround += parseInt(computedStyle['padding' + priorSide], 10) + parseInt(computedStyle['padding' + afterSide], 10);
|
||||
surround += this.getComputedStyleMeasure(computedStyle, 'padding' + priorSide) +
|
||||
this.getComputedStyleMeasure(computedStyle, 'padding' + afterSide);
|
||||
}
|
||||
if (useMargin) {
|
||||
surround += parseInt(computedStyle['margin' + priorSide], 10) + parseInt(computedStyle['margin' + afterSide], 10);
|
||||
surround += this.getComputedStyleMeasure(computedStyle, 'margin' + priorSide) +
|
||||
this.getComputedStyleMeasure(computedStyle, 'margin' + afterSide);
|
||||
}
|
||||
if (useBorder) {
|
||||
surround += parseInt(computedStyle['border' + priorSide], 10) + parseInt(computedStyle['border' + afterSide], 10);
|
||||
surround += this.getComputedStyleMeasure(computedStyle, 'border' + priorSide + 'Width') +
|
||||
this.getComputedStyleMeasure(computedStyle, 'border' + afterSide + 'Width');
|
||||
}
|
||||
if (innerMeasure) {
|
||||
measure = measure > surround ? measure - surround : 0;
|
||||
|
@ -381,7 +384,17 @@ export class CoreDomUtilsProvider {
|
|||
}
|
||||
|
||||
return measure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the computed style measure or 0 if not found or NaN.
|
||||
*
|
||||
* @param {any} style Style from getComputedStyle.
|
||||
* @param {string} measure Measure to get.
|
||||
* @return {number} Result of the measure.
|
||||
*/
|
||||
getComputedStyleMeasure(style: any, measure: string): number {
|
||||
return parseInt(style[measure], 10) || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue