forked from EVOgeek/Vmeda.Online
568 lines
23 KiB
TypeScript
568 lines
23 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } 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 {
|
|
|
|
@ViewChild('editPageForm') formElement: ElementRef;
|
|
|
|
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.
|
|
editorExtraParams: {[name: string]: any} = {};
|
|
|
|
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 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);
|
|
|
|
if (!this.module.id) {
|
|
this.editorExtraParams.type = 'wiki';
|
|
}
|
|
|
|
if (this.pageId) {
|
|
this.editorExtraParams.pageid = this.pageId;
|
|
|
|
if (this.section) {
|
|
this.editorExtraParams.section = this.section;
|
|
}
|
|
} else if (pageTitle) {
|
|
this.editorExtraParams.pagetitle = pageTitle;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 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(() => {
|
|
// 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.textUtils.replacePluginfileUrls(editContents.content, this.subwikiFiles);
|
|
|
|
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 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 title Page title.
|
|
* @return 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 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 Resolved if we can leave it, rejected if not.
|
|
*/
|
|
async ionViewCanLeave(): Promise<void> {
|
|
if (this.forceLeave) {
|
|
return;
|
|
}
|
|
|
|
// Check if data has changed.
|
|
if (this.hasDataChanged()) {
|
|
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
|
}
|
|
|
|
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
|
}
|
|
|
|
/**
|
|
* 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 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 title The current page title.
|
|
* @return 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;
|
|
|
|
text = this.textUtils.restorePluginfileUrls(text, this.subwikiFiles);
|
|
text = this.textUtils.formatHtmlLines(text);
|
|
|
|
if (this.editing) {
|
|
// Edit existing page.
|
|
promise = this.wikiProvider.editPage(this.pageId, text, this.section).then(() => {
|
|
|
|
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, true,
|
|
this.sitesProvider.getCurrentSiteId());
|
|
|
|
// 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) => {
|
|
|
|
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, id > 0,
|
|
this.sitesProvider.getCurrentSiteId());
|
|
|
|
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,
|
|
}, 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 wikiId Wiki ID.
|
|
* @return 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);
|
|
}
|
|
}
|
|
}
|