commit
3d5e3267c0
|
@ -1266,6 +1266,11 @@
|
|||
"core.clicktoseefull": "local_moodlemobileapp",
|
||||
"core.close": "repository",
|
||||
"core.comments": "moodle",
|
||||
"core.comments.addcomment": "moodle",
|
||||
"core.comments.comments": "moodle",
|
||||
"core.comments.commentscount": "moodle",
|
||||
"core.comments.commentsnotworking": "local_moodlemobileapp",
|
||||
"core.comments.nocomments": "moodle",
|
||||
"core.commentscount": "moodle",
|
||||
"core.commentsnotworking": "local_moodlemobileapp",
|
||||
"core.completion-alt-auto-fail": "completion",
|
||||
|
|
|
@ -169,7 +169,13 @@ export class AddonBlogEntriesComponent implements OnInit {
|
|||
* @param {any} refresher Refresher instance.
|
||||
*/
|
||||
refresh(refresher?: any): void {
|
||||
this.blogProvider.invalidateEntries(this.filter).finally(() => {
|
||||
const promises = this.entries.map((entry) => {
|
||||
return this.commentsProvider.invalidateCommentsData('user', entry.userid, this.component, entry.id, 'format_blog');
|
||||
});
|
||||
|
||||
promises.push(this.blogProvider.invalidateEntries(this.filter));
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
this.fetchEntries(true).finally(() => {
|
||||
if (refresher) {
|
||||
refresher.complete();
|
||||
|
|
|
@ -218,6 +218,10 @@ export class AddonModDataEntryPage implements OnDestroy {
|
|||
|
||||
promises.push(this.dataProvider.invalidateDatabaseData(this.courseId));
|
||||
if (this.data) {
|
||||
if (this.data.comments && this.entry && this.entry.id > 0 && this.commentsEnabled) {
|
||||
promises.push(this.commentsProvider.invalidateCommentsData('module', this.data.coursemodule, 'mod_data',
|
||||
this.entry.id, 'database_entry'));
|
||||
}
|
||||
promises.push(this.dataProvider.invalidateEntryData(this.data.id, this.entryId));
|
||||
promises.push(this.groupsProvider.invalidateActivityGroupInfo(this.data.coursemodule));
|
||||
promises.push(this.dataProvider.invalidateEntriesData(this.data.id));
|
||||
|
|
|
@ -1265,9 +1265,16 @@
|
|||
"core.clicktohideshow": "Click to expand or collapse",
|
||||
"core.clicktoseefull": "Click to see full contents.",
|
||||
"core.close": "Close",
|
||||
"core.comments": "Comments",
|
||||
"core.commentscount": "Comments ({{$a}})",
|
||||
"core.commentsnotworking": "Comments cannot be retrieved",
|
||||
"core.comments.addcomment": "Add a comment...",
|
||||
"core.comments.comments": "Comments",
|
||||
"core.comments.commentscount": "Comments ({{$a}})",
|
||||
"core.comments.commentsnotworking": "Comments cannot be retrieved",
|
||||
"core.comments.deletecommentbyon": "Delete comment posted by {{$a.user}} on {{$a.time}}",
|
||||
"core.comments.eventcommentcreated": "Comment created",
|
||||
"core.comments.eventcommentdeleted": "Comment deleted",
|
||||
"core.comments.nocomments": "No comments",
|
||||
"core.comments.savecomment": "Save comment",
|
||||
"core.comments.warningcommentsnotsent": "Couldn't sync comments. {{error}}",
|
||||
"core.completion-alt-auto-fail": "Completed: {{$a}} (did not achieve pass grade)",
|
||||
"core.completion-alt-auto-n": "Not completed: {{$a}}",
|
||||
"core.completion-alt-auto-n-override": "Not completed: {{$a.modname}} (set by {{$a.overrideuser}})",
|
||||
|
@ -1618,7 +1625,6 @@
|
|||
"core.never": "Never",
|
||||
"core.next": "Next",
|
||||
"core.no": "No",
|
||||
"core.nocomments": "No comments",
|
||||
"core.nograde": "No grade",
|
||||
"core.none": "None",
|
||||
"core.nopasswordchangeforced": "You cannot proceed without changing your password.",
|
||||
|
|
|
@ -13,7 +13,12 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreCronDelegate } from '@providers/cron';
|
||||
import { CoreCommentsProvider } from './providers/comments';
|
||||
import { CoreCommentsOfflineProvider } from './providers/offline';
|
||||
import { CoreCommentsSyncCronHandler } from './providers/sync-cron-handler';
|
||||
import { CoreCommentsSyncProvider } from './providers/sync';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -21,7 +26,20 @@ import { CoreCommentsProvider } from './providers/comments';
|
|||
imports: [
|
||||
],
|
||||
providers: [
|
||||
CoreCommentsProvider
|
||||
CoreCommentsProvider,
|
||||
CoreCommentsOfflineProvider,
|
||||
CoreCommentsSyncProvider,
|
||||
CoreCommentsSyncCronHandler
|
||||
]
|
||||
})
|
||||
export class CoreCommentsModule {}
|
||||
export class CoreCommentsModule {
|
||||
constructor(eventsProvider: CoreEventsProvider, cronDelegate: CoreCronDelegate, syncHandler: CoreCommentsSyncCronHandler) {
|
||||
// Reset comments page size.
|
||||
eventsProvider.on(CoreEventsProvider.LOGIN, () => {
|
||||
CoreCommentsProvider.pageSize = null;
|
||||
CoreCommentsProvider.pageSizeOK = false;
|
||||
});
|
||||
|
||||
cronDelegate.register(syncHandler);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
core-comments .core-comments-clickable {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
|
@ -31,13 +31,13 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy {
|
|||
@Input() component: string;
|
||||
@Input() itemId: number;
|
||||
@Input() area = '';
|
||||
@Input() page = 0;
|
||||
@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.
|
||||
|
||||
commentsLoaded = false;
|
||||
commentsCount: number;
|
||||
commentsCount: string;
|
||||
countError = false;
|
||||
disabled = false;
|
||||
|
||||
protected updateSiteObserver;
|
||||
|
@ -72,7 +72,7 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy {
|
|||
*/
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
// If something change, update the fields.
|
||||
if (changes) {
|
||||
if (changes && this.commentsLoaded) {
|
||||
this.fetchData();
|
||||
}
|
||||
}
|
||||
|
@ -85,12 +85,10 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy {
|
|||
this.commentsLoaded = false;
|
||||
this.onLoading.emit(true);
|
||||
|
||||
this.commentsProvider.getComments(this.contextLevel, this.instanceId, this.component, this.itemId, this.area, this.page)
|
||||
.then((comments) => {
|
||||
this.commentsCount = comments && comments.length ? comments.length : 0;
|
||||
}).catch(() => {
|
||||
this.commentsCount = -1;
|
||||
}).finally(() => {
|
||||
this.commentsProvider.getCommentsCount(this.contextLevel, this.instanceId, this.component, this.itemId, this.area)
|
||||
.then((commentsCount) => {
|
||||
this.commentsCount = commentsCount;
|
||||
this.countError = parseInt(this.commentsCount, 10) < 0;
|
||||
this.commentsLoaded = true;
|
||||
this.onLoading.emit(false);
|
||||
});
|
||||
|
@ -100,15 +98,14 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy {
|
|||
* Opens the comments page.
|
||||
*/
|
||||
openComments(): void {
|
||||
if (!this.disabled && this.commentsCount > 0) {
|
||||
if (!this.disabled && !this.countError) {
|
||||
// Open a new state with the interpolated contents.
|
||||
this.navCtrl.push('CoreCommentsViewerPage', {
|
||||
contextLevel: this.contextLevel,
|
||||
instanceId: this.instanceId,
|
||||
component: this.component,
|
||||
componentName: this.component,
|
||||
itemId: this.itemId,
|
||||
area: this.area,
|
||||
page: this.page,
|
||||
title: this.title,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<core-loading *ngIf="!disabled" [hideUntil]="commentsLoaded || !displaySpinner">
|
||||
<div (click)="openComments()" *ngIf="commentsCount >= 0">
|
||||
{{ 'core.commentscount' | translate : {'$a': commentsCount} }}
|
||||
<div (click)="openComments()" *ngIf="!countError" [class.core-comments-clickable]="!disabled">
|
||||
{{ 'core.comments.commentscount' | translate : {'$a': commentsCount} }}
|
||||
</div>
|
||||
<div *ngIf="commentsCount < 0">
|
||||
{{ 'core.commentsnotworking' | translate }}
|
||||
<div *ngIf="countError">
|
||||
{{ 'core.comments.commentsnotworking' | translate }}
|
||||
</div>
|
||||
</core-loading>
|
||||
|
|
|
@ -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}}"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<ion-header>
|
||||
<ion-navbar core-back-button>
|
||||
<ion-title>{{ 'core.comments.addcomment' | translate }}</ion-title>
|
||||
<ion-buttons end>
|
||||
<button ion-button icon-only (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||
<ion-icon name="close"></ion-icon>
|
||||
</button>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<form name="itemEdit" (ngSubmit)="addComment($event)">
|
||||
<ion-item>
|
||||
<ion-textarea placeholder="{{ 'core.comments.addcomment' | translate }}" rows="5" [(ngModel)]="content" name="content" required="required"></ion-textarea>
|
||||
</ion-item>
|
||||
<div padding>
|
||||
<button ion-button block type="submit" [disabled]="processing || content.length < 1">
|
||||
{{ 'core.comments.savecomment' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ion-content>
|
|
@ -0,0 +1,31 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 { IonicPageModule } from 'ionic-angular';
|
||||
import { CoreCommentsAddPage } from './add';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreCommentsAddPage
|
||||
],
|
||||
imports: [
|
||||
CoreDirectivesModule,
|
||||
IonicPageModule.forChild(CoreCommentsAddPage),
|
||||
TranslateModule.forChild()
|
||||
]
|
||||
})
|
||||
export class CoreCommentsAddPageModule {}
|
|
@ -0,0 +1,82 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 } from '@angular/core';
|
||||
import { IonicPage, ViewController, NavParams } from 'ionic-angular';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreCommentsProvider } from '../../providers/comments';
|
||||
|
||||
/**
|
||||
* Component that displays a text area for composing a comment.
|
||||
*/
|
||||
@IonicPage({ segment: 'core-comments-add' })
|
||||
@Component({
|
||||
selector: 'page-core-comments-add',
|
||||
templateUrl: 'add.html',
|
||||
})
|
||||
export class CoreCommentsAddPage {
|
||||
protected contextLevel: string;
|
||||
protected instanceId: number;
|
||||
protected componentName: string;
|
||||
protected itemId: number;
|
||||
protected area = '';
|
||||
|
||||
content = '';
|
||||
processing = false;
|
||||
|
||||
constructor(params: NavParams, private viewCtrl: ViewController, private appProvider: CoreAppProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private commentsProvider: CoreCommentsProvider) {
|
||||
this.contextLevel = params.get('contextLevel');
|
||||
this.instanceId = params.get('instanceId');
|
||||
this.componentName = params.get('componentName');
|
||||
this.itemId = params.get('itemId');
|
||||
this.area = params.get('area') || '';
|
||||
this.content = params.get('content') || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the comment or store it offline.
|
||||
*
|
||||
* @param {Event} e Event.
|
||||
*/
|
||||
addComment(e: Event): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.appProvider.closeKeyboard();
|
||||
const loadingModal = this.domUtils.showModalLoading('core.sending', true);
|
||||
// Freeze the add comment button.
|
||||
this.processing = true;
|
||||
this.commentsProvider.addComment(this.content, this.contextLevel, this.instanceId, this.componentName, this.itemId,
|
||||
this.area).then((commentsResponse) => {
|
||||
this.viewCtrl.dismiss({comments: commentsResponse}).finally(() => {
|
||||
this.domUtils.showToast(commentsResponse ? 'core.comments.eventcommentcreated' : 'core.datastoredoffline', true,
|
||||
3000);
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModal(error);
|
||||
this.processing = false;
|
||||
}).finally(() => {
|
||||
loadingModal.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close modal.
|
||||
*/
|
||||
closeModal(): void {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
}
|
|
@ -1,24 +1,71 @@
|
|||
<ion-header>
|
||||
<ion-navbar core-back-button>
|
||||
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
||||
<ion-buttons end>
|
||||
<button *ngIf="canDeleteComments" item-end ion-button icon-only clear (click)="toggleDelete($event)" [attr.aria-label]="'core.delete' | translate">
|
||||
<ion-icon name="create" ios="md-create"></ion-icon>
|
||||
</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-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher [enabled]="commentsLoaded" (ionRefresh)="refreshComments($event)">
|
||||
<ion-refresher [enabled]="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="chatbubbles" [message]="'core.nocomments' | translate"></core-empty-box>
|
||||
<core-empty-box *ngIf="!comments || !comments.length" icon="chatbubbles" [message]="'core.comments.nocomments' | translate"></core-empty-box>
|
||||
|
||||
<div class="core-warning-card" icon-start *ngIf="hasOffline">
|
||||
<ion-icon name="warning"></ion-icon>
|
||||
{{ 'core.thereisdatatosync' | translate:{$a: 'core.comments.comments' | translate | lowercase } }}
|
||||
</div>
|
||||
|
||||
<ion-card *ngIf="offlineComment" (click)="addComment($event)">
|
||||
<ion-item text-wrap>
|
||||
<ion-avatar core-user-avatar [user]="offlineComment" item-start></ion-avatar>
|
||||
<h2>{{ offlineComment.fullname }}</h2>
|
||||
<p>
|
||||
<ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}
|
||||
</p>
|
||||
<button *ngIf="showDelete" item-end ion-button icon-only clear [@coreSlideInOut]="'fromRight'" color="danger" (click)="deleteComment($event, offlineComment)" [attr.aria-label]="'core.delete' | translate">
|
||||
<ion-icon name="trash"></ion-icon>
|
||||
</button>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<core-format-text clean="true" [text]="offlineComment.content"></core-format-text>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<ion-card *ngFor="let comment of comments">
|
||||
<ion-item text-wrap>
|
||||
<ion-avatar core-user-avatar [user]="comment" item-start></ion-avatar>
|
||||
<h2>{{ comment.fullname }}</h2>
|
||||
<p>{{ comment.time }}</p>
|
||||
<p *ngIf="!comment.deleted">{{ comment.timecreated * 1000 | coreFormatDate: 'strftimerecentfull' }}</p>
|
||||
<p *ngIf="comment.deleted">
|
||||
<ion-icon name="trash"></ion-icon> <span text-wrap>{{ 'core.deletedoffline' | translate }}</span>
|
||||
</p>
|
||||
<button *ngIf="showDelete && !comment.deleted && comment.delete" item-end ion-button icon-only clear [@coreSlideInOut]="'fromRight'" color="danger" (click)="deleteComment($event, comment)" [attr.aria-label]="'core.delete' | translate">
|
||||
<ion-icon name="trash"></ion-icon>
|
||||
</button>
|
||||
<button *ngIf="showDelete && comment.deleted" item-end ion-button icon-only clear color="danger" (click)="undoDeleteComment($event, comment)" [attr.aria-label]="'core.restore' | translate">
|
||||
<ion-icon name="undo"></ion-icon>
|
||||
</button>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<core-format-text clean="true" [text]="comment.content"></core-format-text>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError"></core-infinite-loading>
|
||||
</core-loading>
|
||||
|
||||
<ion-fab core-fab bottom end *ngIf="canAddComments">
|
||||
<button ion-fab (click)="addComment($event)" [attr.aria-label]="'core.comments.addcomment' | translate">
|
||||
<ion-icon name="add"></ion-icon>
|
||||
</button>
|
||||
</ion-fab>
|
||||
</ion-content>
|
||||
|
|
|
@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||
import { CoreCommentsViewerPage } from './viewer';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
import { CoreCommentsComponentsModule } from '../../components/components.module';
|
||||
|
||||
@NgModule({
|
||||
|
@ -27,6 +28,7 @@ import { CoreCommentsComponentsModule } from '../../components/components.module
|
|||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
CoreCommentsComponentsModule,
|
||||
IonicPageModule.forChild(CoreCommentsViewerPage),
|
||||
TranslateModule.forChild()
|
||||
|
|
|
@ -12,13 +12,19 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { IonicPage, Content, NavParams } from 'ionic-angular';
|
||||
import { Component, ViewChild, OnDestroy } from '@angular/core';
|
||||
import { IonicPage, Content, NavParams, ModalController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { coreSlideInOut } from '@classes/animations';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreUserProvider } from '@core/user/providers/user';
|
||||
import { CoreCommentsProvider } from '../../providers/comments';
|
||||
import { CoreCommentsOfflineProvider } from '../../providers/offline';
|
||||
import { CoreCommentsSyncProvider } from '../../providers/sync';
|
||||
|
||||
/**
|
||||
* Page that displays comments.
|
||||
|
@ -27,81 +33,333 @@ import { CoreCommentsProvider } from '../../providers/comments';
|
|||
@Component({
|
||||
selector: 'page-core-comments-viewer',
|
||||
templateUrl: 'viewer.html',
|
||||
animations: [coreSlideInOut]
|
||||
})
|
||||
export class CoreCommentsViewerPage {
|
||||
export class CoreCommentsViewerPage implements OnDestroy {
|
||||
@ViewChild(Content) content: Content;
|
||||
|
||||
comments = [];
|
||||
commentsLoaded = false;
|
||||
contextLevel: string;
|
||||
instanceId: number;
|
||||
component: string;
|
||||
componentName: string;
|
||||
itemId: number;
|
||||
area: string;
|
||||
page: number;
|
||||
title: string;
|
||||
canLoadMore = false;
|
||||
loadMoreError = false;
|
||||
canAddComments = false;
|
||||
canDeleteComments = false;
|
||||
showDelete = false;
|
||||
hasOffline = false;
|
||||
refreshIcon = 'spinner';
|
||||
syncIcon = 'spinner';
|
||||
offlineComment: any;
|
||||
currentUserId: number;
|
||||
|
||||
constructor(navParams: NavParams, sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private translate: TranslateService,
|
||||
private commentsProvider: CoreCommentsProvider) {
|
||||
protected addDeleteCommentsAvailable = false;
|
||||
protected syncObserver: any;
|
||||
protected currentUser: any;
|
||||
|
||||
constructor(navParams: NavParams, private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private modalCtrl: ModalController,
|
||||
private commentsProvider: CoreCommentsProvider, private offlineComments: CoreCommentsOfflineProvider,
|
||||
eventsProvider: CoreEventsProvider, private commentsSync: CoreCommentsSyncProvider,
|
||||
private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) {
|
||||
|
||||
this.contextLevel = navParams.get('contextLevel');
|
||||
this.instanceId = navParams.get('instanceId');
|
||||
this.component = navParams.get('component');
|
||||
this.componentName = navParams.get('componentName');
|
||||
this.itemId = navParams.get('itemId');
|
||||
this.area = navParams.get('area') || '';
|
||||
this.page = navParams.get('page') || 0;
|
||||
this.title = navParams.get('title') || this.translate.instant('core.comments');
|
||||
this.title = navParams.get('title') || this.translate.instant('core.comments.comments');
|
||||
this.page = 0;
|
||||
|
||||
// Refresh data if comments are synchronized automatically.
|
||||
this.syncObserver = eventsProvider.on(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 = 'spinner';
|
||||
this.syncIcon = 'spinner';
|
||||
|
||||
this.domUtils.scrollToTop(this.content);
|
||||
|
||||
this.page = 0;
|
||||
this.comments = [];
|
||||
this.fetchComments(false);
|
||||
}
|
||||
}, sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad(): void {
|
||||
this.fetchComments().finally(() => {
|
||||
this.commentsLoaded = true;
|
||||
this.commentsProvider.isAddCommentsAvailable().then((enabled) => {
|
||||
// Is implicit the user can delete if he can add.
|
||||
this.addDeleteCommentsAvailable = enabled;
|
||||
});
|
||||
|
||||
this.currentUserId = this.sitesProvider.getCurrentSiteUserId();
|
||||
this.fetchComments(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the comments.
|
||||
*
|
||||
* @param {boolean} sync When to resync comments.
|
||||
* @param {boolean} [showErrors] When to display errors or not.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
protected fetchComments(): Promise<any> {
|
||||
// Get comments data.
|
||||
return this.commentsProvider.getComments(this.contextLevel, this.instanceId, this.component, this.itemId,
|
||||
this.area, this.page).then((comments) => {
|
||||
this.comments = comments;
|
||||
this.comments.sort((a, b) => b.timecreated - a.timecreated);
|
||||
this.comments.forEach((comment) => {
|
||||
// Get the user profile image.
|
||||
this.userProvider.getProfile(comment.userid, undefined, true).then((user) => {
|
||||
comment.profileimageurl = user.profileimageurl;
|
||||
protected fetchComments(sync: boolean, showErrors?: boolean): Promise<any> {
|
||||
this.loadMoreError = false;
|
||||
|
||||
const promise = sync ? this.syncComments(showErrors) : Promise.resolve();
|
||||
|
||||
return promise.catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return this.offlineComments.getComment(this.contextLevel, this.instanceId, this.componentName, this.itemId,
|
||||
this.area).then((offlineComment) => {
|
||||
this.offlineComment = offlineComment;
|
||||
|
||||
if (offlineComment && !this.currentUser) {
|
||||
return this.userProvider.getProfile(this.currentUserId, undefined, true).then((user) => {
|
||||
this.currentUser = user;
|
||||
this.offlineComment.profileimageurl = user.profileimageurl;
|
||||
this.offlineComment.fullname = user.fullname;
|
||||
this.offlineComment.userid = user.id;
|
||||
}).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
} else if (offlineComment) {
|
||||
this.offlineComment.profileimageurl = this.currentUser.profileimageurl;
|
||||
this.offlineComment.fullname = this.currentUser.fullname;
|
||||
this.offlineComment.userid = this.currentUser.id;
|
||||
}
|
||||
|
||||
return this.offlineComments.getDeletedComments(this.contextLevel, this.instanceId, this.componentName, this.itemId,
|
||||
this.area);
|
||||
});
|
||||
}).then((deletedComments) => {
|
||||
this.hasOffline = !!this.offlineComment || deletedComments.length > 0;
|
||||
|
||||
// Get comments data.
|
||||
return this.commentsProvider.getComments(this.contextLevel, this.instanceId, this.componentName, this.itemId,
|
||||
this.area, this.page).then((response) => {
|
||||
this.canAddComments = this.addDeleteCommentsAvailable && response.canpost;
|
||||
|
||||
const comments = response.comments.sort((a, b) => b.timecreated - a.timecreated);
|
||||
this.canLoadMore = comments.length >= CoreCommentsProvider.pageSize;
|
||||
|
||||
return Promise.all(comments.map((comment) => {
|
||||
// Get the user profile image.
|
||||
return this.userProvider.getProfile(comment.userid, undefined, true).then((user) => {
|
||||
comment.profileimageurl = user.profileimageurl;
|
||||
|
||||
return comment;
|
||||
}).catch(() => {
|
||||
// Ignore errors.
|
||||
return comment;
|
||||
});
|
||||
}));
|
||||
}).then((comments) => {
|
||||
this.comments = this.comments.concat(comments);
|
||||
|
||||
deletedComments && deletedComments.forEach((deletedComment) => {
|
||||
const comment = this.comments.find((comment) => {
|
||||
return comment.id == deletedComment.commentid;
|
||||
});
|
||||
|
||||
if (comment) {
|
||||
comment.deleted = deletedComment.deleted;
|
||||
}
|
||||
});
|
||||
|
||||
this.canDeleteComments = this.addDeleteCommentsAvailable && (this.hasOffline || this.comments.some((comment) => {
|
||||
return !!comment.delete;
|
||||
}));
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error && this.component == 'assignsubmission_comments') {
|
||||
this.domUtils.showAlertTranslated('core.notice', 'core.commentsnotworking');
|
||||
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
||||
if (error && this.componentName == 'assignsubmission_comments') {
|
||||
this.domUtils.showAlertTranslated('core.notice', 'core.comments.commentsnotworking');
|
||||
} else {
|
||||
this.domUtils.showErrorModalDefault(error, this.translate.instant('core.error') + ': get_comments');
|
||||
}
|
||||
}).finally(() => {
|
||||
this.commentsLoaded = true;
|
||||
this.refreshIcon = 'refresh';
|
||||
this.syncIcon = 'sync';
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to load more commemts.
|
||||
*
|
||||
* @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
loadMore(infiniteComplete?: any): Promise<any> {
|
||||
this.page++;
|
||||
this.canLoadMore = false;
|
||||
|
||||
return this.fetchComments(true).finally(() => {
|
||||
infiniteComplete && infiniteComplete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the comments.
|
||||
*
|
||||
* @param {any} refresher Refresher.
|
||||
* @param {boolean} showErrors Whether to display errors or not.
|
||||
* @param {any} [refresher] Refresher.
|
||||
* @return {Promise<any>} Resolved when done.
|
||||
*/
|
||||
refreshComments(refresher: any): void {
|
||||
this.commentsProvider.invalidateCommentsData(this.contextLevel, this.instanceId, this.component,
|
||||
this.itemId, this.area, this.page).finally(() => {
|
||||
return this.fetchComments().finally(() => {
|
||||
refresher.complete();
|
||||
refreshComments(showErrors: boolean, refresher?: any): Promise<any> {
|
||||
this.refreshIcon = 'spinner';
|
||||
this.syncIcon = 'spinner';
|
||||
|
||||
return this.commentsProvider.invalidateCommentsData(this.contextLevel, this.instanceId, this.componentName,
|
||||
this.itemId, this.area).finally(() => {
|
||||
this.page = 0;
|
||||
this.comments = [];
|
||||
|
||||
return this.fetchComments(true, showErrors).finally(() => {
|
||||
refresher && refresher.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show sync warnings if any.
|
||||
*
|
||||
* @param {string[]} warnings the warnings
|
||||
*/
|
||||
private showSyncWarnings(warnings: string[]): void {
|
||||
const message = this.textUtils.buildMessage(warnings);
|
||||
if (message) {
|
||||
this.domUtils.showErrorModal(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to synchronize comments.
|
||||
*
|
||||
* @param {boolean} showErrors Whether to display errors or not.
|
||||
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||
*/
|
||||
private syncComments(showErrors: boolean): Promise<any> {
|
||||
return this.commentsSync.syncComments(this.contextLevel, this.instanceId, this.componentName, this.itemId,
|
||||
this.area).then((warnings) => {
|
||||
this.showSyncWarnings(warnings);
|
||||
}).catch((error) => {
|
||||
if (showErrors) {
|
||||
this.domUtils.showErrorModalDefault(error, 'core.errorsync', true);
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new comment to the list.
|
||||
*
|
||||
* @param {Event} e Event.
|
||||
*/
|
||||
addComment(e: Event): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const params = {
|
||||
contextLevel: this.contextLevel,
|
||||
instanceId: this.instanceId,
|
||||
componentName: this.componentName,
|
||||
itemId: this.itemId,
|
||||
area: this.area,
|
||||
content: this.hasOffline ? this.offlineComment.content : ''
|
||||
};
|
||||
|
||||
const modal = this.modalCtrl.create('CoreCommentsAddPage', params);
|
||||
modal.onDidDismiss((data) => {
|
||||
if (data && data.comments) {
|
||||
this.comments = data.comments.concat(this.comments);
|
||||
this.canDeleteComments = this.addDeleteCommentsAvailable;
|
||||
} else if (data && !data.comments) {
|
||||
this.fetchComments(false);
|
||||
}
|
||||
});
|
||||
modal.present();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
* @param {any} comment Comment to delete.
|
||||
*/
|
||||
deleteComment(e: Event, comment: any): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const time = this.timeUtils.userDate((comment.lastmodified || comment.timecreated) * 1000, 'core.strftimerecentfull');
|
||||
|
||||
comment.contextlevel = this.contextLevel;
|
||||
comment.instanceid = this.instanceId;
|
||||
comment.component = this.componentName;
|
||||
comment.itemid = this.itemId;
|
||||
comment.area = this.area;
|
||||
|
||||
this.domUtils.showConfirm(this.translate.instant('core.comments.deletecommentbyon', {$a:
|
||||
{ user: comment.fullname || '', time: time } })).then(() => {
|
||||
this.commentsProvider.deleteComment(comment).then(() => {
|
||||
this.showDelete = false;
|
||||
|
||||
this.refreshComments(true);
|
||||
|
||||
this.domUtils.showToast('core.comments.eventcommentdeleted', true, 3000);
|
||||
}).catch((error) => {
|
||||
this.domUtils.showErrorModalDefault(error, 'Delete comment failed.');
|
||||
});
|
||||
}).catch(() => {
|
||||
// User cancelled, nothing to do.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a comment.
|
||||
*
|
||||
* @param {Event} e Click event.
|
||||
* @param {any} comment Comment to delete.
|
||||
*/
|
||||
undoDeleteComment(e: Event, comment: any): void {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.offlineComments.undoDeleteComment(comment.id).then(() => {
|
||||
comment.deleted = false;
|
||||
this.showDelete = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle delete.
|
||||
*/
|
||||
toggleDelete(): void {
|
||||
this.showDelete = !this.showDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Page destroyed.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.syncObserver && this.syncObserver.off();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreCommentsOfflineProvider } from './offline';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding comments.
|
||||
|
@ -23,8 +26,110 @@ import { CoreSite } from '@classes/site';
|
|||
export class CoreCommentsProvider {
|
||||
|
||||
protected ROOT_CACHE_KEY = 'mmComments:';
|
||||
static pageSize = null;
|
||||
static pageSizeOK = false; // If true, the pageSize is definitive. If not, it's a temporal value to reduce WS calls.
|
||||
|
||||
constructor(private sitesProvider: CoreSitesProvider) {}
|
||||
constructor(private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private appProvider: CoreAppProvider,
|
||||
private commentsOffline: CoreCommentsOfflineProvider) {}
|
||||
|
||||
/**
|
||||
* Add a comment.
|
||||
*
|
||||
* @param {string} content Comment text.
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<boolean>} Promise resolved with boolean: true if comment was sent to server, false if stored in device.
|
||||
*/
|
||||
addComment(content: string, contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '',
|
||||
siteId?: string): Promise<boolean> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Convenience function to store a comment to be synchronized later.
|
||||
const storeOffline = (): Promise<any> => {
|
||||
return this.commentsOffline.saveComment(content, contextLevel, instanceId, component, itemId, area, siteId).then(() => {
|
||||
return Promise.resolve(false);
|
||||
});
|
||||
};
|
||||
|
||||
if (!this.appProvider.isOnline()) {
|
||||
// App is offline, store the comment.
|
||||
return storeOffline();
|
||||
}
|
||||
|
||||
// Send comment to server.
|
||||
return this.addCommentOnline(content, contextLevel, instanceId, component, itemId, area, siteId).then((comments) => {
|
||||
return comments;
|
||||
}).catch((error) => {
|
||||
if (this.utils.isWebServiceError(error)) {
|
||||
// It's a WebService error, the user cannot send the message so don't store it.
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// Error sending comment, store it to retry later.
|
||||
return storeOffline();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment. It will fail if offline or cannot connect.
|
||||
*
|
||||
* @param {string} content Comment text.
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when added, rejected otherwise.
|
||||
*/
|
||||
addCommentOnline(content: string, contextLevel: string, instanceId: number, component: string, itemId: number,
|
||||
area: string = '', siteId?: string): Promise<any> {
|
||||
const comments = [
|
||||
{
|
||||
contextlevel: contextLevel,
|
||||
instanceid: instanceId,
|
||||
component: component,
|
||||
itemid: itemId,
|
||||
area: area,
|
||||
content: content
|
||||
}
|
||||
];
|
||||
|
||||
return this.addCommentsOnline(comments, siteId).then((commentsResponse) => {
|
||||
// A cooment was added, invalidate them.
|
||||
return this.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then(() => {
|
||||
return commentsResponse;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add several comments. It will fail if offline or cannot connect.
|
||||
*
|
||||
* @param {any[]} comments Comments to save.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} 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.
|
||||
*/
|
||||
addCommentsOnline(comments: any[], siteId?: string): Promise<any> {
|
||||
if (!comments || !comments.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const data = {
|
||||
comments: comments
|
||||
};
|
||||
|
||||
return site.write('core_comment_add_comments', data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Calendar is disabled in a certain site.
|
||||
|
@ -50,6 +155,97 @@ export class CoreCommentsProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment.
|
||||
*
|
||||
* @param {any} comment Comment object to delete.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<void>} 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.
|
||||
*/
|
||||
deleteComment(comment: any, siteId?: string): Promise<void> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
if (!comment.id) {
|
||||
return this.commentsOffline.removeComment(comment.contextlevel, comment.instanceid, comment.component, comment.itemid,
|
||||
comment.area, siteId);
|
||||
}
|
||||
|
||||
// Convenience function to store the action to be synchronized later.
|
||||
const storeOffline = (): Promise<any> => {
|
||||
return this.commentsOffline.deleteComment(comment.id, comment.contextlevel, comment.instanceid, comment.component,
|
||||
comment.itemid, comment.area, siteId).then(() => {
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
if (!this.appProvider.isOnline()) {
|
||||
// App is offline, store the comment.
|
||||
return storeOffline();
|
||||
}
|
||||
|
||||
// Send comment to server.
|
||||
return this.deleteCommentsOnline([comment.id], comment.contextlevel, comment.instanceid, comment.component, comment.itemid,
|
||||
comment.area, siteId).then(() => {
|
||||
return true;
|
||||
}).catch((error) => {
|
||||
if (this.utils.isWebServiceError(error)) {
|
||||
// It's a WebService error, the user cannot send the comment so don't store it.
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// Error sending comment, store it to retry later.
|
||||
return storeOffline();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment. It will fail if offline or cannot connect.
|
||||
*
|
||||
* @param {number[]} commentIds Comment IDs to delete.
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<void>} 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.
|
||||
*/
|
||||
deleteCommentsOnline(commentIds: number[], contextLevel: string, instanceId: number, component: string, itemId: number,
|
||||
area: string = '', siteId?: string): Promise<void> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const data = {
|
||||
comments: commentIds
|
||||
};
|
||||
|
||||
return site.write('core_comment_delete_comments', data).then((response) => {
|
||||
// A comment was deleted, invalidate comments.
|
||||
return this.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether WS to add/delete comments are available in site.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<boolean>} Promise resolved with true if available, resolved with false or rejected otherwise.
|
||||
* @since 3.8
|
||||
*/
|
||||
isAddCommentsAvailable(siteId?: string): Promise<boolean> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
// 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.
|
||||
*
|
||||
|
@ -58,12 +254,11 @@ export class CoreCommentsProvider {
|
|||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {number} [page=0] Page number (0 based). Default 0.
|
||||
* @return {string} Cache key.
|
||||
*/
|
||||
protected getCommentsCacheKey(contextLevel: string, instanceId: number, component: string,
|
||||
itemId: number, area: string = '', page: number = 0): string {
|
||||
return this.getCommentsPrefixCacheKey(contextLevel, instanceId) + ':' + component + ':' + itemId + ':' + area + ':' + page;
|
||||
protected getCommentsCacheKey(contextLevel: string, instanceId: number, component: string, itemId: number,
|
||||
area: string = ''): string {
|
||||
return this.getCommentsPrefixCacheKey(contextLevel, instanceId) + ':' + component + ':' + itemId + ':' + area;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,8 +284,8 @@ export class CoreCommentsProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the comments.
|
||||
*/
|
||||
getComments(contextLevel: string, instanceId: number, component: string, itemId: number,
|
||||
area: string = '', page: number = 0, siteId?: string): Promise<any> {
|
||||
getComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '', page: number = 0,
|
||||
siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params: any = {
|
||||
contextlevel: contextLevel,
|
||||
|
@ -102,13 +297,13 @@ export class CoreCommentsProvider {
|
|||
};
|
||||
|
||||
const preSets = {
|
||||
cacheKey: this.getCommentsCacheKey(contextLevel, instanceId, component, itemId, area, page),
|
||||
cacheKey: this.getCommentsCacheKey(contextLevel, instanceId, component, itemId, area),
|
||||
updateFrequency: CoreSite.FREQUENCY_SOMETIMES
|
||||
};
|
||||
|
||||
return site.read('core_comment_get_comments', params, preSets).then((response) => {
|
||||
if (response.comments) {
|
||||
return response.comments;
|
||||
return response;
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
|
@ -116,6 +311,61 @@ export class CoreCommentsProvider {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments count number to show on the comments component.
|
||||
*
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<string>} Comments count with plus sign if needed.
|
||||
*/
|
||||
getCommentsCount(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '',
|
||||
siteId?: string): Promise<string> {
|
||||
|
||||
siteId = siteId ? siteId : this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
// Convenience function to get comments number on a page.
|
||||
const getCommentsPageCount = (page: number): Promise<number> => {
|
||||
return this.getComments(contextLevel, instanceId, component, itemId, area, page, siteId).then((response) => {
|
||||
if (response.comments) {
|
||||
// Update pageSize with the greatest count at the moment.
|
||||
if (response.comments && response.comments.length > CoreCommentsProvider.pageSize) {
|
||||
CoreCommentsProvider.pageSize = response.comments.length;
|
||||
}
|
||||
|
||||
return response.comments && response.comments.length ? response.comments.length : 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}).catch(() => {
|
||||
return -1;
|
||||
});
|
||||
};
|
||||
|
||||
return getCommentsPageCount(0).then((count) => {
|
||||
if (CoreCommentsProvider.pageSizeOK && count >= CoreCommentsProvider.pageSize) {
|
||||
// Page Size is ok, show + in case it reached the limit.
|
||||
return (CoreCommentsProvider.pageSize - 1) + '+';
|
||||
} else if (count < 0 || (CoreCommentsProvider.pageSize && count < CoreCommentsProvider.pageSize)) {
|
||||
return count + '';
|
||||
}
|
||||
|
||||
// Call to update page size.
|
||||
return getCommentsPageCount(1).then((countMore) => {
|
||||
// Page limit was reached on the previous call.
|
||||
if (countMore > 0) {
|
||||
|
||||
return (CoreCommentsProvider.pageSize - 1) + '+';
|
||||
}
|
||||
|
||||
return count + '';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates comments data.
|
||||
*
|
||||
|
@ -124,14 +374,20 @@ export class CoreCommentsProvider {
|
|||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {number} [page=0] Page number (0 based). Default 0.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateCommentsData(contextLevel: string, instanceId: number, component: string, itemId: number,
|
||||
area: string = '', page: number = 0, siteId?: string): Promise<any> {
|
||||
area: string = '', siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.invalidateWsCacheForKey(this.getCommentsCacheKey(contextLevel, instanceId, component, itemId, area, page));
|
||||
|
||||
return this.utils.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))
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,338 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
|
||||
/**
|
||||
* Service to handle offline comments.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreCommentsOfflineProvider {
|
||||
|
||||
// Variables for database.
|
||||
static COMMENTS_TABLE = 'core_comments_offline_comments';
|
||||
static COMMENTS_DELETED_TABLE = 'core_comments_deleted_offline_comments';
|
||||
protected siteSchema: CoreSiteSchema = {
|
||||
name: 'CoreCommentsOfflineProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: CoreCommentsOfflineProvider.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: CoreCommentsOfflineProvider.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'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
constructor( private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider) {
|
||||
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all offline comments.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with comments.
|
||||
*/
|
||||
getAllComments(siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return Promise.all([site.getDb().getRecords(CoreCommentsOfflineProvider.COMMENTS_TABLE),
|
||||
site.getDb().getRecords(CoreCommentsOfflineProvider.COMMENTS_DELETED_TABLE)]).then((results) => {
|
||||
return [].concat.apply([], results);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an offline comment.
|
||||
*
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the comments.
|
||||
*/
|
||||
getComment(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '',
|
||||
siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getRecord(CoreCommentsOfflineProvider.COMMENTS_TABLE, {
|
||||
contextlevel: contextLevel,
|
||||
instanceid: instanceId,
|
||||
component: component,
|
||||
itemid: itemId,
|
||||
area: area
|
||||
});
|
||||
}).catch(() => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all offline comments added or deleted of a special area.
|
||||
*
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the comments.
|
||||
*/
|
||||
getComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '',
|
||||
siteId?: string): Promise<any> {
|
||||
let comments = [];
|
||||
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
return this.getComment(contextLevel, instanceId, component, itemId, area, siteId).then((comment) => {
|
||||
comments = comment ? [comment] : [];
|
||||
|
||||
return this.getDeletedComments(contextLevel, instanceId, component, itemId, area, siteId);
|
||||
}).then((deletedComments) => {
|
||||
comments = comments.concat(deletedComments);
|
||||
|
||||
return comments;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all offline deleted comments.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with comments.
|
||||
*/
|
||||
getAllDeletedComments(siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getRecords(CoreCommentsOfflineProvider.COMMENTS_DELETED_TABLE);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an offline comment.
|
||||
*
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the comments.
|
||||
*/
|
||||
getDeletedComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '',
|
||||
siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().getRecords(CoreCommentsOfflineProvider.COMMENTS_DELETED_TABLE, {
|
||||
contextlevel: contextLevel,
|
||||
instanceid: instanceId,
|
||||
component: component,
|
||||
itemid: itemId,
|
||||
area: area
|
||||
});
|
||||
}).catch(() => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an offline comment.
|
||||
*
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if deleted, rejected if failure.
|
||||
*/
|
||||
removeComment(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '',
|
||||
siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().deleteRecords(CoreCommentsOfflineProvider.COMMENTS_TABLE, {
|
||||
contextlevel: contextLevel,
|
||||
instanceid: instanceId,
|
||||
component: component,
|
||||
itemid: itemId,
|
||||
area: area
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an offline deleted comment.
|
||||
*
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if deleted, rejected if failure.
|
||||
*/
|
||||
removeDeletedComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '',
|
||||
siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().deleteRecords(CoreCommentsOfflineProvider.COMMENTS_DELETED_TABLE, {
|
||||
contextlevel: contextLevel,
|
||||
instanceid: instanceId,
|
||||
component: component,
|
||||
itemid: itemId,
|
||||
area: area
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a comment to be sent later.
|
||||
*
|
||||
* @param {string} content Comment text.
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
saveComment(content: string, contextLevel: string, instanceId: number, component: string, itemId: number,
|
||||
area: string = '', siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const now = this.timeUtils.timestamp();
|
||||
const data = {
|
||||
contextlevel: contextLevel,
|
||||
instanceid: instanceId,
|
||||
component: component,
|
||||
itemid: itemId,
|
||||
area: area,
|
||||
content: content,
|
||||
lastmodified: now
|
||||
};
|
||||
|
||||
return site.getDb().insertRecord(CoreCommentsOfflineProvider.COMMENTS_TABLE, data).then(() => {
|
||||
return data;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment offline to be sent later.
|
||||
*
|
||||
* @param {number} commentId Comment ID.
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
deleteComment(commentId: number, contextLevel: string, instanceId: number, component: string, itemId: number,
|
||||
area: string = '', siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const now = this.timeUtils.timestamp();
|
||||
const data = {
|
||||
contextlevel: contextLevel,
|
||||
instanceid: instanceId,
|
||||
component: component,
|
||||
itemid: itemId,
|
||||
area: area,
|
||||
commentid: commentId,
|
||||
deleted: now
|
||||
};
|
||||
|
||||
return site.getDb().insertRecord(CoreCommentsOfflineProvider.COMMENTS_DELETED_TABLE, data).then(() => {
|
||||
return data;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo delete a comment.
|
||||
*
|
||||
* @param {number} commentId Comment ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if deleted, rejected if failure.
|
||||
*/
|
||||
undoDeleteComment(commentId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
return site.getDb().deleteRecords(CoreCommentsOfflineProvider.COMMENTS_DELETED_TABLE, { commentid: commentId });
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 '@providers/cron';
|
||||
import { CoreCommentsSyncProvider } from './sync';
|
||||
|
||||
/**
|
||||
* Synchronization cron handler.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreCommentsSyncCronHandler implements CoreCronHandler {
|
||||
name = 'CoreCommentsSyncCronHandler';
|
||||
|
||||
constructor(private commentsSync: CoreCommentsSyncProvider) {}
|
||||
|
||||
/**
|
||||
* Execute the process.
|
||||
* Receives the ID of the site affected, undefined for all sites.
|
||||
*
|
||||
* @param {string} [siteId] ID of the site affected, undefined for all sites.
|
||||
* @param {boolean} [force] Wether the execution is forced (manual sync).
|
||||
* @return {Promise<any>} Promise resolved when done, rejected if failure.
|
||||
*/
|
||||
execute(siteId?: string, force?: boolean): Promise<any> {
|
||||
return this.commentsSync.syncAllComments(siteId, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time between consecutive executions.
|
||||
*
|
||||
* @return {number} Time between consecutive executions (in ms).
|
||||
*/
|
||||
getInterval(): number {
|
||||
return 300000; // 5 minutes.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,229 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreCommentsOfflineProvider } from './offline';
|
||||
import { CoreCommentsProvider } from './comments';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreSyncProvider } from '@providers/sync';
|
||||
|
||||
/**
|
||||
* Service to sync omments.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreCommentsSyncProvider extends CoreSyncBaseProvider {
|
||||
|
||||
static AUTO_SYNCED = 'core_comments_autom_synced';
|
||||
|
||||
constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider,
|
||||
syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService,
|
||||
private commentsOffline: CoreCommentsOfflineProvider, private utils: CoreUtilsProvider,
|
||||
private eventsProvider: CoreEventsProvider, private commentsProvider: CoreCommentsProvider,
|
||||
timeUtils: CoreTimeUtilsProvider) {
|
||||
|
||||
super('CoreCommentsSync', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to synchronize all the comments in a certain site or in all sites.
|
||||
*
|
||||
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||
* @param {boolean} [force] Wether to force sync not depending on last execution.
|
||||
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
syncAllComments(siteId?: string, force?: boolean): Promise<any> {
|
||||
return this.syncOnSites('all comments', this.syncAllCommentsFunc.bind(this), [force], siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize all the comments in a certain site
|
||||
*
|
||||
* @param {string} siteId Site ID to sync.
|
||||
* @param {boolean} force Wether to force sync not depending on last execution.
|
||||
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
private syncAllCommentsFunc(siteId: string, force: boolean): Promise<any> {
|
||||
return this.commentsOffline.getAllComments(siteId).then((comments) => {
|
||||
|
||||
// Get Unique array.
|
||||
comments.forEach((comment) => {
|
||||
comment.syncId = this.getSyncId(comment.contextlevel, comment.instanceid, comment.component, comment.itemid,
|
||||
comment.area);
|
||||
});
|
||||
|
||||
comments = this.utils.uniqueArray(comments, 'syncId');
|
||||
|
||||
// Sync all courses.
|
||||
const promises = comments.map((comment) => {
|
||||
const promise = 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);
|
||||
|
||||
return promise.then((warnings) => {
|
||||
if (typeof warnings != 'undefined') {
|
||||
// Sync successful, send event.
|
||||
this.eventsProvider.trigger(CoreCommentsSyncProvider.AUTO_SYNCED, {
|
||||
contextLevel: comment.contextlevel,
|
||||
instanceId: comment.instanceid,
|
||||
componentName: comment.component,
|
||||
itemId: comment.itemid,
|
||||
area: comment.area,
|
||||
warnings: warnings
|
||||
}, siteId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync course comments only if a certain time has passed since the last time.
|
||||
*
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when the comments are synced or if they don't need to be synced.
|
||||
*/
|
||||
private syncCommentsIfNeeded(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '',
|
||||
siteId?: string): Promise<void> {
|
||||
const syncId = this.getSyncId(contextLevel, instanceId, component, itemId, area);
|
||||
|
||||
return this.isSyncNeeded(syncId, siteId).then((needed) => {
|
||||
if (needed) {
|
||||
return this.syncComments(contextLevel, instanceId, component, itemId, area, siteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize comments in a particular area.
|
||||
*
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||
*/
|
||||
syncComments(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = '',
|
||||
siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.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);
|
||||
|
||||
const warnings = [];
|
||||
|
||||
// Get offline comments to be sent.
|
||||
const syncPromise = this.commentsOffline.getComments(contextLevel, instanceId, component, itemId, area, siteId)
|
||||
.then((comments) => {
|
||||
if (!comments.length) {
|
||||
// Nothing to sync.
|
||||
return;
|
||||
} else if (!this.appProvider.isOnline()) {
|
||||
// Cannot sync in offline.
|
||||
return Promise.reject(this.translate.instant('core.networkerrormsg'));
|
||||
}
|
||||
|
||||
const errors = [],
|
||||
promises = [],
|
||||
deleteCommentIds = [];
|
||||
|
||||
comments.forEach((comment) => {
|
||||
if (comment.commentid) {
|
||||
deleteCommentIds.push(comment.commentid);
|
||||
} else {
|
||||
promises.push(this.commentsProvider.addCommentOnline(comment.content, contextLevel, instanceId, component,
|
||||
itemId, area, siteId).then((response) => {
|
||||
return this.commentsOffline.removeComment(contextLevel, instanceId, component, itemId, area, siteId);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
if (deleteCommentIds.length > 0) {
|
||||
promises.push(this.commentsProvider.deleteCommentsOnline(deleteCommentIds, contextLevel, instanceId, component,
|
||||
itemId, area, siteId).then((response) => {
|
||||
return this.commentsOffline.removeDeletedComments(contextLevel, instanceId, component, itemId, area,
|
||||
siteId);
|
||||
}));
|
||||
}
|
||||
|
||||
// Send the comments.
|
||||
return Promise.all(promises).then(() => {
|
||||
// Fetch the comments from server to be sure they're up to date.
|
||||
return this.commentsProvider.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId)
|
||||
.then(() => {
|
||||
return this.commentsProvider.getComments(contextLevel, instanceId, component, itemId, area, 0, siteId);
|
||||
}).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (this.utils.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.
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}).then(() => {
|
||||
if (errors && errors.length) {
|
||||
errors.forEach((error) => {
|
||||
warnings.push(this.translate.instant('core.comments.warningcommentsnotsent', {
|
||||
error: error
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
// All done, return the warnings.
|
||||
return warnings;
|
||||
});
|
||||
|
||||
return this.addOngoingSync(syncId, syncPromise, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of a comments sync.
|
||||
*
|
||||
* @param {string} contextLevel Contextlevel system, course, user...
|
||||
* @param {number} instanceId The Instance id of item associated with the context level.
|
||||
* @param {string} component Component name.
|
||||
* @param {number} itemId Associated id.
|
||||
* @param {string} [area=''] String comment area. Default empty.
|
||||
* @return {string} Sync ID.
|
||||
*/
|
||||
protected getSyncId(contextLevel: string, instanceId: number, component: string, itemId: number, area: string = ''): string {
|
||||
return contextLevel + '#' + instanceId + '#' + component + '#' + itemId + '#' + area;
|
||||
}
|
||||
}
|
|
@ -25,9 +25,6 @@
|
|||
"clicktohideshow": "Click to expand or collapse",
|
||||
"clicktoseefull": "Click to see full contents.",
|
||||
"close": "Close",
|
||||
"comments": "Comments",
|
||||
"commentscount": "Comments ({{$a}})",
|
||||
"commentsnotworking": "Comments cannot be retrieved",
|
||||
"completion-alt-auto-fail": "Completed: {{$a}} (did not achieve pass grade)",
|
||||
"completion-alt-auto-n": "Not completed: {{$a}}",
|
||||
"completion-alt-auto-n-override": "Not completed: {{$a.modname}} (set by {{$a.overrideuser}})",
|
||||
|
@ -168,7 +165,6 @@
|
|||
"never": "Never",
|
||||
"next": "Next",
|
||||
"no": "No",
|
||||
"nocomments": "No comments",
|
||||
"nograde": "No grade",
|
||||
"none": "None",
|
||||
"nopasswordchangeforced": "You cannot proceed without changing your password.",
|
||||
|
|
Loading…
Reference in New Issue