forked from CIT/Vmeda.Online
		
	Merge pull request #2703 from crazyserver/MOBILE-3632
MOBILE-3632 notes: Add notes funcionality
This commit is contained in:
		
						commit
						3dc1834a6b
					
				@ -29,6 +29,7 @@ import { AddonQbehaviourModule } from './qbehaviour/qbehaviour.module';
 | 
			
		||||
import { AddonQtypeModule } from './qtype/qtype.module';
 | 
			
		||||
import { AddonBlogModule } from './blog/blog.module';
 | 
			
		||||
import { AddonRemoteThemesModule } from './remotethemes/remotethemes.module';
 | 
			
		||||
import { AddonNotesModule } from './notes/notes.module';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
@ -44,6 +45,7 @@ import { AddonRemoteThemesModule } from './remotethemes/remotethemes.module';
 | 
			
		||||
        AddonNotificationsModule,
 | 
			
		||||
        AddonMessageOutputModule,
 | 
			
		||||
        AddonModModule,
 | 
			
		||||
        AddonNotesModule,
 | 
			
		||||
        AddonQbehaviourModule,
 | 
			
		||||
        AddonQtypeModule,
 | 
			
		||||
        AddonRemoteThemesModule,
 | 
			
		||||
 | 
			
		||||
@ -110,7 +110,7 @@
 | 
			
		||||
                                {{discussion.created * 1000 | coreFormatDate: "strftimerecentfull"}}
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p *ngIf="discussions.isOfflineDiscussion(discussion)">
 | 
			
		||||
                                <ion-icon name="time"></ion-icon>
 | 
			
		||||
                                <ion-icon name="fas-clock"></ion-icon>
 | 
			
		||||
                                {{ 'core.notsent' | translate }}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
@ -119,7 +119,7 @@
 | 
			
		||||
                        class="ion-text-center addon-mod-forum-discussion-more-info">
 | 
			
		||||
                        <ion-col class="ion-text-start">
 | 
			
		||||
                            <ion-note>
 | 
			
		||||
                                <ion-icon name="time"></ion-icon> {{ 'addon.mod_forum.lastpost' | translate }}
 | 
			
		||||
                                <ion-icon name="fas-clock"></ion-icon> {{ 'addon.mod_forum.lastpost' | translate }}
 | 
			
		||||
                                <ng-container *ngIf="discussion.timemodified > discussion.created">
 | 
			
		||||
                                    {{ discussion.timemodified | coreTimeAgo }}
 | 
			
		||||
                                </ng-container>
 | 
			
		||||
@ -151,7 +151,7 @@
 | 
			
		||||
 | 
			
		||||
    <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="forum && canAddDiscussion">
 | 
			
		||||
        <ion-fab-button (click)="openNewDiscussion()" [attr.aria-label]="addDiscussionText">
 | 
			
		||||
            <ion-icon name="add"></ion-icon>
 | 
			
		||||
            <ion-icon name="fas-plus"></ion-icon>
 | 
			
		||||
        </ion-fab-button>
 | 
			
		||||
    </ion-fab>
 | 
			
		||||
</core-split-view>
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@
 | 
			
		||||
        </ion-label>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
    <ion-item class="ion-text-wrap" (click)="deletePost()" *ngIf="offlinePost || (canDelete && isOnline)">
 | 
			
		||||
        <ion-icon name="trash" slot="start"></ion-icon>
 | 
			
		||||
        <ion-icon name="fas-trash" slot="start"></ion-icon>
 | 
			
		||||
        <ion-label>
 | 
			
		||||
            <h2 *ngIf="!offlinePost">{{ 'addon.mod_forum.delete' | translate }}</h2>
 | 
			
		||||
            <h2 *ngIf="offlinePost">{{ 'core.discard' | translate }}</h2>
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,7 @@
 | 
			
		||||
                            </ng-container>
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p *ngIf="post.timecreated">{{post.timecreated * 1000 | coreFormatDate: "strftimerecentfull"}}</p>
 | 
			
		||||
                        <p *ngIf="!post.timecreated"><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</p>
 | 
			
		||||
                        <p *ngIf="!post.timecreated"><ion-icon name="fas-clock"></ion-icon> {{ 'core.notsent' | translate }}</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <ng-container *ngIf="!displaySubject">
 | 
			
		||||
                        <ion-note *ngIf="trackPosts && post.unread"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								src/addons/notes/components/add/add-modal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/addons/notes/components/add/add-modal.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-title>{{ 'addon.notes.addnewnote' | 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)="addNote($event)" #itemEdit>
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-label>{{ 'addon.notes.publishstate' | translate }}</ion-label>
 | 
			
		||||
            <ion-select [(ngModel)]="type" name="publishState" interface="popover">
 | 
			
		||||
                <ion-select-option value="personal">{{ 'addon.notes.personalnotes' | translate }}</ion-select-option>
 | 
			
		||||
                <ion-select-option value="course">{{ 'addon.notes.coursenotes' | translate }}</ion-select-option>
 | 
			
		||||
                <ion-select-option value="site">{{ 'addon.notes.sitenotes' | translate }}</ion-select-option>
 | 
			
		||||
            </ion-select>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <ion-textarea placeholder="{{ 'addon.notes.note' | translate }}" rows="5" [(ngModel)]="text" name="text"
 | 
			
		||||
                    required="required">
 | 
			
		||||
                </ion-textarea>
 | 
			
		||||
            </ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
        <div class="ion-padding">
 | 
			
		||||
            <ion-button expand="block" type="submit" [disabled]="processing || text.length < 2">
 | 
			
		||||
                {{ 'addon.notes.addnewnote' | translate }}
 | 
			
		||||
            </ion-button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										78
									
								
								src/addons/notes/components/add/add-modal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/addons/notes/components/add/add-modal.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,78 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { AddonNotes } from '@addons/notes/services/notes';
 | 
			
		||||
import { Component, ViewChild, ElementRef, Input } from '@angular/core';
 | 
			
		||||
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 note.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    templateUrl: 'add-modal.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonNotesAddComponent {
 | 
			
		||||
 | 
			
		||||
    @ViewChild('itemEdit') formElement?: ElementRef;
 | 
			
		||||
 | 
			
		||||
    @Input() protected courseId!: number;
 | 
			
		||||
    @Input() protected userId?: number;
 | 
			
		||||
    @Input() type = 'personal';
 | 
			
		||||
    text = '';
 | 
			
		||||
    processing = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send the note or store it offline.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e Event.
 | 
			
		||||
     */
 | 
			
		||||
    async addNote(e: Event): Promise<void> {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        CoreApp.closeKeyboard();
 | 
			
		||||
        const loadingModal = await CoreDomUtils.showModalLoading('core.sending', true);
 | 
			
		||||
 | 
			
		||||
        // Freeze the add note button.
 | 
			
		||||
        this.processing = true;
 | 
			
		||||
        try {
 | 
			
		||||
            this.userId = this.userId || CoreSites.getCurrentSiteUserId();
 | 
			
		||||
            const sent = await AddonNotes.addNote(this.userId, this.courseId, this.type, this.text);
 | 
			
		||||
 | 
			
		||||
            CoreDomUtils.triggerFormSubmittedEvent(this.formElement, sent, CoreSites.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
            ModalController.dismiss({ type: this.type, sent: true }).finally(() => {
 | 
			
		||||
                CoreDomUtils.showToast(sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline', true, 3000);
 | 
			
		||||
            });
 | 
			
		||||
        } catch (error){
 | 
			
		||||
            CoreDomUtils.showErrorModal(error);
 | 
			
		||||
            this.processing = false;
 | 
			
		||||
        } finally {
 | 
			
		||||
            loadingModal.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Close modal.
 | 
			
		||||
     */
 | 
			
		||||
    closeModal(): void {
 | 
			
		||||
        CoreDomUtils.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
        ModalController.dismiss({ type: this.type });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/addons/notes/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/addons/notes/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { AddonNotesAddComponent } from './add/add-modal';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonNotesAddComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonNotesAddComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonNotesComponentsModule {}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/addons/notes/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/addons/notes/lang.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
{
 | 
			
		||||
    "addnewnote": "Add a new note",
 | 
			
		||||
    "coursenotes": "Course notes",
 | 
			
		||||
    "deleteconfirm": "Delete this note?",
 | 
			
		||||
    "eventnotecreated": "Note created",
 | 
			
		||||
    "eventnotedeleted": "Note deleted",
 | 
			
		||||
    "nonotes": "There are no notes of this type yet",
 | 
			
		||||
    "note": "Note",
 | 
			
		||||
    "notes": "Notes",
 | 
			
		||||
    "personalnotes": "Personal notes",
 | 
			
		||||
    "publishstate": "Context",
 | 
			
		||||
    "sitenotes": "Site notes",
 | 
			
		||||
    "userwithid": "User with ID {{id}}",
 | 
			
		||||
    "warningnotenotsent": "Couldn't add note(s) to course {{course}}. {{error}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								src/addons/notes/notes-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/addons/notes/notes-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
			
		||||
// (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 { Routes, RouterModule } from '@angular/router';
 | 
			
		||||
import { CoreCommentsComponentsModule } from '@features/comments/components/components.module';
 | 
			
		||||
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
 | 
			
		||||
import { AddonNotesListPage } from './pages/list/list.page';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        component: AddonNotesListPage,
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreCommentsComponentsModule,
 | 
			
		||||
        CoreTagComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [RouterModule],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonNotesListPage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonNotesLazyModule {}
 | 
			
		||||
							
								
								
									
										70
									
								
								src/addons/notes/notes.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/addons/notes/notes.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
 | 
			
		||||
import { AddonNotesProvider } from './services/notes';
 | 
			
		||||
import { AddonNotesOfflineProvider } from './services/notes-offline';
 | 
			
		||||
import { AddonNotesSyncProvider } from './services/notes-sync';
 | 
			
		||||
import { CoreCronDelegate } from '@services/cron';
 | 
			
		||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
 | 
			
		||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
 | 
			
		||||
import { AddonNotesCourseOptionHandler } from './services/handlers/course-option';
 | 
			
		||||
import { AddonNotesSyncCronHandler } from './services/handlers/sync-cron';
 | 
			
		||||
import { AddonNotesUserHandler } from './services/handlers/user';
 | 
			
		||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
			
		||||
import { NOTES_OFFLINE_SITE_SCHEMA } from './services/database/notes';
 | 
			
		||||
import { AddonNotesComponentsModule } from './components/components.module';
 | 
			
		||||
import { Routes } from '@angular/router';
 | 
			
		||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
 | 
			
		||||
 | 
			
		||||
// List of providers (without handlers).
 | 
			
		||||
export const ADDON_NOTES_SERVICES: Type<unknown>[] = [
 | 
			
		||||
    AddonNotesProvider,
 | 
			
		||||
    AddonNotesOfflineProvider,
 | 
			
		||||
    AddonNotesSyncProvider,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: 'notes',
 | 
			
		||||
        loadChildren: () => import('@addons/notes/notes-lazy.module').then(m => m.AddonNotesLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
        CoreCourseIndexRoutingModule.forChild({ children: routes }),
 | 
			
		||||
        AddonNotesComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: CORE_SITE_SCHEMAS,
 | 
			
		||||
            useValue: [NOTES_OFFLINE_SITE_SCHEMA],
 | 
			
		||||
            multi: true,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            provide: APP_INITIALIZER,
 | 
			
		||||
            multi: true,
 | 
			
		||||
            deps: [],
 | 
			
		||||
            useFactory: () => async () => {
 | 
			
		||||
                CoreUserDelegate.registerHandler(AddonNotesUserHandler.instance);
 | 
			
		||||
                CoreCourseOptionsDelegate.registerHandler(AddonNotesCourseOptionHandler.instance);
 | 
			
		||||
                CoreCronDelegate.register(AddonNotesSyncCronHandler.instance);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonNotesModule {}
 | 
			
		||||
							
								
								
									
										100
									
								
								src/addons/notes/pages/list/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/addons/notes/pages/list/list.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,100 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
 | 
			
		||||
        <ion-title>{{ 'addon.notes.notes' | translate }}</ion-title>
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <ion-button [hidden]="!canDeleteNotes" 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]="!(notesLoaded && !hasOffline)" [priority]="100"
 | 
			
		||||
            [content]="'core.refresh' | translate" (action)="refreshNotes(false)"
 | 
			
		||||
            [iconAction]="refreshIcon" [closeOnClick]="true">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [hidden]="!(notesLoaded && hasOffline)" [priority]="100"
 | 
			
		||||
            [content]="'core.settings.synchronizenow' | translate" (action)="refreshNotes(true)"
 | 
			
		||||
            [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
</core-navbar-buttons>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!notesLoaded" (ionRefresh)="refreshNotes(false, $event.target)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <core-loading [hideUntil]="notesLoaded" class="core-loading-center">
 | 
			
		||||
        <ion-item class="ion-text-wrap" *ngIf="user">
 | 
			
		||||
            <core-user-avatar [user]="user" [courseId]="courseId" slot="start" [linkProfile]="false"></core-user-avatar>
 | 
			
		||||
            <ion-label><h2>{{user!.fullname}}</h2></ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
 | 
			
		||||
        <div class="ion-padding">
 | 
			
		||||
            <ion-select [(ngModel)]="type" (ngModelChange)="typeChanged()" interface="popover" class="core-button-select">
 | 
			
		||||
                <ion-select-option value="site">{{ 'addon.notes.sitenotes' | translate }}</ion-select-option>
 | 
			
		||||
                <ion-select-option value="course">{{ 'addon.notes.coursenotes' | translate }}</ion-select-option>
 | 
			
		||||
                <ion-select-option value="personal">{{ 'addon.notes.personalnotes' | translate }}</ion-select-option>
 | 
			
		||||
            </ion-select>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <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: 'addon.notes.notes' | translate | lowercase } }}
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-card>
 | 
			
		||||
 | 
			
		||||
        <core-empty-box *ngIf="notes && notes.length == 0" icon="fas-receipt" [message]="'addon.notes.nonotes' | translate">
 | 
			
		||||
        </core-empty-box>
 | 
			
		||||
 | 
			
		||||
        <ng-container *ngIf="notes && notes.length > 0">
 | 
			
		||||
            <ion-card *ngFor="let note of notes">
 | 
			
		||||
                <ion-item class="ion-text-wrap">
 | 
			
		||||
                    <core-user-avatar [user]="note" [courseId]="courseId" slot="start" *ngIf="!userId"></core-user-avatar>
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <h2 *ngIf="!userId">{{note.userfullname}}</h2>
 | 
			
		||||
                        <p *ngIf="!note.deleted && !note.offline" slot="end">
 | 
			
		||||
                            <span class="ion-text-wrap">{{note.lastmodified | coreDateDayOrTime}}</span>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                    <p *ngIf="note.offline" slot="end">
 | 
			
		||||
                        <ion-icon name="far-clock"></ion-icon> <span class="ion-text-wrap">
 | 
			
		||||
                            {{ 'core.notsent' | translate }}
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p *ngIf="note.deleted" slot="end">
 | 
			
		||||
                        <ion-icon name="fas-trash"></ion-icon> <span class="ion-text-wrap">
 | 
			
		||||
                            {{ 'core.deletedoffline' | translate }}
 | 
			
		||||
                        </span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <ion-button *ngIf="note.deleted" slot="end" fill="clear" color="danger" (click)="undoDeleteNote($event, note)"
 | 
			
		||||
                        [attr.aria-label]="'core.restore' | translate">
 | 
			
		||||
                        <ion-icon name="fas-undo-alt" slot="icon-only"></ion-icon>
 | 
			
		||||
                    </ion-button>
 | 
			
		||||
                    <ion-button *ngIf="showDelete && !note.deleted && (type != 'personal' || note.usermodified == currentUserId)"
 | 
			
		||||
                        slot="end" fill="clear" [@coreSlideInOut]="'fromRight'" color="danger" (click)="deleteNote($event, note)"
 | 
			
		||||
                        [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 [text]="note.content" [filter]="false"></core-format-text></ion-label>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
            </ion-card>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
    </core-loading>
 | 
			
		||||
 | 
			
		||||
    <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="userId && notesLoaded">
 | 
			
		||||
        <ion-fab-button (click)="addNote($event)" [attr.aria-label]="'addon.notes.addnewnote' |translate">
 | 
			
		||||
            <ion-icon name="fas-plus"></ion-icon>
 | 
			
		||||
        </ion-fab-button>
 | 
			
		||||
    </ion-fab>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										295
									
								
								src/addons/notes/pages/list/list.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								src/addons/notes/pages/list/list.page.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,295 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { AddonNotesAddComponent } from '@addons/notes/components/add/add-modal';
 | 
			
		||||
import { AddonNotes, AddonNotesNoteFormatted } from '@addons/notes/services/notes';
 | 
			
		||||
import { AddonNotesOffline } from '@addons/notes/services/notes-offline';
 | 
			
		||||
import { AddonNotesSync, AddonNotesSyncProvider } from '@addons/notes/services/notes-sync';
 | 
			
		||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { CoreAnimations } from '@components/animations';
 | 
			
		||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
 | 
			
		||||
import { IonContent, IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { ModalController } from '@singletons';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a list of notes.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-notes-list-page',
 | 
			
		||||
    templateUrl: 'list.html',
 | 
			
		||||
    animations: [CoreAnimations.SLIDE_IN_OUT],
 | 
			
		||||
})
 | 
			
		||||
export class AddonNotesListPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
     @ViewChild(IonContent) content?: IonContent;
 | 
			
		||||
 | 
			
		||||
    courseId: number;
 | 
			
		||||
    userId?: number;
 | 
			
		||||
    type = 'course';
 | 
			
		||||
    refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
    syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
    notes: AddonNotesNoteFormatted[] = [];
 | 
			
		||||
    hasOffline = false;
 | 
			
		||||
    notesLoaded = false;
 | 
			
		||||
    user?: CoreUserProfile;
 | 
			
		||||
    showDelete = false;
 | 
			
		||||
    canDeleteNotes = false;
 | 
			
		||||
    currentUserId: number;
 | 
			
		||||
 | 
			
		||||
    protected syncObserver: CoreEventObserver;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
 | 
			
		||||
        this.userId = CoreNavigator.getRouteNumberParam('userId');
 | 
			
		||||
 | 
			
		||||
        // Refresh data if notes are synchronized automatically.
 | 
			
		||||
        this.syncObserver = CoreEvents.on(AddonNotesSyncProvider.AUTO_SYNCED, (data) => {
 | 
			
		||||
            if (data.courseId == this.courseId) {
 | 
			
		||||
                // Show the sync warnings.
 | 
			
		||||
                this.showSyncWarnings(data.warnings);
 | 
			
		||||
 | 
			
		||||
                // Refresh the data.
 | 
			
		||||
                this.notesLoaded = false;
 | 
			
		||||
                this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
                this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
 | 
			
		||||
                this.content?.scrollToTop();
 | 
			
		||||
                this.fetchNotes(false);
 | 
			
		||||
            }
 | 
			
		||||
        }, CoreSites.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
        this.currentUserId = CoreSites.getCurrentSiteUserId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        await this.fetchNotes(true);
 | 
			
		||||
 | 
			
		||||
        CoreUtils.ignoreErrors(AddonNotes.logView(this.courseId, this.userId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch notes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sync When to resync notes.
 | 
			
		||||
     * @param showErrors When to display errors or not.
 | 
			
		||||
     * @return Promise with the notes.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchNotes(sync = false, showErrors = false): Promise<void> {
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            await this.syncNotes(showErrors);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const allNotes = await AddonNotes.getNotes(this.courseId, this.userId);
 | 
			
		||||
 | 
			
		||||
            const notesList: AddonNotesNoteFormatted[] = allNotes[this.type + 'notes'] || [];
 | 
			
		||||
 | 
			
		||||
            notesList.forEach((note) => {
 | 
			
		||||
                note.content = CoreTextUtils.decodeHTML(note.content);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            await AddonNotes.setOfflineDeletedNotes(notesList, this.courseId);
 | 
			
		||||
 | 
			
		||||
            this.hasOffline = notesList.some((note) => note.offline || note.deleted);
 | 
			
		||||
 | 
			
		||||
            if (this.userId) {
 | 
			
		||||
                this.notes = notesList;
 | 
			
		||||
 | 
			
		||||
                // Get the user profile to retrieve the user image.
 | 
			
		||||
                this.user = await CoreUser.getProfile(this.userId, this.courseId, true);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.notes = await AddonNotes.getNotesUserData(notesList);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModal(error);
 | 
			
		||||
        } finally {
 | 
			
		||||
            let canDelete = this.notes && this.notes.length > 0;
 | 
			
		||||
            if (canDelete && this.type == 'personal') {
 | 
			
		||||
                canDelete = !!this.notes.find((note) =>  note.usermodified == this.currentUserId);
 | 
			
		||||
            }
 | 
			
		||||
            this.canDeleteNotes = canDelete;
 | 
			
		||||
 | 
			
		||||
            this.notesLoaded = true;
 | 
			
		||||
            this.refreshIcon = CoreConstants.ICON_REFRESH;
 | 
			
		||||
            this.syncIcon = CoreConstants.ICON_SYNC;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh notes on PTR.
 | 
			
		||||
     *
 | 
			
		||||
     * @param showErrors Whether to display errors or not.
 | 
			
		||||
     * @param refresher Refresher instance.
 | 
			
		||||
     */
 | 
			
		||||
    refreshNotes(showErrors: boolean, refresher?: IonRefresher): void {
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
 | 
			
		||||
        AddonNotes.invalidateNotes(this.courseId, this.userId).finally(() => {
 | 
			
		||||
            this.fetchNotes(true, showErrors).finally(() => {
 | 
			
		||||
                if (refresher) {
 | 
			
		||||
                    refresher?.complete();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function called when the type has changed.
 | 
			
		||||
     */
 | 
			
		||||
    async typeChanged(): Promise<void> {
 | 
			
		||||
        this.notesLoaded = false;
 | 
			
		||||
        this.refreshIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
        this.syncIcon = CoreConstants.ICON_LOADING;
 | 
			
		||||
 | 
			
		||||
        await this.fetchNotes(true);
 | 
			
		||||
        CoreUtils.ignoreErrors(AddonNotes.logView(this.courseId, this.userId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a new Note to user and course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e Event.
 | 
			
		||||
     */
 | 
			
		||||
    async addNote(e: Event): Promise<void> {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        const modal = await ModalController.create({
 | 
			
		||||
            component: AddonNotesAddComponent,
 | 
			
		||||
            componentProps: {
 | 
			
		||||
                userId: this.userId,
 | 
			
		||||
                courseId: this.courseId,
 | 
			
		||||
                type: this.type,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await modal.present();
 | 
			
		||||
 | 
			
		||||
        const result = await modal.onDidDismiss();
 | 
			
		||||
 | 
			
		||||
        if (typeof result.data != 'undefined') {
 | 
			
		||||
 | 
			
		||||
            if (result.data.sent && result.data.type) {
 | 
			
		||||
                if (result.data.type != this.type) {
 | 
			
		||||
                    this.type = result.data.type;
 | 
			
		||||
                    this.notesLoaded = false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.refreshNotes(false);
 | 
			
		||||
            } else if (result.data.type && result.data.type != this.type) {
 | 
			
		||||
                this.type = result.data.type;
 | 
			
		||||
                this.typeChanged();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a note.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e Click event.
 | 
			
		||||
     * @param note Note to delete.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteNote(e: Event, note: AddonNotesNoteFormatted): Promise<void> {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreDomUtils.showDeleteConfirm('addon.notes.deleteconfirm');
 | 
			
		||||
            try {
 | 
			
		||||
                await AddonNotes.deleteNote(note, this.courseId);
 | 
			
		||||
                this.showDelete = false;
 | 
			
		||||
 | 
			
		||||
                this.refreshNotes(false);
 | 
			
		||||
 | 
			
		||||
                CoreDomUtils.showToast('addon.notes.eventnotedeleted', true, 3000);
 | 
			
		||||
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(error, 'Delete note failed.');
 | 
			
		||||
            }
 | 
			
		||||
        } catch {
 | 
			
		||||
            // User cancelled, nothing to do.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Restore a note.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e Click event.
 | 
			
		||||
     * @param note Note to delete.
 | 
			
		||||
     */
 | 
			
		||||
    async undoDeleteNote(e: Event, note: AddonNotesNoteFormatted): Promise<void> {
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        await AddonNotesOffline.undoDeleteNote(note.id);
 | 
			
		||||
        this.refreshNotes(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggle delete.
 | 
			
		||||
     */
 | 
			
		||||
    toggleDelete(): void {
 | 
			
		||||
        this.showDelete = !this.showDelete;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Tries to synchronize course notes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param showErrors Whether to display errors or not.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async syncNotes(showErrors: boolean): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            const result = await AddonNotesSync.syncNotes(this.courseId);
 | 
			
		||||
 | 
			
		||||
            this.showSyncWarnings(result.warnings);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (showErrors) {
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(error, 'core.errorsync', true);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show sync warnings if any.
 | 
			
		||||
     *
 | 
			
		||||
     * @param warnings the warnings
 | 
			
		||||
     */
 | 
			
		||||
    protected showSyncWarnings(warnings: string[]): void {
 | 
			
		||||
        const message = CoreTextUtils.buildMessage(warnings);
 | 
			
		||||
 | 
			
		||||
        if (message) {
 | 
			
		||||
            CoreDomUtils.showErrorModal(message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Page destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.syncObserver && this.syncObserver.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										95
									
								
								src/addons/notes/services/database/notes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/addons/notes/services/database/notes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,95 @@
 | 
			
		||||
// (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 AddonNotesOfflineProvider.
 | 
			
		||||
 */
 | 
			
		||||
export const NOTES_TABLE = 'addon_notes_offline_notes';
 | 
			
		||||
export const NOTES_DELETED_TABLE = 'addon_notes_deleted_offline_notes';
 | 
			
		||||
export const NOTES_OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
 | 
			
		||||
    name: 'AddonNotesOfflineProvider',
 | 
			
		||||
    version: 2,
 | 
			
		||||
    tables: [
 | 
			
		||||
        {
 | 
			
		||||
            name: NOTES_TABLE,
 | 
			
		||||
            columns: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'userid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'courseid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'publishstate',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'content',
 | 
			
		||||
                    type: 'TEXT',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'format',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'created',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'lastmodified',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
            primaryKeys: ['userid', 'content', 'created'],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            name: NOTES_DELETED_TABLE,
 | 
			
		||||
            columns: [
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'noteid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                    primaryKey: true,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'deleted',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    name: 'courseid',
 | 
			
		||||
                    type: 'INTEGER',
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AddonNotesDBRecord = {
 | 
			
		||||
    userid: number; // Primary key.
 | 
			
		||||
    content: string; // Primary key.
 | 
			
		||||
    created: number; // Primary key.
 | 
			
		||||
    courseid: number;
 | 
			
		||||
    publishstate: string;
 | 
			
		||||
    format: number;
 | 
			
		||||
    lastmodified: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AddonNotesDeletedDBRecord = {
 | 
			
		||||
    noteid: number; // Primary key.
 | 
			
		||||
    deleted: number;
 | 
			
		||||
    courseid: number;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										81
									
								
								src/addons/notes/services/handlers/course-option.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/addons/notes/services/handlers/course-option.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
			
		||||
// (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 { CoreCourseProvider } from '@features/course/services/course';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCourseAccess,
 | 
			
		||||
    CoreCourseOptionsHandler,
 | 
			
		||||
    CoreCourseOptionsHandlerData,
 | 
			
		||||
} from '@features/course/services/course-options-delegate';
 | 
			
		||||
import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonNotes } from '../notes';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to inject an option into the course main menu.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' } )
 | 
			
		||||
export class AddonNotesCourseOptionHandlerService implements CoreCourseOptionsHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonNotes';
 | 
			
		||||
    priority = 200;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return AddonNotes.isPluginEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabledForCourse(
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        accessData: CoreCourseAccess,
 | 
			
		||||
        navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
 | 
			
		||||
    ): Promise<boolean> {
 | 
			
		||||
        if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
 | 
			
		||||
            return false; // Not enabled for guests.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (navOptions && typeof navOptions.notes != 'undefined') {
 | 
			
		||||
            return navOptions.notes;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return AddonNotes.isPluginViewNotesEnabledForCourse(courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData(): CoreCourseOptionsHandlerData {
 | 
			
		||||
        return {
 | 
			
		||||
            title: 'addon.notes.notes',
 | 
			
		||||
            class: 'addon-notes-course-handler',
 | 
			
		||||
            page: 'notes',
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async prefetch(course: CoreEnrolledCourseDataWithExtraInfoAndOptions): Promise<void> {
 | 
			
		||||
        await AddonNotes.getNotes(course.id, undefined, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonNotesCourseOptionHandler = makeSingleton(AddonNotesCourseOptionHandlerService);
 | 
			
		||||
							
								
								
									
										43
									
								
								src/addons/notes/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/addons/notes/services/handlers/sync-cron.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
// (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 { AddonNotesSync } from '../notes-sync';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Synchronization cron handler.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' } )
 | 
			
		||||
export class AddonNotesSyncCronHandlerService implements CoreCronHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonNotesSyncCronHandler';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    execute(siteId?: string, force?: boolean): Promise<void> {
 | 
			
		||||
        return AddonNotesSync.syncAllNotes(siteId, force);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getInterval(): number {
 | 
			
		||||
        return 300000; // 5 minutes.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonNotesSyncCronHandler = makeSingleton(AddonNotesSyncCronHandlerService);
 | 
			
		||||
							
								
								
									
										73
									
								
								src/addons/notes/services/handlers/user.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/addons/notes/services/handlers/user.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,73 @@
 | 
			
		||||
// (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 { CoreUserProfile } from '@features/user/services/user';
 | 
			
		||||
import { CoreUserProfileHandler, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonNotes } from '../notes';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Profile notes handler.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' } )
 | 
			
		||||
export class AddonNotesUserHandlerService implements CoreUserProfileHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonNotes:notes';
 | 
			
		||||
    priority = 100;
 | 
			
		||||
    type = CoreUserDelegateService.TYPE_NEW_PAGE;
 | 
			
		||||
    cacheEnabled = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return AddonNotes.isPluginEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
 | 
			
		||||
        // Active course required.
 | 
			
		||||
        if (!courseId || user.id == CoreSites.getCurrentSiteUserId()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // We are not using isEnabledForCourse because we need to cache the call.
 | 
			
		||||
        return AddonNotes.isPluginViewNotesEnabledForCourse(courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData(): CoreUserProfileHandlerData {
 | 
			
		||||
        return {
 | 
			
		||||
            icon: 'fas-receipt',
 | 
			
		||||
            title: 'addon.notes.notes',
 | 
			
		||||
            class: 'addon-notes-handler',
 | 
			
		||||
            action: (event, user, courseId): void => {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
                event.stopPropagation();
 | 
			
		||||
                CoreNavigator.navigateToSitePath('/notes', {
 | 
			
		||||
                    params: { courseId, userId: user.id },
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonNotesUserHandler = makeSingleton(AddonNotesUserHandlerService);
 | 
			
		||||
							
								
								
									
										259
									
								
								src/addons/notes/services/notes-offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								src/addons/notes/services/notes-offline.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,259 @@
 | 
			
		||||
// (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 { AddonNotesDBRecord, AddonNotesDeletedDBRecord, NOTES_DELETED_TABLE, NOTES_TABLE } from './database/notes';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to handle offline notes.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' } )
 | 
			
		||||
export class AddonNotesOfflineProvider {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete an offline note.
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId User ID the note is about.
 | 
			
		||||
     * @param content The note content.
 | 
			
		||||
     * @param timecreated The time the note was created.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if deleted, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteOfflineNote(userId: number, content: string, timecreated: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.getDb().deleteRecords(NOTES_TABLE, {
 | 
			
		||||
            userid: userId,
 | 
			
		||||
            content: content,
 | 
			
		||||
            created: timecreated,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all offline deleted notes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with notes.
 | 
			
		||||
     */
 | 
			
		||||
    async getAllDeletedNotes(siteId?: string): Promise<AddonNotesDeletedDBRecord[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return site.getDb().getRecords(NOTES_DELETED_TABLE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get course offline deleted notes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with notes.
 | 
			
		||||
     */
 | 
			
		||||
    async getCourseDeletedNotes(courseId: number, siteId?: string): Promise<AddonNotesDeletedDBRecord[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return site.getDb().getRecords(NOTES_DELETED_TABLE, { courseid: courseId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all offline notes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with notes.
 | 
			
		||||
     */
 | 
			
		||||
    async getAllNotes(siteId?: string): Promise<AddonNotesDBRecord[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return site.getDb().getRecords(NOTES_TABLE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get an offline note.
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId User ID the note is about.
 | 
			
		||||
     * @param content The note content.
 | 
			
		||||
     * @param timecreated The time the note was created.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with the notes.
 | 
			
		||||
     */
 | 
			
		||||
    async getNote(userId: number, content: string, timecreated: number, siteId?: string): Promise<AddonNotesDBRecord> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return site.getDb().getRecord(NOTES_TABLE, {
 | 
			
		||||
            userid: userId,
 | 
			
		||||
            content: content,
 | 
			
		||||
            created: timecreated,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get offline notes for a certain course and user.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param userId User ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with notes.
 | 
			
		||||
     */
 | 
			
		||||
    async getNotesForCourseAndUser(courseId: number, userId?: number, siteId?: string): Promise<AddonNotesDBRecord[]> {
 | 
			
		||||
        if (!userId) {
 | 
			
		||||
            return this.getNotesForCourse(courseId, siteId);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return await site.getDb().getRecords(NOTES_TABLE, { userid: userId, courseid: courseId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get offline notes for a certain course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with notes.
 | 
			
		||||
     */
 | 
			
		||||
    async getNotesForCourse(courseId: number, siteId?: string): Promise<AddonNotesDBRecord[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return site.getDb().getRecords(NOTES_TABLE, { courseid: courseId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get offline notes for a certain user.
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId User ID the notes are about.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with notes.
 | 
			
		||||
     */
 | 
			
		||||
    async getNotesForUser(userId: number, siteId?: string): Promise<AddonNotesDBRecord[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return await site.getDb().getRecords(NOTES_TABLE, { userid: userId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get offline notes with a certain publish state (Personal, Site or Course).
 | 
			
		||||
     *
 | 
			
		||||
     * @param state Publish state ('personal', 'site' or 'course').
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with notes.
 | 
			
		||||
     */
 | 
			
		||||
    async getNotesWithPublishState(state: string, siteId?: string): Promise<AddonNotesDBRecord[]> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return await site.getDb().getRecords(NOTES_TABLE, { publishstate: state });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if there are offline notes for a certain course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with boolean: true if has offline notes, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async hasNotesForCourse(courseId: number, siteId?: string): Promise<boolean> {
 | 
			
		||||
        const notes = await this.getNotesForCourse(courseId, siteId);
 | 
			
		||||
 | 
			
		||||
        return !!notes.length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if there are offline notes for a certain user.
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId User ID the notes are about.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with boolean: true if has offline notes, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async hasNotesForUser(userId: number, siteId?: string): Promise<boolean> {
 | 
			
		||||
        const notes = await this.getNotesForUser(userId, siteId);
 | 
			
		||||
 | 
			
		||||
        return !!notes.length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if there are offline notes with a certain publish state (Personal, Site or Course).
 | 
			
		||||
     *
 | 
			
		||||
     * @param state Publish state ('personal', 'site' or 'course').
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with boolean: true if has offline notes, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async hasNotesWithPublishState(state: string, siteId?: string): Promise<boolean> {
 | 
			
		||||
        const notes = await this.getNotesWithPublishState(state, siteId);
 | 
			
		||||
 | 
			
		||||
        return !!notes.length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Save a note to be sent later.
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId User ID the note is about.
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param state Publish state ('personal', 'site' or 'course').
 | 
			
		||||
     * @param content The note content.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if stored, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async saveNote(userId: number, courseId: number, state: string, content: string, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const now = CoreTimeUtils.timestamp();
 | 
			
		||||
        const data: AddonNotesDBRecord = {
 | 
			
		||||
            userid: userId,
 | 
			
		||||
            courseid: courseId,
 | 
			
		||||
            publishstate: state,
 | 
			
		||||
            content: content,
 | 
			
		||||
            format: 1,
 | 
			
		||||
            created: now,
 | 
			
		||||
            lastmodified: now,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await site.getDb().insertRecord(NOTES_TABLE, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a note offline to be sent later.
 | 
			
		||||
     *
 | 
			
		||||
     * @param noteId Note ID.
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if stored, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteNote(noteId: number, courseId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const data: AddonNotesDeletedDBRecord = {
 | 
			
		||||
            noteid: noteId,
 | 
			
		||||
            courseid: courseId,
 | 
			
		||||
            deleted: CoreTimeUtils.timestamp(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await site.getDb().insertRecord(NOTES_DELETED_TABLE, data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Undo delete a note.
 | 
			
		||||
     *
 | 
			
		||||
     * @param noteId Note ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if deleted, rejected if failure.
 | 
			
		||||
     */
 | 
			
		||||
    async undoDeleteNote(noteId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.getDb().deleteRecords(NOTES_DELETED_TABLE, { noteid: noteId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonNotesOffline = makeSingleton(AddonNotesOfflineProvider);
 | 
			
		||||
							
								
								
									
										270
									
								
								src/addons/notes/services/notes-sync.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										270
									
								
								src/addons/notes/services/notes-sync.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,270 @@
 | 
			
		||||
// (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 { CoreNetworkError } from '@classes/errors/network-error';
 | 
			
		||||
import { CoreCourses } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { Translate, makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { AddonNotesDBRecord, AddonNotesDeletedDBRecord } from './database/notes';
 | 
			
		||||
import { AddonNotes, AddonNotesCreateNoteData } from './notes';
 | 
			
		||||
import { AddonNotesOffline } from './notes-offline';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to sync notes.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' } )
 | 
			
		||||
export class AddonNotesSyncProvider extends CoreSyncBaseProvider<AddonNotesSyncResult> {
 | 
			
		||||
 | 
			
		||||
    static readonly AUTO_SYNCED = 'addon_notes_autom_synced';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonNotesSync');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Try to synchronize all the notes 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.
 | 
			
		||||
     */
 | 
			
		||||
    syncAllNotes(siteId?: string, force?: boolean): Promise<void> {
 | 
			
		||||
        return this.syncOnSites('all notes', this.syncAllNotesFunc.bind(this, siteId, force), siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronize all the notes 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.
 | 
			
		||||
     */
 | 
			
		||||
    protected async syncAllNotesFunc(siteId: string, force: boolean): Promise<void> {
 | 
			
		||||
        const notesArray = await Promise.all([
 | 
			
		||||
            AddonNotesOffline.getAllNotes(siteId),
 | 
			
		||||
            AddonNotesOffline.getAllDeletedNotes(siteId),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        // Get all the courses to be synced.
 | 
			
		||||
        const courseIds: number[] = [];
 | 
			
		||||
        notesArray.forEach((notes: (AddonNotesDeletedDBRecord | AddonNotesDBRecord)[]) => {
 | 
			
		||||
            const courseIds = notes.map((note) => note.courseid);
 | 
			
		||||
 | 
			
		||||
            courseIds.concat(courseIds);
 | 
			
		||||
        }, []);
 | 
			
		||||
 | 
			
		||||
        CoreUtils.uniqueArray(courseIds);
 | 
			
		||||
 | 
			
		||||
        // Sync all courses.
 | 
			
		||||
        const promises = courseIds.map(async (courseId) => {
 | 
			
		||||
            const result = await (force
 | 
			
		||||
                ? this.syncNotes(courseId, siteId)
 | 
			
		||||
                : this.syncNotesIfNeeded(courseId, siteId));
 | 
			
		||||
 | 
			
		||||
            if (typeof result != 'undefined') {
 | 
			
		||||
                // Sync successful, send event.
 | 
			
		||||
                CoreEvents.trigger(AddonNotesSyncProvider.AUTO_SYNCED, {
 | 
			
		||||
                    courseId,
 | 
			
		||||
                    warnings: result.warnings,
 | 
			
		||||
                }, siteId);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sync course notes only if a certain time has passed since the last time.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the notes are synced or if they don't need to be synced.
 | 
			
		||||
     */
 | 
			
		||||
    protected async syncNotesIfNeeded(courseId: number, siteId?: string): Promise<AddonNotesSyncResult | undefined> {
 | 
			
		||||
        const needed = await this.isSyncNeeded(courseId, siteId);
 | 
			
		||||
 | 
			
		||||
        if (needed) {
 | 
			
		||||
            return this.syncNotes(courseId, siteId);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronize notes of a course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    syncNotes(courseId: number, siteId?: string): Promise<AddonNotesSyncResult> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        if (this.isSyncing(courseId, siteId)) {
 | 
			
		||||
            // There's already a sync ongoing for notes, return the promise.
 | 
			
		||||
            return this.getOngoingSync(courseId, siteId)!;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.logger.debug('Try to sync notes for course ' + courseId);
 | 
			
		||||
 | 
			
		||||
        const syncPromise = this.performSyncNotes(courseId, siteId);
 | 
			
		||||
 | 
			
		||||
        return this.addOngoingSync(courseId, syncPromise, siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform the synchronization of the notes of a course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param siteId Site ID.
 | 
			
		||||
     * @return Promise resolved if sync is successful, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async performSyncNotes(courseId: number, siteId?: string): Promise<AddonNotesSyncResult> {
 | 
			
		||||
        const result: AddonNotesSyncResult = {
 | 
			
		||||
            warnings: [],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Get offline notes to be sent and deleted.
 | 
			
		||||
        const [offlineNotes, deletedNotes] = await Promise.all([
 | 
			
		||||
            AddonNotesOffline.getAllNotes(siteId),
 | 
			
		||||
            AddonNotesOffline.getAllDeletedNotes(siteId),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        if (!offlineNotes.length && !deletedNotes.length) {
 | 
			
		||||
            // Nothing to sync.
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!CoreApp.isOnline()) {
 | 
			
		||||
            // Cannot sync in offline.
 | 
			
		||||
            throw new CoreNetworkError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const errors: string[] = [];
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        // Format the notes to be sent.
 | 
			
		||||
        const notesToSend: AddonNotesCreateNoteData[] = offlineNotes.map((note) => ({
 | 
			
		||||
            userid: note.userid,
 | 
			
		||||
            publishstate: note.publishstate,
 | 
			
		||||
            courseid: note.courseid,
 | 
			
		||||
            text: note.content,
 | 
			
		||||
            format: 1,
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        // Send the notes.
 | 
			
		||||
        promises.push(AddonNotes.addNotesOnline(notesToSend, siteId).then((response) => {
 | 
			
		||||
            // Search errors in the response.
 | 
			
		||||
            response.forEach((entry) => {
 | 
			
		||||
                if (entry.noteid === -1 && entry.errormessage && errors.indexOf(entry.errormessage) == -1) {
 | 
			
		||||
                    errors.push(entry.errormessage);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }).catch((error) => {
 | 
			
		||||
            if (CoreUtils.isWebServiceError(error)) {
 | 
			
		||||
                // It's a WebService error, this means the user cannot send notes.
 | 
			
		||||
                errors.push(error);
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Not a WebService error, reject the synchronization to try again.
 | 
			
		||||
            throw error;
 | 
			
		||||
        }).then(async () => {
 | 
			
		||||
            // Notes were sent, delete them from local DB.
 | 
			
		||||
            const promises: Promise<void>[] = offlineNotes.map((note) =>
 | 
			
		||||
                AddonNotesOffline.deleteOfflineNote(note.userid, note.content, note.created, siteId));
 | 
			
		||||
 | 
			
		||||
            await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        // Format the notes to be sent.
 | 
			
		||||
        const notesToDelete = deletedNotes.map((note) => note.noteid);
 | 
			
		||||
 | 
			
		||||
        // Delete the notes.
 | 
			
		||||
        promises.push(AddonNotes.deleteNotesOnline(notesToDelete, courseId, siteId).catch((error) => {
 | 
			
		||||
            if (CoreUtils.isWebServiceError(error)) {
 | 
			
		||||
                // It's a WebService error, this means the user cannot send notes.
 | 
			
		||||
                errors.push(error);
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Not a WebService error, reject the synchronization to try again.
 | 
			
		||||
            throw error;
 | 
			
		||||
        }).then(async () => {
 | 
			
		||||
            // Notes were sent, delete them from local DB.
 | 
			
		||||
            const promises = notesToDelete.map((noteId) => AddonNotesOffline.undoDeleteNote(noteId, siteId));
 | 
			
		||||
 | 
			
		||||
            await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
        // Fetch the notes from server to be sure they're up to date.
 | 
			
		||||
        await CoreUtils.ignoreErrors(AddonNotes.invalidateNotes(courseId, undefined, siteId));
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.ignoreErrors(AddonNotes.getNotes(courseId, undefined, false, true, siteId));
 | 
			
		||||
 | 
			
		||||
        if (errors && errors.length) {
 | 
			
		||||
            // At least an error occurred, get course name and add errors to warnings array.
 | 
			
		||||
            const course = await CoreUtils.ignoreErrors(CoreCourses.getUserCourse(courseId, true, siteId), {});
 | 
			
		||||
 | 
			
		||||
            result.warnings = errors.map((error) =>
 | 
			
		||||
                Translate.instant('addon.notes.warningnotenotsent', {
 | 
			
		||||
                    course: 'fullname' in course ? course.fullname : courseId,
 | 
			
		||||
                    error: error,
 | 
			
		||||
                }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // All done, return the warnings.
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonNotesSync = makeSingleton(AddonNotesSyncProvider);
 | 
			
		||||
 | 
			
		||||
export type AddonNotesSyncResult = {
 | 
			
		||||
    warnings: string[]; // List of warnings.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data passed to AUTO_SYNCED event.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonNotesSyncAutoSyncData = {
 | 
			
		||||
    courseId: number;
 | 
			
		||||
    warnings: string[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
declare module '@singletons/events' {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Augment CoreEventsData interface with events specific to this service.
 | 
			
		||||
     *
 | 
			
		||||
     * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
 | 
			
		||||
     */
 | 
			
		||||
    export interface CoreEventsData {
 | 
			
		||||
        [AddonNotesSyncProvider.AUTO_SYNCED]: AddonNotesSyncAutoSyncData;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										506
									
								
								src/addons/notes/services/notes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										506
									
								
								src/addons/notes/services/notes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,506 @@
 | 
			
		||||
// (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 { CoreWSError } from '@classes/errors/wserror';
 | 
			
		||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
			
		||||
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
 | 
			
		||||
import { CoreUser } from '@features/user/services/user';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreWSExternalWarning } from '@services/ws';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { AddonNotesOffline } from './notes-offline';
 | 
			
		||||
 | 
			
		||||
const ROOT_CACHE_KEY = 'mmaNotes:';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to handle notes.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable( { providedIn: 'root' } )
 | 
			
		||||
export class AddonNotesProvider {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a note.
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId User ID of the person to add the note.
 | 
			
		||||
     * @param courseId Course ID where the note belongs.
 | 
			
		||||
     * @param publishState Personal, Site or Course.
 | 
			
		||||
     * @param noteText The note text.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with boolean: true if note was sent to server, false if stored in device.
 | 
			
		||||
     */
 | 
			
		||||
    async addNote(userId: number, courseId: number, publishState: string, noteText: string, siteId?: string): Promise<boolean> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        // Convenience function to store a note to be synchronized later.
 | 
			
		||||
        const storeOffline = async (): Promise<boolean> => {
 | 
			
		||||
            await AddonNotesOffline.saveNote(userId, courseId, publishState, noteText, siteId);
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (!CoreApp.isOnline()) {
 | 
			
		||||
            // App is offline, store the note.
 | 
			
		||||
            return storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Send note to server.
 | 
			
		||||
        try {
 | 
			
		||||
            await this.addNoteOnline(userId, courseId, publishState, noteText, siteId);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (CoreUtils.isWebServiceError(error)) {
 | 
			
		||||
                // It's a WebService error, the user cannot send the message so don't store it.
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a note. It will fail if offline or cannot connect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId User ID of the person to add the note.
 | 
			
		||||
     * @param courseId Course ID where the note belongs.
 | 
			
		||||
     * @param publishState Personal, Site or Course.
 | 
			
		||||
     * @param noteText The note text.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when added, rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async addNoteOnline(userId: number, courseId: number, publishState: string, noteText: string, siteId?: string): Promise<void> {
 | 
			
		||||
        const notes: AddonNotesCreateNoteData[] = [
 | 
			
		||||
            {
 | 
			
		||||
                courseid: courseId,
 | 
			
		||||
                format: 1,
 | 
			
		||||
                publishstate: publishState,
 | 
			
		||||
                text: noteText,
 | 
			
		||||
                userid: userId,
 | 
			
		||||
            },
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        const response = await this.addNotesOnline(notes, siteId);
 | 
			
		||||
        if (response && response[0] && response[0].noteid === -1) {
 | 
			
		||||
            // There was an error, and it should be translated already.
 | 
			
		||||
            throw new CoreWSError({ message: response[0].errormessage });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.ignoreErrors(this.invalidateNotes(courseId, undefined, siteId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add several notes. It will fail if offline or cannot connect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param notes Notes to save.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when added, rejected otherwise. Promise resolved doesn't mean that notes
 | 
			
		||||
     *         have been added, the resolve param can contain errors for notes not sent.
 | 
			
		||||
     */
 | 
			
		||||
    async addNotesOnline(notes: AddonNotesCreateNoteData[], siteId?: string): Promise<AddonNotesCreateNotesWSResponse> {
 | 
			
		||||
        if (!notes || !notes.length) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const data: AddonNotesCreateNotesWSParams = {
 | 
			
		||||
            notes: notes,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return site.write('core_notes_create_notes', data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a note.
 | 
			
		||||
     *
 | 
			
		||||
     * @param note Note object to delete.
 | 
			
		||||
     * @param courseId Course ID where the note belongs.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes
 | 
			
		||||
     *         have been deleted, the resolve param can contain errors for notes not deleted.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteNote(note: AddonNotesNoteFormatted, courseId: number, siteId?: string): Promise<boolean> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        if (note.offline) {
 | 
			
		||||
            await AddonNotesOffline.deleteOfflineNote(note.userid, note.content, note.created, siteId);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Convenience function to store the action to be synchronized later.
 | 
			
		||||
        const storeOffline = async (): Promise<boolean> => {
 | 
			
		||||
            await AddonNotesOffline.deleteNote(note.id, courseId, siteId);
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (!CoreApp.isOnline()) {
 | 
			
		||||
            // App is offline, store the note.
 | 
			
		||||
            return storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Send note to server.
 | 
			
		||||
        try {
 | 
			
		||||
            await this.deleteNotesOnline([note.id], courseId, siteId);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (CoreUtils.isWebServiceError(error)) {
 | 
			
		||||
                // It's a WebService error, the user cannot send the note so don't store it.
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return await storeOffline();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete a note. It will fail if offline or cannot connect.
 | 
			
		||||
     *
 | 
			
		||||
     * @param noteIds Note IDs to delete.
 | 
			
		||||
     * @param courseId Course ID where the note belongs.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when deleted, rejected otherwise. Promise resolved doesn't mean that notes
 | 
			
		||||
     *         have been deleted, the resolve param can contain errors for notes not deleted.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteNotesOnline(noteIds: number[], courseId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const params: AddonNotesDeleteNotesWSParams = {
 | 
			
		||||
            notes: noteIds,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await site.write('core_notes_delete_notes', params);
 | 
			
		||||
 | 
			
		||||
        CoreUtils.ignoreErrors(this.invalidateNotes(courseId, undefined, siteId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether or not the notes plugin is enabled for a certain site.
 | 
			
		||||
     *
 | 
			
		||||
     * This method is called quite often and thus should only perform a quick
 | 
			
		||||
     * check, we should not be calling WS from here.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with true if enabled, resolved with false or rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async isPluginEnabled(siteId?: string): Promise<boolean> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return site.canUseAdvancedFeature('enablenotes');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether or not the add note plugin is enabled for a certain course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId ID of the course.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with true if enabled, resolved with false or rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async isPluginAddNoteEnabledForCourse(courseId: number, siteId?: string): Promise<boolean> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        // The only way to detect if it's enabled is to perform a WS call.
 | 
			
		||||
        // We use an invalid user ID (-1) to avoid saving the note if the user has permissions.
 | 
			
		||||
        const params: AddonNotesCreateNotesWSParams = {
 | 
			
		||||
            notes: [
 | 
			
		||||
                {
 | 
			
		||||
                    userid: -1,
 | 
			
		||||
                    publishstate: 'personal',
 | 
			
		||||
                    courseid: courseId,
 | 
			
		||||
                    text: '',
 | 
			
		||||
                    format: 1,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        };
 | 
			
		||||
        const preSets: CoreSiteWSPreSets = {
 | 
			
		||||
            updateFrequency: CoreSite.FREQUENCY_RARELY,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Use .read to cache data and be able to check it in offline. This means that, if a user loses the capabilities
 | 
			
		||||
        // to add notes, he'll still see the option in the app.
 | 
			
		||||
        return CoreUtils.promiseWorks(site.read('core_notes_create_notes', params, preSets));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether or not the read notes plugin is enabled for a certain course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId ID of the course.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with true if enabled, resolved with false or rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    isPluginViewNotesEnabledForCourse(courseId: number, siteId?: string): Promise<boolean> {
 | 
			
		||||
        return CoreUtils.promiseWorks(this.getNotes(courseId, undefined, false, true, siteId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get prefix cache key for course notes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId ID of the course to get the notes from.
 | 
			
		||||
     * @return Cache key.
 | 
			
		||||
     */
 | 
			
		||||
    getNotesPrefixCacheKey(courseId: number): string {
 | 
			
		||||
        return ROOT_CACHE_KEY + 'notes:' + courseId + ':';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the cache key for the get notes call.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId ID of the course to get the notes from.
 | 
			
		||||
     * @param userId ID of the user to get the notes from if requested.
 | 
			
		||||
     * @return Cache key.
 | 
			
		||||
     */
 | 
			
		||||
    getNotesCacheKey(courseId: number, userId?: number): string {
 | 
			
		||||
        return this.getNotesPrefixCacheKey(courseId) + (userId ? userId : '');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get users notes for a certain site, course and personal notes.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId ID of the course to get the notes from.
 | 
			
		||||
     * @param userId ID of the user to get the notes from if requested.
 | 
			
		||||
     * @param ignoreCache True when we should not get the value from the cache.
 | 
			
		||||
     * @param onlyOnline True to return only online notes, false to return both online and offline.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise to be resolved when the notes are retrieved.
 | 
			
		||||
     */
 | 
			
		||||
    async getNotes(
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        userId?: number,
 | 
			
		||||
        ignoreCache = false,
 | 
			
		||||
        onlyOnline = false,
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<AddonNotesGetCourseNotesWSResponse> {
 | 
			
		||||
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
        const params: AddonNotesGetCourseNotesWSParams = {
 | 
			
		||||
            courseid: courseId,
 | 
			
		||||
        };
 | 
			
		||||
        if (userId) {
 | 
			
		||||
            params.userid = userId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const preSets: CoreSiteWSPreSets = {
 | 
			
		||||
            cacheKey: this.getNotesCacheKey(courseId, userId),
 | 
			
		||||
            updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (ignoreCache) {
 | 
			
		||||
            preSets.getFromCache = false;
 | 
			
		||||
            preSets.emergencyCache = false;
 | 
			
		||||
        }
 | 
			
		||||
        const notes = await site.read<AddonNotesGetCourseNotesWSResponse>('core_notes_get_course_notes', params, preSets);
 | 
			
		||||
        if (onlyOnline) {
 | 
			
		||||
            return notes;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const offlineNotes = await AddonNotesOffline.getNotesForCourseAndUser(courseId, userId, siteId);
 | 
			
		||||
        offlineNotes.forEach((note: AddonNotesNote) => {
 | 
			
		||||
            const fieldName = note.publishstate + 'notes';
 | 
			
		||||
            if (!notes[fieldName]) {
 | 
			
		||||
                notes[fieldName] = [];
 | 
			
		||||
            }
 | 
			
		||||
            note.offline = true;
 | 
			
		||||
            // Add note to the start of array since last notes are shown first.
 | 
			
		||||
            notes[fieldName].unshift(note);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return notes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get offline deleted notes and set the state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param notes Array of notes.
 | 
			
		||||
     * @param courseId ID of the course the notes belong to.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async setOfflineDeletedNotes(
 | 
			
		||||
        notes: AddonNotesNoteFormatted[],
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        siteId?: string,
 | 
			
		||||
    ): Promise<void> {
 | 
			
		||||
        const deletedNotes = await AddonNotesOffline.getCourseDeletedNotes(courseId, siteId);
 | 
			
		||||
 | 
			
		||||
        notes.forEach((note) => {
 | 
			
		||||
            note.deleted = deletedNotes.some((n) => n.noteid == note.id);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get user data for notes since they only have userid.
 | 
			
		||||
     *
 | 
			
		||||
     * @param notes Notes to get the data for.
 | 
			
		||||
     * @return Promise always resolved. Resolve param is the formatted notes.
 | 
			
		||||
     */
 | 
			
		||||
    async getNotesUserData(notes: AddonNotesNoteFormatted[]): Promise<AddonNotesNoteFormatted[]> {
 | 
			
		||||
        const promises = notes.map((note) =>
 | 
			
		||||
            // Get the user profile to retrieve the user image.
 | 
			
		||||
            CoreUser.getProfile(note.userid, note.courseid, true).then((user) => {
 | 
			
		||||
                note.userfullname = user.fullname;
 | 
			
		||||
                note.userprofileimageurl = user.profileimageurl;
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }).catch(() => {
 | 
			
		||||
                note.userfullname = Translate.instant('addon.notes.userwithid', { id: note.userid });
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
        return notes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidate get notes WS call.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param userId User ID if needed.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when data is invalidated.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateNotes(courseId: number, userId?: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        if (userId) {
 | 
			
		||||
            await site.invalidateWsCacheForKey(this.getNotesCacheKey(courseId, userId));
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await site.invalidateWsCacheForKeyStartingWith(this.getNotesPrefixCacheKey(courseId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Report notes as being viewed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId ID of the course.
 | 
			
		||||
     * @param userId User ID if needed.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the WS call is successful.
 | 
			
		||||
     */
 | 
			
		||||
    async logView(courseId: number, userId?: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const params: AddonNotesViewNotesWSParams = {
 | 
			
		||||
            courseid: courseId,
 | 
			
		||||
            userid: userId || 0,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        CorePushNotifications.logViewListEvent('notes', 'core_notes_view_notes', params, site.getId());
 | 
			
		||||
 | 
			
		||||
        await site.write('core_notes_view_notes', params);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonNotes = makeSingleton(AddonNotesProvider);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_notes_view_notes WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonNotesViewNotesWSParams = {
 | 
			
		||||
    courseid: number; // Course id, 0 for notes at system level.
 | 
			
		||||
    userid?: number; // User id, 0 means view all the user notes.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_notes_get_course_notes WS.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonNotesGetCourseNotesWSParams = {
 | 
			
		||||
    courseid: number; // Course id, 0 for SITE.
 | 
			
		||||
    userid?: number; // User id.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Note data returned by core_notes_get_course_notes.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonNotesNote = {
 | 
			
		||||
    id: number; // Id of this note.
 | 
			
		||||
    courseid: number; // Id of the course.
 | 
			
		||||
    userid: number; // User id.
 | 
			
		||||
    content: string; // The content text formated.
 | 
			
		||||
    format: number; // Content format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | 
			
		||||
    created: number; // Time created (timestamp).
 | 
			
		||||
    lastmodified: number; // Time of last modification (timestamp).
 | 
			
		||||
    usermodified: number; // User id of the creator of this note.
 | 
			
		||||
    publishstate: string; // State of the note (i.e. draft, public, site).
 | 
			
		||||
    offline?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Result of WS core_notes_get_course_notes.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonNotesGetCourseNotesWSResponse = {
 | 
			
		||||
    sitenotes?: AddonNotesNote[]; // Site notes.
 | 
			
		||||
    coursenotes?: AddonNotesNote[]; // Couse notes.
 | 
			
		||||
    personalnotes?: AddonNotesNote[]; // Personal notes.
 | 
			
		||||
    canmanagesystemnotes?: boolean; // @since 3.7. Whether the user can manage notes at system level.
 | 
			
		||||
    canmanagecoursenotes?: boolean; // @since 3.7. Whether the user can manage notes at the given course.
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Result of WS core_notes_view_notes.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonNotesViewNotesResult = {
 | 
			
		||||
    status: boolean; // Status: true if success.
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Notes with some calculated data.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonNotesNoteFormatted = AddonNotesNote & {
 | 
			
		||||
    offline?: boolean; // Calculated in the app. Whether it's an offline note.
 | 
			
		||||
    deleted?: boolean; // Calculated in the app. Whether the note was deleted in offline.
 | 
			
		||||
    userfullname?: string; // Calculated in the app. Full name of the user the note refers to.
 | 
			
		||||
    userprofileimageurl?: string; // Calculated in the app. Avatar url of the user the note refers to.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AddonNotesCreateNoteData = {
 | 
			
		||||
    userid: number; // Id of the user the note is about.
 | 
			
		||||
    publishstate: string; // 'personal', 'course' or 'site'.
 | 
			
		||||
    courseid: number; // Course id of the note (in Moodle a note can only be created into a course,
 | 
			
		||||
    // even for site and personal notes).
 | 
			
		||||
    text: string; // The text of the message - text or HTML.
 | 
			
		||||
    format?: number; // Text format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | 
			
		||||
    clientnoteid?: string; // Your own client id for the note. If this id is provided, the fail message id will be returned to you.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_notes_create_notes WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonNotesCreateNotesWSParams = {
 | 
			
		||||
    notes: AddonNotesCreateNoteData[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Note returned by WS core_notes_create_notes.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonNotesCreateNotesWSResponse = {
 | 
			
		||||
    clientnoteid?: string; // Your own id for the note.
 | 
			
		||||
    noteid: number; // ID of the created note when successful, -1 when failed.
 | 
			
		||||
    errormessage?: string; // Error message - if failed.
 | 
			
		||||
}[];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_notes_delete_notes WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonNotesDeleteNotesWSParams = {
 | 
			
		||||
    notes: number[]; // Array of Note Ids to be deleted.
 | 
			
		||||
};
 | 
			
		||||
@ -27,8 +27,7 @@ export class CoreWSError extends CoreError {
 | 
			
		||||
    debuginfo?: string; // Debug info. Only if debug mode is enabled.
 | 
			
		||||
    backtrace?: string; // Backtrace. Only if debug mode is enabled.
 | 
			
		||||
 | 
			
		||||
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
			
		||||
    constructor(error: any) {
 | 
			
		||||
    constructor(error: CoreWSErrorData) {
 | 
			
		||||
        super(error.message);
 | 
			
		||||
 | 
			
		||||
        this.exception = error.exception;
 | 
			
		||||
@ -41,3 +40,14 @@ export class CoreWSError extends CoreError {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CoreWSErrorData = {
 | 
			
		||||
    message?: string;
 | 
			
		||||
    exception?: string; // Name of the Moodle exception.
 | 
			
		||||
    errorcode?: string;
 | 
			
		||||
    warningcode?: string;
 | 
			
		||||
    link?: string; // Link to the site.
 | 
			
		||||
    moreinfourl?: string; // Link to a page with more info.
 | 
			
		||||
    debuginfo?: string; // Debug info. Only if debug mode is enabled.
 | 
			
		||||
    backtrace?: string; // Backtrace. Only if debug mode is enabled.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -141,10 +141,10 @@ import { ADDON_MOD_RESOURCE_SERVICES } from '@addons/mod/resource/resource.modul
 | 
			
		||||
import { ADDON_MOD_URL_SERVICES } from '@addons/mod/url/url.module';
 | 
			
		||||
// @todo import { ADDON_MOD_WIKI_SERVICES } from '@addons/mod/wiki/wiki.module';
 | 
			
		||||
// @todo import { ADDON_MOD_WORKSHOP_SERVICES } from '@addons/mod/workshop/workshop.module';
 | 
			
		||||
// @todo import { ADDON_NOTES_SERVICES } from '@addons/notes/notes.module';
 | 
			
		||||
import { ADDON_NOTES_SERVICES } from '@addons/notes/notes.module';
 | 
			
		||||
import { ADDON_NOTIFICATIONS_SERVICES } from '@addons/notifications/notifications.module';
 | 
			
		||||
import { ADDON_PRIVATEFILES_SERVICES } from '@addons/privatefiles/privatefiles.module';
 | 
			
		||||
// @todo import { ADDON_REMOTETHEMES_SERVICES } from '@addons/remotethemes/remotethemes.module';
 | 
			
		||||
import { ADDON_REMOTETHEMES_SERVICES } from '@addons/remotethemes/remotethemes.module';
 | 
			
		||||
 | 
			
		||||
// Import some addon modules that define components, directives and pipes. Only import the important ones.
 | 
			
		||||
import { AddonModAssignComponentsModule } from '@addons/mod/assign/components/components.module';
 | 
			
		||||
@ -306,10 +306,10 @@ export class CoreCompileProvider {
 | 
			
		||||
            ...ADDON_MOD_URL_SERVICES,
 | 
			
		||||
            // @todo ...ADDON_MOD_WIKI_SERVICES,
 | 
			
		||||
            // @todo ...ADDON_MOD_WORKSHOP_SERVICES,
 | 
			
		||||
            // @todo ...ADDON_NOTES_SERVICES,
 | 
			
		||||
            ...ADDON_NOTES_SERVICES,
 | 
			
		||||
            ...ADDON_NOTIFICATIONS_SERVICES,
 | 
			
		||||
            ...ADDON_PRIVATEFILES_SERVICES,
 | 
			
		||||
            // @todo ...ADDON_REMOTETHEMES_SERVICES,
 | 
			
		||||
            ...ADDON_REMOTETHEMES_SERVICES,
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        // We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreSiteWSPreSets, CoreSite } from '@classes/site';
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { makeSingleton, Platform, Translate } from '@singletons';
 | 
			
		||||
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile } from '@services/ws';
 | 
			
		||||
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
 | 
			
		||||
 | 
			
		||||
import { CoreCourseStatusDBRecord, COURSE_STATUS_TABLE } from './database/course';
 | 
			
		||||
import { CoreCourseOffline } from './course-offline';
 | 
			
		||||
@ -1404,7 +1404,7 @@ type CoreCourseGetCourseModuleByInstanceWSParams = {
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCourseGetCourseModuleWSResponse = {
 | 
			
		||||
    cm: CoreCourseModuleBasicInfo;
 | 
			
		||||
    warnings?: CoreStatusWithWarningsWSResponse[];
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 | 
			
		||||
@ -1086,7 +1086,7 @@ export class CoreLoginHelperProvider {
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                if (!result.status) {
 | 
			
		||||
                    throw new CoreWSError(result.warnings?.[0]);
 | 
			
		||||
                    throw new CoreWSError(result.warnings![0]);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const message = Translate.instant('core.login.emailconfirmsentsuccess');
 | 
			
		||||
 | 
			
		||||
@ -209,7 +209,10 @@ class CoreUserParticipantsManager extends CorePageItemsListManager<CoreUserParti
 | 
			
		||||
     */
 | 
			
		||||
    async select(participant: CoreUserParticipant | CoreUserData): Promise<void> {
 | 
			
		||||
        if (CoreScreen.isMobile) {
 | 
			
		||||
            await CoreNavigator.navigateToSitePath('/user/profile', { params: { userId: participant.id } });
 | 
			
		||||
            await CoreNavigator.navigateToSitePath(
 | 
			
		||||
                '/user/profile',
 | 
			
		||||
                { params: { userId: participant.id, courseId: this.courseId } },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user