From 8f991cecd08ff440ebc262ee5dab4e6df05c6518 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 12 Apr 2021 09:17:50 +0200 Subject: [PATCH] MOBILE-3644 glossary: Migrate edit and entry page --- .../mod/glossary/components/index/index.ts | 42 +- .../mod/glossary/glossary-lazy.module.ts | 34 +- src/addons/mod/glossary/glossary.module.ts | 12 +- src/addons/mod/glossary/pages/edit/edit.html | 77 ++++ .../mod/glossary/pages/edit/edit.module.ts | 38 ++ src/addons/mod/glossary/pages/edit/edit.ts | 370 ++++++++++++++++++ .../mod/glossary/pages/entry/entry.html | 85 ++++ .../mod/glossary/pages/entry/entry.module.ts | 40 ++ src/addons/mod/glossary/pages/entry/entry.ts | 146 +++++++ src/core/directives/auto-rows.ts | 11 +- 10 files changed, 827 insertions(+), 28 deletions(-) create mode 100644 src/addons/mod/glossary/pages/edit/edit.html create mode 100644 src/addons/mod/glossary/pages/edit/edit.module.ts create mode 100644 src/addons/mod/glossary/pages/edit/edit.ts create mode 100644 src/addons/mod/glossary/pages/entry/entry.html create mode 100644 src/addons/mod/glossary/pages/entry/entry.module.ts create mode 100644 src/addons/mod/glossary/pages/entry/entry.ts diff --git a/src/addons/mod/glossary/components/index/index.ts b/src/addons/mod/glossary/components/index/index.ts index 3cf621c08..4b15485ff 100644 --- a/src/addons/mod/glossary/components/index/index.ts +++ b/src/addons/mod/glossary/components/index/index.ts @@ -464,15 +464,6 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity */ openNewEntry(): void { this.entries.select({ newEntry: true }); - // @todo - // const params = { - // courseId: this.courseId, - // module: this.module, - // glossary: this.glossary, - // entry: entry, - // }; - // this.splitviewCtrl.getMasterNav().push('AddonModGlossaryEditPage', params); - // this.selectedEntry = 0; } /** @@ -537,19 +528,6 @@ class AddonModGlossaryEntriesManager extends CorePageItemsListManager super(pageComponent); } - /** - * @inheritdoc - */ - getItemQueryParams(entry: EntryItem): Params { - // @todo - return { - // courseId: this.component.courseId, - // cmId: this.component.module.id, - // forumId: this.component.forum!.id, - // ...(this.isOnlineDiscussion(discussion) ? { discussion, trackPosts: this.component.trackPosts } : {}), - }; - } - /** * Type guard to infer NewEntryForm objects. * @@ -641,6 +619,26 @@ class AddonModGlossaryEntriesManager extends CorePageItemsListManager return 'edit/0'; } + /** + * @inheritdoc + */ + getItemQueryParams(entry: EntryItem): Params { + if (this.isOfflineEntry(entry)) { + return { + concept: entry.concept, + }; + } + + return {}; + } + + /** + * @inheritdoc + */ + protected getDefaultItem(): EntryItem | null { + return this.onlineEntries[0] || null; + } + } export type AddonModGlossaryFetchMode = 'author_all' | 'cat_all' | 'newest_first' | 'recently_updated' | 'letter_all'; diff --git a/src/addons/mod/glossary/glossary-lazy.module.ts b/src/addons/mod/glossary/glossary-lazy.module.ts index 03fe01535..c05d7af3f 100644 --- a/src/addons/mod/glossary/glossary-lazy.module.ts +++ b/src/addons/mod/glossary/glossary-lazy.module.ts @@ -18,12 +18,44 @@ import { RouterModule, Routes } from '@angular/router'; import { CoreSharedModule } from '@/core/shared.module'; import { AddonModGlossaryComponentsModule } from './components/components.module'; import { AddonModGlossaryIndexPage } from './pages/index/index'; +import { conditionalRoutes } from '@/app/app-routing.module'; +import { CoreScreen } from '@services/screen'; -const routes: Routes = [ +const mobileRoutes: Routes = [ { path: ':courseId/:cmId', component: AddonModGlossaryIndexPage, }, + { + path: ':courseId/:cmId/entry/:entryId', + loadChildren: () => import('./pages/entry/entry.module').then(m => m.AddonModGlossaryEntryPageModule), + }, + { + path: ':courseId/:cmId/edit/:timecreated', + loadChildren: () => import('./pages/edit/edit.module').then(m => m.AddonModGlossaryEditPageModule), + }, +]; + +const tabletRoutes: Routes = [ + { + path: ':courseId/:cmId', + component: AddonModGlossaryIndexPage, + children: [ + { + path: 'entry/:entryId', + loadChildren: () => import('./pages/entry/entry.module').then(m => m.AddonModGlossaryEntryPageModule), + }, + { + path: 'edit/:timecreated', + loadChildren: () => import('./pages/edit/edit.module').then(m => m.AddonModGlossaryEditPageModule), + }, + ], + }, +]; + +const routes: Routes = [ + ...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile), + ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet), ]; @NgModule({ diff --git a/src/addons/mod/glossary/glossary.module.ts b/src/addons/mod/glossary/glossary.module.ts index 5a114381d..69dfda5de 100644 --- a/src/addons/mod/glossary/glossary.module.ts +++ b/src/addons/mod/glossary/glossary.module.ts @@ -43,7 +43,15 @@ export const ADDON_MOD_GLOSSARY_SERVICES: Type[] = [ AddonModGlossaryHelperProvider, ]; -const routes: Routes = [ +const mainMenuRoutes: Routes = [ + { + path: `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/entry/:entryId`, + loadChildren: () => import('./pages/entry/entry.module').then(m => m.AddonModGlossaryEntryPageModule), + }, + { + path: `${AddonModGlossaryModuleHandlerService.PAGE_NAME}/edit/:timecreated`, + loadChildren: () => import('./pages/edit/edit.module').then(m => m.AddonModGlossaryEditPageModule), + }, { path: AddonModGlossaryModuleHandlerService.PAGE_NAME, loadChildren: () => import('./glossary-lazy.module').then(m => m.AddonModGlossaryLazyModule), @@ -52,7 +60,7 @@ const routes: Routes = [ @NgModule({ imports: [ - CoreMainMenuTabRoutingModule.forChild(routes), + CoreMainMenuTabRoutingModule.forChild(mainMenuRoutes), AddonModGlossaryComponentsModule, ], providers: [ diff --git a/src/addons/mod/glossary/pages/edit/edit.html b/src/addons/mod/glossary/pages/edit/edit.html new file mode 100644 index 000000000..3a8d076c1 --- /dev/null +++ b/src/addons/mod/glossary/pages/edit/edit.html @@ -0,0 +1,77 @@ + + + + + + + + + + + + + +
+ + {{ 'addon.mod_glossary.concept' | translate }} + + + + + {{ 'addon.mod_glossary.definition' | translate }} + + + + + + {{ 'addon.mod_glossary.categories' | translate }} + + + + {{ category.name }} + + + + + + {{ 'addon.mod_glossary.aliases' | translate }} + + + + + + {{ 'addon.mod_glossary.attachment' | translate }} + + + + + + {{ 'addon.mod_glossary.linking' | translate }} + + + {{ 'addon.mod_glossary.entryusedynalink' | translate }} + + + + {{ 'addon.mod_glossary.casesensitive' | translate }} + + + + + {{ 'addon.mod_glossary.fullmatch' | translate }} + + + + + {{ 'core.save' | translate }} + +
+
+
diff --git a/src/addons/mod/glossary/pages/edit/edit.module.ts b/src/addons/mod/glossary/pages/edit/edit.module.ts new file mode 100644 index 000000000..1ff7808da --- /dev/null +++ b/src/addons/mod/glossary/pages/edit/edit.module.ts @@ -0,0 +1,38 @@ +// (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 { NgModule } from '@angular/core'; +import { AddonModGlossaryEditPage } from './edit'; +import { CoreSharedModule } from '@/core/shared.module'; +import { CoreEditorComponentsModule } from '@features/editor/components/components.module'; +import { RouterModule, Routes } from '@angular/router'; +import { CanLeaveGuard } from '@guards/can-leave'; + +const routes: Routes = [{ + path: '', + component: AddonModGlossaryEditPage, + canDeactivate: [CanLeaveGuard], +}]; + +@NgModule({ + declarations: [ + AddonModGlossaryEditPage, + ], + imports: [ + RouterModule.forChild(routes), + CoreSharedModule, + CoreEditorComponentsModule, + ], +}) +export class AddonModGlossaryEditPageModule {} diff --git a/src/addons/mod/glossary/pages/edit/edit.ts b/src/addons/mod/glossary/pages/edit/edit.ts new file mode 100644 index 000000000..854c74247 --- /dev/null +++ b/src/addons/mod/glossary/pages/edit/edit.ts @@ -0,0 +1,370 @@ +// (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, ViewChild, ElementRef, Optional } from '@angular/core'; +import { FormControl } from '@angular/forms'; +import { CoreError } from '@classes/errors/error'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader'; +import { CanLeave } from '@guards/can-leave'; +import { FileEntry } from '@ionic-native/file/ngx'; +import { CoreNavigator } from '@services/navigator'; +import { CoreSites } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreTextUtils } from '@services/utils/text'; +import { Translate } from '@singletons'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; +import { CoreForms } from '@singletons/form'; +import { + AddonModGlossary, + AddonModGlossaryCategory, + AddonModGlossaryEntryOption, + AddonModGlossaryGlossary, + AddonModGlossaryNewEntry, + AddonModGlossaryNewEntryWithFiles, + AddonModGlossaryProvider, +} from '../../services/glossary'; +import { AddonModGlossaryHelper } from '../../services/glossary-helper'; +import { AddonModGlossaryOffline } from '../../services/glossary-offline'; + +/** + * Page that displays the edit form. + */ +@Component({ + selector: 'page-addon-mod-glossary-edit', + templateUrl: 'edit.html', +}) +export class AddonModGlossaryEditPage implements OnInit, CanLeave { + + @ViewChild('editFormEl') formElement?: ElementRef; + + component = AddonModGlossaryProvider.COMPONENT; + cmId!: number; + courseId!: number; + loaded = false; + glossary?: AddonModGlossaryGlossary; + attachments: FileEntry[] = []; + definitionControl = new FormControl(); + categories: AddonModGlossaryCategory[] = []; + editorExtraParams: Record = {}; + entry: AddonModGlossaryNewEntry = { + concept: '', + definition: '', + timecreated: 0, + }; + + options = { + categories: [], + aliases: '', + usedynalink: false, + casesensitive: false, + fullmatch: false, + }; + + protected timecreated!: number; + protected concept?: string; + protected syncId?: string; + protected syncObserver?: CoreEventObserver; + protected isDestroyed = false; + protected originalData?: AddonModGlossaryNewEntryWithFiles; + protected saved = false; + + constructor(@Optional() protected splitView: CoreSplitViewComponent) {} + + /** + * Component being initialized. + */ + ngOnInit(): void { + this.cmId = CoreNavigator.getRouteNumberParam('cmId')!; + this.courseId = CoreNavigator.getRouteNumberParam('courseId')!; + this.timecreated = CoreNavigator.getRouteNumberParam('timecreated')!; + this.concept = CoreNavigator.getRouteParam('concept')!; + this.editorExtraParams.timecreated = this.timecreated; + + this.fetchData(); + } + + /** + * Fetch required data. + * + * @return Promise resolved when done. + */ + protected async fetchData(): Promise { + try { + this.glossary = await AddonModGlossary.getGlossary(this.courseId, this.cmId); + + if (this.timecreated > 0) { + await this.loadOfflineData(); + } + + this.categories = await AddonModGlossary.getAllCategories(this.glossary.id, { + cmId: this.cmId, + }); + + this.loaded = true; + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingglossary', true); + + CoreNavigator.back(); + } + } + + /** + * Load offline data when editing an offline entry. + * + * @return Promise resolved when done. + */ + protected async loadOfflineData(): Promise { + const entry = await AddonModGlossaryOffline.getNewEntry(this.glossary!.id, this.concept || '', this.timecreated); + + this.entry.concept = entry.concept || ''; + this.entry.definition = entry.definition || ''; + this.entry.timecreated = entry.timecreated; + + this.originalData = { + concept: this.entry.concept, + definition: this.entry.definition, + files: [], + timecreated: entry.timecreated, + }; + + if (entry.options) { + this.options.categories = (entry.options.categories && ( entry.options.categories).split(',')) || []; + this.options.aliases = entry.options.aliases || ''; + this.options.usedynalink = !!entry.options.usedynalink; + if (this.options.usedynalink) { + this.options.casesensitive = !!entry.options.casesensitive; + this.options.fullmatch = !!entry.options.fullmatch; + } + } + + // Treat offline attachments if any. + if (entry.attachments?.offline) { + this.attachments = await AddonModGlossaryHelper.getStoredFiles(this.glossary!.id, entry.concept, entry.timecreated); + + this.originalData.files = this.attachments.slice(); + } + + this.definitionControl.setValue(this.entry.definition); + } + + /** + * Reset the form data. + */ + protected resetForm(): void { + this.entry.concept = ''; + this.entry.definition = ''; + this.entry.timecreated = 0; + this.originalData = undefined; + + this.options.categories = []; + this.options.aliases = ''; + this.options.usedynalink = false; + this.options.casesensitive = false; + this.options.fullmatch = false; + this.attachments.length = 0; // Empty the array. + + this.definitionControl.setValue(''); + } + + /** + * Definition changed. + * + * @param text The new text. + */ + onDefinitionChange(text: string): void { + this.entry.definition = text; + } + + /** + * Check if we can leave the page or not. + * + * @return Resolved if we can leave it, rejected if not. + */ + async canLeave(): Promise { + if (this.saved) { + return true; + } + + if (AddonModGlossaryHelper.hasEntryDataChanged(this.entry, this.attachments, this.originalData)) { + // Show confirmation if some data has been modified. + await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit')); + } + + // Delete the local files from the tmp folder. + CoreFileUploader.clearTmpFiles(this.attachments); + + CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId()); + + return true; + } + + /** + * Save the entry. + */ + async save(): Promise { + let definition = this.entry.definition; + let entryId: number | undefined; + const timecreated = this.entry.timecreated || Date.now(); + + if (!this.entry.concept || !definition) { + CoreDomUtils.showErrorModal('addon.mod_glossary.fillfields', true); + + return; + } + + const modal = await CoreDomUtils.showModalLoading('core.sending', true); + definition = CoreTextUtils.formatHtmlLines(definition); + + try { + // Upload attachments first if any. + const { saveOffline, attachmentsResult } = await this.uploadAttachments(timecreated); + + const options: Record = { + aliases: this.options.aliases, + categories: this.options.categories.join(','), + }; + + if (this.glossary!.usedynalink) { + options.usedynalink = this.options.usedynalink ? 1 : 0; + if (this.options.usedynalink) { + options.casesensitive = this.options.casesensitive ? 1 : 0; + options.fullmatch = this.options.fullmatch ? 1 : 0; + } + } + + if (saveOffline) { + if (this.entry && !this.glossary!.allowduplicatedentries) { + // Check if the entry is duplicated in online or offline mode. + const isUsed = await AddonModGlossary.isConceptUsed(this.glossary!.id, this.entry.concept, { + timeCreated: this.entry.timecreated, + cmId: this.cmId, + }); + + if (isUsed) { + // There's a entry with same name, reject with error message. + throw new CoreError(Translate.instant('addon.mod_glossary.errconceptalreadyexists')); + } + } + + // Save entry in offline. + await AddonModGlossaryOffline.addNewEntry( + this.glossary!.id, + this.entry.concept, + definition, + this.courseId, + options, + attachmentsResult, + timecreated, + undefined, + undefined, + this.entry, + ); + } else { + // Try to send it to server. + // Don't allow offline if there are attachments since they were uploaded fine. + await AddonModGlossary.addEntry( + this.glossary!.id, + this.entry.concept, + definition, + this.courseId, + options, + attachmentsResult, + { + timeCreated: timecreated, + discardEntry: this.entry, + allowOffline: !this.attachments.length, + checkDuplicates: !this.glossary!.allowduplicatedentries, + }, + ); + } + + // Delete the local files from the tmp folder. + CoreFileUploader.clearTmpFiles(this.attachments); + + if (entryId) { + // Data sent to server, delete stored files (if any). + AddonModGlossaryHelper.deleteStoredFiles(this.glossary!.id, this.entry.concept, timecreated); + CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: 'glossary' }); + } + + CoreEvents.trigger(AddonModGlossaryProvider.ADD_ENTRY_EVENT, { + glossaryId: this.glossary!.id, + entryId: entryId, + }, CoreSites.getCurrentSiteId()); + + CoreForms.triggerFormSubmittedEvent(this.formElement, !!entryId, CoreSites.getCurrentSiteId()); + + if (this.splitView?.outletActivated) { + if (this.timecreated > 0) { + // Reload the data. + await this.loadOfflineData(); + } else { + // Empty form. + this.resetForm(); + } + } else { + this.saved = true; + CoreNavigator.back(); + } + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.cannoteditentry', true); + } finally { + modal.dismiss(); + } + } + + /** + * Upload entry attachments if any. + * + * @param timecreated Entry's timecreated. + * @return Promise resolved when done. + */ + protected async uploadAttachments( + timecreated: number, + ): Promise<{saveOffline: boolean; attachmentsResult?: number | CoreFileUploaderStoreFilesResult}> { + if (!this.attachments.length) { + return { + saveOffline: false, + }; + } + + try { + const attachmentsResult = await CoreFileUploader.uploadOrReuploadFiles( + this.attachments, + AddonModGlossaryProvider.COMPONENT, + this.glossary!.id, + ); + + return { + saveOffline: false, + attachmentsResult, + }; + } catch { + // Cannot upload them in online, save them in offline. + const attachmentsResult = await AddonModGlossaryHelper.storeFiles( + this.glossary!.id, + this.entry.concept, + timecreated, + this.attachments, + ); + + return { + saveOffline: true, + attachmentsResult, + }; + } + } + +} diff --git a/src/addons/mod/glossary/pages/entry/entry.html b/src/addons/mod/glossary/pages/entry/entry.html new file mode 100644 index 000000000..9e3f1b07a --- /dev/null +++ b/src/addons/mod/glossary/pages/entry/entry.html @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + +

+ + +

+

{{ entry.userfullname }}

+
+ {{ entry.timemodified | coreDateDayOrTime }} +
+ + +

+ + +

+
+ {{ entry.timemodified | coreDateDayOrTime }} +
+ + + + + + +
+ + +
+ + +
{{ 'core.tag.tags' | translate }}:
+ +
+
+ +

{{ 'addon.mod_glossary.entrypendingapproval' | translate }}

+
+ + + + + + + + + + +
+ + + + {{ 'addon.mod_glossary.errorloadingentry' | translate }} + + +
+
diff --git a/src/addons/mod/glossary/pages/entry/entry.module.ts b/src/addons/mod/glossary/pages/entry/entry.module.ts new file mode 100644 index 000000000..24da954a9 --- /dev/null +++ b/src/addons/mod/glossary/pages/entry/entry.module.ts @@ -0,0 +1,40 @@ +// (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 { NgModule } from '@angular/core'; +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonModGlossaryEntryPage } from './entry'; +import { CoreCommentsComponentsModule } from '@features/comments/components/components.module'; +import { CoreRatingComponentsModule } from '@features/rating/components/components.module'; +import { CoreTagComponentsModule } from '@features/tag/components/components.module'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [{ + path: '', + component: AddonModGlossaryEntryPage, +}]; + +@NgModule({ + declarations: [ + AddonModGlossaryEntryPage, + ], + imports: [ + RouterModule.forChild(routes), + CoreSharedModule, + CoreCommentsComponentsModule, + CoreRatingComponentsModule, + CoreTagComponentsModule, + ], +}) +export class AddonModGlossaryEntryPageModule {} diff --git a/src/addons/mod/glossary/pages/entry/entry.ts b/src/addons/mod/glossary/pages/entry/entry.ts new file mode 100644 index 000000000..5db347e85 --- /dev/null +++ b/src/addons/mod/glossary/pages/entry/entry.ts @@ -0,0 +1,146 @@ +// (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, ViewChild } from '@angular/core'; +import { CoreCommentsCommentsComponent } from '@features/comments/components/comments/comments'; +import { CoreComments } from '@features/comments/services/comments'; +import { CoreRatingInfo } from '@features/rating/services/rating'; +import { CoreTag } from '@features/tag/services/tag'; +import { IonRefresher } from '@ionic/angular'; +import { CoreNavigator } from '@services/navigator'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; +import { + AddonModGlossary, + AddonModGlossaryEntry, + AddonModGlossaryGlossary, + AddonModGlossaryProvider, +} from '../../services/glossary'; + +/** + * Page that displays a glossary entry. + */ +@Component({ + selector: 'page-addon-mod-glossary-entry', + templateUrl: 'entry.html', +}) +export class AddonModGlossaryEntryPage implements OnInit { + + @ViewChild(CoreCommentsCommentsComponent) comments?: CoreCommentsCommentsComponent; + + component = AddonModGlossaryProvider.COMPONENT; + componentId?: number; + entry?: AddonModGlossaryEntry; + glossary?: AddonModGlossaryGlossary; + loaded = false; + showAuthor = false; + showDate = false; + ratingInfo?: CoreRatingInfo; + tagsEnabled = false; + commentsEnabled = false; + courseId!: number; + + protected entryId!: number; + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + this.courseId = CoreNavigator.getRouteNumberParam('courseId')!; + this.entryId = CoreNavigator.getRouteNumberParam('entryId')!; + this.tagsEnabled = CoreTag.areTagsAvailableInSite(); + this.commentsEnabled = !CoreComments.areCommentsDisabledInSite(); + + try { + await this.fetchEntry(); + + if (!this.glossary) { + return; + } + + await CoreUtils.ignoreErrors(AddonModGlossary.logEntryView(this.entryId, this.componentId!, this.glossary.name)); + } finally { + this.loaded = true; + } + } + + /** + * Refresh the data. + * + * @param refresher Refresher. + * @return Promise resolved when done. + */ + async doRefresh(refresher?: IonRefresher): Promise { + if (this.glossary?.allowcomments && this.entry && this.entry.id > 0 && this.commentsEnabled && this.comments) { + // Refresh comments. Don't add it to promises because we don't want the comments fetch to block the entry fetch. + CoreUtils.ignoreErrors(this.comments.doRefresh()); + } + + try { + await CoreUtils.ignoreErrors(AddonModGlossary.invalidateEntry(this.entryId)); + + await this.fetchEntry(); + } finally { + refresher?.complete(); + } + } + + /** + * Convenience function to get the glossary entry. + * + * @return Promise resolved when done. + */ + protected async fetchEntry(): Promise { + try { + const result = await AddonModGlossary.getEntry(this.entryId); + + this.entry = result.entry; + this.ratingInfo = result.ratinginfo; + + if (this.glossary) { + // Glossary already loaded, nothing else to load. + return; + } + + // Load the glossary. + this.glossary = await AddonModGlossary.getGlossaryById(this.courseId, this.entry.glossaryid); + this.componentId = this.glossary.coursemodule; + + switch (this.glossary.displayformat) { + case 'fullwithauthor': + case 'encyclopedia': + this.showAuthor = true; + this.showDate = true; + break; + case 'fullwithoutauthor': + this.showAuthor = false; + this.showDate = true; + break; + default: // Default, and faq, simple, entrylist, continuous. + this.showAuthor = false; + this.showDate = false; + } + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true); + } + } + + /** + * Function called when rating is updated online. + */ + ratingUpdated(): void { + AddonModGlossary.invalidateEntry(this.entryId); + } + +} diff --git a/src/core/directives/auto-rows.ts b/src/core/directives/auto-rows.ts index 05d4f12bc..00c570e38 100644 --- a/src/core/directives/auto-rows.ts +++ b/src/core/directives/auto-rows.ts @@ -59,10 +59,15 @@ export class CoreAutoRowsDirective implements AfterViewInit { * Resize the textarea. */ protected resize(): void { - let nativeElement = this.element.nativeElement; + let nativeElement: HTMLElement = this.element.nativeElement; if (nativeElement.tagName == 'ION-TEXTAREA') { - // The first child of ion-textarea is the actual textarea element. - nativeElement = nativeElement.firstElementChild; + // Search the actual textarea. + const textarea = nativeElement.querySelector('textarea'); + if (!textarea) { + return; + } + + nativeElement = textarea; } // Set height to 1px to force scroll height to calculate correctly.