forked from CIT/Vmeda.Online
		
	
						commit
						f48433215b
					
				@ -28,8 +28,5 @@ import { AddonBlockActivityModulesComponent } from './activitymodules/activitymo
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockActivityModulesComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockActivityModulesComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockActivityModulesComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockActivityResultsComponent } from './activityresults/activityre
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockActivityResultsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockActivityResultsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockActivityResultsComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockBadgesComponent } from './badges/badges';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockBadgesComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockBadgesComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockBadgesComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockBlogMenuComponent } from './blogmenu/blogmenu';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockBlogMenuComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockBlogMenuComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockBlogMenuComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockBlogRecentComponent } from './blogrecent/blogrecent';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockBlogRecentComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockBlogRecentComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockBlogRecentComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockBlogTagsComponent } from './blogtags/blogtags';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockBlogTagsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockBlogTagsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockBlogTagsComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -29,8 +29,5 @@ import { AddonBlockMyOverviewComponent } from './myoverview/myoverview';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockMyOverviewComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockMyOverviewComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockMyOverviewComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockNewsItemsComponent } from './newsitems/newsitems';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockNewsItemsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockNewsItemsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockNewsItemsComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockOnlineUsersComponent } from './onlineusers/onlineusers';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockOnlineUsersComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockOnlineUsersComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockOnlineUsersComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockRecentActivityComponent } from './recentactivity/recentactivi
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockRecentActivityComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockRecentActivityComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockRecentActivityComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -30,8 +30,5 @@ import { AddonBlockRecentlyAccessedCoursesComponent } from './recentlyaccessedco
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockRecentlyAccessedCoursesComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockRecentlyAccessedCoursesComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockRecentlyAccessedCoursesComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -30,8 +30,5 @@ import { AddonBlockRecentlyAccessedItemsComponent } from './recentlyaccesseditem
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockRecentlyAccessedItemsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockRecentlyAccessedItemsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockRecentlyAccessedItemsComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockRssClientComponent } from './rssclient/rssclient';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockRssClientComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockRssClientComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockRssClientComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -30,8 +30,5 @@ import { AddonBlockSiteMainMenuComponent } from './sitemainmenu/sitemainmenu';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockSiteMainMenuComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockSiteMainMenuComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockSiteMainMenuComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -30,8 +30,5 @@ import { AddonBlockStarredCoursesComponent } from './starredcourses/starredcours
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockStarredCoursesComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockStarredCoursesComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockStarredCoursesComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,5 @@ import { AddonBlockTagsComponent } from './tags/tags';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonBlockTagsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockTagsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockTagsComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -35,9 +35,5 @@ import { AddonBlockTimelineEventsComponent } from './events/events';
 | 
			
		||||
        AddonBlockTimelineComponent,
 | 
			
		||||
        AddonBlockTimelineEventsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonBlockTimelineComponent,
 | 
			
		||||
        AddonBlockTimelineEventsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockTimelineComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -36,8 +36,5 @@ import { AddonCalendarFilterPopoverComponent } from './filter/filter';
 | 
			
		||||
        AddonCalendarUpcomingEventsComponent,
 | 
			
		||||
        AddonCalendarFilterPopoverComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonCalendarFilterPopoverComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonCalendarComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -25,8 +25,5 @@ import { AddonMessagesConversationInfoComponent } from './conversation-info/conv
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonMessagesConversationInfoComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonMessagesComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -40,8 +40,5 @@ import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModAssignFeedbackCommentsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonModAssignFeedbackCommentsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModAssignFeedbackCommentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,5 @@ import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModAssignFeedbackEditPdfComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonModAssignFeedbackEditPdfComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModAssignFeedbackEditPdfModule {}
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,5 @@ import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModAssignFeedbackFileComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonModAssignFeedbackFileComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModAssignFeedbackFileModule {}
 | 
			
		||||
 | 
			
		||||
@ -40,8 +40,5 @@ import { CoreCommentsComponentsModule } from '@features/comments/components/comp
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModAssignSubmissionCommentsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonModAssignSubmissionCommentsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModAssignSubmissionCommentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,5 @@ import { AddonModAssignSubmissionDelegate } from '../../services/submission-dele
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModAssignSubmissionFileComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonModAssignSubmissionFileComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModAssignSubmissionFileModule {}
 | 
			
		||||
 | 
			
		||||
@ -40,8 +40,5 @@ import { AddonModAssignSubmissionDelegate } from '../../services/submission-dele
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModAssignSubmissionOnlineTextComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonModAssignSubmissionOnlineTextComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModAssignSubmissionOnlineTextModule {}
 | 
			
		||||
 | 
			
		||||
@ -19,9 +19,8 @@ import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { AddonModChoice, AddonModChoiceProvider } from './choice';
 | 
			
		||||
import { AddonModChoiceOffline } from './choice-offline';
 | 
			
		||||
@ -192,11 +191,7 @@ export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
            await AddonModChoiceOffline.deleteResponse(choiceId, siteId, userId);
 | 
			
		||||
 | 
			
		||||
            // Responses deleted, add a warning.
 | 
			
		||||
            result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                component: this.componentTranslate,
 | 
			
		||||
                name: data.name,
 | 
			
		||||
                error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
            }));
 | 
			
		||||
            this.addOfflineDataDeletedWarning(result.warnings, data.name, error);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Data has been sent to server, prefetch choice if needed.
 | 
			
		||||
 | 
			
		||||
@ -235,28 +235,20 @@ export class AddonModDataSyncProvider extends CoreCourseActivitySyncBaseProvider
 | 
			
		||||
        result: AddonModDataSyncResult,
 | 
			
		||||
        siteId: string,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const synEntryResult = await this.performSyncEntry(database, entryActions, result, siteId);
 | 
			
		||||
        const syncEntryResult = await this.performSyncEntry(database, entryActions, result, siteId);
 | 
			
		||||
 | 
			
		||||
        if (synEntryResult.discardError) {
 | 
			
		||||
        if (syncEntryResult.discardError) {
 | 
			
		||||
            // Submission was discarded, add a warning.
 | 
			
		||||
            const message = Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                component: this.componentTranslate,
 | 
			
		||||
                name: database.name,
 | 
			
		||||
                error: synEntryResult.discardError,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (result.warnings.indexOf(message) == -1) {
 | 
			
		||||
                result.warnings.push(message);
 | 
			
		||||
            }
 | 
			
		||||
            this.addOfflineDataDeletedWarning(result.warnings, database.name, syncEntryResult.discardError);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Sync done. Send event.
 | 
			
		||||
        CoreEvents.trigger(AddonModDataSyncProvider.AUTO_SYNCED, {
 | 
			
		||||
            dataId: database.id,
 | 
			
		||||
            entryId: synEntryResult.entryId,
 | 
			
		||||
            offlineEntryId: synEntryResult.offlineId,
 | 
			
		||||
            entryId: syncEntryResult.entryId,
 | 
			
		||||
            offlineEntryId: syncEntryResult.offlineId,
 | 
			
		||||
            warnings: result.warnings,
 | 
			
		||||
            deleted: synEntryResult.deleted,
 | 
			
		||||
            deleted: syncEntryResult.deleted,
 | 
			
		||||
        }, siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								src/addons/mod/glossary/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/addons/mod/glossary/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
// (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 { AddonModGlossaryIndexComponent } from './index/index';
 | 
			
		||||
import { AddonModGlossaryModePickerPopoverComponent } from './mode-picker/mode-picker';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
 | 
			
		||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModGlossaryIndexComponent,
 | 
			
		||||
        AddonModGlossaryModePickerPopoverComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreCourseComponentsModule,
 | 
			
		||||
        CoreSearchComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModGlossaryIndexComponent,
 | 
			
		||||
        AddonModGlossaryModePickerPopoverComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModGlossaryComponentsModule {}
 | 
			
		||||
@ -0,0 +1,107 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <ion-button *ngIf="glossary && glossary.browsemodes && glossary.browsemodes.length > 1" (click)="openModePicker($event)"
 | 
			
		||||
        [attr.aria-label]="'addon.mod_glossary.browsemode' | translate">
 | 
			
		||||
        <ion-icon name="fas-sort"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
    <ion-button *ngIf="glossary" (click)="toggleSearch()" [attr.aria-label]="'addon.mod_glossary.bysearch' | translate">
 | 
			
		||||
        <ion-icon name="fas-search"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
 | 
			
		||||
            [href]="externalUrl" iconAction="fas-external-link-alt">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"
 | 
			
		||||
            iconAction="far-newspaper" (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700"
 | 
			
		||||
            [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon"
 | 
			
		||||
            [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600"
 | 
			
		||||
            (action)="doRefresh(null, $event, true)" [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon"
 | 
			
		||||
            [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="canAdd" [priority]="550" [content]="'addon.mod_glossary.addentry' | translate"
 | 
			
		||||
            (action)="openNewEntry()" iconAction="fas-plus">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
</core-navbar-buttons>
 | 
			
		||||
 | 
			
		||||
<!-- Content. -->
 | 
			
		||||
<core-split-view>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <core-search-box *ngIf="isSearch" (onSubmit)="search($event)" [placeholder]="'addon.mod_glossary.searchquery' | translate"
 | 
			
		||||
        [autoFocus]="true" [lengthCheck]="2" (onClear)="toggleSearch()" searchArea="AddonModGlossary-{{module.id}}">
 | 
			
		||||
    </core-search-box>
 | 
			
		||||
 | 
			
		||||
    <core-loading [hideUntil]="entries.loaded" class="core-loading-center">
 | 
			
		||||
        <core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
 | 
			
		||||
            contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
 | 
			
		||||
        </core-course-module-description>
 | 
			
		||||
 | 
			
		||||
        <!-- Has offline data to be synchronized -->
 | 
			
		||||
        <ion-card class="core-warning-card" *ngIf="hasOffline || hasOfflineRatings">
 | 
			
		||||
            <ion-item>
 | 
			
		||||
                <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
 | 
			
		||||
                <ion-label>{{ 'core.hasdatatosync' | translate:{$a: moduleName} }}</ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <ion-list *ngIf="!isSearch && entries.offlineEntries.length > 0">
 | 
			
		||||
            <ion-item-divider>
 | 
			
		||||
                <ion-label>{{ 'addon.mod_glossary.entriestobesynced' | translate }}</ion-label>
 | 
			
		||||
            </ion-item-divider>
 | 
			
		||||
            <ion-item *ngFor="let entry of entries.offlineEntries" (click)="entries.select(entry)" detail="false"
 | 
			
		||||
                [class.core-selected-item]="entries.isSelected(entry)">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="glossary!.coursemodule"
 | 
			
		||||
                        [courseId]="courseId">
 | 
			
		||||
                    </core-format-text>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-list>
 | 
			
		||||
 | 
			
		||||
        <ion-list *ngIf="entries.onlineEntries.length > 0">
 | 
			
		||||
            <ng-container *ngFor="let entry of entries.onlineEntries; let index = index">
 | 
			
		||||
                <ion-item-divider *ngIf="getDivider && showDivider(entry, entries.onlineEntries[index - 1])">
 | 
			
		||||
                    {{ getDivider!(entry) }}
 | 
			
		||||
                </ion-item-divider>
 | 
			
		||||
 | 
			
		||||
                <ion-item (click)="entries.select(entry)" [class.core-selected-item]="entries.isSelected(entry)" detail="false">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="glossary!.coursemodule"
 | 
			
		||||
                            [courseId]="courseId">
 | 
			
		||||
                        </core-format-text>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
        </ion-list>
 | 
			
		||||
 | 
			
		||||
        <core-empty-box *ngIf="entries.empty" icon="fas-list" [message]="'addon.mod_glossary.noentriesfound' | translate">
 | 
			
		||||
        </core-empty-box>
 | 
			
		||||
 | 
			
		||||
        <core-infinite-loading [enabled]="!entries.completed" [error]="loadMoreError" (action)="loadMoreEntries($event)">
 | 
			
		||||
        </core-infinite-loading>
 | 
			
		||||
    </core-loading>
 | 
			
		||||
 | 
			
		||||
    <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAdd">
 | 
			
		||||
        <ion-fab-button (click)="openNewEntry()" [attr.aria-label]="'addon.mod_glossary.addentry' | translate">
 | 
			
		||||
            <ion-icon name="fas-plus"></ion-icon>
 | 
			
		||||
        </ion-fab-button>
 | 
			
		||||
    </ion-fab>
 | 
			
		||||
</core-split-view>
 | 
			
		||||
							
								
								
									
										644
									
								
								src/addons/mod/glossary/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										644
									
								
								src/addons/mod/glossary/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,644 @@
 | 
			
		||||
// (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 { ContextLevel } from '@/core/constants';
 | 
			
		||||
import { AfterViewInit, Component, OnDestroy, OnInit, Optional, ViewChild } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, Params } from '@angular/router';
 | 
			
		||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
 | 
			
		||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
 | 
			
		||||
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 { CoreRatingProvider } from '@features/rating/services/rating';
 | 
			
		||||
import { CoreRatingOffline } from '@features/rating/services/rating-offline';
 | 
			
		||||
import { CoreRatingSyncProvider } from '@features/rating/services/rating-sync';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { PopoverController, Translate } from '@singletons';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModGlossary,
 | 
			
		||||
    AddonModGlossaryEntry,
 | 
			
		||||
    AddonModGlossaryEntryWithCategory,
 | 
			
		||||
    AddonModGlossaryGetEntriesOptions,
 | 
			
		||||
    AddonModGlossaryGetEntriesWSResponse,
 | 
			
		||||
    AddonModGlossaryGlossary,
 | 
			
		||||
    AddonModGlossaryProvider,
 | 
			
		||||
} from '../../services/glossary';
 | 
			
		||||
import { AddonModGlossaryOffline, AddonModGlossaryOfflineEntry } from '../../services/glossary-offline';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModGlossaryAutoSyncData,
 | 
			
		||||
    AddonModGlossarySyncProvider,
 | 
			
		||||
    AddonModGlossarySyncResult,
 | 
			
		||||
} from '../../services/glossary-sync';
 | 
			
		||||
import { AddonModGlossaryPrefetchHandler } from '../../services/handlers/prefetch';
 | 
			
		||||
import { AddonModGlossaryModePickerPopoverComponent } from '../mode-picker/mode-picker';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component that displays a glossary entry page.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'addon-mod-glossary-index',
 | 
			
		||||
    templateUrl: 'addon-mod-glossary-index.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivityComponent
 | 
			
		||||
    implements OnInit, AfterViewInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
 | 
			
		||||
 | 
			
		||||
    component = AddonModGlossaryProvider.COMPONENT;
 | 
			
		||||
    moduleName = 'glossary';
 | 
			
		||||
 | 
			
		||||
    isSearch = false;
 | 
			
		||||
    canAdd = false;
 | 
			
		||||
    loadMoreError = false;
 | 
			
		||||
    loadingMessage?: string;
 | 
			
		||||
    entries: AddonModGlossaryEntriesManager;
 | 
			
		||||
    hasOfflineRatings = false;
 | 
			
		||||
    glossary?: AddonModGlossaryGlossary;
 | 
			
		||||
 | 
			
		||||
    protected syncEventName = AddonModGlossarySyncProvider.AUTO_SYNCED;
 | 
			
		||||
    protected fetchFunction?: (options?: AddonModGlossaryGetEntriesOptions) => AddonModGlossaryGetEntriesWSResponse;
 | 
			
		||||
    protected fetchInvalidate?: () => Promise<void>;
 | 
			
		||||
    protected addEntryObserver?: CoreEventObserver;
 | 
			
		||||
    protected fetchMode?: AddonModGlossaryFetchMode;
 | 
			
		||||
    protected viewMode?: string;
 | 
			
		||||
    protected fetchedEntriesCanLoadMore = false;
 | 
			
		||||
    protected fetchedEntries: AddonModGlossaryEntry[] = [];
 | 
			
		||||
    protected ratingOfflineObserver?: CoreEventObserver;
 | 
			
		||||
    protected ratingSyncObserver?: CoreEventObserver;
 | 
			
		||||
 | 
			
		||||
    getDivider?: (entry: AddonModGlossaryEntry) => string;
 | 
			
		||||
    showDivider: (entry: AddonModGlossaryEntry, previous?: AddonModGlossaryEntry) => boolean = () => false;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        route: ActivatedRoute,
 | 
			
		||||
        protected content?: IonContent,
 | 
			
		||||
        @Optional() courseContentsPage?: CoreCourseContentsPage,
 | 
			
		||||
    ) {
 | 
			
		||||
        super('AddonModGlossaryIndexComponent', content, courseContentsPage);
 | 
			
		||||
 | 
			
		||||
        this.entries = new AddonModGlossaryEntriesManager(
 | 
			
		||||
            route.component,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        super.ngOnInit();
 | 
			
		||||
 | 
			
		||||
        this.loadingMessage = Translate.instant('core.loading');
 | 
			
		||||
 | 
			
		||||
        // When an entry is added, we reload the data.
 | 
			
		||||
        this.addEntryObserver = CoreEvents.on(AddonModGlossaryProvider.ADD_ENTRY_EVENT, (data) => {
 | 
			
		||||
            if (this.glossary && this.glossary.id === data.glossaryId) {
 | 
			
		||||
                this.showLoadingAndRefresh(false);
 | 
			
		||||
 | 
			
		||||
                // Check completion since it could be configured to complete once the user adds a new entry.
 | 
			
		||||
                CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Listen for offline ratings saved and synced.
 | 
			
		||||
        this.ratingOfflineObserver = CoreEvents.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => {
 | 
			
		||||
            if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module'
 | 
			
		||||
                    && data.instanceId == this.glossary.coursemodule) {
 | 
			
		||||
                this.hasOfflineRatings = true;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        this.ratingSyncObserver = CoreEvents.on(CoreRatingSyncProvider.SYNCED_EVENT, (data) => {
 | 
			
		||||
            if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module'
 | 
			
		||||
                    && data.instanceId == this.glossary.coursemodule) {
 | 
			
		||||
                this.hasOfflineRatings = false;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngAfterViewInit(): Promise<void> {
 | 
			
		||||
        await this.loadContent(false, true);
 | 
			
		||||
 | 
			
		||||
        if (!this.glossary) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.entries.start(this.splitView);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await AddonModGlossary.logView(this.glossary.id, this.viewMode!, this.glossary.name);
 | 
			
		||||
 | 
			
		||||
            CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            this.glossary = await AddonModGlossary.getGlossary(this.courseId, this.module.id);
 | 
			
		||||
 | 
			
		||||
            this.description = this.glossary.intro || this.description;
 | 
			
		||||
            this.canAdd = (AddonModGlossary.isPluginEnabledForEditing() && !!this.glossary.canaddentry) || false;
 | 
			
		||||
 | 
			
		||||
            this.dataRetrieved.emit(this.glossary);
 | 
			
		||||
 | 
			
		||||
            if (!this.fetchMode) {
 | 
			
		||||
                this.switchMode('letter_all');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (sync) {
 | 
			
		||||
                // Try to synchronize the glossary.
 | 
			
		||||
                await this.syncActivity(showErrors);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const [hasOfflineRatings] = await Promise.all([
 | 
			
		||||
                CoreRatingOffline.hasRatings('mod_glossary', 'entry', ContextLevel.MODULE, this.glossary.coursemodule),
 | 
			
		||||
                this.fetchEntries(),
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            this.hasOfflineRatings = hasOfflineRatings;
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convenience function to fetch entries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param append True if fetched entries are appended to exsiting ones.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchEntries(append: boolean = false): Promise<void> {
 | 
			
		||||
        if (!this.fetchFunction) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.loadMoreError = false;
 | 
			
		||||
        const from = append ? this.entries.onlineEntries.length : 0;
 | 
			
		||||
 | 
			
		||||
        const result = await this.fetchFunction({
 | 
			
		||||
            from: from,
 | 
			
		||||
            cmId: this.module.id,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const hasMoreEntries = from + result.entries.length < result.count;
 | 
			
		||||
 | 
			
		||||
        if (append) {
 | 
			
		||||
            this.entries.setItems(this.entries.items.concat(result.entries), hasMoreEntries);
 | 
			
		||||
        } else {
 | 
			
		||||
            this.entries.setOnlineEntries(result.entries, hasMoreEntries);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Now get the ofline entries.
 | 
			
		||||
        // Check if there are responses stored in offline.
 | 
			
		||||
        const offlineEntries = await AddonModGlossaryOffline.getGlossaryNewEntries(this.glossary!.id);
 | 
			
		||||
 | 
			
		||||
        offlineEntries.sort((a, b) => a.concept.localeCompare(b.concept));
 | 
			
		||||
        this.hasOffline = !!offlineEntries.length;
 | 
			
		||||
        this.entries.setOfflineEntries(offlineEntries);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async invalidateContent(): Promise<void> {
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        if (this.fetchInvalidate) {
 | 
			
		||||
            promises.push(this.fetchInvalidate());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        promises.push(AddonModGlossary.invalidateCourseGlossaries(this.courseId));
 | 
			
		||||
 | 
			
		||||
        if (this.glossary) {
 | 
			
		||||
            promises.push(AddonModGlossary.invalidateCategories(this.glossary.id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs the sync of the activity.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected sync(): Promise<AddonModGlossarySyncResult> {
 | 
			
		||||
        return AddonModGlossaryPrefetchHandler.sync(this.module, this.courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if sync has succeed from result sync data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param result Data returned on the sync function.
 | 
			
		||||
     * @return Whether it succeed or not.
 | 
			
		||||
     */
 | 
			
		||||
    protected hasSyncSucceed(result: AddonModGlossarySyncResult): boolean {
 | 
			
		||||
        return result.updated;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Compares sync event data with current data to check if refresh content is needed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param syncEventData Data receiven on sync observer.
 | 
			
		||||
     * @return True if refresh is needed, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    protected isRefreshSyncNeeded(syncEventData: AddonModGlossaryAutoSyncData): boolean {
 | 
			
		||||
        return !!this.glossary && syncEventData.glossaryId == this.glossary.id &&
 | 
			
		||||
                syncEventData.userId == CoreSites.getCurrentSiteUserId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Change fetch mode.
 | 
			
		||||
     *
 | 
			
		||||
     * @param mode New mode.
 | 
			
		||||
     */
 | 
			
		||||
    protected switchMode(mode: AddonModGlossaryFetchMode): void {
 | 
			
		||||
        this.fetchMode = mode;
 | 
			
		||||
        this.isSearch = false;
 | 
			
		||||
 | 
			
		||||
        switch (mode) {
 | 
			
		||||
            case 'author_all':
 | 
			
		||||
                // Browse by author.
 | 
			
		||||
                this.viewMode = 'author';
 | 
			
		||||
                this.fetchFunction = AddonModGlossary.getEntriesByAuthor.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    'ALL',
 | 
			
		||||
                    'LASTNAME',
 | 
			
		||||
                    'ASC',
 | 
			
		||||
                );
 | 
			
		||||
                this.fetchInvalidate = AddonModGlossary.invalidateEntriesByAuthor.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    'ALL',
 | 
			
		||||
                    'LASTNAME',
 | 
			
		||||
                    'ASC',
 | 
			
		||||
                );
 | 
			
		||||
                this.getDivider = (entry) => entry.userfullname;
 | 
			
		||||
                this.showDivider = (entry, previous) => !previous || entry.userid != previous.userid;
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'cat_all':
 | 
			
		||||
                // Browse by category.
 | 
			
		||||
                this.viewMode = 'cat';
 | 
			
		||||
                this.fetchFunction = AddonModGlossary.getEntriesByCategory.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    AddonModGlossaryProvider.SHOW_ALL_CATEGORIES,
 | 
			
		||||
                );
 | 
			
		||||
                this.fetchInvalidate = AddonModGlossary.invalidateEntriesByCategory.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    AddonModGlossaryProvider.SHOW_ALL_CATEGORIES,
 | 
			
		||||
                );
 | 
			
		||||
                this.getDivider = (entry: AddonModGlossaryEntryWithCategory) => entry.categoryname || '';
 | 
			
		||||
                this.showDivider = (entry, previous) => !previous || this.getDivider!(entry) != this.getDivider!(previous);
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'newest_first':
 | 
			
		||||
                // Newest first.
 | 
			
		||||
                this.viewMode = 'date';
 | 
			
		||||
                this.fetchFunction = AddonModGlossary.getEntriesByDate.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    'CREATION',
 | 
			
		||||
                    'DESC',
 | 
			
		||||
                );
 | 
			
		||||
                this.fetchInvalidate = AddonModGlossary.invalidateEntriesByDate.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    'CREATION',
 | 
			
		||||
                    'DESC',
 | 
			
		||||
                );
 | 
			
		||||
                this.getDivider = undefined;
 | 
			
		||||
                this.showDivider = () => false;
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'recently_updated':
 | 
			
		||||
                // Recently updated.
 | 
			
		||||
                this.viewMode = 'date';
 | 
			
		||||
                this.fetchFunction = AddonModGlossary.getEntriesByDate.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    'UPDATE',
 | 
			
		||||
                    'DESC',
 | 
			
		||||
                );
 | 
			
		||||
                this.fetchInvalidate = AddonModGlossary.invalidateEntriesByDate.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    'UPDATE',
 | 
			
		||||
                    'DESC',
 | 
			
		||||
                );
 | 
			
		||||
                this.getDivider = undefined;
 | 
			
		||||
                this.showDivider = () => false;
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'letter_all':
 | 
			
		||||
            default:
 | 
			
		||||
                // Consider it is 'letter_all'.
 | 
			
		||||
                this.viewMode = 'letter';
 | 
			
		||||
                this.fetchMode = 'letter_all';
 | 
			
		||||
                this.fetchFunction = AddonModGlossary.getEntriesByLetter.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    'ALL',
 | 
			
		||||
                );
 | 
			
		||||
                this.fetchInvalidate = AddonModGlossary.invalidateEntriesByLetter.bind(
 | 
			
		||||
                    AddonModGlossary.instance,
 | 
			
		||||
                    this.glossary!.id,
 | 
			
		||||
                    'ALL',
 | 
			
		||||
                );
 | 
			
		||||
                this.getDivider = (entry) => {
 | 
			
		||||
                    // Try to get the first letter without HTML tags.
 | 
			
		||||
                    const noTags = CoreTextUtils.cleanTags(entry.concept);
 | 
			
		||||
 | 
			
		||||
                    return (noTags || entry.concept).substr(0, 1).toUpperCase();
 | 
			
		||||
                };
 | 
			
		||||
                this.showDivider = (entry, previous) => !previous || this.getDivider!(entry) != this.getDivider!(previous);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convenience function to load more entries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async loadMoreEntries(infiniteComplete?: () => void): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await this.fetchEntries(true);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            this.loadMoreError = true;
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentries', true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            infiniteComplete && infiniteComplete();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show the mode picker menu.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event Event.
 | 
			
		||||
     */
 | 
			
		||||
    async openModePicker(event: MouseEvent): Promise<void> {
 | 
			
		||||
        const popover = await PopoverController.create({
 | 
			
		||||
            component: AddonModGlossaryModePickerPopoverComponent,
 | 
			
		||||
            componentProps: {
 | 
			
		||||
                browseModes: this.glossary!.browsemodes,
 | 
			
		||||
                selectedMode: this.isSearch ? '' : this.fetchMode,
 | 
			
		||||
            },
 | 
			
		||||
            event,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        popover.present();
 | 
			
		||||
 | 
			
		||||
        const result = await popover.onDidDismiss<AddonModGlossaryFetchMode>();
 | 
			
		||||
 | 
			
		||||
        const mode = result.data;
 | 
			
		||||
        if (mode) {
 | 
			
		||||
            if (mode !== this.fetchMode) {
 | 
			
		||||
                this.changeFetchMode(mode);
 | 
			
		||||
            } else if (this.isSearch) {
 | 
			
		||||
                this.toggleSearch();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggles between search and fetch mode.
 | 
			
		||||
     */
 | 
			
		||||
    toggleSearch(): void {
 | 
			
		||||
        if (this.isSearch) {
 | 
			
		||||
            this.isSearch = false;
 | 
			
		||||
            this.entries.setOnlineEntries(this.fetchedEntries, this.fetchedEntriesCanLoadMore);
 | 
			
		||||
            this.switchMode(this.fetchMode!);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Search for entries. The fetch function will be set when searching.
 | 
			
		||||
            this.getDivider = undefined;
 | 
			
		||||
            this.showDivider = () => false;
 | 
			
		||||
            this.isSearch = true;
 | 
			
		||||
 | 
			
		||||
            this.fetchedEntries = this.entries.onlineEntries;
 | 
			
		||||
            this.fetchedEntriesCanLoadMore = !this.entries.completed;
 | 
			
		||||
            this.entries.setItems([], false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Change fetch mode.
 | 
			
		||||
     *
 | 
			
		||||
     * @param mode Mode.
 | 
			
		||||
     */
 | 
			
		||||
    changeFetchMode(mode: AddonModGlossaryFetchMode): void {
 | 
			
		||||
        this.isSearch = false;
 | 
			
		||||
        this.loadingMessage = Translate.instant('core.loading');
 | 
			
		||||
        this.content?.scrollToTop();
 | 
			
		||||
        this.switchMode(mode);
 | 
			
		||||
        this.loaded = false;
 | 
			
		||||
        this.loadContent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Opens new entry editor.
 | 
			
		||||
     */
 | 
			
		||||
    openNewEntry(): void {
 | 
			
		||||
        this.entries.select({ newEntry: true });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Search entries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param query Text entered on the search box.
 | 
			
		||||
     */
 | 
			
		||||
    search(query: string): void {
 | 
			
		||||
        this.loadingMessage = Translate.instant('core.searching');
 | 
			
		||||
        this.fetchFunction = AddonModGlossary.getEntriesBySearch.bind(
 | 
			
		||||
            AddonModGlossary.instance,
 | 
			
		||||
            this.glossary!.id,
 | 
			
		||||
            query,
 | 
			
		||||
            true,
 | 
			
		||||
            'CONCEPT',
 | 
			
		||||
            'ASC',
 | 
			
		||||
        );
 | 
			
		||||
        this.fetchInvalidate = AddonModGlossary.invalidateEntriesBySearch.bind(
 | 
			
		||||
            AddonModGlossary.instance,
 | 
			
		||||
            this.glossary!.id,
 | 
			
		||||
            query,
 | 
			
		||||
            true,
 | 
			
		||||
            'CONCEPT',
 | 
			
		||||
            'ASC',
 | 
			
		||||
        );
 | 
			
		||||
        this.loaded = false;
 | 
			
		||||
        this.loadContent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        super.ngOnDestroy();
 | 
			
		||||
 | 
			
		||||
        this.addEntryObserver?.off();
 | 
			
		||||
        this.ratingOfflineObserver?.off();
 | 
			
		||||
        this.ratingSyncObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type to select the new entry form.
 | 
			
		||||
 */
 | 
			
		||||
type NewEntryForm = { newEntry: true };
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of items that can be held by the entries manager.
 | 
			
		||||
 */
 | 
			
		||||
 type EntryItem = AddonModGlossaryEntry | AddonModGlossaryOfflineEntry | NewEntryForm;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Entries manager.
 | 
			
		||||
 */
 | 
			
		||||
class AddonModGlossaryEntriesManager extends CorePageItemsListManager<EntryItem> {
 | 
			
		||||
 | 
			
		||||
    onlineEntries: AddonModGlossaryEntry[] = [];
 | 
			
		||||
    offlineEntries: AddonModGlossaryOfflineEntry[] = [];
 | 
			
		||||
 | 
			
		||||
    constructor(pageComponent: unknown) {
 | 
			
		||||
        super(pageComponent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Type guard to infer NewEntryForm objects.
 | 
			
		||||
     *
 | 
			
		||||
     * @param entry Item to check.
 | 
			
		||||
     * @return Whether the item is a new entry form.
 | 
			
		||||
     */
 | 
			
		||||
    isNewEntryForm(entry: EntryItem): entry is NewEntryForm {
 | 
			
		||||
        return 'newEntry' in entry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Type guard to infer entry objects.
 | 
			
		||||
     *
 | 
			
		||||
     * @param entry Item to check.
 | 
			
		||||
     * @return Whether the item is an offline entry.
 | 
			
		||||
     */
 | 
			
		||||
    isOfflineEntry(entry: EntryItem): entry is AddonModGlossaryOfflineEntry {
 | 
			
		||||
        return !this.isNewEntryForm(entry) && !this.isOnlineEntry(entry);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Type guard to infer entry objects.
 | 
			
		||||
     *
 | 
			
		||||
     * @param entry Item to check.
 | 
			
		||||
     * @return Whether the item is an offline entry.
 | 
			
		||||
     */
 | 
			
		||||
    isOnlineEntry(entry: EntryItem): entry is AddonModGlossaryEntry {
 | 
			
		||||
        return 'id' in entry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update online entries items.
 | 
			
		||||
     *
 | 
			
		||||
     * @param onlineEntries Online entries.
 | 
			
		||||
     */
 | 
			
		||||
    setOnlineEntries(onlineEntries: AddonModGlossaryEntry[], hasMoreItems: boolean = false): void {
 | 
			
		||||
        this.setItems((<EntryItem[]> this.offlineEntries).concat(onlineEntries), hasMoreItems);
 | 
			
		||||
        this.onlineEntries.concat(onlineEntries);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update offline entries items.
 | 
			
		||||
     *
 | 
			
		||||
     * @param offlineEntries Offline entries.
 | 
			
		||||
     */
 | 
			
		||||
    setOfflineEntries(offlineEntries: AddonModGlossaryOfflineEntry[]): void {
 | 
			
		||||
        this.setItems((<EntryItem[]> offlineEntries).concat(this.onlineEntries), this.hasMoreItems);
 | 
			
		||||
        this.offlineEntries = offlineEntries;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    setItems(entries: EntryItem[], hasMoreItems: boolean = false): void {
 | 
			
		||||
        super.setItems(entries, hasMoreItems);
 | 
			
		||||
 | 
			
		||||
        this.onlineEntries = [];
 | 
			
		||||
        this.offlineEntries = [];
 | 
			
		||||
        this.items.forEach(entry => {
 | 
			
		||||
            if (this.isOfflineEntry(entry)) {
 | 
			
		||||
                this.offlineEntries.push(entry);
 | 
			
		||||
            } else if (this.isOnlineEntry(entry)) {
 | 
			
		||||
                this.onlineEntries.push(entry);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    resetItems(): void {
 | 
			
		||||
        super.resetItems();
 | 
			
		||||
        this.onlineEntries = [];
 | 
			
		||||
        this.offlineEntries = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected getItemPath(entry: EntryItem): string {
 | 
			
		||||
        if (this.isOnlineEntry(entry)) {
 | 
			
		||||
            return `entry/${entry.id}`;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.isOfflineEntry(entry)) {
 | 
			
		||||
            return `edit/${entry.timecreated}`;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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';
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
<ion-radio-group [(ngModel)]="selectedMode" (ionChange)="modePicked()">
 | 
			
		||||
    <ion-item class="ion-text-wrap" *ngFor="let mode of modes">
 | 
			
		||||
        <ion-label>{{ mode.langkey | translate }}</ion-label>
 | 
			
		||||
        <ion-radio slot="end" [value]="mode.key"></ion-radio>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
</ion-radio-group>
 | 
			
		||||
@ -0,0 +1,64 @@
 | 
			
		||||
// (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, Input, OnInit } from '@angular/core';
 | 
			
		||||
import { PopoverController } from '@singletons';
 | 
			
		||||
import { AddonModGlossaryFetchMode } from '../index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to display the mode picker.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'addon-mod-glossary-mode-picker-popover',
 | 
			
		||||
    templateUrl: 'addon-mod-glossary-mode-picker.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModGlossaryModePickerPopoverComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    @Input() browseModes: string[] = [];
 | 
			
		||||
    @Input() selectedMode = '';
 | 
			
		||||
 | 
			
		||||
    modes: { key: AddonModGlossaryFetchMode; langkey: string }[] = [];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit(): void {
 | 
			
		||||
        this.browseModes.forEach((mode) => {
 | 
			
		||||
            switch (mode) {
 | 
			
		||||
                case 'letter' :
 | 
			
		||||
                    this.modes.push({ key: 'letter_all', langkey: 'addon.mod_glossary.byalphabet' });
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'cat' :
 | 
			
		||||
                    this.modes.push({ key: 'cat_all', langkey: 'addon.mod_glossary.bycategory' });
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'date' :
 | 
			
		||||
                    this.modes.push({ key: 'newest_first', langkey: 'addon.mod_glossary.bynewestfirst' });
 | 
			
		||||
                    this.modes.push({ key: 'recently_updated', langkey: 'addon.mod_glossary.byrecentlyupdated' });
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'author' :
 | 
			
		||||
                    this.modes.push({ key: 'author_all', langkey: 'addon.mod_glossary.byauthor' });
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function called when a mode is clicked.
 | 
			
		||||
     */
 | 
			
		||||
    modePicked(): void {
 | 
			
		||||
        PopoverController.dismiss(this.selectedMode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								src/addons/mod/glossary/glossary-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/addons/mod/glossary/glossary-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
			
		||||
// (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 { 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 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({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        AddonModGlossaryComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModGlossaryIndexPage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModGlossaryLazyModule {}
 | 
			
		||||
							
								
								
									
										89
									
								
								src/addons/mod/glossary/glossary.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/addons/mod/glossary/glossary.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
// (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 { APP_INITIALIZER, NgModule, Type } from '@angular/core';
 | 
			
		||||
import { Routes } from '@angular/router';
 | 
			
		||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
 | 
			
		||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
 | 
			
		||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
 | 
			
		||||
import { CoreCronDelegate } from '@services/cron';
 | 
			
		||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
			
		||||
import { AddonModGlossaryComponentsModule } from './components/components.module';
 | 
			
		||||
import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/database/glossary';
 | 
			
		||||
import { AddonModGlossaryProvider } from './services/glossary';
 | 
			
		||||
import { AddonModGlossaryHelperProvider } from './services/glossary-helper';
 | 
			
		||||
import { AddonModGlossaryOfflineProvider } from './services/glossary-offline';
 | 
			
		||||
import { AddonModGlossarySyncProvider } from './services/glossary-sync';
 | 
			
		||||
import { AddonModGlossaryEditLinkHandler } from './services/handlers/edit-link';
 | 
			
		||||
import { AddonModGlossaryEntryLinkHandler } from './services/handlers/entry-link';
 | 
			
		||||
import { AddonModGlossaryIndexLinkHandler } from './services/handlers/index-link';
 | 
			
		||||
import { AddonModGlossaryListLinkHandler } from './services/handlers/list-link';
 | 
			
		||||
import { AddonModGlossaryModuleHandler, AddonModGlossaryModuleHandlerService } from './services/handlers/module';
 | 
			
		||||
import { AddonModGlossaryPrefetchHandler } from './services/handlers/prefetch';
 | 
			
		||||
import { AddonModGlossarySyncCronHandler } from './services/handlers/sync-cron';
 | 
			
		||||
import { AddonModGlossaryTagAreaHandler } from './services/handlers/tag-area';
 | 
			
		||||
 | 
			
		||||
export const ADDON_MOD_GLOSSARY_SERVICES: Type<unknown>[] = [
 | 
			
		||||
    AddonModGlossaryProvider,
 | 
			
		||||
    AddonModGlossaryOfflineProvider,
 | 
			
		||||
    AddonModGlossarySyncProvider,
 | 
			
		||||
    AddonModGlossaryHelperProvider,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
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),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(mainMenuRoutes),
 | 
			
		||||
        AddonModGlossaryComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: CORE_SITE_SCHEMAS,
 | 
			
		||||
            useValue: [SITE_SCHEMA, OFFLINE_SITE_SCHEMA],
 | 
			
		||||
            multi: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            provide: APP_INITIALIZER,
 | 
			
		||||
            multi: true,
 | 
			
		||||
            deps: [],
 | 
			
		||||
            useFactory: () => () => {
 | 
			
		||||
                CoreCourseModuleDelegate.registerHandler(AddonModGlossaryModuleHandler.instance);
 | 
			
		||||
                CoreCourseModulePrefetchDelegate.registerHandler(AddonModGlossaryPrefetchHandler.instance);
 | 
			
		||||
                CoreCronDelegate.register(AddonModGlossarySyncCronHandler.instance);
 | 
			
		||||
                CoreContentLinksDelegate.registerHandler(AddonModGlossaryIndexLinkHandler.instance);
 | 
			
		||||
                CoreContentLinksDelegate.registerHandler(AddonModGlossaryListLinkHandler.instance);
 | 
			
		||||
                CoreContentLinksDelegate.registerHandler(AddonModGlossaryEditLinkHandler.instance);
 | 
			
		||||
                CoreContentLinksDelegate.registerHandler(AddonModGlossaryEntryLinkHandler.instance);
 | 
			
		||||
                CoreTagAreaDelegate.registerHandler(AddonModGlossaryTagAreaHandler.instance);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModGlossaryModule {}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/addons/mod/glossary/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/addons/mod/glossary/lang.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
{
 | 
			
		||||
    "addentry": "Add a new entry",
 | 
			
		||||
    "aliases": "Keyword(s)",
 | 
			
		||||
    "attachment": "Attachment",
 | 
			
		||||
    "browsemode": "Browse entries",
 | 
			
		||||
    "byalphabet": "Alphabetically",
 | 
			
		||||
    "byauthor": "Group by author",
 | 
			
		||||
    "bycategory": "Group by category",
 | 
			
		||||
    "bynewestfirst": "Newest first",
 | 
			
		||||
    "byrecentlyupdated": "Recently updated",
 | 
			
		||||
    "bysearch": "Search",
 | 
			
		||||
    "cannoteditentry": "Cannot edit entry",
 | 
			
		||||
    "casesensitive": "This entry is case sensitive",
 | 
			
		||||
    "categories": "Categories",
 | 
			
		||||
    "concept": "Concept",
 | 
			
		||||
    "definition": "Definition",
 | 
			
		||||
    "entriestobesynced": "Entries to be synced",
 | 
			
		||||
    "entrypendingapproval": "This entry is pending approval.",
 | 
			
		||||
    "entryusedynalink": "This entry should be automatically linked",
 | 
			
		||||
    "errconceptalreadyexists": "This concept already exists. No duplicates allowed in this glossary.",
 | 
			
		||||
    "errorloadingentries": "An error occurred while loading entries.",
 | 
			
		||||
    "errorloadingentry": "An error occurred while loading the entry.",
 | 
			
		||||
    "errorloadingglossary": "An error occurred while loading the glossary.",
 | 
			
		||||
    "fillfields": "Concept and definition are mandatory fields.",
 | 
			
		||||
    "fullmatch": "Match whole words only",
 | 
			
		||||
    "linking": "Auto-linking",
 | 
			
		||||
    "modulenameplural": "Glossaries",
 | 
			
		||||
    "noentriesfound": "No entries were found.",
 | 
			
		||||
    "searchquery": "Search query",
 | 
			
		||||
    "tagarea_glossary_entries": "Glossary entries"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								src/addons/mod/glossary/pages/edit/edit.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/addons/mod/glossary/pages/edit/edit.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <ion-title *ngIf="glossary">
 | 
			
		||||
            <core-format-text [text]="glossary.name" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
 | 
			
		||||
            </core-format-text>
 | 
			
		||||
        </ion-title>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <core-loading [hideUntil]="loaded">
 | 
			
		||||
        <form #editFormEl *ngIf="glossary">
 | 
			
		||||
            <ion-item>
 | 
			
		||||
                <ion-label position="stacked">{{ 'addon.mod_glossary.concept' | translate }}</ion-label>
 | 
			
		||||
                <ion-input type="text" [placeholder]="'addon.mod_glossary.concept' | translate" [(ngModel)]="entry.concept"
 | 
			
		||||
                    name="concept">
 | 
			
		||||
                </ion-input>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item>
 | 
			
		||||
                <ion-label position="stacked">{{ 'addon.mod_glossary.definition' | translate }}</ion-label>
 | 
			
		||||
                <core-rich-text-editor [control]="definitionControl" (contentChanged)="onDefinitionChange($event)"
 | 
			
		||||
                    [placeholder]="'addon.mod_glossary.definition' | translate" name="addon_mod_glossary_edit"
 | 
			
		||||
                    [component]="component" [componentId]="cmId" [autoSave]="true" contextLevel="module"
 | 
			
		||||
                    [contextInstanceId]="cmId" elementId="definition_editor" [draftExtraParams]="editorExtraParams">
 | 
			
		||||
                </core-rich-text-editor>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item *ngIf="categories.length > 0">
 | 
			
		||||
                <ion-label position="stacked" id="addon-mod-glossary-categories-label">
 | 
			
		||||
                    {{ 'addon.mod_glossary.categories' | translate }}
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-select [(ngModel)]="options.categories" multiple="true" aria-labelledby="addon-mod-glossary-categories-label"
 | 
			
		||||
                    interface="action-sheet" [placeholder]="'addon.mod_glossary.categories' | translate" name="categories">
 | 
			
		||||
                    <ion-select-option *ngFor="let category of categories" [value]="category.id">
 | 
			
		||||
                        {{ category.name }}
 | 
			
		||||
                    </ion-select-option>
 | 
			
		||||
                </ion-select>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item>
 | 
			
		||||
                <ion-label position="stacked" id="addon-mod-glossary-aliases-label">
 | 
			
		||||
                    {{ 'addon.mod_glossary.aliases' | translate }}
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-textarea [(ngModel)]="options.aliases" rows="1" core-auto-rows
 | 
			
		||||
                    aria-labelledby="addon-mod-glossary-aliases-label" name="aliases">
 | 
			
		||||
                </ion-textarea>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item-divider>
 | 
			
		||||
                <ion-label>{{ 'addon.mod_glossary.attachment' | translate }}</ion-label>
 | 
			
		||||
            </ion-item-divider>
 | 
			
		||||
            <core-attachments [files]="attachments" [component]="component" [componentId]="glossary.coursemodule"
 | 
			
		||||
                [allowOffline]="true">
 | 
			
		||||
            </core-attachments>
 | 
			
		||||
            <ng-container *ngIf="glossary.usedynalink">
 | 
			
		||||
                <ion-item-divider>
 | 
			
		||||
                    <ion-label>{{ 'addon.mod_glossary.linking' | translate }}</ion-label>
 | 
			
		||||
                </ion-item-divider>
 | 
			
		||||
                <ion-item class="ion-text-wrap">
 | 
			
		||||
                    <ion-label>{{ 'addon.mod_glossary.entryusedynalink' | translate }}</ion-label>
 | 
			
		||||
                    <ion-toggle [(ngModel)]="options.usedynalink" name="usedynalink"></ion-toggle>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <ion-item class="ion-text-wrap">
 | 
			
		||||
                    <ion-label>{{ 'addon.mod_glossary.casesensitive' | translate }}</ion-label>
 | 
			
		||||
                    <ion-toggle [disabled]="!options.usedynalink" [(ngModel)]="options.casesensitive" name="casesensitive">
 | 
			
		||||
                    </ion-toggle>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <ion-item class="ion-text-wrap">
 | 
			
		||||
                    <ion-label>{{ 'addon.mod_glossary.fullmatch' | translate }}</ion-label>
 | 
			
		||||
                    <ion-toggle [disabled]="!options.usedynalink" [(ngModel)]="options.fullmatch" name="fullmatch"></ion-toggle>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
            <ion-button class="ion-margin" expand="block" [disabled]="!entry.concept || !entry.definition" (click)="save()">
 | 
			
		||||
                {{ 'core.save' | translate }}
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </form>
 | 
			
		||||
    </core-loading>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										38
									
								
								src/addons/mod/glossary/pages/edit/edit.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/addons/mod/glossary/pages/edit/edit.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -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 {}
 | 
			
		||||
							
								
								
									
										370
									
								
								src/addons/mod/glossary/pages/edit/edit.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										370
									
								
								src/addons/mod/glossary/pages/edit/edit.ts
									
									
									
									
									
										Normal file
									
								
							@ -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<string, unknown> = {};
 | 
			
		||||
    entry: AddonModGlossaryNewEntry = {
 | 
			
		||||
        concept: '',
 | 
			
		||||
        definition: '',
 | 
			
		||||
        timecreated: 0,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    options = {
 | 
			
		||||
        categories: <string[]> [],
 | 
			
		||||
        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<string>('concept')!;
 | 
			
		||||
        this.editorExtraParams.timecreated = this.timecreated;
 | 
			
		||||
 | 
			
		||||
        this.fetchData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch required data.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchData(): Promise<void> {
 | 
			
		||||
        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<void> {
 | 
			
		||||
        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 && (<string> entry.options.categories).split(',')) || [];
 | 
			
		||||
            this.options.aliases = <string> 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<boolean> {
 | 
			
		||||
        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<void> {
 | 
			
		||||
        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<string, AddonModGlossaryEntryOption> = {
 | 
			
		||||
                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,
 | 
			
		||||
                    <CoreFileUploaderStoreFilesResult> 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,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								src/addons/mod/glossary/pages/entry/entry.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/addons/mod/glossary/pages/entry/entry.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <ion-title *ngIf="entry">
 | 
			
		||||
            <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="componentId" [courseId]="courseId">
 | 
			
		||||
            </core-format-text>
 | 
			
		||||
            </ion-title>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <core-loading [hideUntil]="loaded">
 | 
			
		||||
        <ng-container *ngIf="entry && loaded">
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngIf="showAuthor">
 | 
			
		||||
                <core-user-avatar [user]="entry" slot="start"></core-user-avatar>
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <h2>
 | 
			
		||||
                        <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="componentId"
 | 
			
		||||
                            [courseId]="courseId">
 | 
			
		||||
                        </core-format-text>
 | 
			
		||||
                    </h2>
 | 
			
		||||
                    <p>{{ entry.userfullname }}</p>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-note slot="end" *ngIf="showDate">{{ entry.timemodified | coreDateDayOrTime }}</ion-note>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngIf="!showAuthor">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <h2>
 | 
			
		||||
                        <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="componentId">
 | 
			
		||||
                        </core-format-text>
 | 
			
		||||
                    </h2>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-note slot="end" *ngIf="showDate">{{ entry.timemodified | coreDateDayOrTime }}</ion-note>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item class="ion-text-wrap">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <core-format-text [component]="component" [componentId]="componentId" [text]="entry.definition"
 | 
			
		||||
                        contextLevel="module" [contextInstanceId]="componentId" [courseId]="courseId">
 | 
			
		||||
                    </core-format-text>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <div *ngIf="entry.attachment" lines="none">
 | 
			
		||||
                <core-file *ngFor="let file of entry.attachments" [file]="file" [component]="component"
 | 
			
		||||
                    [componentId]="componentId">
 | 
			
		||||
                </core-file>
 | 
			
		||||
            </div>
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngIf="tagsEnabled && entry && entry.tags && entry.tags.length > 0">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <div slot="start">{{ 'core.tag.tags' | translate }}:</div>
 | 
			
		||||
                    <core-tag-list [tags]="entry.tags"></core-tag-list>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngIf="!entry.approved">
 | 
			
		||||
                <ion-label><p><em>{{ 'addon.mod_glossary.entrypendingapproval' | translate }}</em></p></ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item *ngIf="glossary && glossary.allowcomments && entry && entry.id > 0 && commentsEnabled">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <core-comments contextLevel="module" [instanceId]="glossary.coursemodule" component="mod_glossary"
 | 
			
		||||
                        [itemId]="entry.id" area="glossary_entry" [courseId]="glossary.course">
 | 
			
		||||
                    </core-comments>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <core-rating-rate *ngIf="glossary && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module"
 | 
			
		||||
                [instanceId]="glossary.coursemodule" [itemId]="entry.id" [itemSetId]="0" [courseId]="glossary.course"
 | 
			
		||||
                [aggregateMethod]="glossary.assessed" [scaleId]="glossary.scale" [userId]="entry.userid"
 | 
			
		||||
                (onUpdate)="ratingUpdated()">
 | 
			
		||||
            </core-rating-rate>
 | 
			
		||||
            <core-rating-aggregate *ngIf="glossary && ratingInfo" [ratingInfo]="ratingInfo" contextLevel="module"
 | 
			
		||||
                [instanceId]="glossary.coursemodule" [itemId]="entry.id" [courseId]="glossary.course"
 | 
			
		||||
                [aggregateMethod]="glossary.assessed" [scaleId]="glossary.scale">
 | 
			
		||||
            </core-rating-aggregate>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
 | 
			
		||||
        <ion-card *ngIf="!entry" class="core-warning-card">
 | 
			
		||||
            <ion-item>
 | 
			
		||||
                <ion-label>{{ 'addon.mod_glossary.errorloadingentry' | translate }}</ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
    </core-loading>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										40
									
								
								src/addons/mod/glossary/pages/entry/entry.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/addons/mod/glossary/pages/entry/entry.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -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 {}
 | 
			
		||||
							
								
								
									
										146
									
								
								src/addons/mod/glossary/pages/entry/entry.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/addons/mod/glossary/pages/entry/entry.ts
									
									
									
									
									
										Normal file
									
								
							@ -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<void> {
 | 
			
		||||
        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<void> {
 | 
			
		||||
        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<void> {
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/addons/mod/glossary/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/addons/mod/glossary/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <ion-title>
 | 
			
		||||
            <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
 | 
			
		||||
            </core-format-text>
 | 
			
		||||
        </ion-title>
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <!-- The buttons defined by the component will be added in here. -->
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <addon-mod-glossary-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)">
 | 
			
		||||
    </addon-mod-glossary-index>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										30
									
								
								src/addons/mod/glossary/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/addons/mod/glossary/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
// (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, ViewChild } from '@angular/core';
 | 
			
		||||
import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
 | 
			
		||||
import { AddonModGlossaryIndexComponent } from '../../components/index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a glossary.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-mod-glossary-index',
 | 
			
		||||
    templateUrl: 'index.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModGlossaryIndexPage extends CoreCourseModuleMainActivityPage<AddonModGlossaryIndexComponent> {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(AddonModGlossaryIndexComponent) activityComponent?: AddonModGlossaryIndexComponent;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										121
									
								
								src/addons/mod/glossary/services/database/glossary.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/addons/mod/glossary/services/database/glossary.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,121 @@
 | 
			
		||||
// (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 { CoreSiteSchema } from '@services/sites';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Database variables for AddonModGlossaryProvider.
 | 
			
		||||
 */
 | 
			
		||||
export const ENTRIES_TABLE_NAME = 'addon_mod_glossary_entry_glossaryid';
 | 
			
		||||
export const SITE_SCHEMA: CoreSiteSchema = {
 | 
			
		||||
    name: 'AddonModGlossaryProvider',
 | 
			
		||||
    version: 1,
 | 
			
		||||
    tables: [
 | 
			
		||||
        {
 | 
			
		||||
            name: ENTRIES_TABLE_NAME,
 | 
			
		||||
            columns: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'entryid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                    primaryKey: true,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'glossaryid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'pagefrom',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Database variables for AddonModGlossaryOfflineProvider.
 | 
			
		||||
 */
 | 
			
		||||
export const OFFLINE_ENTRIES_TABLE_NAME = 'addon_mod_glossary_entrues';
 | 
			
		||||
export const OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
 | 
			
		||||
    name: 'AddonModGlossaryOfflineProvider',
 | 
			
		||||
    version: 1,
 | 
			
		||||
    tables: [
 | 
			
		||||
        {
 | 
			
		||||
            name: OFFLINE_ENTRIES_TABLE_NAME,
 | 
			
		||||
            columns: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'glossaryid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'courseid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'concept',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'definition',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'definitionformat',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'userid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'timecreated',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'options',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'attachments',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            primaryKeys: ['glossaryid', 'concept', 'timecreated'],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Glossary entry to get glossaryid from entryid.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModGlossaryEntryDBRecord = {
 | 
			
		||||
    entryid: number;
 | 
			
		||||
    glossaryid: number;
 | 
			
		||||
    pagefrom: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Glossary offline entry.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModGlossaryOfflineEntryDBRecord = {
 | 
			
		||||
    glossaryid: number;
 | 
			
		||||
    courseid: number;
 | 
			
		||||
    concept: string;
 | 
			
		||||
    definition: string;
 | 
			
		||||
    definitionformat: string;
 | 
			
		||||
    userid: number;
 | 
			
		||||
    timecreated: number;
 | 
			
		||||
    options: string;
 | 
			
		||||
    attachments: string;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										112
									
								
								src/addons/mod/glossary/services/glossary-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/addons/mod/glossary/services/glossary-helper.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
			
		||||
// (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 { Injectable } from '@angular/core';
 | 
			
		||||
import { FileEntry } from '@ionic-native/file/ngx';
 | 
			
		||||
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
			
		||||
import { CoreFile } from '@services/file';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { AddonModGlossaryOffline } from './glossary-offline';
 | 
			
		||||
import { AddonModGlossaryNewEntry, AddonModGlossaryNewEntryWithFiles } from './glossary';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreWSExternalFile } from '@services/ws';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper to gather some common functions for glossary.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossaryHelperProvider {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete stored attachment files for a new entry.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param entryName The name of the entry.
 | 
			
		||||
     * @param timeCreated The time the entry was created.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when deleted.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteStoredFiles(glossaryId: number, entryName: string, timeCreated: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const folderPath = await AddonModGlossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId);
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.ignoreErrors(CoreFile.removeDir(folderPath));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a list of stored attachment files for a new entry. See AddonModGlossaryHelperProvider#storeFiles.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId lossary ID.
 | 
			
		||||
     * @param entryName The name of the entry.
 | 
			
		||||
     * @param timeCreated The time the entry was created.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with the files.
 | 
			
		||||
     */
 | 
			
		||||
    async getStoredFiles(glossaryId: number, entryName: string, timeCreated: number, siteId?: string): Promise<FileEntry[]> {
 | 
			
		||||
        const folderPath = await AddonModGlossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId);
 | 
			
		||||
 | 
			
		||||
        return CoreFileUploader.getStoredFiles(folderPath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the data of an entry has changed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param entry Current data.
 | 
			
		||||
     * @param files Files attached.
 | 
			
		||||
     * @param original Original content.
 | 
			
		||||
     * @return True if data has changed, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    hasEntryDataChanged(
 | 
			
		||||
        entry: AddonModGlossaryNewEntry,
 | 
			
		||||
        files: (CoreWSExternalFile | FileEntry)[],
 | 
			
		||||
        original?: AddonModGlossaryNewEntryWithFiles,
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        if (!original || typeof original.concept == 'undefined') {
 | 
			
		||||
            // There is no original data.
 | 
			
		||||
            return !!(entry.definition || entry.concept || files.length > 0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (original.definition != entry.definition || original.concept != entry.concept) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return CoreFileUploader.areFileListDifferent(files, original.files);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a list of files (either online files or local files), store the local files in a local folder
 | 
			
		||||
     * to be submitted later.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param entryName The name of the entry.
 | 
			
		||||
     * @param timeCreated The time the entry was created.
 | 
			
		||||
     * @param files List of files.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if success, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async storeFiles(
 | 
			
		||||
        glossaryId: number,
 | 
			
		||||
        entryName: string,
 | 
			
		||||
        timeCreated: number,
 | 
			
		||||
        files: (CoreWSExternalFile | FileEntry)[],
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreFileUploaderStoreFilesResult> {
 | 
			
		||||
        // Get the folder where to store the files.
 | 
			
		||||
        const folderPath = await AddonModGlossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId);
 | 
			
		||||
 | 
			
		||||
        return CoreFileUploader.storeFilesToUpload(folderPath, files);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossaryHelper = makeSingleton(AddonModGlossaryHelperProvider);
 | 
			
		||||
							
								
								
									
										258
									
								
								src/addons/mod/glossary/services/glossary-offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								src/addons/mod/glossary/services/glossary-offline.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,258 @@
 | 
			
		||||
// (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 { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
 | 
			
		||||
import { CoreFile } from '@services/file';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModGlossaryOfflineEntryDBRecord, OFFLINE_ENTRIES_TABLE_NAME } from './database/glossary';
 | 
			
		||||
import { AddonModGlossaryDiscardedEntry, AddonModGlossaryEntryOption } from './glossary';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to handle offline glossary.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossaryOfflineProvider {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a new entry.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param concept Glossary entry concept.
 | 
			
		||||
     * @param timeCreated The time the entry was created.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if deleted, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteNewEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
 | 
			
		||||
            glossaryid: glossaryId,
 | 
			
		||||
            concept: concept,
 | 
			
		||||
            timecreated: timeCreated,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await site.getDb().deleteRecords(OFFLINE_ENTRIES_TABLE_NAME, conditions);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all the stored new entries from all the glossaries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with entries.
 | 
			
		||||
     */
 | 
			
		||||
    async getAllNewEntries(siteId?: string): Promise<AddonModGlossaryOfflineEntry[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const records = await site.getDb().getRecords<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME);
 | 
			
		||||
 | 
			
		||||
        return records.map(record => this.parseRecord(record));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a stored new entry.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param concept Glossary entry concept.
 | 
			
		||||
     * @param timeCreated The time the entry was created.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with entry.
 | 
			
		||||
     */
 | 
			
		||||
    async getNewEntry(
 | 
			
		||||
        glossaryId: number,
 | 
			
		||||
        concept: string,
 | 
			
		||||
        timeCreated: number,
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<AddonModGlossaryOfflineEntry> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
 | 
			
		||||
            glossaryid: glossaryId,
 | 
			
		||||
            concept: concept,
 | 
			
		||||
            timecreated: timeCreated,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const record = await site.getDb().getRecord<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME, conditions);
 | 
			
		||||
 | 
			
		||||
        return this.parseRecord(record);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all the stored add entry data from a certain glossary.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @param userId User the entries belong to. If not defined, current user in site.
 | 
			
		||||
     * @return Promise resolved with entries.
 | 
			
		||||
     */
 | 
			
		||||
    async getGlossaryNewEntries(glossaryId: number, siteId?: string, userId?: number): Promise<AddonModGlossaryOfflineEntry[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
 | 
			
		||||
            glossaryid: glossaryId,
 | 
			
		||||
            userid: userId || site.getUserId(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const records = await site.getDb().getRecords<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME, conditions);
 | 
			
		||||
 | 
			
		||||
        return records.map(record => this.parseRecord(record));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if a concept is used offline.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param concept Concept to check.
 | 
			
		||||
     * @param timeCreated Time of the entry we are editing.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with true if concept is found, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async isConceptUsed(glossaryId: number, concept: string, timeCreated?: number, siteId?: string): Promise<boolean> {
 | 
			
		||||
        try {
 | 
			
		||||
            const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
            const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
 | 
			
		||||
                glossaryid: glossaryId,
 | 
			
		||||
                concept: concept,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            const entries =
 | 
			
		||||
                await site.getDb().getRecords<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME, conditions);
 | 
			
		||||
 | 
			
		||||
            if (!entries.length) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (entries.length > 1 || !timeCreated) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // If there's only one entry, check that is not the one we are editing.
 | 
			
		||||
            return CoreUtils.promiseFails(this.getNewEntry(glossaryId, concept, timeCreated, siteId));
 | 
			
		||||
        } catch {
 | 
			
		||||
            // No offline data found, return false.
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save a new entry to be sent later.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param concept Glossary entry concept.
 | 
			
		||||
     * @param definition Glossary entry concept definition.
 | 
			
		||||
     * @param courseId Course ID of the glossary.
 | 
			
		||||
     * @param options Options for the entry.
 | 
			
		||||
     * @param attachments Result of CoreFileUploaderProvider#storeFilesToUpload for attachments.
 | 
			
		||||
     * @param timeCreated The time the entry was created. If not defined, current time.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @param userId User the entry belong to. If not defined, current user in site.
 | 
			
		||||
     * @param discardEntry The entry provided will be discarded if found.
 | 
			
		||||
     * @return Promise resolved if stored, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async addNewEntry(
 | 
			
		||||
        glossaryId: number,
 | 
			
		||||
        concept: string,
 | 
			
		||||
        definition: string,
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        options?: Record<string, AddonModGlossaryEntryOption>,
 | 
			
		||||
        attachments?: CoreFileUploaderStoreFilesResult,
 | 
			
		||||
        timeCreated?: number,
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
        userId?: number,
 | 
			
		||||
        discardEntry?: AddonModGlossaryDiscardedEntry,
 | 
			
		||||
    ): Promise<false> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const entry: AddonModGlossaryOfflineEntryDBRecord = {
 | 
			
		||||
            glossaryid: glossaryId,
 | 
			
		||||
            courseid: courseId,
 | 
			
		||||
            concept: concept,
 | 
			
		||||
            definition: definition,
 | 
			
		||||
            definitionformat: 'html',
 | 
			
		||||
            options: JSON.stringify(options || {}),
 | 
			
		||||
            attachments: JSON.stringify(attachments),
 | 
			
		||||
            userid: userId || site.getUserId(),
 | 
			
		||||
            timecreated: timeCreated || Date.now(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // If editing an offline entry, delete previous first.
 | 
			
		||||
        if (discardEntry) {
 | 
			
		||||
            await this.deleteNewEntry(glossaryId, discardEntry.concept, discardEntry.timecreated, site.getId());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await site.getDb().insertRecord(OFFLINE_ENTRIES_TABLE_NAME, entry);
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the path to the folder where to store files for offline attachments in a glossary.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with the path.
 | 
			
		||||
     */
 | 
			
		||||
    async getGlossaryFolder(glossaryId: number, siteId?: string): Promise<string> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const siteFolderPath = CoreFile.getSiteFolder(site.getId());
 | 
			
		||||
        const folderPath = 'offlineglossary/' + glossaryId;
 | 
			
		||||
 | 
			
		||||
        return CoreTextUtils.concatenatePaths(siteFolderPath, folderPath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the path to the folder where to store files for a new offline entry.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param concept The name of the entry.
 | 
			
		||||
     * @param timeCreated Time to allow duplicated entries.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with the path.
 | 
			
		||||
     */
 | 
			
		||||
    async getEntryFolder(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<string> {
 | 
			
		||||
        const folderPath = await this.getGlossaryFolder(glossaryId, siteId);
 | 
			
		||||
 | 
			
		||||
        return CoreTextUtils.concatenatePaths(folderPath, 'newentry_' + concept + '_' + timeCreated);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Parse "options" and "attachments" columns of a fetched record.
 | 
			
		||||
     *
 | 
			
		||||
     * @param records Record object
 | 
			
		||||
     * @return Record object with columns parsed.
 | 
			
		||||
     */
 | 
			
		||||
    protected parseRecord(record: AddonModGlossaryOfflineEntryDBRecord): AddonModGlossaryOfflineEntry {
 | 
			
		||||
        return Object.assign(record, {
 | 
			
		||||
            options: <Record<string, AddonModGlossaryEntryOption>> CoreTextUtils.parseJSON(record.options),
 | 
			
		||||
            attachments: record.attachments ?
 | 
			
		||||
                <CoreFileUploaderStoreFilesResult> CoreTextUtils.parseJSON(record.attachments) : undefined,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossaryOffline = makeSingleton(AddonModGlossaryOfflineProvider);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Glossary offline entry with parsed data.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModGlossaryOfflineEntry = Omit<AddonModGlossaryOfflineEntryDBRecord, 'options'|'attachments'> & {
 | 
			
		||||
    options: Record<string, AddonModGlossaryEntryOption>;
 | 
			
		||||
    attachments?: CoreFileUploaderStoreFilesResult;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										359
									
								
								src/addons/mod/glossary/services/glossary-sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								src/addons/mod/glossary/services/glossary-sync.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,359 @@
 | 
			
		||||
// (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 { ContextLevel } from '@/core/constants';
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { FileEntry } from '@ionic-native/file/ngx';
 | 
			
		||||
import { CoreSyncBlockedError } from '@classes/base-sync';
 | 
			
		||||
import { CoreNetworkError } from '@classes/errors/network-error';
 | 
			
		||||
import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
 | 
			
		||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
			
		||||
import { CoreRatingSync } from '@features/rating/services/rating-sync';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreSync } from '@services/sync';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreWSExternalFile } from '@services/ws';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { AddonModGlossary, AddonModGlossaryProvider } from './glossary';
 | 
			
		||||
import { AddonModGlossaryHelper } from './glossary-helper';
 | 
			
		||||
import { AddonModGlossaryOffline, AddonModGlossaryOfflineEntry } from './glossary-offline';
 | 
			
		||||
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to sync glossaries.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossarySyncProvider extends CoreCourseActivitySyncBaseProvider<AddonModGlossarySyncResult> {
 | 
			
		||||
 | 
			
		||||
    static readonly AUTO_SYNCED = 'addon_mod_glossary_autom_synced';
 | 
			
		||||
 | 
			
		||||
    protected componentTranslatableString = 'glossary';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModGlossarySyncProvider');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Try to synchronize all the glossaries in a certain site or in all sites.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID to sync. If not defined, sync all sites.
 | 
			
		||||
     * @param force Wether to force sync not depending on last execution.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected if sync fails.
 | 
			
		||||
     */
 | 
			
		||||
    syncAllGlossaries(siteId?: string, force?: boolean): Promise<void> {
 | 
			
		||||
        return this.syncOnSites('all glossaries', this.syncAllGlossariesFunc.bind(this, !!force), siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sync all glossaries on a site.
 | 
			
		||||
     *
 | 
			
		||||
     * @param force Wether to force sync not depending on last execution.
 | 
			
		||||
     * @param siteId Site ID to sync.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected if sync fails.
 | 
			
		||||
     */
 | 
			
		||||
    protected async syncAllGlossariesFunc(force: boolean, siteId: string): Promise<void> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
            this.syncAllGlossariesEntries(force, siteId),
 | 
			
		||||
            this.syncRatings(undefined, force, siteId),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sync entried of all glossaries on a site.
 | 
			
		||||
     *
 | 
			
		||||
     * @param force Wether to force sync not depending on last execution.
 | 
			
		||||
     * @param siteId Site ID to sync.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected if sync fails.
 | 
			
		||||
     */
 | 
			
		||||
    protected async syncAllGlossariesEntries(force: boolean, siteId: string): Promise<void> {
 | 
			
		||||
        const entries = await AddonModGlossaryOffline.getAllNewEntries(siteId);
 | 
			
		||||
 | 
			
		||||
        // Do not sync same glossary twice.
 | 
			
		||||
        const treated: Record<number, boolean> = {};
 | 
			
		||||
 | 
			
		||||
        await Promise.all(entries.map(async (entry) => {
 | 
			
		||||
            if (treated[entry.glossaryid]) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            treated[entry.glossaryid] = true;
 | 
			
		||||
 | 
			
		||||
            const result = force ?
 | 
			
		||||
                await this.syncGlossaryEntries(entry.glossaryid, entry.userid, siteId) :
 | 
			
		||||
                await this.syncGlossaryEntriesIfNeeded(entry.glossaryid, entry.userid, siteId);
 | 
			
		||||
 | 
			
		||||
            if (result?.updated) {
 | 
			
		||||
                // Sync successful, send event.
 | 
			
		||||
                CoreEvents.trigger(AddonModGlossarySyncProvider.AUTO_SYNCED, {
 | 
			
		||||
                    glossaryId: entry.glossaryid,
 | 
			
		||||
                    userId: entry.userid,
 | 
			
		||||
                    warnings: result.warnings,
 | 
			
		||||
                }, siteId);
 | 
			
		||||
            }
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sync a glossary only if a certain time has passed since the last time.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param userId User the entry belong to.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the glossary is synced or if it doesn't need to be synced.
 | 
			
		||||
     */
 | 
			
		||||
    async syncGlossaryEntriesIfNeeded(
 | 
			
		||||
        glossaryId: number,
 | 
			
		||||
        userId: number,
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<AddonModGlossarySyncResult | undefined> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const syncId = this.getGlossarySyncId(glossaryId, userId);
 | 
			
		||||
 | 
			
		||||
        const needed = await this.isSyncNeeded(syncId, siteId);
 | 
			
		||||
 | 
			
		||||
        if (needed) {
 | 
			
		||||
            return this.syncGlossaryEntries(glossaryId, userId, siteId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronize all offline entries of a glossary.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID to be synced.
 | 
			
		||||
     * @param userId User the entries belong to.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    syncGlossaryEntries(glossaryId: number, userId?: number, siteId?: string): Promise<AddonModGlossarySyncResult> {
 | 
			
		||||
        userId = userId || CoreSites.getCurrentSiteUserId();
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const syncId = this.getGlossarySyncId(glossaryId, userId);
 | 
			
		||||
        if (this.isSyncing(syncId, siteId)) {
 | 
			
		||||
            // There's already a sync ongoing for this glossary, return the promise.
 | 
			
		||||
            return this.getOngoingSync(syncId, siteId)!;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Verify that glossary isn't blocked.
 | 
			
		||||
        if (CoreSync.isBlocked(AddonModGlossaryProvider.COMPONENT, syncId, siteId)) {
 | 
			
		||||
            this.logger.debug('Cannot sync glossary ' + glossaryId + ' because it is blocked.');
 | 
			
		||||
 | 
			
		||||
            throw new CoreSyncBlockedError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.logger.debug('Try to sync glossary ' + glossaryId + ' for user ' + userId);
 | 
			
		||||
 | 
			
		||||
        const syncPromise = this.performSyncGlossaryEntries(glossaryId, userId, siteId);
 | 
			
		||||
 | 
			
		||||
        return this.addOngoingSync(syncId, syncPromise, siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected async performSyncGlossaryEntries(
 | 
			
		||||
        glossaryId: number,
 | 
			
		||||
        userId: number,
 | 
			
		||||
        siteId: string,
 | 
			
		||||
    ): Promise<AddonModGlossarySyncResult> {
 | 
			
		||||
        const result: AddonModGlossarySyncResult = {
 | 
			
		||||
            warnings: [],
 | 
			
		||||
            updated: false,
 | 
			
		||||
        };
 | 
			
		||||
        const syncId = this.getGlossarySyncId(glossaryId, userId);
 | 
			
		||||
 | 
			
		||||
        // Sync offline logs.
 | 
			
		||||
        await CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(AddonModGlossaryProvider.COMPONENT, glossaryId, siteId));
 | 
			
		||||
 | 
			
		||||
        // Get offline responses to be sent.
 | 
			
		||||
        const entries = await CoreUtils.ignoreErrors(
 | 
			
		||||
            AddonModGlossaryOffline.getGlossaryNewEntries(glossaryId, siteId, userId),
 | 
			
		||||
            <AddonModGlossaryOfflineEntry[]> [],
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if (!entries.length) {
 | 
			
		||||
            // Nothing to sync.
 | 
			
		||||
            await CoreUtils.ignoreErrors(this.setSyncTime(syncId, siteId));
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
        } else if (!CoreApp.isOnline()) {
 | 
			
		||||
            // Cannot sync in offline.
 | 
			
		||||
            throw new CoreNetworkError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let courseId: number | undefined;
 | 
			
		||||
 | 
			
		||||
        await Promise.all(entries.map(async (data) => {
 | 
			
		||||
            courseId = courseId || data.courseid;
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                // First of all upload the attachments (if any).
 | 
			
		||||
                const itemId = await this.uploadAttachments(glossaryId, data, siteId);
 | 
			
		||||
 | 
			
		||||
                // Now try to add the entry.
 | 
			
		||||
                await AddonModGlossary.addEntryOnline(glossaryId, data.concept, data.definition, data.options, itemId, siteId);
 | 
			
		||||
 | 
			
		||||
                result.updated = true;
 | 
			
		||||
 | 
			
		||||
                await this.deleteAddEntry(glossaryId, data.concept, data.timecreated, siteId);
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                if (!CoreUtils.isWebServiceError(error)) {
 | 
			
		||||
                    // Couldn't connect to server, reject.
 | 
			
		||||
                    throw error;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // The WebService has thrown an error, this means that responses cannot be submitted. Delete them.
 | 
			
		||||
                result.updated = true;
 | 
			
		||||
 | 
			
		||||
                await this.deleteAddEntry(glossaryId, data.concept, data.timecreated, siteId);
 | 
			
		||||
 | 
			
		||||
                // Responses deleted, add a warning.
 | 
			
		||||
                this.addOfflineDataDeletedWarning(result.warnings, data.concept, error);
 | 
			
		||||
            }
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        if (result.updated && courseId) {
 | 
			
		||||
            // Data has been sent to server. Now invalidate the WS calls.
 | 
			
		||||
            try {
 | 
			
		||||
                const glossary = await AddonModGlossary.getGlossaryById(courseId, glossaryId);
 | 
			
		||||
 | 
			
		||||
                await AddonModGlossary.invalidateGlossaryEntries(glossary, true);
 | 
			
		||||
            } catch {
 | 
			
		||||
                // Ignore errors.
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Sync finished, set sync time.
 | 
			
		||||
        await CoreUtils.ignoreErrors(this.setSyncTime(syncId, siteId));
 | 
			
		||||
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronize offline ratings.
 | 
			
		||||
     *
 | 
			
		||||
     * @param cmId Course module to be synced. If not defined, sync all glossaries.
 | 
			
		||||
     * @param force Wether to force sync not depending on last execution.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async syncRatings(cmId?: number, force?: boolean, siteId?: string): Promise<AddonModGlossarySyncResult> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const results = await CoreRatingSync.syncRatings('mod_glossary', 'entry', ContextLevel.MODULE, cmId, 0, force, siteId);
 | 
			
		||||
 | 
			
		||||
        let updated = false;
 | 
			
		||||
        const warnings: string[] = [];
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.allPromises(results.map(async (result) => {
 | 
			
		||||
            if (result.updated.length) {
 | 
			
		||||
                updated = true;
 | 
			
		||||
 | 
			
		||||
                // Invalidate entry of updated ratings.
 | 
			
		||||
                await Promise.all(result.updated.map((itemId) => AddonModGlossary.invalidateEntry(itemId, siteId)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (result.warnings.length) {
 | 
			
		||||
                const glossary = await AddonModGlossary.getGlossary(result.itemSet.courseId, result.itemSet.instanceId, { siteId });
 | 
			
		||||
 | 
			
		||||
                result.warnings.forEach((warning) => {
 | 
			
		||||
                    this.addOfflineDataDeletedWarning(warnings, glossary.name, warning);
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        return { updated, warnings };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a new entry.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param concept Glossary entry concept.
 | 
			
		||||
     * @param timeCreated Time to allow duplicated entries.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when deleted.
 | 
			
		||||
     */
 | 
			
		||||
    protected async deleteAddEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<void> {
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
            AddonModGlossaryOffline.deleteNewEntry(glossaryId, concept, timeCreated, siteId),
 | 
			
		||||
            AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timeCreated, siteId),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Upload attachments of an offline entry.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param entry Offline entry.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with draftid if uploaded, resolved with 0 if nothing to upload.
 | 
			
		||||
     */
 | 
			
		||||
    protected async uploadAttachments(glossaryId: number, entry: AddonModGlossaryOfflineEntry, siteId?: string): Promise<number> {
 | 
			
		||||
        if (!entry.attachments) {
 | 
			
		||||
            // No attachments.
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Has some attachments to sync.
 | 
			
		||||
        let files: (CoreWSExternalFile | FileEntry)[] = entry.attachments.online || [];
 | 
			
		||||
 | 
			
		||||
        if (entry.attachments.offline) {
 | 
			
		||||
            // Has offline files.
 | 
			
		||||
            const storedFiles = await CoreUtils.ignoreErrors(
 | 
			
		||||
                AddonModGlossaryHelper.getStoredFiles(glossaryId, entry.concept, entry.timecreated, siteId),
 | 
			
		||||
                [], // Folder not found, no files to add.
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            files = files.concat(storedFiles);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return CoreFileUploader.uploadOrReuploadFiles(files, AddonModGlossaryProvider.COMPONENT, glossaryId, siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the ID of a glossary sync.
 | 
			
		||||
     *
 | 
			
		||||
     * @param glossaryId Glossary ID.
 | 
			
		||||
     * @param userId User the entries belong to.. If not defined, current user.
 | 
			
		||||
     * @return Sync ID.
 | 
			
		||||
     */
 | 
			
		||||
    protected getGlossarySyncId(glossaryId: number, userId?: number): string {
 | 
			
		||||
        userId = userId || CoreSites.getCurrentSiteUserId();
 | 
			
		||||
 | 
			
		||||
        return 'glossary#' + glossaryId + '#' + userId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossarySync = makeSingleton(AddonModGlossarySyncProvider);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by a glossary sync.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModGlossarySyncResult = {
 | 
			
		||||
    warnings: string[]; // List of warnings.
 | 
			
		||||
    updated: boolean; // Whether some data was sent to the server or offline data was updated.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data passed to AUTO_SYNCED event.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModGlossaryAutoSyncData = {
 | 
			
		||||
    glossaryId: number;
 | 
			
		||||
    userId: number;
 | 
			
		||||
    warnings: string[];
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										1465
									
								
								src/addons/mod/glossary/services/glossary.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1465
									
								
								src/addons/mod/glossary/services/glossary.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										78
									
								
								src/addons/mod/glossary/services/handlers/edit-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/addons/mod/glossary/services/handlers/edit-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
			
		||||
// (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 { 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 { AddonModGlossaryModuleHandlerService } from './module';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Content links handler for glossary new entry.
 | 
			
		||||
 * Match mod/glossary/edit.php?cmid=6 with a valid data.
 | 
			
		||||
 * Currently it only supports new entry.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossaryEditLinkHandlerService extends CoreContentLinksHandlerBase {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModGlossaryEditLinkHandler';
 | 
			
		||||
    featureName = 'CoreCourseModuleDelegate_AddonModGlossary';
 | 
			
		||||
    pattern = /\/mod\/glossary\/edit\.php.*([?&](cmid)=\d+)/;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
 | 
			
		||||
        return [{
 | 
			
		||||
            action: async (siteId: string) => {
 | 
			
		||||
                const modal = await CoreDomUtils.showModalLoading();
 | 
			
		||||
 | 
			
		||||
                const cmId = Number(params.cmid);
 | 
			
		||||
 | 
			
		||||
                try {
 | 
			
		||||
                    const module = await CoreCourse.getModuleBasicInfo(cmId, siteId);
 | 
			
		||||
 | 
			
		||||
                    await CoreNavigator.navigateToSitePath(
 | 
			
		||||
                        AddonModGlossaryModuleHandlerService.PAGE_NAME + '/edit/0',
 | 
			
		||||
                        {
 | 
			
		||||
                            params: {
 | 
			
		||||
                                cmId: module.id,
 | 
			
		||||
                                courseId: module.course,
 | 
			
		||||
                            },
 | 
			
		||||
                            siteId,
 | 
			
		||||
                        },
 | 
			
		||||
                    );
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingglossary', true);
 | 
			
		||||
                } finally {
 | 
			
		||||
                    // Just in case. In fact we need to dismiss the modal before showing a toast or error message.
 | 
			
		||||
                    modal.dismiss();
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        }];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(siteId: string, url: string, params: Record<string, string>): Promise<boolean> {
 | 
			
		||||
        return typeof params.cmid != 'undefined';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossaryEditLinkHandler = makeSingleton(AddonModGlossaryEditLinkHandlerService);
 | 
			
		||||
							
								
								
									
										75
									
								
								src/addons/mod/glossary/services/handlers/entry-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/addons/mod/glossary/services/handlers/entry-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
// (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 { 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 { AddonModGlossary } from '../glossary';
 | 
			
		||||
import { AddonModGlossaryModuleHandlerService } from './module';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to glossary entries.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossaryEntryLinkHandlerService extends CoreContentLinksHandlerBase {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModGlossaryEntryLinkHandler';
 | 
			
		||||
    featureName = 'CoreCourseModuleDelegate_AddonModGlossary';
 | 
			
		||||
    pattern = /\/mod\/glossary\/(showentry|view)\.php.*([&?](eid|g|mode|hook)=\d+)/;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
 | 
			
		||||
        return [{
 | 
			
		||||
            action: async (siteId: string) => {
 | 
			
		||||
                const modal = await CoreDomUtils.showModalLoading();
 | 
			
		||||
 | 
			
		||||
                try {
 | 
			
		||||
                    const entryId = params.mode == 'entry' ? Number(params.hook) : Number(params.eid);
 | 
			
		||||
 | 
			
		||||
                    const response = await AddonModGlossary.getEntry(entryId, { siteId });
 | 
			
		||||
 | 
			
		||||
                    const module = await CoreCourse.getModuleBasicInfoByInstance(
 | 
			
		||||
                        response.entry.glossaryid,
 | 
			
		||||
                        'glossary',
 | 
			
		||||
                        siteId,
 | 
			
		||||
                    );
 | 
			
		||||
 | 
			
		||||
                    await CoreNavigator.navigateToSitePath(
 | 
			
		||||
                        AddonModGlossaryModuleHandlerService.PAGE_NAME + `/entry/${entryId}`,
 | 
			
		||||
                        {
 | 
			
		||||
                            params: {
 | 
			
		||||
                                cmId: module.id,
 | 
			
		||||
                                courseId: module.course,
 | 
			
		||||
                            },
 | 
			
		||||
                            siteId,
 | 
			
		||||
                        },
 | 
			
		||||
                    );
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);
 | 
			
		||||
                } finally {
 | 
			
		||||
                    modal.dismiss();
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        }];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossaryEntryLinkHandler = makeSingleton(AddonModGlossaryEntryLinkHandlerService);
 | 
			
		||||
							
								
								
									
										33
									
								
								src/addons/mod/glossary/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/addons/mod/glossary/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
// (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 { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to glossary index.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossaryIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModGlossaryIndexLinkHandler';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModGlossary', 'glossary', 'g');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossaryIndexLinkHandler = makeSingleton(AddonModGlossaryIndexLinkHandlerService);
 | 
			
		||||
							
								
								
									
										33
									
								
								src/addons/mod/glossary/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/addons/mod/glossary/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
// (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 { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to glossary list page.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossaryListLinkHandlerService extends CoreContentLinksModuleListHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModGlossaryListLinkHandler';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModGlossary', 'glossary');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossaryListLinkHandler = makeSingleton(AddonModGlossaryListLinkHandlerService);
 | 
			
		||||
							
								
								
									
										92
									
								
								src/addons/mod/glossary/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/addons/mod/glossary/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,92 @@
 | 
			
		||||
// (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 { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { Injectable, Type } from '@angular/core';
 | 
			
		||||
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseModule } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
 | 
			
		||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModGlossaryIndexComponent } from '../../components/index/index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to support glossary modules.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossaryModuleHandlerService implements CoreCourseModuleHandler {
 | 
			
		||||
 | 
			
		||||
    static readonly PAGE_NAME = 'mod_glossary';
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModGlossary';
 | 
			
		||||
    modName = 'glossary';
 | 
			
		||||
 | 
			
		||||
    supportedFeatures = {
 | 
			
		||||
        [CoreConstants.FEATURE_GROUPS]: false,
 | 
			
		||||
        [CoreConstants.FEATURE_GROUPINGS]: false,
 | 
			
		||||
        [CoreConstants.FEATURE_MOD_INTRO]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_COMPLETION_HAS_RULES]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_GRADE_HAS_GRADE]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_GRADE_OUTCOMES]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_RATE]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_PLAGIARISM]: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getData(module: CoreCourseAnyModuleData): CoreCourseModuleHandlerData {
 | 
			
		||||
        return {
 | 
			
		||||
            icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined),
 | 
			
		||||
            title: module.name,
 | 
			
		||||
            class: 'addon-mod_glossary-handler',
 | 
			
		||||
            showDownloadButton: true,
 | 
			
		||||
            action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => {
 | 
			
		||||
                options = options || {};
 | 
			
		||||
                options.params = options.params || {};
 | 
			
		||||
                Object.assign(options.params, { module });
 | 
			
		||||
                const routeParams = '/' + courseId + '/' + module.id;
 | 
			
		||||
 | 
			
		||||
                CoreNavigator.navigateToSitePath(AddonModGlossaryModuleHandlerService.PAGE_NAME + routeParams, options);
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async getMainComponent(): Promise<Type<unknown>> {
 | 
			
		||||
        return AddonModGlossaryIndexComponent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    displayRefresherInSingleActivity(): boolean {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossaryModuleHandler = makeSingleton(AddonModGlossaryModuleHandlerService);
 | 
			
		||||
							
								
								
									
										235
									
								
								src/addons/mod/glossary/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								src/addons/mod/glossary/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,235 @@
 | 
			
		||||
// (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 { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreComments } from '@features/comments/services/comments';
 | 
			
		||||
import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
 | 
			
		||||
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
 | 
			
		||||
import { CoreUser } from '@features/user/services/user';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
			
		||||
import { CoreWSExternalFile } from '@services/ws';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModGlossary, AddonModGlossaryEntry, AddonModGlossaryGlossary, AddonModGlossaryProvider } from '../glossary';
 | 
			
		||||
import { AddonModGlossarySync, AddonModGlossarySyncResult } from '../glossary-sync';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to prefetch forums.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossaryPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModGlossary';
 | 
			
		||||
    modName = 'glossary';
 | 
			
		||||
    component = AddonModGlossaryProvider.COMPONENT;
 | 
			
		||||
    updatesNames = /^configuration$|^.*files$|^entries$/;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
 | 
			
		||||
        try {
 | 
			
		||||
            const glossary = await AddonModGlossary.getGlossary(courseId, module.id);
 | 
			
		||||
 | 
			
		||||
            const entries = await AddonModGlossary.fetchAllEntries(
 | 
			
		||||
                AddonModGlossary.getEntriesByLetter.bind(AddonModGlossary.instance, glossary.id, 'ALL'),
 | 
			
		||||
                {
 | 
			
		||||
                    cmId: module.id,
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            return this.getFilesFromGlossaryAndEntries(module, glossary, entries);
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Glossary not found, return empty list.
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the list of downloadable files. It includes entry embedded files.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module Module to get the files.
 | 
			
		||||
     * @param glossary Glossary
 | 
			
		||||
     * @param entries Entries of the Glossary.
 | 
			
		||||
     * @return List of Files.
 | 
			
		||||
     */
 | 
			
		||||
    protected getFilesFromGlossaryAndEntries(
 | 
			
		||||
        module: CoreCourseAnyModuleData,
 | 
			
		||||
        glossary: AddonModGlossaryGlossary,
 | 
			
		||||
        entries: AddonModGlossaryEntry[],
 | 
			
		||||
    ): CoreWSExternalFile[] {
 | 
			
		||||
        let files = this.getIntroFilesFromInstance(module, glossary);
 | 
			
		||||
 | 
			
		||||
        const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
 | 
			
		||||
 | 
			
		||||
        // Get entries files.
 | 
			
		||||
        entries.forEach((entry) => {
 | 
			
		||||
            files = files.concat(entry.attachments || []);
 | 
			
		||||
 | 
			
		||||
            if (getInlineFiles && entry.definitioninlinefiles && entry.definitioninlinefiles.length) {
 | 
			
		||||
                files = files.concat(entry.definitioninlinefiles);
 | 
			
		||||
            } else if (entry.definition && !getInlineFiles) {
 | 
			
		||||
                files = files.concat(CoreFilepool.extractDownloadableFilesFromHtmlAsFakeFileObjects(entry.definition));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return files;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    invalidateContent(moduleId: number, courseId: number): Promise<void> {
 | 
			
		||||
        return AddonModGlossary.invalidateContent(moduleId, courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    prefetch(module: CoreCourseAnyModuleData, courseId?: number): Promise<void> {
 | 
			
		||||
        return this.prefetchPackage(module, courseId, this.prefetchGlossary.bind(this, module, courseId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prefetch a glossary.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module The module object returned by WS.
 | 
			
		||||
     * @param courseId Course ID the module belongs to.
 | 
			
		||||
     * @param siteId Site ID.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async prefetchGlossary(module: CoreCourseAnyModuleData, courseId: number, siteId: string): Promise<void> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const options = {
 | 
			
		||||
            cmId: module.id,
 | 
			
		||||
            readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
 | 
			
		||||
            siteId,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Prefetch the glossary data.
 | 
			
		||||
        const glossary = await AddonModGlossary.getGlossary(courseId, module.id, { siteId });
 | 
			
		||||
 | 
			
		||||
        const promises: Promise<unknown>[] = [];
 | 
			
		||||
 | 
			
		||||
        glossary.browsemodes.forEach((mode) => {
 | 
			
		||||
            switch (mode) {
 | 
			
		||||
                case 'letter': // Always done. Look bellow.
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'cat':
 | 
			
		||||
                    promises.push(AddonModGlossary.fetchAllEntries(
 | 
			
		||||
                        AddonModGlossary.getEntriesByCategory.bind(
 | 
			
		||||
                            AddonModGlossary.instance,
 | 
			
		||||
                            glossary.id,
 | 
			
		||||
                            AddonModGlossaryProvider.SHOW_ALL_CATEGORIES,
 | 
			
		||||
                        ),
 | 
			
		||||
                        options,
 | 
			
		||||
                    ));
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'date':
 | 
			
		||||
                    promises.push(AddonModGlossary.fetchAllEntries(
 | 
			
		||||
                        AddonModGlossary.getEntriesByDate.bind(
 | 
			
		||||
                            AddonModGlossary.instance,
 | 
			
		||||
                            glossary.id,
 | 
			
		||||
                            'CREATION',
 | 
			
		||||
                            'DESC',
 | 
			
		||||
                        ),
 | 
			
		||||
                        options,
 | 
			
		||||
                    ));
 | 
			
		||||
                    promises.push(AddonModGlossary.fetchAllEntries(
 | 
			
		||||
                        AddonModGlossary.getEntriesByDate.bind(
 | 
			
		||||
                            AddonModGlossary.instance,
 | 
			
		||||
                            glossary.id,
 | 
			
		||||
                            'UPDATE',
 | 
			
		||||
                            'DESC',
 | 
			
		||||
                        ),
 | 
			
		||||
                        options,
 | 
			
		||||
                    ));
 | 
			
		||||
                    break;
 | 
			
		||||
                case 'author':
 | 
			
		||||
                    promises.push(AddonModGlossary.fetchAllEntries(
 | 
			
		||||
                        AddonModGlossary.getEntriesByAuthor.bind(
 | 
			
		||||
                            AddonModGlossary.instance,
 | 
			
		||||
                            glossary.id,
 | 
			
		||||
                            'ALL',
 | 
			
		||||
                            'LASTNAME',
 | 
			
		||||
                            'ASC',
 | 
			
		||||
                        ),
 | 
			
		||||
                        options,
 | 
			
		||||
                    ));
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Fetch all entries to get information from.
 | 
			
		||||
        promises.push(AddonModGlossary.fetchAllEntries(
 | 
			
		||||
            AddonModGlossary.getEntriesByLetter.bind(AddonModGlossary.instance, glossary.id, 'ALL'),
 | 
			
		||||
            options,
 | 
			
		||||
        ).then((entries) => {
 | 
			
		||||
            const promises: Promise<unknown>[] = [];
 | 
			
		||||
            const commentsEnabled = !CoreComments.areCommentsDisabledInSite();
 | 
			
		||||
 | 
			
		||||
            entries.forEach((entry) => {
 | 
			
		||||
                // Don't fetch individual entries, it's too many WS calls.
 | 
			
		||||
                if (glossary.allowcomments && commentsEnabled) {
 | 
			
		||||
                    promises.push(CoreComments.getComments(
 | 
			
		||||
                        'module',
 | 
			
		||||
                        glossary.coursemodule,
 | 
			
		||||
                        'mod_glossary',
 | 
			
		||||
                        entry.id,
 | 
			
		||||
                        'glossary_entry',
 | 
			
		||||
                        0,
 | 
			
		||||
                        siteId,
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            const files = this.getFilesFromGlossaryAndEntries(module, glossary, entries);
 | 
			
		||||
            promises.push(CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id));
 | 
			
		||||
 | 
			
		||||
            // Prefetch user avatars.
 | 
			
		||||
            promises.push(CoreUser.prefetchUserAvatars(entries, 'userpictureurl', siteId));
 | 
			
		||||
 | 
			
		||||
            return Promise.all(promises);
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        // Get all categories.
 | 
			
		||||
        promises.push(AddonModGlossary.getAllCategories(glossary.id, options));
 | 
			
		||||
 | 
			
		||||
        // Prefetch data for link handlers.
 | 
			
		||||
        promises.push(CoreCourse.getModuleBasicInfo(module.id, siteId));
 | 
			
		||||
        promises.push(CoreCourse.getModuleBasicInfoByInstance(glossary.id, 'glossary', siteId));
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise<AddonModGlossarySyncResult> {
 | 
			
		||||
        const results = await Promise.all([
 | 
			
		||||
            AddonModGlossarySync.syncGlossaryEntries(module.instance!, undefined, siteId),
 | 
			
		||||
            AddonModGlossarySync.syncRatings(module.id, undefined, siteId),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            updated: results[0].updated || results[1].updated,
 | 
			
		||||
            warnings: results[0].warnings.concat(results[1].warnings),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossaryPrefetchHandler = makeSingleton(AddonModGlossaryPrefetchHandlerService);
 | 
			
		||||
							
								
								
									
										44
									
								
								src/addons/mod/glossary/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/addons/mod/glossary/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
// (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 { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreCronHandler } from '@services/cron';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModGlossarySync } from '../glossary-sync';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Synchronization cron handler.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossarySyncCronHandlerService implements CoreCronHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModGlossarySyncCronHandler';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    execute(siteId?: string, force?: boolean): Promise<void> {
 | 
			
		||||
        return AddonModGlossarySync.syncAllGlossaries(siteId, force);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getInterval(): number {
 | 
			
		||||
        return AddonModGlossarySync.syncInterval;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossarySyncCronHandler = makeSingleton(AddonModGlossarySyncCronHandlerService);
 | 
			
		||||
							
								
								
									
										53
									
								
								src/addons/mod/glossary/services/handlers/tag-area.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/addons/mod/glossary/services/handlers/tag-area.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
// (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 { Injectable, Type } from '@angular/core';
 | 
			
		||||
import { CoreTagFeedComponent } from '@features/tag/components/feed/feed';
 | 
			
		||||
import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate';
 | 
			
		||||
import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to support tags.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModGlossaryTagAreaHandlerService implements CoreTagAreaHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModGlossaryTagAreaHandler';
 | 
			
		||||
    type = 'mod_glossary/glossary_entries';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    parseContent(content: string): CoreTagFeedElement[] {
 | 
			
		||||
        return CoreTagHelper.parseFeedContent(content);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getComponent(): Type<unknown> {
 | 
			
		||||
        return CoreTagFeedComponent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const AddonModGlossaryTagAreaHandler = makeSingleton(AddonModGlossaryTagAreaHandlerService);
 | 
			
		||||
@ -32,6 +32,7 @@ import { AddonModSurveyModule } from './survey/survey.module';
 | 
			
		||||
import { AddonModScormModule } from './scorm/scorm.module';
 | 
			
		||||
import { AddonModChoiceModule } from './choice/choice.module';
 | 
			
		||||
import { AddonModWikiModule } from './wiki/wiki.module';
 | 
			
		||||
import { AddonModGlossaryModule } from './glossary/glossary.module';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
@ -53,6 +54,7 @@ import { AddonModWikiModule } from './wiki/wiki.module';
 | 
			
		||||
        AddonModScormModule,
 | 
			
		||||
        AddonModChoiceModule,
 | 
			
		||||
        AddonModWikiModule,
 | 
			
		||||
        AddonModGlossaryModule,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModModule { }
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,6 @@ import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreGroups } from '@services/groups';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreSync } from '@services/sync';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
@ -260,11 +259,7 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider<AddonModWikiS
 | 
			
		||||
                result.updated = true;
 | 
			
		||||
 | 
			
		||||
                // Page deleted, add the page to discarded pages and add a warning.
 | 
			
		||||
                const warning = Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
                    component: Translate.instant('addon.mod_wiki.wikipage'),
 | 
			
		||||
                    name: page.title,
 | 
			
		||||
                    error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
                });
 | 
			
		||||
                const warning = this.getOfflineDataDeletedWarning(page.title, error);
 | 
			
		||||
 | 
			
		||||
                result.discarded.push({
 | 
			
		||||
                    title: page.title,
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,5 @@ import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonUserProfileFieldCheckboxComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonUserProfileFieldCheckboxComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonUserProfileFieldCheckboxModule {}
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,5 @@ import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonUserProfileFieldDatetimeComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonUserProfileFieldDatetimeComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonUserProfileFieldDatetimeModule {}
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,5 @@ import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonUserProfileFieldMenuComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonUserProfileFieldMenuComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonUserProfileFieldMenuModule {}
 | 
			
		||||
 | 
			
		||||
@ -38,8 +38,5 @@ import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonUserProfileFieldTextComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonUserProfileFieldTextComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonUserProfileFieldTextModule {}
 | 
			
		||||
 | 
			
		||||
@ -40,8 +40,5 @@ import { CoreEditorComponentsModule } from '@features/editor/components/componen
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonUserProfileFieldTextareaComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        AddonUserProfileFieldTextareaComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonUserProfileFieldTextareaModule {}
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,6 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [AppComponent],
 | 
			
		||||
    entryComponents: [],
 | 
			
		||||
    imports: [
 | 
			
		||||
        BrowserModule,
 | 
			
		||||
        BrowserAnimationsModule,
 | 
			
		||||
 | 
			
		||||
@ -72,11 +72,7 @@ export class CoreSyncBaseProvider<T = void> {
 | 
			
		||||
     * @param error Specific error message.
 | 
			
		||||
     */
 | 
			
		||||
    protected addOfflineDataDeletedWarning(warnings: string[], name: string, error: CoreAnyError): void {
 | 
			
		||||
        const warning = Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
            component: this.componentTranslate,
 | 
			
		||||
            name: name,
 | 
			
		||||
            error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
        });
 | 
			
		||||
        const warning = this.getOfflineDataDeletedWarning(name, error);
 | 
			
		||||
 | 
			
		||||
        if (warnings.indexOf(warning) == -1) {
 | 
			
		||||
            warnings.push(warning);
 | 
			
		||||
@ -113,6 +109,21 @@ export class CoreSyncBaseProvider<T = void> {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add an offline data deleted warning to a list of warnings.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name Instance name.
 | 
			
		||||
     * @param error Specific error message.
 | 
			
		||||
     * @return Warning message.
 | 
			
		||||
     */
 | 
			
		||||
    protected getOfflineDataDeletedWarning(name: string, error: CoreAnyError): string {
 | 
			
		||||
        return Translate.instant('core.warningofflinedatadeleted', {
 | 
			
		||||
            component: this.componentTranslate,
 | 
			
		||||
            name: name,
 | 
			
		||||
            error: CoreTextUtils.getErrorMessageFromError(error),
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * If there's an ongoing sync for a certain identifier return it.
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -51,8 +51,6 @@ import { CoreLogger } from '@singletons/logger';
 | 
			
		||||
 *         <p>Cannot render the data.</p>
 | 
			
		||||
 *     </core-dynamic-component>
 | 
			
		||||
 *
 | 
			
		||||
 * Please notice that the component that you pass needs to be declared in entryComponents of the module to be created dynamically.
 | 
			
		||||
 *
 | 
			
		||||
 * Alternatively, you can also supply a ComponentRef instead of the class of the component. In this case, the component won't
 | 
			
		||||
 * be instantiated because it already is, it will be attached to the view and the right data will be passed to it.
 | 
			
		||||
 * Passing ComponentRef is meant for site plugins.
 | 
			
		||||
@ -119,8 +117,8 @@ export class CoreDynamicComponent implements OnChanges, DoCheck {
 | 
			
		||||
            const changes = this.differ.diff(this.data || {});
 | 
			
		||||
            if (changes) {
 | 
			
		||||
                this.setInputData();
 | 
			
		||||
                if (this.ngOnChanges) {
 | 
			
		||||
                    this.ngOnChanges(CoreDomUtils.createChangesFromKeyValueDiff(changes));
 | 
			
		||||
                if (this.instance.ngOnChanges) {
 | 
			
		||||
                    this.instance.ngOnChanges(CoreDomUtils.createChangesFromKeyValueDiff(changes));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
@ -35,10 +35,5 @@ import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
        CoreBlockPreRenderedComponent,
 | 
			
		||||
        CoreBlockCourseBlocksComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        CoreBlockOnlyTitleComponent,
 | 
			
		||||
        CoreBlockPreRenderedComponent,
 | 
			
		||||
        CoreBlockCourseBlocksComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreBlockComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -29,8 +29,5 @@ import { CoreCommentsCommentsComponent } from './comments/comments';
 | 
			
		||||
        CoreCommentsCommentsComponent,
 | 
			
		||||
        CoreCommentsAddComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        CoreCommentsCommentsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCommentsComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -130,7 +130,7 @@ import { ADDON_MOD_DATA_SERVICES } from '@addons/mod/data/data.module';
 | 
			
		||||
// @todo import { ADDON_MOD_FEEDBACK_SERVICES } from '@addons/mod/feedback/feedback.module';
 | 
			
		||||
import { ADDON_MOD_FOLDER_SERVICES } from '@addons/mod/folder/folder.module';
 | 
			
		||||
import { ADDON_MOD_FORUM_SERVICES } from '@addons/mod/forum/forum.module';
 | 
			
		||||
// @todo import { ADDON_MOD_GLOSSARY_SERVICES } from '@addons/mod/glossary/glossary.module';
 | 
			
		||||
import { ADDON_MOD_GLOSSARY_SERVICES } from '@addons/mod/glossary/glossary.module';
 | 
			
		||||
import { ADDON_MOD_H5P_ACTIVITY_SERVICES } from '@addons/mod/h5pactivity/h5pactivity.module';
 | 
			
		||||
import { ADDON_MOD_IMSCP_SERVICES } from '@addons/mod/imscp/imscp.module';
 | 
			
		||||
import { ADDON_MOD_LESSON_SERVICES } from '@addons/mod/lesson/lesson.module';
 | 
			
		||||
@ -296,7 +296,7 @@ export class CoreCompileProvider {
 | 
			
		||||
            // @todo ...ADDON_MOD_FEEDBACK_SERVICES,
 | 
			
		||||
            ...ADDON_MOD_FOLDER_SERVICES,
 | 
			
		||||
            ...ADDON_MOD_FORUM_SERVICES,
 | 
			
		||||
            // @todo ...ADDON_MOD_GLOSSARY_SERVICES,
 | 
			
		||||
            ...ADDON_MOD_GLOSSARY_SERVICES,
 | 
			
		||||
            ...ADDON_MOD_H5P_ACTIVITY_SERVICES,
 | 
			
		||||
            ...ADDON_MOD_IMSCP_SERVICES,
 | 
			
		||||
            ...ADDON_MOD_LESSON_SERVICES,
 | 
			
		||||
 | 
			
		||||
@ -36,8 +36,5 @@ import { CoreCoursesSelfEnrolPasswordComponent } from './self-enrol-password/sel
 | 
			
		||||
        CoreCoursesCourseOptionsMenuComponent,
 | 
			
		||||
        CoreCoursesSelfEnrolPasswordComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        CoreCoursesCourseOptionsMenuComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCoursesComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -29,8 +29,5 @@ import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
    exports: [
 | 
			
		||||
        CoreEditorRichTextEditorComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        CoreEditorRichTextEditorComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreEditorComponentsModule {}
 | 
			
		||||
 | 
			
		||||
@ -32,8 +32,5 @@ import { CoreRatingRatingsComponent } from './ratings/ratings';
 | 
			
		||||
        CoreRatingRateComponent,
 | 
			
		||||
        CoreRatingRatingsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        CoreRatingRatingsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreRatingComponentsModule {}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user