forked from CIT/Vmeda.Online
		
	Merge pull request #2679 from crazyserver/MOBILE-3658
MOBILE-3658 comments: Comments module
This commit is contained in:
		
						commit
						6d6cb0e36f
					
				@ -37,19 +37,13 @@ export class AddonBlockCommentsHandlerService extends CoreBlockBaseHandler {
 | 
			
		||||
     * @return Data or promise resolved with the data.
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
 | 
			
		||||
        // @todo
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            title: 'addon.block_comments.pluginname',
 | 
			
		||||
            class: 'addon-block-comments',
 | 
			
		||||
            component: CoreBlockOnlyTitleComponent,
 | 
			
		||||
            link: 'CoreCommentsViewerPage',
 | 
			
		||||
            link: 'comments/' + contextLevel + '/' + instanceId + '/block_comments/0',
 | 
			
		||||
            linkParams: {
 | 
			
		||||
                contextLevel: contextLevel,
 | 
			
		||||
                instanceId: instanceId,
 | 
			
		||||
                componentName: 'block_comments',
 | 
			
		||||
                area: 'page_comments',
 | 
			
		||||
                itemId: 0,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								src/core/features/comments/comments-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/core/features/comments/comments-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
// (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';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: ':contextLevel/:instanceId/:componentName/:itemId',
 | 
			
		||||
        loadChildren: () => import('./pages/viewer/viewer.module').then( m => m.CoreCommentsViewerPageModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [RouterModule.forChild(routes)],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCommentsLazyModule {}
 | 
			
		||||
							
								
								
									
										54
									
								
								src/core/features/comments/comments.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/core/features/comments/comments.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
// (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 } from '@angular/core';
 | 
			
		||||
import { Routes } from '@angular/router';
 | 
			
		||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
import { CoreCronDelegate } from '@services/cron';
 | 
			
		||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
			
		||||
import { CoreCommentsComponentsModule } from './components/components.module';
 | 
			
		||||
import { CoreComments } from './services/comments';
 | 
			
		||||
import { COMMENTS_OFFLINE_SITE_SCHEMA } from './services/database/comments';
 | 
			
		||||
import { CoreCommentsSyncCronHandler } from './services/handlers/sync-cron';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: 'comments',
 | 
			
		||||
        loadChildren: () => import('@features/comments/comments-lazy.module').then(m => m.CoreCommentsLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreCommentsComponentsModule,
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: CORE_SITE_SCHEMAS,
 | 
			
		||||
            useValue: [COMMENTS_OFFLINE_SITE_SCHEMA],
 | 
			
		||||
            multi: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            provide: APP_INITIALIZER,
 | 
			
		||||
            multi: true,
 | 
			
		||||
            useValue: () => {
 | 
			
		||||
                CoreCronDelegate.instance.register(CoreCommentsSyncCronHandler.instance);
 | 
			
		||||
 | 
			
		||||
                CoreComments.instance.initialize();
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCommentsModule {}
 | 
			
		||||
							
								
								
									
										94
									
								
								src/core/features/comments/components/add/add-modal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/core/features/comments/components/add/add-modal.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
			
		||||
// (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, ElementRef, Input } from '@angular/core';
 | 
			
		||||
import { CoreComments } from '@features/comments/services/comments';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { ModalController } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component that displays a text area for composing a comment.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-comments-add',
 | 
			
		||||
    templateUrl: 'add.html',
 | 
			
		||||
})
 | 
			
		||||
export class CoreCommentsAddComponent {
 | 
			
		||||
 | 
			
		||||
    @ViewChild('commentForm') formElement?: ElementRef;
 | 
			
		||||
 | 
			
		||||
    @Input() protected contextLevel!: string;
 | 
			
		||||
    @Input() protected instanceId!: number;
 | 
			
		||||
    @Input() protected componentName!: string;
 | 
			
		||||
    @Input() protected itemId!: number;
 | 
			
		||||
    @Input() protected area = '';
 | 
			
		||||
    @Input() content = '';
 | 
			
		||||
 | 
			
		||||
    processing = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send the comment or store it offline.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e Event.
 | 
			
		||||
     */
 | 
			
		||||
    async addComment(e: Event): Promise<void> {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        CoreApp.instance.closeKeyboard();
 | 
			
		||||
        const loadingModal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
 | 
			
		||||
        // Freeze the add comment button.
 | 
			
		||||
        this.processing = true;
 | 
			
		||||
        try {
 | 
			
		||||
            const commentsResponse = await CoreComments.instance.addComment(
 | 
			
		||||
                this.content,
 | 
			
		||||
                this.contextLevel,
 | 
			
		||||
                this.instanceId,
 | 
			
		||||
                this.componentName,
 | 
			
		||||
                this.itemId,
 | 
			
		||||
                this.area,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            CoreDomUtils.instance.triggerFormSubmittedEvent(
 | 
			
		||||
                this.formElement,
 | 
			
		||||
                !!commentsResponse,
 | 
			
		||||
                CoreSites.instance.getCurrentSiteId(),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            ModalController.instance.dismiss({ comment: commentsResponse }).finally(() => {
 | 
			
		||||
                CoreDomUtils.instance.showToast(
 | 
			
		||||
                    commentsResponse ? 'core.comments.eventcommentcreated' : 'core.datastoredoffline',
 | 
			
		||||
                    true,
 | 
			
		||||
                    3000,
 | 
			
		||||
                );
 | 
			
		||||
            });
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.instance.showErrorModal(error);
 | 
			
		||||
            this.processing = false;
 | 
			
		||||
        } finally {
 | 
			
		||||
            loadingModal.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Close modal.
 | 
			
		||||
     */
 | 
			
		||||
    closeModal(): void {
 | 
			
		||||
        CoreDomUtils.instance.triggerFormCancelledEvent(this.formElement, CoreSites.instance.getCurrentSiteId());
 | 
			
		||||
        ModalController.instance.dismiss();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								src/core/features/comments/components/add/add.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/core/features/comments/components/add/add.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
<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.comments.addcomment' | translate }}</ion-title>
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <ion-button (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
 | 
			
		||||
                <ion-icon name="fas-times" slot="icon-only"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <form name="itemEdit" (ngSubmit)="addComment($event)" #commentForm>
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <ion-textarea placeholder="{{ 'core.comments.addcomment' | translate }}" rows="5" [(ngModel)]="content"
 | 
			
		||||
                    name="content" required="required">
 | 
			
		||||
                </ion-textarea>
 | 
			
		||||
            </ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
        <div class="ion-padding">
 | 
			
		||||
            <ion-button expand="block" type="submit" [disabled]="processing || content.length < 1">
 | 
			
		||||
                {{ 'core.comments.savecomment' | translate }}
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</ion-content>
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
.core-comments-clickable {
 | 
			
		||||
    pointer-events: auto;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										220
									
								
								src/core/features/comments/components/comments/comments.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								src/core/features/comments/components/comments/comments.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,220 @@
 | 
			
		||||
// (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, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChange, OnInit } from '@angular/core';
 | 
			
		||||
import {
 | 
			
		||||
    CoreComments,
 | 
			
		||||
    CoreCommentsCountChangedEventData,
 | 
			
		||||
    CoreCommentsProvider,
 | 
			
		||||
    CoreCommentsRefreshCommentsEventData,
 | 
			
		||||
} from '../../services/comments';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { ContextLevel } from '@/core/constants';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component that displays the count of comments.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-comments',
 | 
			
		||||
    templateUrl: 'core-comments.html',
 | 
			
		||||
    styleUrls: ['comments.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCommentsCommentsComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @Input() contextLevel!: ContextLevel;
 | 
			
		||||
    @Input() instanceId!: number;
 | 
			
		||||
    @Input() component!: string;
 | 
			
		||||
    @Input() itemId!: number;
 | 
			
		||||
    @Input() area = '';
 | 
			
		||||
    @Input() title?: string;
 | 
			
		||||
    @Input() displaySpinner = true; // Whether to display the loading spinner.
 | 
			
		||||
    @Output() onLoading: EventEmitter<boolean>; // Eevent that indicates whether the component is loading data.
 | 
			
		||||
    @Input() courseId?: number; // Course ID the comments belong to. It can be used to improve performance with filters.
 | 
			
		||||
 | 
			
		||||
    commentsLoaded = false;
 | 
			
		||||
    commentsCount = '';
 | 
			
		||||
    countError = false;
 | 
			
		||||
    disabled = false;
 | 
			
		||||
 | 
			
		||||
    protected updateSiteObserver?: CoreEventObserver;
 | 
			
		||||
    protected refreshCommentsObserver?: CoreEventObserver;
 | 
			
		||||
    protected commentsCountObserver?: CoreEventObserver;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
 | 
			
		||||
        this.onLoading = new EventEmitter<boolean>();
 | 
			
		||||
 | 
			
		||||
        this.disabled = CoreComments.instance.areCommentsDisabledInSite();
 | 
			
		||||
 | 
			
		||||
        // Update visibility if current site info is updated.
 | 
			
		||||
        this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
			
		||||
            const wasDisabled = this.disabled;
 | 
			
		||||
 | 
			
		||||
            this.disabled = CoreComments.instance.areCommentsDisabledInSite();
 | 
			
		||||
 | 
			
		||||
            if (wasDisabled && !this.disabled) {
 | 
			
		||||
                this.fetchData();
 | 
			
		||||
            }
 | 
			
		||||
        }, CoreSites.instance.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
        // Refresh comments if event received.
 | 
			
		||||
        this.refreshCommentsObserver = CoreEvents.on<CoreCommentsRefreshCommentsEventData>(
 | 
			
		||||
            CoreCommentsProvider.REFRESH_COMMENTS_EVENT,
 | 
			
		||||
            (data) => {
 | 
			
		||||
            // Verify these comments need to be updated.
 | 
			
		||||
                if (this.undefinedOrEqual(data, 'contextLevel') && this.undefinedOrEqual(data, 'instanceId') &&
 | 
			
		||||
                    this.undefinedOrEqual(data, 'component') && this.undefinedOrEqual(data, 'itemId') &&
 | 
			
		||||
                    this.undefinedOrEqual(data, 'area')) {
 | 
			
		||||
 | 
			
		||||
                    CoreUtils.instance.ignoreErrors(this.doRefresh());
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            CoreSites.instance.getCurrentSiteId(),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Refresh comments count if event received.
 | 
			
		||||
        this.commentsCountObserver = CoreEvents.on<CoreCommentsCountChangedEventData>(
 | 
			
		||||
            CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT,
 | 
			
		||||
            (data) => {
 | 
			
		||||
            // Verify these comments need to be updated.
 | 
			
		||||
                if (!this.commentsCount.endsWith('+') && this.undefinedOrEqual(data, 'contextLevel') &&
 | 
			
		||||
                    this.undefinedOrEqual(data, 'instanceId') && this.undefinedOrEqual(data, 'component') &&
 | 
			
		||||
                    this.undefinedOrEqual(data, 'itemId') && this.undefinedOrEqual(data, 'area') && !this.countError) {
 | 
			
		||||
                    let newNumber = parseInt(this.commentsCount, 10) + data.countChange;
 | 
			
		||||
                    newNumber = newNumber >= 0 ? newNumber : 0;
 | 
			
		||||
 | 
			
		||||
                    // Parse and unparse string.
 | 
			
		||||
                    this.commentsCount = newNumber + '';
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            CoreSites.instance.getCurrentSiteId(),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * View loaded.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit(): void {
 | 
			
		||||
        this.fetchData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Listen to changes.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnChanges(changes: { [name: string]: SimpleChange }): void {
 | 
			
		||||
        // If something change, update the fields.
 | 
			
		||||
        if (changes && this.commentsLoaded) {
 | 
			
		||||
            this.fetchData();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch comments data.
 | 
			
		||||
     */
 | 
			
		||||
    async fetchData(): Promise<void> {
 | 
			
		||||
        if (this.disabled) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.commentsLoaded = false;
 | 
			
		||||
        this.onLoading.emit(true);
 | 
			
		||||
 | 
			
		||||
        const commentsCount = await CoreComments.instance.getCommentsCount(
 | 
			
		||||
            this.contextLevel,
 | 
			
		||||
            this.instanceId,
 | 
			
		||||
            this.component,
 | 
			
		||||
            this.itemId,
 | 
			
		||||
            this.area,
 | 
			
		||||
        );
 | 
			
		||||
        this.commentsCount = commentsCount;
 | 
			
		||||
        this.countError = parseInt(this.commentsCount, 10) < 0;
 | 
			
		||||
        this.commentsLoaded = true;
 | 
			
		||||
        this.onLoading.emit(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh comments.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async doRefresh(): Promise<void> {
 | 
			
		||||
        await this.invalidateComments();
 | 
			
		||||
 | 
			
		||||
        await this.fetchData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidate comments data.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateComments(): Promise<void> {
 | 
			
		||||
        await CoreComments.instance.invalidateCommentsData(
 | 
			
		||||
            this.contextLevel,
 | 
			
		||||
            this.instanceId,
 | 
			
		||||
            this.component,
 | 
			
		||||
            this.itemId,
 | 
			
		||||
            this.area,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Opens the comments page.
 | 
			
		||||
     */
 | 
			
		||||
    openComments(e?: Event): void {
 | 
			
		||||
        if (e) {
 | 
			
		||||
            e.preventDefault();
 | 
			
		||||
            e.stopPropagation();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.disabled || this.countError) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CoreNavigator.instance.navigateToSitePath(
 | 
			
		||||
            'comments/' + this.contextLevel + '/' + this.instanceId + '/' + this.component + '/' + this.itemId + '/',
 | 
			
		||||
            {
 | 
			
		||||
                params: {
 | 
			
		||||
                    area: this.area,
 | 
			
		||||
                    title: this.title,
 | 
			
		||||
                    courseId: this.courseId,
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.updateSiteObserver?.off();
 | 
			
		||||
        this.refreshCommentsObserver?.off();
 | 
			
		||||
        this.commentsCountObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if a certain value in data is undefined or equal to this instance value.
 | 
			
		||||
     *
 | 
			
		||||
     * @param data Data object.
 | 
			
		||||
     * @param name Name of the property to check.
 | 
			
		||||
     * @return Whether it's undefined or equal.
 | 
			
		||||
     */
 | 
			
		||||
    protected undefinedOrEqual(data: Record<string, unknown>, name: string): boolean {
 | 
			
		||||
        return typeof data[name] == 'undefined' || data[name] == this[name];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,8 @@
 | 
			
		||||
<core-loading *ngIf="!disabled" [hideUntil]="commentsLoaded || !displaySpinner">
 | 
			
		||||
    <div *ngIf="!countError" (click)="openComments($event)" [class.core-comments-clickable]="!disabled">
 | 
			
		||||
        {{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div *ngIf="countError">
 | 
			
		||||
        {{ 'core.comments.commentsnotworking' | translate }}
 | 
			
		||||
    </div>
 | 
			
		||||
</core-loading>
 | 
			
		||||
							
								
								
									
										36
									
								
								src/core/features/comments/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/core/features/comments/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
// (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 { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { CoreCommentsAddComponent } from './add/add-modal';
 | 
			
		||||
import { CoreCommentsCommentsComponent } from './comments/comments';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreCommentsCommentsComponent,
 | 
			
		||||
        CoreCommentsAddComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        CoreCommentsCommentsComponent,
 | 
			
		||||
        CoreCommentsAddComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    entryComponents: [
 | 
			
		||||
        CoreCommentsCommentsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCommentsComponentsModule {}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/core/features/comments/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/core/features/comments/lang.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
    "addcomment": "Add a comment...",
 | 
			
		||||
    "comments": "Comments",
 | 
			
		||||
    "commentscount": "Comments ({{$a}})",
 | 
			
		||||
    "commentsnotworking": "Comments cannot be retrieved",
 | 
			
		||||
    "deletecommentbyon": "Delete comment posted by {{$a.user}} on {{$a.time}}",
 | 
			
		||||
    "eventcommentcreated": "Comment created",
 | 
			
		||||
    "eventcommentdeleted": "Comment deleted",
 | 
			
		||||
    "nocomments": "No comments",
 | 
			
		||||
    "savecomment": "Save comment",
 | 
			
		||||
    "warningcommentsnotsent": "Couldn't sync comments. {{error}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								src/core/features/comments/pages/viewer/viewer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/core/features/comments/pages/viewer/viewer.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,108 @@
 | 
			
		||||
<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]="contextLevel" [contextInstanceId]="instanceId" [courseId]="courseId">
 | 
			
		||||
            </core-format-text>
 | 
			
		||||
        </ion-title>
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <ion-button *ngIf="canDeleteComments" slot="end" fill="clear" (click)="toggleDelete()"
 | 
			
		||||
                [attr.aria-label]="'core.delete' | translate">
 | 
			
		||||
                <ion-icon name="fas-pen" slot="icon-only"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
            <core-context-menu>
 | 
			
		||||
                <core-context-menu-item [hidden]="!(commentsLoaded && !hasOffline)" [priority]="100"
 | 
			
		||||
                    [content]="'core.refresh' | translate" (action)="refreshComments(false)" [iconAction]="refreshIcon"
 | 
			
		||||
                    [closeOnClick]="true">
 | 
			
		||||
                </core-context-menu-item>
 | 
			
		||||
                <core-context-menu-item [hidden]="!(commentsLoaded && hasOffline)" [priority]="100"
 | 
			
		||||
                    [content]="'core.settings.synchronizenow' | translate" (action)="refreshComments(true)" [iconAction]="syncIcon"
 | 
			
		||||
                    [closeOnClick]="false">
 | 
			
		||||
                </core-context-menu-item>
 | 
			
		||||
            </core-context-menu>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!commentsLoaded" (ionRefresh)="refreshComments(false, $event)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
    <core-loading [hideUntil]="commentsLoaded">
 | 
			
		||||
        <core-empty-box *ngIf="!comments || !comments.length" icon="fas-comments"
 | 
			
		||||
            [message]="'core.comments.nocomments' | translate">
 | 
			
		||||
        </core-empty-box>
 | 
			
		||||
 | 
			
		||||
        <ion-card class="core-warning-card" *ngIf="hasOffline">
 | 
			
		||||
            <ion-item>
 | 
			
		||||
                <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    {{ 'core.thereisdatatosync' | translate:{$a: 'core.comments.comments' | translate | lowercase } }}
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <ion-card *ngIf="offlineComment" (click)="addComment($event)">
 | 
			
		||||
            <ion-item class="ion-text-wrap">
 | 
			
		||||
                <core-user-avatar [user]="offlineComment" slot="start"></core-user-avatar>
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <h2>{{ offlineComment.fullname }}</h2>
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <ion-icon name="far-clock"></ion-icon> {{ 'core.notsent' | translate }}
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-button *ngIf="showDelete" slot="end" fill="clear" [@coreSlideInOut]="'fromRight'" color="danger"
 | 
			
		||||
                    (click)="deleteComment($event, offlineComment)" [attr.aria-label]="'core.delete' | translate">
 | 
			
		||||
                    <ion-icon name="fas-trash" slot="icon-only"></ion-icon>
 | 
			
		||||
                </ion-button>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item class="ion-text-wrap">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <core-format-text clean="true" [text]="offlineComment.content" [contextLevel]="contextLevel"
 | 
			
		||||
                        [contextInstanceId]="instanceId" [courseId]="courseId">
 | 
			
		||||
                    </core-format-text>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <ion-card *ngFor="let comment of comments">
 | 
			
		||||
            <ion-item class="ion-text-wrap">
 | 
			
		||||
                <core-user-avatar [user]="comment" slot="start"></core-user-avatar>
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <h2>{{ comment.fullname }}</h2>
 | 
			
		||||
                    <p *ngIf="!comment.deleted">{{ comment.timecreated * 1000 | coreFormatDate: 'strftimerecentfull' }}</p>
 | 
			
		||||
                    <p *ngIf="comment.deleted">
 | 
			
		||||
                        <ion-icon name="fas-trash"></ion-icon> <span class="ion-text-wrap">
 | 
			
		||||
                            {{ 'core.deletedoffline' | translate }}
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
                <ion-button *ngIf="showDelete && !comment.deleted && comment.delete" slot="end" fill="clear"
 | 
			
		||||
                    [@coreSlideInOut]="'fromRight'" color="danger" (click)="deleteComment($event, comment)"
 | 
			
		||||
                    [attr.aria-label]="'core.delete' | translate">
 | 
			
		||||
                    <ion-icon name="fas-trash" slot="icon-only"></ion-icon>
 | 
			
		||||
                </ion-button>
 | 
			
		||||
                <ion-button *ngIf="showDelete && comment.deleted" slot="end" fill="clear" color="danger"
 | 
			
		||||
                    (click)="undoDeleteComment($event, comment)" [attr.aria-label]="'core.restore' | translate">
 | 
			
		||||
                    <ion-icon name="fas-undo-alt" slot="icon-only"></ion-icon>
 | 
			
		||||
                </ion-button>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item class="ion-text-wrap">
 | 
			
		||||
                <ion-label>
 | 
			
		||||
                    <core-format-text [text]="comment.content" [contextLevel]="contextLevel" [contextInstanceId]="instanceId"
 | 
			
		||||
                        [courseId]="courseId">
 | 
			
		||||
                    </core-format-text>
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError"></core-infinite-loading>
 | 
			
		||||
    </core-loading>
 | 
			
		||||
 | 
			
		||||
    <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAddComments">
 | 
			
		||||
        <ion-fab-button (click)="addComment($event)" [attr.aria-label]="'core.comments.addcomment' | translate">
 | 
			
		||||
            <ion-icon name="fas-plus"></ion-icon>
 | 
			
		||||
        </ion-fab-button>
 | 
			
		||||
    </ion-fab>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										38
									
								
								src/core/features/comments/pages/viewer/viewer.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/core/features/comments/pages/viewer/viewer.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 { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreCommentsViewerPage } from './viewer.page';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        component: CoreCommentsViewerPage,
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreCommentsViewerPage,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [RouterModule],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCommentsViewerPageModule {}
 | 
			
		||||
							
								
								
									
										527
									
								
								src/core/features/comments/pages/viewer/viewer.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										527
									
								
								src/core/features/comments/pages/viewer/viewer.page.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,527 @@
 | 
			
		||||
// (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, OnDestroy, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreAnimations } from '@components/animations';
 | 
			
		||||
import { ActivatedRoute, Params } from '@angular/router';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import {
 | 
			
		||||
    CoreComments,
 | 
			
		||||
    CoreCommentsCommentBasicData,
 | 
			
		||||
    CoreCommentsCountChangedEventData,
 | 
			
		||||
    CoreCommentsData,
 | 
			
		||||
    CoreCommentsProvider,
 | 
			
		||||
} from '@features/comments/services/comments';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCommentsSync,
 | 
			
		||||
    CoreCommentsSyncAutoSyncData,
 | 
			
		||||
    CoreCommentsSyncProvider,
 | 
			
		||||
} from '@features/comments/services/comments-sync';
 | 
			
		||||
import { IonContent, IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { ContextLevel, CoreConstants } from '@/core/constants';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { ModalController, Translate } from '@singletons';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { CoreCommentsOffline } from '@features/comments/services/comments-offline';
 | 
			
		||||
import { CoreCommentsDBRecord } from '@features/comments/services/database/comments';
 | 
			
		||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
			
		||||
import { CoreCommentsAddComponent } from '@features/comments/components/add/add-modal';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays comments.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-core-comments-viewer',
 | 
			
		||||
    templateUrl: 'viewer.html',
 | 
			
		||||
    animations: [CoreAnimations.SLIDE_IN_OUT],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCommentsViewerPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(IonContent) content?: IonContent;
 | 
			
		||||
 | 
			
		||||
    comments: CoreCommentsDataWithUser[] = [];
 | 
			
		||||
    commentsLoaded = false;
 | 
			
		||||
    contextLevel!: ContextLevel;
 | 
			
		||||
    instanceId!: number;
 | 
			
		||||
    componentName!: string;
 | 
			
		||||
    itemId = 0;
 | 
			
		||||
    area = '';
 | 
			
		||||
    page = 0;
 | 
			
		||||
    title = '';
 | 
			
		||||
    courseId?: number;
 | 
			
		||||
    canLoadMore = false;
 | 
			
		||||
    loadMoreError = false;
 | 
			
		||||
    canAddComments = false;
 | 
			
		||||
    canDeleteComments = false;
 | 
			
		||||
    showDelete = false;
 | 
			
		||||
    hasOffline = false;
 | 
			
		||||
    refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
    syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
    offlineComment?: CoreCommentsOfflineWithUser;
 | 
			
		||||
    currentUserId: number;
 | 
			
		||||
 | 
			
		||||
    protected addDeleteCommentsAvailable = false;
 | 
			
		||||
    protected syncObserver?: CoreEventObserver;
 | 
			
		||||
    protected currentUser?: CoreUserProfile;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected route: ActivatedRoute,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.currentUserId = CoreSites.instance.getCurrentSiteUserId();
 | 
			
		||||
 | 
			
		||||
        // Refresh data if comments are synchronized automatically.
 | 
			
		||||
        this.syncObserver = CoreEvents.on<CoreCommentsSyncAutoSyncData>(CoreCommentsSyncProvider.AUTO_SYNCED, (data) => {
 | 
			
		||||
            if (data.contextLevel == this.contextLevel && data.instanceId == this.instanceId &&
 | 
			
		||||
                    data.componentName == this.componentName && data.itemId == this.itemId && data.area == this.area) {
 | 
			
		||||
                // Show the sync warnings.
 | 
			
		||||
                this.showSyncWarnings(data.warnings);
 | 
			
		||||
 | 
			
		||||
                // Refresh the data.
 | 
			
		||||
                this.commentsLoaded = false;
 | 
			
		||||
                this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
                this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
 | 
			
		||||
                this.content?.scrollToTop();
 | 
			
		||||
 | 
			
		||||
                this.page = 0;
 | 
			
		||||
                this.comments = [];
 | 
			
		||||
                this.fetchComments(false);
 | 
			
		||||
            }
 | 
			
		||||
        }, CoreSites.instance.getCurrentSiteId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * View loaded.
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        // Is implicit the user can delete if he can add.
 | 
			
		||||
        this.addDeleteCommentsAvailable = await CoreComments.instance.isAddCommentsAvailable();
 | 
			
		||||
        this.currentUserId = CoreSites.instance.getCurrentSiteUserId();
 | 
			
		||||
 | 
			
		||||
        this.commentsLoaded = false;
 | 
			
		||||
        this.contextLevel = CoreNavigator.instance.getRouteParam<ContextLevel>('contextLevel')!;
 | 
			
		||||
        this.instanceId = CoreNavigator.instance.getRouteNumberParam('instanceId')!;
 | 
			
		||||
        this.componentName = CoreNavigator.instance.getRouteParam<string>('componentName')!;
 | 
			
		||||
        this.itemId = CoreNavigator.instance.getRouteNumberParam('itemId')!;
 | 
			
		||||
        this.area = CoreNavigator.instance.getRouteParam('area') || '';
 | 
			
		||||
        this.title = CoreNavigator.instance.getRouteNumberParam('title') ||
 | 
			
		||||
            Translate.instance.instant('core.comments.comments');
 | 
			
		||||
        this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId');
 | 
			
		||||
 | 
			
		||||
        await this.fetchComments(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetches the comments.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sync When to resync comments.
 | 
			
		||||
     * @param showErrors When to display errors or not.
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchComments(sync: boolean, showErrors = false): Promise<void> {
 | 
			
		||||
        this.loadMoreError = false;
 | 
			
		||||
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            await CoreUtils.instance.ignoreErrors(this.syncComments(showErrors));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // Get comments data.
 | 
			
		||||
            const commentsResponse = await CoreComments.instance.getComments(
 | 
			
		||||
                this.contextLevel,
 | 
			
		||||
                this.instanceId,
 | 
			
		||||
                this.componentName,
 | 
			
		||||
                this.itemId,
 | 
			
		||||
                this.area,
 | 
			
		||||
                this.page,
 | 
			
		||||
            );
 | 
			
		||||
            this.canAddComments = this.addDeleteCommentsAvailable && !!commentsResponse.canpost;
 | 
			
		||||
 | 
			
		||||
            let comments = commentsResponse.comments.sort((a, b) => b.timecreated - a.timecreated);
 | 
			
		||||
            if (typeof commentsResponse.count != 'undefined') {
 | 
			
		||||
                this.canLoadMore = (this.comments.length + comments.length) > commentsResponse.count;
 | 
			
		||||
            } else {
 | 
			
		||||
                // Old style.
 | 
			
		||||
                this.canLoadMore = commentsResponse.comments.length > 0 &&
 | 
			
		||||
                    commentsResponse.comments.length >= CoreCommentsProvider.pageSize;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            comments = await Promise.all(comments.map((comment) => this.loadCommentProfile(comment)));
 | 
			
		||||
 | 
			
		||||
            this.comments = this.comments.concat(comments);
 | 
			
		||||
 | 
			
		||||
            this.canDeleteComments = this.addDeleteCommentsAvailable &&
 | 
			
		||||
                (this.hasOffline || this.comments.some((comment) => !!comment.delete));
 | 
			
		||||
 | 
			
		||||
            await this.loadOfflineData();
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
 | 
			
		||||
            if (error && this.componentName == 'assignsubmission_comments') {
 | 
			
		||||
                CoreDomUtils.instance.showAlertTranslated('core.notice', 'core.comments.commentsnotworking');
 | 
			
		||||
            } else {
 | 
			
		||||
                CoreDomUtils.instance.showErrorModalDefault(error, Translate.instance.instant('core.error') + ': get_comments');
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.commentsLoaded = true;
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
            this.syncIcon = CoreConstants.ICON_SYNC;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function to load more commemts.
 | 
			
		||||
     *
 | 
			
		||||
     * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    loadMore(infiniteComplete?: () => void): Promise<void> {
 | 
			
		||||
        this.page++;
 | 
			
		||||
        this.canLoadMore = false;
 | 
			
		||||
 | 
			
		||||
        return this.fetchComments(true).finally(() => {
 | 
			
		||||
            infiniteComplete && infiniteComplete();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh the comments.
 | 
			
		||||
     *
 | 
			
		||||
     * @param showErrors Whether to display errors or not.
 | 
			
		||||
     * @param refresher Refresher.
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async refreshComments(showErrors: boolean, refresher?: CustomEvent<IonRefresher>): Promise<void> {
 | 
			
		||||
        this.commentsLoaded = false;
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await this.invalidateComments();
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.page = 0;
 | 
			
		||||
            this.comments = [];
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                await this.fetchComments(true, showErrors);
 | 
			
		||||
            } finally {
 | 
			
		||||
                refresher?.detail.complete();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show sync warnings if any.
 | 
			
		||||
     *
 | 
			
		||||
     * @param warnings the warnings
 | 
			
		||||
     */
 | 
			
		||||
    private showSyncWarnings(warnings: string[]): void {
 | 
			
		||||
        const message = CoreTextUtils.instance.buildMessage(warnings);
 | 
			
		||||
        if (message) {
 | 
			
		||||
            CoreDomUtils.instance.showErrorModal(message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Tries to synchronize comments.
 | 
			
		||||
     *
 | 
			
		||||
     * @param showErrors Whether to display errors or not.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    private async syncComments(showErrors: boolean): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            const result = await CoreCommentsSync.instance.syncComments(
 | 
			
		||||
                this.contextLevel,
 | 
			
		||||
                this.instanceId,
 | 
			
		||||
                this.componentName,
 | 
			
		||||
                this.itemId,
 | 
			
		||||
                this.area,
 | 
			
		||||
            );
 | 
			
		||||
            this.showSyncWarnings(result?.warnings || []);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (showErrors) {
 | 
			
		||||
                CoreDomUtils.instance.showErrorModalDefault(error, 'core.errorsync', true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            throw new CoreError(error);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a new comment to the list.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e Event.
 | 
			
		||||
     */
 | 
			
		||||
    async addComment(e: Event): Promise<void> {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        const params: Params = {
 | 
			
		||||
            contextLevel: this.contextLevel,
 | 
			
		||||
            instanceId: this.instanceId,
 | 
			
		||||
            componentName: this.componentName,
 | 
			
		||||
            itemId: this.itemId,
 | 
			
		||||
            area: this.area,
 | 
			
		||||
            content: this.offlineComment ? this.offlineComment!.content : '',
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const modal = await ModalController.instance.create({
 | 
			
		||||
            component: CoreCommentsAddComponent,
 | 
			
		||||
            componentProps: params,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await modal.present();
 | 
			
		||||
 | 
			
		||||
        const result = await modal.onDidDismiss();
 | 
			
		||||
 | 
			
		||||
        if (result?.data?.comment) {
 | 
			
		||||
            this.invalidateComments();
 | 
			
		||||
 | 
			
		||||
            const addedComments = await this.loadCommentProfile(result.data.comment);
 | 
			
		||||
            // Add the comment to the top.
 | 
			
		||||
            this.comments = [addedComments].concat(this.comments);
 | 
			
		||||
            this.canDeleteComments = this.addDeleteCommentsAvailable;
 | 
			
		||||
 | 
			
		||||
            CoreEvents.trigger<CoreCommentsCountChangedEventData>(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, {
 | 
			
		||||
                contextLevel: this.contextLevel,
 | 
			
		||||
                instanceId: this.instanceId,
 | 
			
		||||
                component: this.componentName,
 | 
			
		||||
                itemId: this.itemId,
 | 
			
		||||
                area: this.area,
 | 
			
		||||
                countChange: 1,
 | 
			
		||||
            }, CoreSites.instance.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
        } else if (result?.data?.comment === false) {
 | 
			
		||||
            // Comments added in offline mode.
 | 
			
		||||
            return this.loadOfflineData();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a comment.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e Click event.
 | 
			
		||||
     * @param comment Comment to delete.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteComment(e: Event, comment: CoreCommentsDataWithUser | CoreCommentsOfflineWithUser): Promise<void> {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        const modified = 'lastmodified' in comment
 | 
			
		||||
            ? comment.lastmodified
 | 
			
		||||
            : comment.timecreated;
 | 
			
		||||
        const time = CoreTimeUtils.instance.userDate(
 | 
			
		||||
            modified * 1000,
 | 
			
		||||
            'core.strftimerecentfull',
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const deleteComment: CoreCommentsCommentBasicData = {
 | 
			
		||||
            contextlevel: this.contextLevel,
 | 
			
		||||
            instanceid: this.instanceId,
 | 
			
		||||
            component: this.componentName,
 | 
			
		||||
            itemid: this.itemId,
 | 
			
		||||
            area: this.area,
 | 
			
		||||
            content: comment.content,
 | 
			
		||||
            id: 'id' in comment ? comment.id : undefined,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreDomUtils.instance.showDeleteConfirm('core.comments.deletecommentbyon', {
 | 
			
		||||
                $a:
 | 
			
		||||
                    { user: comment.fullname || '', time: time },
 | 
			
		||||
            });
 | 
			
		||||
        } catch {
 | 
			
		||||
            // User cancelled, nothing to do.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const deletedOnline = await CoreComments.instance.deleteComment(deleteComment);
 | 
			
		||||
            this.showDelete = false;
 | 
			
		||||
 | 
			
		||||
            if (deletedOnline) {
 | 
			
		||||
                const index = this.comments.findIndex((comment) => comment.id == comment.id);
 | 
			
		||||
                if (index >= 0) {
 | 
			
		||||
                    this.comments.splice(index, 1);
 | 
			
		||||
 | 
			
		||||
                    CoreEvents.trigger<CoreCommentsCountChangedEventData>(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, {
 | 
			
		||||
                        contextLevel: this.contextLevel,
 | 
			
		||||
                        instanceId: this.instanceId,
 | 
			
		||||
                        component: this.componentName,
 | 
			
		||||
                        itemId: this.itemId,
 | 
			
		||||
                        area: this.area,
 | 
			
		||||
                        countChange: -1,
 | 
			
		||||
                    }, CoreSites.instance.getCurrentSiteId());
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                this.loadOfflineData();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.invalidateComments();
 | 
			
		||||
 | 
			
		||||
            CoreDomUtils.instance.showToast('core.comments.eventcommentdeleted', true, 3000);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.instance.showErrorModalDefault(error, 'Delete comment failed.');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidate comments.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected invalidateComments(): Promise<void> {
 | 
			
		||||
        return CoreComments.instance.invalidateCommentsData(
 | 
			
		||||
            this.contextLevel,
 | 
			
		||||
            this.instanceId,
 | 
			
		||||
            this.componentName,
 | 
			
		||||
            this.itemId,
 | 
			
		||||
            this.area,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads the profile info onto the comment object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param  comment Comment object.
 | 
			
		||||
     * @return Promise resolved with modified comment when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadCommentProfile(comment: CoreCommentsDataWithUser): Promise<CoreCommentsDataWithUser> {
 | 
			
		||||
        // Get the user profile image.
 | 
			
		||||
        try {
 | 
			
		||||
            const user = await CoreUser.instance.getProfile(comment.userid!, undefined, true);
 | 
			
		||||
            comment.profileimageurl = user.profileimageurl;
 | 
			
		||||
            comment.fullname = user.fullname;
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return comment;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load offline comments.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadOfflineData(): Promise<void> {
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
        let hasDeletedComments = false;
 | 
			
		||||
 | 
			
		||||
        // Load the only offline comment allowed if any.
 | 
			
		||||
        promises.push(CoreCommentsOffline.instance.getComment(
 | 
			
		||||
            this.contextLevel,
 | 
			
		||||
            this.instanceId,
 | 
			
		||||
            this.componentName,
 | 
			
		||||
            this.itemId,
 | 
			
		||||
            this.area,
 | 
			
		||||
        ).then(async (offlineComment) => {
 | 
			
		||||
            this.offlineComment = offlineComment;
 | 
			
		||||
 | 
			
		||||
            if (!offlineComment) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!this.currentUser) {
 | 
			
		||||
                this.currentUser = await CoreUser.instance.getProfile(this.currentUserId, undefined, true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.currentUser) {
 | 
			
		||||
                this.offlineComment!.profileimageurl = this.currentUser.profileimageurl;
 | 
			
		||||
                this.offlineComment!.fullname = this.currentUser.fullname;
 | 
			
		||||
            }
 | 
			
		||||
            this.offlineComment!.userid = this.currentUserId;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        // Load deleted comments offline.
 | 
			
		||||
        promises.push(CoreCommentsOffline.instance.getDeletedComments(
 | 
			
		||||
            this.contextLevel,
 | 
			
		||||
            this.instanceId,
 | 
			
		||||
            this.componentName,
 | 
			
		||||
            this.itemId,
 | 
			
		||||
            this.area,
 | 
			
		||||
        ).then((deletedComments) => {
 | 
			
		||||
            hasDeletedComments = deletedComments && deletedComments.length > 0;
 | 
			
		||||
 | 
			
		||||
            if (hasDeletedComments) {
 | 
			
		||||
                deletedComments.forEach((deletedComment) => {
 | 
			
		||||
                    const comment = this.comments.find((comment) => comment.id == deletedComment.commentid);
 | 
			
		||||
 | 
			
		||||
                    if (comment) {
 | 
			
		||||
                        comment.deleted = !!deletedComment.deleted;
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
        this.hasOffline = !!this.offlineComment || hasDeletedComments;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Restore a comment.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e Click event.
 | 
			
		||||
     * @param comment Comment to delete.
 | 
			
		||||
     */
 | 
			
		||||
    async undoDeleteComment(e: Event, comment: CoreCommentsDataWithUser): Promise<void> {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        await CoreCommentsOffline.instance.undoDeleteComment(comment.id);
 | 
			
		||||
 | 
			
		||||
        comment.deleted = false;
 | 
			
		||||
        this.showDelete = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggle delete.
 | 
			
		||||
     */
 | 
			
		||||
    toggleDelete(): void {
 | 
			
		||||
        this.showDelete = !this.showDelete;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Page destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.syncObserver && this.syncObserver.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type CoreCommentsDataWithUser = CoreCommentsData & {
 | 
			
		||||
    profileimageurl?: string;
 | 
			
		||||
    fullname?: string;
 | 
			
		||||
    deleted?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type CoreCommentsOfflineWithUser = CoreCommentsDBRecord & {
 | 
			
		||||
    profileimageurl?: string;
 | 
			
		||||
    fullname?: string;
 | 
			
		||||
    userid?: number;
 | 
			
		||||
    deleted?: boolean;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										304
									
								
								src/core/features/comments/services/comments-offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										304
									
								
								src/core/features/comments/services/comments-offline.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,304 @@
 | 
			
		||||
// (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 { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { COMMENTS_TABLE, COMMENTS_DELETED_TABLE, CoreCommentsDBRecord, CoreCommentsDeletedDBRecord } from './database/comments';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to handle offline comments.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class CoreCommentsOfflineProvider {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all offline comments.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with comments.
 | 
			
		||||
     */
 | 
			
		||||
    async getAllComments(siteId?: string): Promise<(CoreCommentsDBRecord | CoreCommentsDeletedDBRecord)[]> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
        const results = await Promise.all([
 | 
			
		||||
            site.getDb().getRecords(COMMENTS_TABLE),
 | 
			
		||||
            site.getDb().getRecords(COMMENTS_DELETED_TABLE),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        return [].concat.apply([], results);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get an offline comment.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with the comments.
 | 
			
		||||
     */
 | 
			
		||||
    async getComment(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreCommentsDBRecord | undefined> {
 | 
			
		||||
        try {
 | 
			
		||||
            const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
            return await site.getDb().getRecord(COMMENTS_TABLE, {
 | 
			
		||||
                contextlevel: contextLevel,
 | 
			
		||||
                instanceid: instanceId,
 | 
			
		||||
                component: component,
 | 
			
		||||
                itemid: itemId,
 | 
			
		||||
                area: area,
 | 
			
		||||
            });
 | 
			
		||||
        } catch {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all offline comments added or deleted of a special area.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with the comments.
 | 
			
		||||
     */
 | 
			
		||||
    async getComments(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<(CoreCommentsDBRecord | CoreCommentsDeletedDBRecord)[]> {
 | 
			
		||||
        let comments: (CoreCommentsDBRecord | CoreCommentsDeletedDBRecord)[] = [];
 | 
			
		||||
 | 
			
		||||
        siteId = siteId || CoreSites.instance.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const comment = await this.getComment(contextLevel, instanceId, component, itemId, area, siteId);
 | 
			
		||||
 | 
			
		||||
        comments = comment ? [comment] : [];
 | 
			
		||||
 | 
			
		||||
        const deletedComments = await this.getDeletedComments(contextLevel, instanceId, component, itemId, area, siteId);
 | 
			
		||||
        comments = comments.concat(deletedComments);
 | 
			
		||||
 | 
			
		||||
        return comments;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all offline deleted comments.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with comments.
 | 
			
		||||
     */
 | 
			
		||||
    async getAllDeletedComments(siteId?: string): Promise<CoreCommentsDeletedDBRecord[]> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return await site.getDb().getRecords(COMMENTS_DELETED_TABLE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get an offline comment.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with the comments.
 | 
			
		||||
     */
 | 
			
		||||
    async getDeletedComments(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreCommentsDeletedDBRecord[]> {
 | 
			
		||||
        try {
 | 
			
		||||
            const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
            return await site.getDb().getRecords(COMMENTS_DELETED_TABLE, {
 | 
			
		||||
                contextlevel: contextLevel,
 | 
			
		||||
                instanceid: instanceId,
 | 
			
		||||
                component: component,
 | 
			
		||||
                itemid: itemId,
 | 
			
		||||
                area: area,
 | 
			
		||||
            });
 | 
			
		||||
        } catch {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove an offline comment.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if deleted, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async removeComment(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.getDb().deleteRecords(COMMENTS_TABLE, {
 | 
			
		||||
            contextlevel: contextLevel,
 | 
			
		||||
            instanceid: instanceId,
 | 
			
		||||
            component: component,
 | 
			
		||||
            itemid: itemId,
 | 
			
		||||
            area: area,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove an offline deleted comment.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if deleted, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async removeDeletedComments(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.getDb().deleteRecords(COMMENTS_DELETED_TABLE, {
 | 
			
		||||
            contextlevel: contextLevel,
 | 
			
		||||
            instanceid: instanceId,
 | 
			
		||||
            component: component,
 | 
			
		||||
            itemid: itemId,
 | 
			
		||||
            area: area,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save a comment to be sent later.
 | 
			
		||||
     *
 | 
			
		||||
     * @param content Comment text.
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if stored, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async saveComment(
 | 
			
		||||
        content: string,
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreCommentsDBRecord> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
        const now = CoreTimeUtils.instance.timestamp();
 | 
			
		||||
        const data: CoreCommentsDBRecord = {
 | 
			
		||||
            contextlevel: contextLevel,
 | 
			
		||||
            instanceid: instanceId,
 | 
			
		||||
            component: component,
 | 
			
		||||
            itemid: itemId,
 | 
			
		||||
            area: area,
 | 
			
		||||
            content: content,
 | 
			
		||||
            lastmodified: now,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await site.getDb().insertRecord(COMMENTS_TABLE, data);
 | 
			
		||||
 | 
			
		||||
        return data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a comment offline to be sent later.
 | 
			
		||||
     *
 | 
			
		||||
     * @param commentId Comment ID.
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if stored, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteComment(
 | 
			
		||||
        commentId: number,
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
        const now = CoreTimeUtils.instance.timestamp();
 | 
			
		||||
        const data: CoreCommentsDeletedDBRecord = {
 | 
			
		||||
            contextlevel: contextLevel,
 | 
			
		||||
            instanceid: instanceId,
 | 
			
		||||
            component: component,
 | 
			
		||||
            itemid: itemId,
 | 
			
		||||
            area: area,
 | 
			
		||||
            commentid: commentId,
 | 
			
		||||
            deleted: now,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await site.getDb().insertRecord(COMMENTS_DELETED_TABLE, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Undo delete a comment.
 | 
			
		||||
     *
 | 
			
		||||
     * @param commentId Comment ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if deleted, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async undoDeleteComment(commentId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.getDb().deleteRecords(COMMENTS_DELETED_TABLE, { commentid: commentId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const CoreCommentsOffline = makeSingleton(CoreCommentsOfflineProvider);
 | 
			
		||||
							
								
								
									
										336
									
								
								src/core/features/comments/services/comments-sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								src/core/features/comments/services/comments-sync.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,336 @@
 | 
			
		||||
// (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 { CoreSyncBaseProvider } from '@classes/base-sync';
 | 
			
		||||
import { CoreComments, CoreCommentsCountChangedEventData, CoreCommentsProvider } from './comments';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { CoreCommentsOffline } from './comments-offline';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreNetworkError } from '@classes/errors/network-error';
 | 
			
		||||
import { CoreCommentsDBRecord, CoreCommentsDeletedDBRecord } from './database/comments';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to sync omments.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class CoreCommentsSyncProvider extends CoreSyncBaseProvider<CoreCommentsSyncResult> {
 | 
			
		||||
 | 
			
		||||
    static readonly AUTO_SYNCED = 'core_comments_autom_synced';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('CoreCommentsSync');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Try to synchronize all the comments 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.
 | 
			
		||||
     */
 | 
			
		||||
    syncAllComments(siteId?: string, force?: boolean): Promise<void> {
 | 
			
		||||
        return this.syncOnSites('all comments', this.syncAllCommentsFunc.bind(this, siteId, force), siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronize all the comments in a certain site
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID to sync.
 | 
			
		||||
     * @param force Wether to force sync not depending on last execution.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected if sync fails.
 | 
			
		||||
     */
 | 
			
		||||
    private async syncAllCommentsFunc(siteId: string, force: boolean): Promise<void> {
 | 
			
		||||
        const comments = await CoreCommentsOffline.instance.getAllComments(siteId);
 | 
			
		||||
 | 
			
		||||
        const commentsUnique: { [syncId: string]: (CoreCommentsDBRecord | CoreCommentsDeletedDBRecord) } = {};
 | 
			
		||||
        // Get Unique array.
 | 
			
		||||
        comments.forEach((comment) => {
 | 
			
		||||
            const syncId = this.getSyncId(
 | 
			
		||||
                comment.contextlevel,
 | 
			
		||||
                comment.instanceid,
 | 
			
		||||
                comment.component,
 | 
			
		||||
                comment.itemid,
 | 
			
		||||
                comment.area,
 | 
			
		||||
            );
 | 
			
		||||
            commentsUnique[syncId] = comment;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Sync all courses.
 | 
			
		||||
        const promises = Object.keys(commentsUnique).map(async (key) => {
 | 
			
		||||
            const comment = commentsUnique[key];
 | 
			
		||||
 | 
			
		||||
            const result = await (force
 | 
			
		||||
                ? this.syncComments(
 | 
			
		||||
                    comment.contextlevel,
 | 
			
		||||
                    comment.instanceid,
 | 
			
		||||
                    comment.component,
 | 
			
		||||
                    comment.itemid,
 | 
			
		||||
                    comment.area,
 | 
			
		||||
                    siteId,
 | 
			
		||||
                )
 | 
			
		||||
                : this.syncCommentsIfNeeded(
 | 
			
		||||
                    comment.contextlevel,
 | 
			
		||||
                    comment.instanceid,
 | 
			
		||||
                    comment.component,
 | 
			
		||||
                    comment.itemid,
 | 
			
		||||
                    comment.area,
 | 
			
		||||
                    siteId,
 | 
			
		||||
                ));
 | 
			
		||||
 | 
			
		||||
            if (typeof result != 'undefined') {
 | 
			
		||||
                // Sync successful, send event.
 | 
			
		||||
                CoreEvents.trigger<CoreCommentsSyncAutoSyncData>(CoreCommentsSyncProvider.AUTO_SYNCED, {
 | 
			
		||||
                    contextLevel: comment.contextlevel,
 | 
			
		||||
                    instanceId: comment.instanceid,
 | 
			
		||||
                    componentName: comment.component,
 | 
			
		||||
                    itemId: comment.itemid,
 | 
			
		||||
                    area: comment.area,
 | 
			
		||||
                    warnings: result.warnings,
 | 
			
		||||
                }, siteId);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sync course comments only if a certain time has passed since the last time.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the comments are synced or if they don't need to be synced.
 | 
			
		||||
     */
 | 
			
		||||
    private async syncCommentsIfNeeded(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreCommentsSyncResult | undefined> {
 | 
			
		||||
        const syncId = this.getSyncId(contextLevel, instanceId, component, itemId, area);
 | 
			
		||||
 | 
			
		||||
        const needed = await this.isSyncNeeded(syncId, siteId);
 | 
			
		||||
 | 
			
		||||
        if (needed) {
 | 
			
		||||
            return this.syncComments(contextLevel, instanceId, component, itemId, area, siteId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronize comments in a particular area.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    syncComments(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreCommentsSyncResult> {
 | 
			
		||||
        siteId = siteId || CoreSites.instance.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const syncId = this.getSyncId(contextLevel, instanceId, component, itemId, area);
 | 
			
		||||
 | 
			
		||||
        if (this.isSyncing(syncId, siteId)) {
 | 
			
		||||
            // There's already a sync ongoing for comments, return the promise.
 | 
			
		||||
            return this.getOngoingSync(syncId, siteId)!;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.logger.debug('Try to sync comments ' + syncId + ' in site ' + siteId);
 | 
			
		||||
 | 
			
		||||
        const syncPromise = this.performSyncComments(contextLevel, instanceId, component, itemId, area, siteId);
 | 
			
		||||
 | 
			
		||||
        return this.addOngoingSync(syncId, syncPromise, siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Performs the syncronization of comments in a particular area.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    private async performSyncComments(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId: string,
 | 
			
		||||
    ): Promise<CoreCommentsSyncResult> {
 | 
			
		||||
 | 
			
		||||
        const result: CoreCommentsSyncResult = {
 | 
			
		||||
            warnings: [],
 | 
			
		||||
            updated: false,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Get offline comments to be sent.
 | 
			
		||||
        const comments = await CoreCommentsOffline.instance.getComments(contextLevel, instanceId, component, itemId, area, siteId);
 | 
			
		||||
        if (!comments.length) {
 | 
			
		||||
            // Nothing to sync.
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!CoreApp.instance.isOnline()) {
 | 
			
		||||
            // Cannot sync in offline.
 | 
			
		||||
            throw new CoreNetworkError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const errors: string[] = [];
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
        const deleteCommentIds: number[] = [];
 | 
			
		||||
        let countChange = 0;
 | 
			
		||||
 | 
			
		||||
        comments.forEach((comment) => {
 | 
			
		||||
            if ('deleted' in comment) {
 | 
			
		||||
                deleteCommentIds.push(comment.commentid);
 | 
			
		||||
            } else {
 | 
			
		||||
                promises.push(CoreComments.instance.addCommentOnline(
 | 
			
		||||
                    comment.content,
 | 
			
		||||
                    contextLevel,
 | 
			
		||||
                    instanceId,
 | 
			
		||||
                    component,
 | 
			
		||||
                    itemId,
 | 
			
		||||
                    area,
 | 
			
		||||
                    siteId,
 | 
			
		||||
                ).then(() => {
 | 
			
		||||
                    countChange++;
 | 
			
		||||
 | 
			
		||||
                    return CoreCommentsOffline.instance.removeComment(contextLevel, instanceId, component, itemId, area, siteId);
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (deleteCommentIds.length > 0) {
 | 
			
		||||
            promises.push(CoreComments.instance.deleteCommentsOnline(
 | 
			
		||||
                deleteCommentIds,
 | 
			
		||||
                contextLevel,
 | 
			
		||||
                instanceId,
 | 
			
		||||
                component,
 | 
			
		||||
                itemId,
 | 
			
		||||
                area,
 | 
			
		||||
                siteId,
 | 
			
		||||
            ).then(() => {
 | 
			
		||||
                countChange--;
 | 
			
		||||
 | 
			
		||||
                return CoreCommentsOffline.instance.removeDeletedComments(
 | 
			
		||||
                    contextLevel,
 | 
			
		||||
                    instanceId,
 | 
			
		||||
                    component,
 | 
			
		||||
                    itemId,
 | 
			
		||||
                    area,
 | 
			
		||||
                    siteId,
 | 
			
		||||
                );
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Send the comments.
 | 
			
		||||
        try {
 | 
			
		||||
            await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
            result.updated = true;
 | 
			
		||||
 | 
			
		||||
            CoreEvents.trigger<CoreCommentsCountChangedEventData>(CoreCommentsProvider.COMMENTS_COUNT_CHANGED_EVENT, {
 | 
			
		||||
                contextLevel: contextLevel,
 | 
			
		||||
                instanceId: instanceId,
 | 
			
		||||
                component,
 | 
			
		||||
                itemId: itemId,
 | 
			
		||||
                area: area,
 | 
			
		||||
                countChange: countChange,
 | 
			
		||||
            }, CoreSites.instance.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
            // Fetch the comments from server to be sure they're up to date.
 | 
			
		||||
            await CoreUtils.instance.ignoreErrors(
 | 
			
		||||
                CoreComments.instance.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId),
 | 
			
		||||
            );
 | 
			
		||||
            await CoreUtils.instance.ignoreErrors(
 | 
			
		||||
                CoreComments.instance.getComments(contextLevel, instanceId, component, itemId, area, 0, siteId),
 | 
			
		||||
            );
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (CoreUtils.instance.isWebServiceError(error)) {
 | 
			
		||||
            // It's a WebService error, this means the user cannot send comments.
 | 
			
		||||
                errors.push(error.message);
 | 
			
		||||
            } else {
 | 
			
		||||
                // Not a WebService error, reject the synchronization to try again.
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (errors && errors.length) {
 | 
			
		||||
            errors.forEach((error) => {
 | 
			
		||||
                result.warnings.push(Translate.instance.instant('core.comments.warningcommentsnotsent', {
 | 
			
		||||
                    error: error,
 | 
			
		||||
                }));
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // All done, return the warnings.
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the ID of a comments sync.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @return Sync ID.
 | 
			
		||||
     */
 | 
			
		||||
    protected getSyncId(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = ''): string {
 | 
			
		||||
        return contextLevel + '#' + instanceId + '#' + component + '#' + itemId + '#' + area;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const CoreCommentsSync = makeSingleton(CoreCommentsSyncProvider);
 | 
			
		||||
 | 
			
		||||
export type CoreCommentsSyncResult = {
 | 
			
		||||
    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 CoreCommentsSyncAutoSyncData = {
 | 
			
		||||
    contextLevel: string;
 | 
			
		||||
    instanceId: number;
 | 
			
		||||
    componentName: string;
 | 
			
		||||
    itemId: number;
 | 
			
		||||
    area: string;
 | 
			
		||||
    warnings: string[];
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										625
									
								
								src/core/features/comments/services/comments.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										625
									
								
								src/core/features/comments/services/comments.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,625 @@
 | 
			
		||||
// (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 { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreWSExternalWarning } from '@services/ws';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreCommentsOffline } from './comments-offline';
 | 
			
		||||
 | 
			
		||||
const ROOT_CACHE_KEY = 'mmComments:';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service that provides some features regarding comments.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class CoreCommentsProvider {
 | 
			
		||||
 | 
			
		||||
    static readonly REFRESH_COMMENTS_EVENT = 'core_comments_refresh_comments';
 | 
			
		||||
    static readonly COMMENTS_COUNT_CHANGED_EVENT = 'core_comments_count_changed';
 | 
			
		||||
 | 
			
		||||
    static pageSize = 1; // At least it will be one.
 | 
			
		||||
    static pageSizeOK = false; // If true, the pageSize is definitive. If not, it's a temporal value to reduce WS calls.
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize the module service.
 | 
			
		||||
     */
 | 
			
		||||
    initialize(): void {
 | 
			
		||||
        // Reset comments page size.
 | 
			
		||||
        CoreEvents.on(CoreEvents.LOGIN, () => {
 | 
			
		||||
            CoreCommentsProvider.pageSize = 1;
 | 
			
		||||
            CoreCommentsProvider.pageSizeOK = false;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a comment.
 | 
			
		||||
     *
 | 
			
		||||
     * @param content Comment text.
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with boolean: true if comment was sent to server, false if stored in device.
 | 
			
		||||
     */
 | 
			
		||||
    async addComment(
 | 
			
		||||
        content: string,
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreCommentsData | boolean> {
 | 
			
		||||
        siteId = siteId || CoreSites.instance.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        // Convenience function to store a comment to be synchronized later.
 | 
			
		||||
        const storeOffline = async (): Promise<boolean> => {
 | 
			
		||||
            await CoreCommentsOffline.instance.saveComment(content, contextLevel, instanceId, component, itemId, area, siteId);
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (!CoreApp.instance.isOnline()) {
 | 
			
		||||
            // App is offline, store the comment.
 | 
			
		||||
            return storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Send comment to server.
 | 
			
		||||
        try {
 | 
			
		||||
            return await this.addCommentOnline(content, contextLevel, instanceId, component, itemId, area, siteId);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (CoreUtils.instance.isWebServiceError(error)) {
 | 
			
		||||
                // It's a WebService error, the user cannot send the message so don't store it.
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a comment. It will fail if offline or cannot connect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param content Comment text.
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when added, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async addCommentOnline(
 | 
			
		||||
        content: string,
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreCommentsData> {
 | 
			
		||||
        const comments: CoreCommentsCommentBasicData[] = [
 | 
			
		||||
            {
 | 
			
		||||
                contextlevel: contextLevel,
 | 
			
		||||
                instanceid: instanceId,
 | 
			
		||||
                component: component,
 | 
			
		||||
                itemid: itemId,
 | 
			
		||||
                area: area,
 | 
			
		||||
                content: content,
 | 
			
		||||
            },
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        const commentsResponse = await this.addCommentsOnline(comments, siteId);
 | 
			
		||||
 | 
			
		||||
        // A comment was added, invalidate them.
 | 
			
		||||
        await CoreUtils.instance.ignoreErrors(
 | 
			
		||||
            this.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return commentsResponse![0];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add several comments. It will fail if offline or cannot connect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param comments Comments to save.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that comments
 | 
			
		||||
     *         have been added, the resolve param can contain errors for comments not sent.
 | 
			
		||||
     */
 | 
			
		||||
    async addCommentsOnline(
 | 
			
		||||
        comments: CoreCommentsCommentBasicData[],
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreCommentsAddCommentsWSResponse | undefined> {
 | 
			
		||||
        if (!comments || !comments.length) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
        const data: CoreCommentsAddCommentsWSParams = {
 | 
			
		||||
            comments: comments,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return await site.write('core_comment_add_comments', data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if Calendar is disabled in a certain site.
 | 
			
		||||
     *
 | 
			
		||||
     * @param site Site. If not defined, use current site.
 | 
			
		||||
     * @return Whether it's disabled.
 | 
			
		||||
     */
 | 
			
		||||
    areCommentsDisabledInSite(site?: CoreSite): boolean {
 | 
			
		||||
        site = site || CoreSites.instance.getCurrentSite();
 | 
			
		||||
 | 
			
		||||
        return !!site?.isFeatureDisabled('NoDelegate_CoreComments');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if comments are disabled in a certain site.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site Id. If not defined, use current site.
 | 
			
		||||
     * @return Promise resolved with true if disabled, rejected or resolved with false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async areCommentsDisabled(siteId?: string): Promise<boolean> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return this.areCommentsDisabledInSite(site);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a comment.
 | 
			
		||||
     *
 | 
			
		||||
     * @param comment Comment object to delete.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when deleted (with true if deleted in online, false otherwise), rejected otherwise. Promise resolved
 | 
			
		||||
     *         doesn't mean that comments have been deleted, the resolve param can contain errors for comments not deleted.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteComment(comment: CoreCommentsCommentBasicData, siteId?: string): Promise<boolean> {
 | 
			
		||||
        siteId = siteId || CoreSites.instance.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        // Offline comment, just delete it.
 | 
			
		||||
        if (!comment.id) {
 | 
			
		||||
            await CoreCommentsOffline.instance.removeComment(
 | 
			
		||||
                comment.contextlevel,
 | 
			
		||||
                comment.instanceid,
 | 
			
		||||
                comment.component,
 | 
			
		||||
                comment.itemid,
 | 
			
		||||
                comment.area,
 | 
			
		||||
                siteId,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Convenience function to store the action to be synchronized later.
 | 
			
		||||
        const storeOffline = async (): Promise<boolean> => {
 | 
			
		||||
            await CoreCommentsOffline.instance.deleteComment(
 | 
			
		||||
                comment.id!,
 | 
			
		||||
                comment.contextlevel,
 | 
			
		||||
                comment.instanceid,
 | 
			
		||||
                comment.component,
 | 
			
		||||
                comment.itemid,
 | 
			
		||||
                comment.area,
 | 
			
		||||
                siteId,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (!CoreApp.instance.isOnline()) {
 | 
			
		||||
            // App is offline, store the comment.
 | 
			
		||||
            return storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Send comment to server.
 | 
			
		||||
        try {
 | 
			
		||||
            await this.deleteCommentsOnline(
 | 
			
		||||
                [comment.id],
 | 
			
		||||
                comment.contextlevel,
 | 
			
		||||
                comment.instanceid,
 | 
			
		||||
                comment.component,
 | 
			
		||||
                comment.itemid,
 | 
			
		||||
                comment.area,
 | 
			
		||||
                siteId,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (CoreUtils.instance.isWebServiceError(error)) {
 | 
			
		||||
                // It's a WebService error, the user cannot send the comment so don't store it.
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a comment. It will fail if offline or cannot connect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param commentIds Comment IDs to delete.
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that comments
 | 
			
		||||
     *         have been deleted, the resolve param can contain errors for comments not deleted.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteCommentsOnline(
 | 
			
		||||
        commentIds: number[],
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const data: CoreCommentsDeleteCommentsWSParams = {
 | 
			
		||||
            comments: commentIds,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await site.write('core_comment_delete_comments', data);
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.instance.ignoreErrors(
 | 
			
		||||
            this.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId),
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether WS to add/delete comments are available in site.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with true if available, resolved with false or rejected otherwise.
 | 
			
		||||
     * @since 3.8
 | 
			
		||||
     */
 | 
			
		||||
    async isAddCommentsAvailable(siteId?: string): Promise<boolean> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        // First check if it's disabled.
 | 
			
		||||
        if (this.areCommentsDisabledInSite(site)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return site.wsAvailable('core_comment_add_comments');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get cache key for get comments data WS calls.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @return Cache key.
 | 
			
		||||
     */
 | 
			
		||||
    protected getCommentsCacheKey(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
    ): string {
 | 
			
		||||
        return this.getCommentsPrefixCacheKey(contextLevel, instanceId) + ':' + component + ':' + itemId + ':' + area;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get cache key for get comments instance data WS calls.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @return Cache key.
 | 
			
		||||
     */
 | 
			
		||||
    protected getCommentsPrefixCacheKey(contextLevel: string, instanceId: number): string {
 | 
			
		||||
        return ROOT_CACHE_KEY + 'comments:' + contextLevel + ':' + instanceId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieve a list of comments.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param page Page number (0 based). Default 0.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with the comments.
 | 
			
		||||
     */
 | 
			
		||||
    async getComments(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        page: number = 0,
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<CoreCommentsGetCommentsWSResponse> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const params: CoreCommentsGetCommentsWSParams = {
 | 
			
		||||
            contextlevel: contextLevel,
 | 
			
		||||
            instanceid: instanceId,
 | 
			
		||||
            component: component,
 | 
			
		||||
            itemid: itemId,
 | 
			
		||||
            area: area,
 | 
			
		||||
            page: page,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const preSets: CoreSiteWSPreSets = {
 | 
			
		||||
            cacheKey: this.getCommentsCacheKey(contextLevel, instanceId, component, itemId, area),
 | 
			
		||||
            updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
 | 
			
		||||
        };
 | 
			
		||||
        const response = await site.read<CoreCommentsGetCommentsWSResponse>('core_comment_get_comments', params, preSets);
 | 
			
		||||
 | 
			
		||||
        if (response.comments) {
 | 
			
		||||
            // Update pageSize with the greatest count at the moment.
 | 
			
		||||
            if (typeof response.count == 'undefined' && response.comments.length > CoreCommentsProvider.pageSize) {
 | 
			
		||||
                CoreCommentsProvider.pageSize = response.comments.length;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new CoreError('No comments returned');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get comments count number to show on the comments component.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Comments count with plus sign if needed.
 | 
			
		||||
     */
 | 
			
		||||
    async getCommentsCount(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<string> {
 | 
			
		||||
 | 
			
		||||
        siteId = siteId ? siteId : CoreSites.instance.getCurrentSiteId();
 | 
			
		||||
        let trueCount = false;
 | 
			
		||||
 | 
			
		||||
        // Convenience function to get comments number on a page.
 | 
			
		||||
        const getCommentsPageCount = async (page: number): Promise<number> => {
 | 
			
		||||
            try {
 | 
			
		||||
                const response = await this.getComments(contextLevel, instanceId, component, itemId, area, page, siteId);
 | 
			
		||||
                // Count is only available in 3.8 onwards.
 | 
			
		||||
 | 
			
		||||
                if (typeof response.count != 'undefined') {
 | 
			
		||||
                    trueCount = true;
 | 
			
		||||
 | 
			
		||||
                    return response.count;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (response.comments) {
 | 
			
		||||
                    return response.comments.length || 0;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return -1;
 | 
			
		||||
            } catch {
 | 
			
		||||
                return -1;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const count = await getCommentsPageCount(0);
 | 
			
		||||
 | 
			
		||||
        if (trueCount || count < CoreCommentsProvider.pageSize) {
 | 
			
		||||
            return count + '';
 | 
			
		||||
        } else if (CoreCommentsProvider.pageSizeOK && count >= CoreCommentsProvider.pageSize) {
 | 
			
		||||
            // Page Size is ok, show + in case it reached the limit.
 | 
			
		||||
            return (CoreCommentsProvider.pageSize - 1) + '+';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const countMore = await getCommentsPageCount(1);
 | 
			
		||||
        // Page limit was reached on the previous call.
 | 
			
		||||
        if (countMore > 0) {
 | 
			
		||||
            CoreCommentsProvider.pageSizeOK = true;
 | 
			
		||||
 | 
			
		||||
            return (CoreCommentsProvider.pageSize - 1) + '+';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return count + '';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidates comments data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param component Component name.
 | 
			
		||||
     * @param itemId Associated id.
 | 
			
		||||
     * @param area String comment area. Default empty.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the data is invalidated.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateCommentsData(
 | 
			
		||||
        contextLevel: string,
 | 
			
		||||
        instanceId: number,
 | 
			
		||||
        component: string,
 | 
			
		||||
        itemId: number,
 | 
			
		||||
        area: string = '',
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.instance.allPromises([
 | 
			
		||||
            // This is done with starting with to avoid conflicts with previous keys that were including page.
 | 
			
		||||
            site.invalidateWsCacheForKeyStartingWith(this.getCommentsCacheKey(
 | 
			
		||||
                contextLevel,
 | 
			
		||||
                instanceId,
 | 
			
		||||
                component,
 | 
			
		||||
                itemId,
 | 
			
		||||
                area,
 | 
			
		||||
            ) + ':'),
 | 
			
		||||
 | 
			
		||||
            site.invalidateWsCacheForKey(this.getCommentsCacheKey(contextLevel, instanceId, component, itemId, area)),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidates all comments data for an instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @param contextLevel Contextlevel system, course, user...
 | 
			
		||||
     * @param instanceId The Instance id of item associated with the context level.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the data is invalidated.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateCommentsByInstance(contextLevel: string, instanceId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.instance.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.invalidateWsCacheForKeyStartingWith(this.getCommentsPrefixCacheKey(contextLevel, instanceId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const CoreComments = makeSingleton(CoreCommentsProvider);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by comment_area_exporter.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCommentsArea = {
 | 
			
		||||
    component: string; // Component.
 | 
			
		||||
    commentarea: string; // Commentarea.
 | 
			
		||||
    itemid: number; // Itemid.
 | 
			
		||||
    courseid: number; // Courseid.
 | 
			
		||||
    contextid: number; // Contextid.
 | 
			
		||||
    cid: string; // Cid.
 | 
			
		||||
    autostart: boolean; // Autostart.
 | 
			
		||||
    canpost: boolean; // Canpost.
 | 
			
		||||
    canview: boolean; // Canview.
 | 
			
		||||
    count: number; // Count.
 | 
			
		||||
    collapsediconkey: string; // @since 3.3. Collapsediconkey.
 | 
			
		||||
    displaytotalcount: boolean; // Displaytotalcount.
 | 
			
		||||
    displaycancel: boolean; // Displaycancel.
 | 
			
		||||
    fullwidth: boolean; // Fullwidth.
 | 
			
		||||
    linktext: string; // Linktext.
 | 
			
		||||
    notoggle: boolean; // Notoggle.
 | 
			
		||||
    template: string; // Template.
 | 
			
		||||
    canpostorhascomments: boolean; // Canpostorhascomments.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_comment_add_comments WS.
 | 
			
		||||
 */
 | 
			
		||||
type CoreCommentsAddCommentsWSParams = {
 | 
			
		||||
    comments: CoreCommentsCommentBasicData[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type CoreCommentsCommentBasicData = {
 | 
			
		||||
    id?: number; // Comment ID.
 | 
			
		||||
    contextlevel: string; // Contextlevel system, course, user...
 | 
			
		||||
    instanceid: number; // The id of item associated with the contextlevel.
 | 
			
		||||
    component: string; // Component.
 | 
			
		||||
    content: string; // Component.
 | 
			
		||||
    itemid: number; // Associated id.
 | 
			
		||||
    area?: string; // String comment area.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Comments Data returned by WS.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCommentsData = {
 | 
			
		||||
    id: number; // Comment ID.
 | 
			
		||||
    content: string; // The content text formatted.
 | 
			
		||||
    format: number; // Content format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | 
			
		||||
    timecreated: number; // Time created (timestamp).
 | 
			
		||||
    strftimeformat: string; // Time format.
 | 
			
		||||
    profileurl: string; // URL profile.
 | 
			
		||||
    fullname: string; // Fullname.
 | 
			
		||||
    time: string; // Time in human format.
 | 
			
		||||
    avatar: string; // HTML user picture.
 | 
			
		||||
    userid: number; // User ID.
 | 
			
		||||
    delete?: boolean; // Permission to delete=true/false.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by core_comment_add_comments WS.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCommentsAddCommentsWSResponse = CoreCommentsData[];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_comment_delete_comments WS.
 | 
			
		||||
 */
 | 
			
		||||
type CoreCommentsDeleteCommentsWSParams = {
 | 
			
		||||
    comments: number[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_comment_get_comments WS.
 | 
			
		||||
 */
 | 
			
		||||
type CoreCommentsGetCommentsWSParams = {
 | 
			
		||||
    contextlevel: string; // Contextlevel system, course, user...
 | 
			
		||||
    instanceid: number; // The Instance id of item associated with the context level.
 | 
			
		||||
    component: string; // Component.
 | 
			
		||||
    itemid: number; // Associated id.
 | 
			
		||||
    area?: string; // String comment area.
 | 
			
		||||
    page?: number; // Page number (0 based).
 | 
			
		||||
    sortdirection?: string; // Sort direction: ASC or DESC.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by core_comment_get_comments WS.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCommentsGetCommentsWSResponse = {
 | 
			
		||||
    comments: CoreCommentsData[]; // List of comments.
 | 
			
		||||
    count?: number; // @since 3.8. Total number of comments.
 | 
			
		||||
    perpage?: number; // @since 3.8.  Number of comments per page.
 | 
			
		||||
    canpost?: boolean; // Whether the user can post in this comment area.
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data sent by COMMENTS_COUNT_CHANGED_EVENT event.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCommentsCountChangedEventData = {
 | 
			
		||||
    contextLevel: string;
 | 
			
		||||
    instanceId: number;
 | 
			
		||||
    component: string;
 | 
			
		||||
    itemId: number;
 | 
			
		||||
    area: string;
 | 
			
		||||
    countChange: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data sent by REFRESH_COMMENTS_EVENT event.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCommentsRefreshCommentsEventData = {
 | 
			
		||||
    contextLevel?: string;
 | 
			
		||||
    instanceId?: number;
 | 
			
		||||
    component?: string;
 | 
			
		||||
    itemId?: number;
 | 
			
		||||
    area?: string;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										115
									
								
								src/core/features/comments/services/database/comments.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								src/core/features/comments/services/database/comments.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,115 @@
 | 
			
		||||
// (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 CoreCommentsOfflineProvider.
 | 
			
		||||
 */
 | 
			
		||||
export const COMMENTS_TABLE = 'core_comments_offline_comments';
 | 
			
		||||
export const COMMENTS_DELETED_TABLE = 'core_comments_deleted_offline_comments';
 | 
			
		||||
export const COMMENTS_OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
 | 
			
		||||
    name: 'CoreCommentsOfflineProvider',
 | 
			
		||||
    version: 1,
 | 
			
		||||
    tables: [
 | 
			
		||||
        {
 | 
			
		||||
            name: COMMENTS_TABLE,
 | 
			
		||||
            columns: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'contextlevel',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'instanceid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'component',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'itemid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'area',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'content',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'lastmodified',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            primaryKeys: ['contextlevel', 'instanceid', 'component', 'itemid', 'area'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            name: COMMENTS_DELETED_TABLE,
 | 
			
		||||
            columns: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'commentid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                    primaryKey: true,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'contextlevel',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'instanceid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'component',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'itemid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'area',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'deleted',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type CoreCommentsDBRecord = {
 | 
			
		||||
    contextlevel: string; // Primary key.
 | 
			
		||||
    instanceid: number; // Primary key.
 | 
			
		||||
    component: string; // Primary key.
 | 
			
		||||
    itemid: number; // Primary key.
 | 
			
		||||
    area: string; // Primary key.
 | 
			
		||||
    content: string;
 | 
			
		||||
    lastmodified: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type CoreCommentsDeletedDBRecord = {
 | 
			
		||||
    commentid: number; // Primary key.
 | 
			
		||||
    contextlevel: string;
 | 
			
		||||
    instanceid: number;
 | 
			
		||||
    component: string;
 | 
			
		||||
    itemid: number;
 | 
			
		||||
    area: string;
 | 
			
		||||
    deleted: number;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										49
									
								
								src/core/features/comments/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/core/features/comments/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
// (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 { CoreCommentsSync } from '../comments-sync';
 | 
			
		||||
/**
 | 
			
		||||
 * Synchronization cron handler.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' })
 | 
			
		||||
export class CoreCommentsSyncCronHandlerService implements CoreCronHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'CoreCommentsSyncCronHandler';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute the process.
 | 
			
		||||
     * Receives the ID of the site affected, undefined for all sites.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId ID of the site affected, undefined for all sites.
 | 
			
		||||
     * @param force Wether the execution is forced (manual sync).
 | 
			
		||||
     * @return Promise resolved when done, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    execute(siteId?: string, force?: boolean): Promise<void> {
 | 
			
		||||
        return CoreCommentsSync.instance.syncAllComments(siteId, force);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the time between consecutive executions.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Time between consecutive executions (in ms).
 | 
			
		||||
     */
 | 
			
		||||
    getInterval(): number {
 | 
			
		||||
        return 300000; // 5 minutes.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const CoreCommentsSyncCronHandler = makeSingleton(CoreCommentsSyncCronHandlerService);
 | 
			
		||||
@ -30,6 +30,7 @@ import { CorePushNotificationsModule } from './pushnotifications/pushnotificatio
 | 
			
		||||
import { CoreXAPIModule } from './xapi/xapi.module';
 | 
			
		||||
import { CoreViewerModule } from './viewer/viewer.module';
 | 
			
		||||
import { CoreSearchModule } from './search/search.module';
 | 
			
		||||
import { CoreCommentsModule } from './comments/comments.module';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
@ -49,6 +50,7 @@ import { CoreSearchModule } from './search/search.module';
 | 
			
		||||
        CoreXAPIModule,
 | 
			
		||||
        CoreH5PModule,
 | 
			
		||||
        CoreViewerModule,
 | 
			
		||||
        CoreCommentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreFeaturesModule {}
 | 
			
		||||
 | 
			
		||||
@ -50,7 +50,7 @@
 | 
			
		||||
                <ion-item button *ngIf="enteredSiteUrl" (click)="connect($event, enteredSiteUrl.url)"
 | 
			
		||||
                    [attr.aria-label]="'core.login.connect' | translate" detail-push class="core-login-entered-site">
 | 
			
		||||
                    <ion-thumbnail slot="start">
 | 
			
		||||
                        <ion-icon name="fas-pencil-alt"></ion-icon>
 | 
			
		||||
                        <ion-icon name="fas-pen"></ion-icon>
 | 
			
		||||
                    </ion-thumbnail>
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <h2 text-wrap>{{ 'core.login.yourenteredsite' | translate }}</h2>
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <ion-button *ngIf="sites && sites.length > 0" (click)="toggleDelete()" [attr.aria-label]="'core.delete' | translate">
 | 
			
		||||
                <ion-icon slot="icon-only" name="fas-pencil-alt"></ion-icon>
 | 
			
		||||
                <ion-icon slot="icon-only" name="fas-pen"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
            <ion-button router-direction="forward" routerLink="/settings"
 | 
			
		||||
                [attr.aria-label]="'core.settings.appsettings' | translate">
 | 
			
		||||
 | 
			
		||||
@ -1286,7 +1286,7 @@ export class CoreDomUtilsProvider {
 | 
			
		||||
     */
 | 
			
		||||
    showDeleteConfirm(
 | 
			
		||||
        translateMessage: string = 'core.areyousure',
 | 
			
		||||
        translateArgs: Record<string, string> = {},
 | 
			
		||||
        translateArgs: Record<string, unknown> = {},
 | 
			
		||||
        options?: AlertOptions,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        return this.showConfirm(
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user