diff --git a/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html b/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html index a66d401c0..4885fe49c 100644 --- a/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html +++ b/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html @@ -1,13 +1,15 @@ - - - + + + - - + + @@ -46,7 +48,7 @@ -
+
@@ -80,9 +82,9 @@ -
+
{{ 'core.tag.tags' | translate }}: - +
diff --git a/src/addons/mod/wiki/components/index/index.ts b/src/addons/mod/wiki/components/index/index.ts index 6c720ff6e..b32c69121 100644 --- a/src/addons/mod/wiki/components/index/index.ts +++ b/src/addons/mod/wiki/components/index/index.ts @@ -13,11 +13,12 @@ // limitations under the License. import { Component, Optional, Input, OnInit, OnDestroy } from '@angular/core'; +import { Params } from '@angular/router'; import { CoreError } from '@classes/errors/error'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; import { CoreCourse } from '@features/course/services/course'; -import { CoreTag } from '@features/tag/services/tag'; +import { CoreTag, CoreTagItem } from '@features/tag/services/tag'; import { CoreUser } from '@features/user/services/user'; import { IonContent } from '@ionic/angular'; import { CoreGroup, CoreGroups } from '@services/groups'; @@ -84,6 +85,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp pageContent?: string; // Page content to display. tagsEnabled = false; currentPageObj?: AddonModWikiPageContents | AddonModWikiPageDBRecord; // Object of the current loaded page. + tags: CoreTagItem[] = []; subwikiData: AddonModWikiSubwikiListData = { // Data for the subwiki selector. subwikiSelected: 0, userSelected: 0, @@ -100,7 +102,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp protected manualSyncObserver?: CoreEventObserver; // An observer to watch for manual sync events. protected ignoreManualSyncEvent = false; // Whether manual sync event should be ignored. protected currentUserId?: number; // Current user ID. - protected hasEdited = false; // Whether the user has opened the edit page. protected currentPath!: string; constructor( @@ -213,7 +214,10 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp // Get the wiki instance. this.wiki = await AddonModWiki.getWiki(this.courseId, this.module.id); - this.dataRetrieved.emit(this.wiki); + if (this.pageContent === undefined) { + // Page not loaded yet, emit the data to update the page title. + this.dataRetrieved.emit(this.wiki); + } AddonModWiki.wikiPageOpened(this.wiki.id, this.currentPath); if (sync) { @@ -293,19 +297,15 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp } // No page ID but we received a title. This means we're trying to load an offline page. - if (!this.currentSubwiki) { - return; - } - try { const title = this.pageTitle || this.wiki!.firstpagetitle!; const offlinePage = await AddonModWikiOffline.getNewPage( title, - this.currentSubwiki.id, - this.currentSubwiki.wikiid, - this.currentSubwiki.userid, - this.currentSubwiki.groupid, + this.currentSubwiki!.id, + this.currentSubwiki!.wikiid, + this.currentSubwiki!.userid, + this.currentSubwiki!.groupid, ); this.pageIsOffline = true; @@ -354,6 +354,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp const firstPage = subwikiPages.find((page) => page.firstpage ); if (firstPage) { this.currentPage = firstPage.id; + this.pageTitle = firstPage.title; } } @@ -417,9 +418,11 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp this.dataRetrieved.emit(pageContents.title); this.setSelectedWiki(pageContents.subwikiid, pageContents.userid, pageContents.groupid); + this.pageTitle = pageContents.title; this.pageContent = this.replaceEditLinks(pageContents.cachedcontent); this.canEdit = !!pageContents.caneditpage; this.currentPageObj = pageContents; + this.tags = ('tags' in pageContents && pageContents.tags) || []; } } @@ -440,15 +443,14 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp * Open the view to create the first page of the wiki. */ protected goToCreateFirstPage(): void { - // @todo - // CoreNavigator.push('AddonModWikiEditPage', { - // module: this.module, - // courseId: this.courseId, - // pageTitle: this.wiki.firstpagetitle, - // wikiId: this.currentSubwiki.wikiid, - // userId: this.currentSubwiki.userid, - // groupId: this.currentSubwiki.groupid - // }); + CoreNavigator.navigate('../../edit', { + params: { + pageTitle: this.wiki!.firstpagetitle, + wikiId: this.currentSubwiki?.wikiid, + userId: this.currentSubwiki?.userid, + groupId: this.currentSubwiki?.groupid, + }, + }); } /** @@ -459,28 +461,28 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp return; } - // @todo - // if (this.currentPageObj) { - // // Current page exists, go to edit it. - // const pageParams: any = { - // module: this.module, - // courseId: this.courseId, - // pageId: this.currentPageObj.id, - // pageTitle: this.currentPageObj.title, - // subwikiId: this.currentPageObj.subwikiid - // }; + if (this.currentPageObj) { + // Current page exists, go to edit it. + const pageParams: Params = { + pageTitle: this.currentPageObj.title, + subwikiId: this.currentPageObj.subwikiid, + }; - // if (this.currentSubwiki) { - // pageParams.wikiId = this.currentSubwiki.wikiid; - // pageParams.userId = this.currentSubwiki.userid; - // pageParams.groupId = this.currentSubwiki.groupid; - // } + if ('id' in this.currentPageObj) { + pageParams.pageId = this.currentPageObj.id; + } - // this.navCtrl.push('AddonModWikiEditPage', pageParams); - // } else if (this.currentSubwiki) { - // // No page loaded, the wiki doesn't have first page. - // this.goToCreateFirstPage(); - // } + if (this.currentSubwiki) { + pageParams.wikiId = this.currentSubwiki.wikiid; + pageParams.userId = this.currentSubwiki.userid; + pageParams.groupId = this.currentSubwiki.groupid; + } + + CoreNavigator.navigate('../../edit', { params: pageParams }); + } else if (this.currentSubwiki) { + // No page loaded, the wiki doesn't have first page. + this.goToCreateFirstPage(); + } } /** @@ -491,26 +493,23 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp return; } - // @todo - // if (this.currentPageObj) { - // // Current page exists, go to edit it. - // const pageParams: any = { - // module: this.module, - // courseId: this.courseId, - // subwikiId: this.currentPageObj.subwikiid - // }; + if (this.currentPageObj) { + // Current page exists, go to edit it. + const pageParams: Params = { + subwikiId: this.currentPageObj.subwikiid, + }; - // if (this.currentSubwiki) { - // pageParams.wikiId = this.currentSubwiki.wikiid; - // pageParams.userId = this.currentSubwiki.userid; - // pageParams.groupId = this.currentSubwiki.groupid; - // } + if (this.currentSubwiki) { + pageParams.wikiId = this.currentSubwiki.wikiid; + pageParams.userId = this.currentSubwiki.userid; + pageParams.groupId = this.currentSubwiki.groupid; + } - // this.navCtrl.push('AddonModWikiEditPage', pageParams); - // } else if (this.currentSubwiki) { - // // No page loaded, the wiki doesn't have first page. - // this.goToCreateFirstPage(); - // } + CoreNavigator.navigate('../../edit', { params: pageParams }); + } else if (this.currentSubwiki) { + // No page loaded, the wiki doesn't have first page. + this.goToCreateFirstPage(); + } } /** @@ -522,44 +521,43 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp if (!('id' in page)) { // It's an offline page. Check if we are already in the same offline page. if (this.currentPage || !this.pageTitle || page.title != this.pageTitle) { - const hash = Md5.hashAsciiStr(JSON.stringify({ + this.openPageOrSubwiki({ pageTitle: page.title, subwikiId: page.subwikiid, - timestamp: Date.now(), - })); - - CoreNavigator.navigate(`../${hash}`, { - params: { - module: this.module, - pageTitle: page.title, - wikiId: this.wiki!.id, - subwikiId: page.subwikiid, - }, }); } } else if (this.currentPage != page.id) { // Add a new State. const pageContents = await this.fetchPageContents(page.id); - const hash = Md5.hashAsciiStr(JSON.stringify({ + this.openPageOrSubwiki({ pageTitle: pageContents.title, pageId: pageContents.id, subwikiId: page.subwikiid, - timestamp: Date.now(), - })); - - CoreNavigator.navigate(`../${hash}`, { - params: { - module: this.module, - pageTitle: pageContents.title, - pageId: pageContents.id, - wikiId: pageContents.wikiid, - subwikiId: pageContents.subwikiid, - }, }); } } + /** + * Open a page or a subwiki in the current wiki. + * + * @param options Options + * @return Promise. + */ + protected async openPageOrSubwiki(options: AddonModWikiOpenPageOptions): Promise { + const hash = Md5.hashAsciiStr(JSON.stringify({ + ...options, + timestamp: Date.now(), + })); + + await CoreNavigator.navigate(`../${hash}`, { + params: { + module: this.module, + ...options, + }, + }); + } + /** * Show the map. */ @@ -569,10 +567,10 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp component: AddonModWikiMapModalComponent, componentProps: { pages: this.subwikiPages, - selected: this.currentPageObj && 'id' in this.currentPageObj && this.currentPageObj.id, homeView: this.getWikiHomeView(), moduleId: this.module.id, courseId: this.courseId, + selectedTitle: this.currentPageObj && this.currentPageObj.title, }, cssClass: 'core-modal-lateral', showBackdrop: true, @@ -613,21 +611,10 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp if (subwikiId != this.currentSubwiki!.id || userId != this.currentSubwiki!.userid || groupId != this.currentSubwiki!.groupid) { - const hash = Md5.hashAsciiStr(JSON.stringify({ + this.openPageOrSubwiki({ subwikiId: subwikiId, userId: userId, groupId: groupId, - timestamp: Date.now(), - })); - - CoreNavigator.navigate(`../${hash}`, { - params: { - module: this.module, - wikiId: this.wiki!.id, - subwikiId: subwikiId, - userId: userId, - groupId: groupId, - }, }); } } @@ -714,24 +701,43 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp ionViewDidEnter(): void { super.ionViewDidEnter(); - if (this.hasEdited) { - this.hasEdited = false; + const editedPageData = AddonModWiki.consumeEditedPageData(); + if (!editedPageData) { + return; + } + + // User has just edited a page. Check if it's the current page. + if (this.pageId && editedPageData.pageId === this.pageId) { + this.showLoadingAndRefresh(true, false); + + return; + } + + const sameSubwiki = this.currentSubwiki && + ((this.currentSubwiki.id && this.currentSubwiki.id === editedPageData.subwikiId) || + (this.currentSubwiki.userid === editedPageData.userId && this.currentSubwiki.groupid === editedPageData.groupId)); + + if (sameSubwiki && editedPageData.pageTitle === this.pageTitle) { + this.showLoadingAndRefresh(true, false); + + return; + } + + // Not same page or we cannot tell. Open the page. + this.openPageOrSubwiki({ + pageId: editedPageData.pageId, + pageTitle: editedPageData.pageTitle, + subwikiId: editedPageData.subwikiId, + userId: editedPageData.wikiId, + groupId: editedPageData.groupId, + }); + + if (editedPageData.pageId && (!this.pageContent || this.pageContent.indexOf('/mod/wiki/create.php') != -1)) { + // Refresh current page anyway because the new page could have been created using the create link. this.showLoadingAndRefresh(true, false); } } - /** - * User left the page that contains the component. - */ - ionViewDidLeave(): void { - super.ionViewDidLeave(); - - // @todo - // if (this.navCtrl.getActive().component.name == 'AddonModWikiEditPage') { - // this.hasEdited = true; - // } - } - /** * @inheritdoc */ @@ -1039,3 +1045,11 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp } } + +type AddonModWikiOpenPageOptions = { + subwikiId?: number; + pageTitle?: string; + pageId?: number; + userId?: number; + groupId?: number; +}; diff --git a/src/addons/mod/wiki/components/map/map.html b/src/addons/mod/wiki/components/map/map.html index c8991404a..56ad87d73 100644 --- a/src/addons/mod/wiki/components/map/map.html +++ b/src/addons/mod/wiki/components/map/map.html @@ -3,7 +3,7 @@ {{ 'addon.mod_wiki.map' | translate }} - + @@ -21,17 +21,17 @@ {{ letter.label }} + [class.core-selected-item]="selectedTitle == page.title" tappable> - - - {{ 'core.notsent' | translate }} - + + + {{ 'core.notsent' | translate }} + diff --git a/src/addons/mod/wiki/components/map/map.ts b/src/addons/mod/wiki/components/map/map.ts index 12a7eb99c..f4127c631 100644 --- a/src/addons/mod/wiki/components/map/map.ts +++ b/src/addons/mod/wiki/components/map/map.ts @@ -27,7 +27,7 @@ import { AddonModWikiSubwikiPage } from '../../services/wiki'; export class AddonModWikiMapModalComponent implements OnInit { @Input() pages: (AddonModWikiSubwikiPage | AddonModWikiPageDBRecord)[] = []; - @Input() selected?: number; + @Input() selectedTitle?: string; @Input() moduleId?: number; @Input() courseId?: number; @Input() homeView?: string; diff --git a/src/addons/mod/wiki/components/subwiki-picker/addon-mod-wiki-subwiki-picker.html b/src/addons/mod/wiki/components/subwiki-picker/addon-mod-wiki-subwiki-picker.html index 0fbf77270..41eb324b0 100644 --- a/src/addons/mod/wiki/components/subwiki-picker/addon-mod-wiki-subwiki-picker.html +++ b/src/addons/mod/wiki/components/subwiki-picker/addon-mod-wiki-subwiki-picker.html @@ -7,7 +7,7 @@ [attr.disabled]="!subwiki.canedit && subwiki.id <= 0 ? true : null" tappable [class.core-selected-item]="isSubwikiSelected(subwiki)" detail="false"> {{ subwiki.name }} - + diff --git a/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts b/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts index 2e00dd4e3..beb98788c 100644 --- a/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts +++ b/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts @@ -34,7 +34,7 @@ export class AddonModWikiSubwikiPickerComponent { * @param subwiki Subwiki to check. * @return Whether it's the selected subwiki. */ - protected isSubwikiSelected(subwiki: AddonModWikiSubwiki): boolean { + isSubwikiSelected(subwiki: AddonModWikiSubwiki): boolean { if (subwiki.id > 0 && this.currentSubwiki.id > 0) { return subwiki.id == this.currentSubwiki.id; diff --git a/src/addons/mod/wiki/pages/edit/edit.html b/src/addons/mod/wiki/pages/edit/edit.html new file mode 100644 index 000000000..eab756228 --- /dev/null +++ b/src/addons/mod/wiki/pages/edit/edit.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + {{ 'core.save' | translate }} + + + + + + +
+ + + + + + + + + + + + + + + {{ 'addon.mod_wiki.wrongversionlock' | translate }} + + +
+
+
diff --git a/src/addons/mod/wiki/pages/edit/edit.ts b/src/addons/mod/wiki/pages/edit/edit.ts new file mode 100644 index 000000000..312c77050 --- /dev/null +++ b/src/addons/mod/wiki/pages/edit/edit.ts @@ -0,0 +1,448 @@ +// (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 { FormControl, FormGroup, FormBuilder } from '@angular/forms'; +import { CoreError } from '@classes/errors/error'; +import { CoreCourse } from '@features/course/services/course'; +import { CanLeave } from '@guards/can-leave'; +import { CoreNavigator } from '@services/navigator'; +import { CoreSites } from '@services/sites'; +import { CoreSync } from '@services/sync'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreTextUtils } from '@services/utils/text'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreWSExternalFile } from '@services/ws'; +import { Translate } from '@singletons'; +import { CoreEvents } from '@singletons/events'; +import { CoreForms } from '@singletons/form'; +import { AddonModWiki, AddonModWikiProvider } from '../../services/wiki'; +import { AddonModWikiOffline } from '../../services/wiki-offline'; +import { AddonModWikiSync } from '../../services/wiki-sync'; + +/** + * Page that allows adding or editing a wiki page. + */ +@Component({ + selector: 'page-addon-mod-wiki-edit', + templateUrl: 'edit.html', +}) +export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { + + @ViewChild('editPageForm') formElement?: ElementRef; + + cmId!: number; // Course module ID. + courseId!: number; // Course the wiki belongs to. + title?: string; // Title to display. + pageForm?: FormGroup; // The form group. + contentControl?: FormControl; // The FormControl for the page content. + canEditTitle = false; // Whether title can be edited. + loaded = false; // Whether the data has been loaded. + component = AddonModWikiProvider.COMPONENT; // Component to link the files to. + wrongVersionLock = false; // Whether the page lock doesn't match the initial one. + editorExtraParams: Record = {}; + + protected subwikiId?: number; // Subwiki ID the page belongs to. + 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 = false; // Whether the user is editing a page (true) or creating a new one (false). + protected editOffline = false; // Whether the user is editing an offline page. + protected subwikiFiles: CoreWSExternalFile[] = []; // List of files of the subwiki. + protected originalContent?: string; // The original page content. + protected version?: number; // Page version. + protected renewLockInterval?: number; // 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. + + constructor( + protected formBuilder: FormBuilder, + ) { } + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + this.cmId = CoreNavigator.getRouteNumberParam('cmId')!; + this.courseId = CoreNavigator.getRouteNumberParam('courseId')!; + this.subwikiId = CoreNavigator.getRouteNumberParam('subwikiId'); + this.wikiId = CoreNavigator.getRouteNumberParam('wikiId'); + this.pageId = CoreNavigator.getRouteNumberParam('pageId'); + this.section = CoreNavigator.getRouteParam('section'); + this.groupId = CoreNavigator.getRouteNumberParam('groupId'); + this.userId = CoreNavigator.getRouteNumberParam('userId'); + + let pageTitle = CoreNavigator.getRouteParam('pageTitle'); + pageTitle = pageTitle ? pageTitle.replace(/\+/g, ' ') : ''; + + this.canEditTitle = !pageTitle; + this.title = pageTitle ? + Translate.instant('addon.mod_wiki.editingpage', { $a: pageTitle }) : + Translate.instant('addon.mod_wiki.newpagehdr'); + this.blockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId); + + // Create the form group and its controls. + this.contentControl = this.formBuilder.control(''); + this.pageForm = this.formBuilder.group({ + title: pageTitle, + }); + this.pageForm.addControl('text', this.contentControl); + + // Block the wiki so it cannot be synced. + CoreSync.blockOperation(this.component, this.blockId); + + if (this.pageId) { + this.editorExtraParams.pageid = this.pageId; + + if (this.section) { + this.editorExtraParams.section = this.section; + } + } else if (pageTitle) { + this.editorExtraParams.pagetitle = pageTitle; + } + + try { + const success = await this.fetchWikiPageData(); + + if (success && !this.isDestroyed) { + // Block the subwiki now that we have blockId for sure. + const newBlockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId); + if (newBlockId != this.blockId) { + CoreSync.unblockOperation(this.component, this.blockId); + this.blockId = newBlockId; + CoreSync.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 async fetchWikiPageData(): Promise { + let canEdit = false; + let fetchFailed = false; + + try { + // Wait for sync to be over (if any). + const syncResult = await AddonModWikiSync.waitForSync(this.blockId!); + + 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 + const pageContents = await AddonModWiki.getPageContents(this.pageId, { cmId: this.cmId }); + + this.pageForm!.controls.title.setValue(pageContents.title); // Set the title in the form group. + this.wikiId = pageContents.wikiid; + this.subwikiId = pageContents.subwikiid; + this.title = Translate.instant('addon.mod_wiki.editingpage', { $a: pageContents.title }); + this.groupId = pageContents.groupid; + this.userId = pageContents.userid; + canEdit = pageContents.caneditpage; + + // Get subwiki files, needed to replace URLs for rich text editor. + this.subwikiFiles = await AddonModWiki.getSubwikiFiles(this.wikiId, { + groupId: this.groupId, + userId: this.userId, + cmId: this.cmId, + }); + + // Get editable text of the page/section. + const editContents = await AddonModWiki.getPageForEditing(this.pageId, this.section); + + // Get the original page contents, treating file URLs if needed. + const content = CoreTextUtils.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 = window.setInterval(() => { + this.renewLock(); + }, AddonModWikiProvider.RENEW_LOCK_TIME); + } + } else { + const pageTitle = this.pageForm!.controls.title.value; + this.editing = false; + canEdit = !!this.blockId; // If no blockId, the user cannot edit the page. + + // Make sure we have the wiki ID. + if (!this.wikiId) { + const module = await CoreCourse.getModule(this.cmId, this.courseId, undefined, true); + + this.wikiId = module.instance; + } + + if (pageTitle) { + // Title is set, it could be editing an offline page or creating a new page using an edit link. + // First of all, verify if this page was created in the current sync. + if (syncResult) { + const page = syncResult.created.find((page) => 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. + const page = await CoreUtils.ignoreErrors( + AddonModWikiOffline.getNewPage(pageTitle, this.subwikiId, this.wikiId, this.userId, this.groupId), + ); + + if (page) { + // Load offline content. + this.contentControl!.setValue(page.cachedcontent); + this.originalContent = page.cachedcontent; + this.editOffline = true; + } else { + // No offline data found. + this.editOffline = false; + } + } else { + this.editOffline = false; + } + } + + return true; + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'Error getting wiki data.'); + fetchFailed = true; + + // Go back. + this.forceLeavePage(); + + return false; + } finally { + if (!canEdit && !fetchFailed) { + // Cannot edit, show alert and go back. + CoreDomUtils.showAlert(Translate.instant('core.notice'), Translate.instant('addon.mod_wiki.cannoteditpage')); + this.forceLeavePage(); + } + } + } + + /** + * Force leaving the page, without checking for changes. + */ + protected forceLeavePage(): void { + this.forceLeave = true; + CoreNavigator.back(); + } + + /** + * Navigate to a page. + * + * @param title Page title. + */ + protected goToPage(title: string): void { + // Not the firstpage. + AddonModWiki.setEditedPageData({ + cmId: this.cmId, + courseId: this.courseId, + pageId: this.pageId, + pageTitle: title, + wikiId: this.wikiId!, + subwikiId: this.subwikiId, + userId: this.userId, + groupId: this.groupId, + }); + + 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)); + } + + /** + * @inheritdoc + */ + async canLeave(): Promise { + if (this.forceLeave) { + return true; + } + + // Check if data has changed. + if (this.hasDataChanged()) { + await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit')); + } + + CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId()); + + return true; + } + + /** + * @inheritdoc + */ + ionViewDidLeave(): void { + // When going back, the ionViewDidEnter of the previous page should be called before this ionViewDidLeave. + // But just in case, use a timeout to make sure it does. + setTimeout(() => { + // Remove the edited page data (if any) if the previous page isn't a wiki page. + AddonModWiki.consumeEditedPageData(); + }, 200); + } + + /** + * Save the data. + */ + async save(): Promise { + const values = this.pageForm!.value; + const title = values.title; + let text = values.text; + + const modal = await CoreDomUtils.showModalLoading('core.sending', true); + + text = CoreTextUtils.restorePluginfileUrls(text, this.subwikiFiles); + text = CoreTextUtils.formatHtmlLines(text); + + try { + if (this.editing) { + // Edit existing page. + await AddonModWiki.editPage(this.pageId!, text, this.section); + + CoreForms.triggerFormSubmittedEvent(this.formElement, true, CoreSites.getCurrentSiteId()); + + // Invalidate page since it changed. + await AddonModWiki.invalidatePage(this.pageId!); + + return this.goToPage(title); + } + + // Creating a new page. + if (!title) { + // Title is mandatory, stop. + modal.dismiss(); + CoreDomUtils.showAlert( + Translate.instant('core.notice'), + Translate.instant('addon.mod_wiki.titleshouldnotbeempty'), + ); + + return; + } + + if (!this.editOffline) { + // Check if the user has an offline page with the same title. + const page = await CoreUtils.ignoreErrors( + AddonModWikiOffline.getNewPage(title, this.subwikiId, this.wikiId, this.userId, this.groupId), + ); + + if (page) { + // There's a page with same title, reject with error message. + throw new CoreError(Translate.instant('addon.mod_wiki.pageexists')); + } + } + + // Try to send the page. + const id = await AddonModWiki.newPage(title, text, { + subwikiId: this.subwikiId, + wikiId: this.wikiId, + userId: this.userId, + groupId: this.groupId, + cmId: this.cmId, + }); + + CoreForms.triggerFormSubmittedEvent(this.formElement, id > 0, CoreSites.getCurrentSiteId()); + + if (id <= 0) { + // Page stored in offline. Go to see the offline page. + return this.goToPage(title); + } + + // Page was created, get its data and go to the page. + CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'wiki' }); + this.pageId = id; + + const pageContents = await AddonModWiki.getPageContents(this.pageId, { cmId: this.cmId }); + + const promises: Promise[] = []; + this.wikiId = pageContents.wikiid; + + // Invalidate subwiki pages since there are new. + promises.push(AddonModWiki.invalidateSubwikiPages(this.wikiId)); + if (!this.subwikiId) { + // Subwiki was not created, invalidate subwikis as well. + promises.push(AddonModWiki.invalidateSubwikis(this.wikiId)); + } + + this.subwikiId = pageContents.subwikiid; + this.userId = pageContents.userid; + this.groupId = pageContents.groupid; + + await CoreUtils.ignoreErrors(Promise.all(promises)); + + // Notify page created. + CoreEvents.trigger(AddonModWikiProvider.PAGE_CREATED_EVENT, { + pageId: this.pageId, + subwikiId: this.subwikiId, + pageTitle: title, + }, CoreSites.getCurrentSiteId()); + + this.goToPage(title); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'Error saving wiki data.'); + } finally { + modal.dismiss(); + } + } + + /** + * Renew lock and control versions. + */ + protected async renewLock(): Promise { + const response = await AddonModWiki.getPageForEditing(this.pageId!, this.section, true); + + if (response.version && this.version != response.version) { + this.wrongVersionLock = true; + } + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.isDestroyed = true; + clearInterval(this.renewLockInterval); + + // Unblock the subwiki. + if (this.blockId) { + CoreSync.unblockOperation(this.component, this.blockId); + } + } + +} diff --git a/src/addons/mod/wiki/services/handlers/create-link.ts b/src/addons/mod/wiki/services/handlers/create-link.ts index b95d31817..473a2c0e9 100644 --- a/src/addons/mod/wiki/services/handlers/create-link.ts +++ b/src/addons/mod/wiki/services/handlers/create-link.ts @@ -14,13 +14,14 @@ import { Injectable } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { CoreError } from '@classes/errors/error'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; -import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; +import { CoreCourse } from '@features/course/services/course'; import { CoreNavigator } from '@services/navigator'; import { CoreSitesReadingStrategy } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { makeSingleton, Translate } from '@singletons'; +import { makeSingleton } from '@singletons'; import { AddonModWikiIndexPage } from '../../pages/index'; import { AddonModWiki } from '../wiki'; import { AddonModWikiModuleHandlerService } from './module'; @@ -105,37 +106,39 @@ export class AddonModWikiCreateLinkHandlerService extends CoreContentLinksHandle const route = CoreNavigator.getCurrentRoute({ pageComponent: AddonModWikiIndexPage }); const subwikiId = parseInt(params.swid, 10); const wikiId = parseInt(params.wid, 10); - let module: CoreCourseAnyModuleData; + let moduleId: number; // Check if the link is inside the same wiki. const isSameWiki = await this.currentStateIsSameWiki(route, subwikiId, siteId); if (isSameWiki) { // User is seeing the wiki, we can get the module from the wiki params. - module = route!.snapshot.queryParams.module; + moduleId = route!.snapshot.params.cmId; + courseId = route!.snapshot.params.courseId; } else if (wikiId) { // The URL specifies which wiki it belongs to. Get the module. - module = await CoreCourse.getModuleBasicInfoByInstance(wikiId, 'wiki', siteId); + const module = await CoreCourse.getModuleBasicInfoByInstance(wikiId, 'wiki', siteId); + + moduleId = module.id; + courseId = module.course; } else { // Not enough data. - CoreDomUtils.showErrorModal(Translate.instant('addon.mod_wiki.errorloadingpage')); - - return; + throw new CoreError(); } // Open the page. CoreNavigator.navigateToSitePath( - AddonModWikiModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/edit`, + AddonModWikiModuleHandlerService.PAGE_NAME + `/${courseId}/${moduleId}/edit`, { params: { - module: module, - courseId: courseId || module.course || route?.snapshot.params.courseId, pageTitle: params.title, subwikiId: subwikiId, }, siteId, }, ); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_wiki.errorloadingpage', true); } finally { modal.dismiss(); } diff --git a/src/addons/mod/wiki/services/handlers/edit-link.ts b/src/addons/mod/wiki/services/handlers/edit-link.ts index f92a865ec..6a164a954 100644 --- a/src/addons/mod/wiki/services/handlers/edit-link.ts +++ b/src/addons/mod/wiki/services/handlers/edit-link.ts @@ -15,7 +15,12 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate'; +import { CoreCourse } from '@features/course/services/course'; +import { CoreNavigator } from '@services/navigator'; +import { CoreDomUtils } from '@services/utils/dom'; import { makeSingleton } from '@singletons'; +import { AddonModWiki } from '../wiki'; +import { AddonModWikiModuleHandlerService } from './module'; /** * Handler to treat links to edit a wiki page. @@ -36,23 +41,40 @@ export class AddonModWikiEditLinkHandlerService extends CoreContentLinksHandlerB params: Record, courseId?: number, ): CoreContentLinksAction[] | Promise { - courseId = Number(courseId || params.courseid || params.cid); return [{ - action: (siteId: string) => { + action: async (siteId: string) => { + const modal = await CoreDomUtils.showModalLoading(); - let section = ''; - if (typeof params.section != 'undefined') { - section = params.section.replace(/\+/g, ' '); + try { + const pageId = Number(params.pageid); + + const pageContents = await AddonModWiki.getPageContents(pageId, { siteId }); + + const module = await CoreCourse.getModuleBasicInfoByInstance(pageContents.wikiid, 'wiki', siteId); + + let section = ''; + if (typeof params.section != 'undefined') { + section = params.section.replace(/\+/g, ' '); + } + + courseId = module.course || courseId || Number(params.courseid || params.cid); + + CoreNavigator.navigateToSitePath( + AddonModWikiModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/edit`, + { + params: { + section: section, + pageId: pageId, + }, + siteId, + }, + ); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_wiki.errorloadingpage', true); + } finally { + modal.dismiss(); } - - const pageParams = { - courseId: courseId, - section: section, - pageId: parseInt(params.pageid, 10), - }; - - // @todo this.linkHelper.goInSite(navCtrl, 'AddonModWikiEditPage', pageParams, siteId); }, }]; } diff --git a/src/addons/mod/wiki/services/handlers/module.ts b/src/addons/mod/wiki/services/handlers/module.ts index 793b2d479..917ad30d1 100644 --- a/src/addons/mod/wiki/services/handlers/module.ts +++ b/src/addons/mod/wiki/services/handlers/module.ts @@ -65,7 +65,7 @@ export class AddonModWikiModuleHandlerService implements CoreCourseModuleHandler options = options || {}; options.params = options.params || {}; Object.assign(options.params, { module }); - const routeParams = `/${courseId}/${module.id}/root`; + const routeParams = `/${courseId}/${module.id}/page/root`; CoreNavigator.navigateToSitePath(AddonModWikiModuleHandlerService.PAGE_NAME + routeParams, options); }, diff --git a/src/addons/mod/wiki/services/handlers/page-or-map-link.ts b/src/addons/mod/wiki/services/handlers/page-or-map-link.ts index 28fecbfa2..0aea30fbf 100644 --- a/src/addons/mod/wiki/services/handlers/page-or-map-link.ts +++ b/src/addons/mod/wiki/services/handlers/page-or-map-link.ts @@ -67,7 +67,7 @@ export class AddonModWikiPageOrMapLinkHandlerService extends CoreContentLinksHan courseId = courseId || module.course; CoreNavigator.navigateToSitePath( - AddonModWikiModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/${hash}`, + AddonModWikiModuleHandlerService.PAGE_NAME + `/${courseId}/${module.id}/page/${hash}`, { params: { pageId: page.id, diff --git a/src/addons/mod/wiki/services/wiki-sync.ts b/src/addons/mod/wiki/services/wiki-sync.ts index f285bfe7c..0f9375231 100644 --- a/src/addons/mod/wiki/services/wiki-sync.ts +++ b/src/addons/mod/wiki/services/wiki-sync.ts @@ -52,7 +52,7 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider 0) { diff --git a/src/addons/mod/wiki/services/wiki.ts b/src/addons/mod/wiki/services/wiki.ts index 3a9a13c2e..da636b222 100644 --- a/src/addons/mod/wiki/services/wiki.ts +++ b/src/addons/mod/wiki/services/wiki.ts @@ -43,6 +43,7 @@ export class AddonModWikiProvider { protected subwikiListsCache: {[wikiId: number]: AddonModWikiSubwikiListData} = {}; protected wikiFirstViewedPage: Record> = {}; + protected editedPage?: AddonModWikiEditedPageData; constructor() { // Clear subwiki lists cache on logout. @@ -64,6 +65,18 @@ export class AddonModWikiProvider { } } + /** + * Delete and return the edited page data if any. + * + * @return Edited page data, undefined if no data. + */ + consumeEditedPageData(): AddonModWikiEditedPageData | undefined { + const editedPage = this.editedPage; + delete this.editedPage; + + return editedPage; + } + /** * Save wiki contents on a page or section. * @@ -754,6 +767,15 @@ export class AddonModWikiProvider { return response.pageid; } + /** + * Set edited page data. + * + * @param data Data. + */ + setEditedPageData(data: AddonModWikiEditedPageData): void { + this.editedPage = data; + } + /** * Save subwiki list for a wiki to the cache. * @@ -1189,3 +1211,17 @@ export type AddonModWikiPageCreatedData = { subwikiId: number; pageTitle: string; }; + +/** + * Data about a page that was just edited. + */ +export type AddonModWikiEditedPageData = { + cmId: number; + courseId: number; + wikiId: number; + pageTitle: string; + subwikiId?: number; + userId?: number; + groupId?: number; + pageId?: number; +}; diff --git a/src/addons/mod/wiki/wiki-lazy.module.ts b/src/addons/mod/wiki/wiki-lazy.module.ts index 4049bbcea..37015b8f4 100644 --- a/src/addons/mod/wiki/wiki-lazy.module.ts +++ b/src/addons/mod/wiki/wiki-lazy.module.ts @@ -17,12 +17,20 @@ import { RouterModule, Routes } from '@angular/router'; import { CoreSharedModule } from '@/core/shared.module'; import { AddonModWikiComponentsModule } from './components/components.module'; import { AddonModWikiIndexPage } from './pages/index/index'; +import { CoreEditorComponentsModule } from '@features/editor/components/components.module'; +import { CanLeaveGuard } from '@guards/can-leave'; +import { AddonModWikiEditPage } from './pages/edit/edit'; const routes: Routes = [ { - path: ':courseId/:cmId/:hash', + path: ':courseId/:cmId/page/:hash', component: AddonModWikiIndexPage, }, + { + path: ':courseId/:cmId/edit', + component: AddonModWikiEditPage, + canDeactivate: [CanLeaveGuard], + }, ]; @NgModule({ @@ -30,9 +38,11 @@ const routes: Routes = [ RouterModule.forChild(routes), CoreSharedModule, AddonModWikiComponentsModule, + CoreEditorComponentsModule, ], declarations: [ AddonModWikiIndexPage, + AddonModWikiEditPage, ], }) export class AddonModWikiLazyModule {}