MOBILE-3323 editor: Create offline provider for editor
parent
96c8622608
commit
d2f4df452e
|
@ -14,14 +14,25 @@
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CoreEditorComponentsModule } from './components/components.module';
|
import { CoreEditorComponentsModule } from './components/components.module';
|
||||||
|
import { CoreEditorOfflineProvider } from './providers/editor-offline';
|
||||||
|
|
||||||
|
// List of providers (without handlers).
|
||||||
|
export const CORE_GRADES_PROVIDERS: any[] = [
|
||||||
|
CoreEditorOfflineProvider,
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CoreEditorComponentsModule
|
CoreEditorComponentsModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
]
|
CoreEditorOfflineProvider,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class CoreEditorModule {}
|
export class CoreEditorModule {
|
||||||
|
constructor(editorOffline: CoreEditorOfflineProvider) {
|
||||||
|
// Inject the helper even if it isn't used here it's instantiated.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,254 @@
|
||||||
|
// (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 { CoreLoggerProvider } from '@providers/logger';
|
||||||
|
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service with features regarding rich text editor in offline.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreEditorOfflineProvider {
|
||||||
|
|
||||||
|
protected DRAFT_TABLE = 'editor_draft';
|
||||||
|
|
||||||
|
protected logger;
|
||||||
|
protected siteSchema: CoreSiteSchema = {
|
||||||
|
name: 'CoreEditorProvider',
|
||||||
|
version: 1,
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: this.DRAFT_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'contextlevel',
|
||||||
|
type: 'TEXT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'contextinstanceid',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'elementid',
|
||||||
|
type: 'TEXT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'extraparams', // Moodle web uses a page hash built with URL. App will use some params stringified.
|
||||||
|
type: 'TEXT',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'drafttext',
|
||||||
|
type: 'TEXT',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pageinstance',
|
||||||
|
type: 'TEXT',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timecreated',
|
||||||
|
type: 'INTEGER',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timemodified',
|
||||||
|
type: 'INTEGER',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
primaryKeys: ['contextlevel', 'contextinstanceid', 'elementid', 'extraparams']
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
logger: CoreLoggerProvider,
|
||||||
|
protected sitesProvider: CoreSitesProvider,
|
||||||
|
protected textUtils: CoreTextUtilsProvider,
|
||||||
|
protected utils: CoreUtilsProvider) {
|
||||||
|
this.logger = logger.getInstance('CoreEditorProvider');
|
||||||
|
|
||||||
|
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a draft from DB.
|
||||||
|
*
|
||||||
|
* @param contextLevel Context level.
|
||||||
|
* @param contextInstanceId The instance ID related to the context.
|
||||||
|
* @param elementId Element ID.
|
||||||
|
* @param extraParams Object with extra params to identify the draft.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async deleteDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
|
||||||
|
siteId?: string): Promise<void> {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const db = await this.sitesProvider.getSiteDb(siteId);
|
||||||
|
|
||||||
|
const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||||
|
|
||||||
|
return db.deleteRecords(this.DRAFT_TABLE, params);
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors, probably no draft stored.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an object with the draft primary data converted to the right format.
|
||||||
|
*
|
||||||
|
* @param contextLevel Context level.
|
||||||
|
* @param contextInstanceId The instance ID related to the context.
|
||||||
|
* @param elementId Element ID.
|
||||||
|
* @param extraParams Object with extra params to identify the draft.
|
||||||
|
* @return Object with the fixed primary data.
|
||||||
|
*/
|
||||||
|
protected fixDraftPrimaryData(contextLevel: string, contextInstanceId: number, elementId: string,
|
||||||
|
extraParams: {[name: string]: any}): CoreEditorDraftPrimaryData {
|
||||||
|
|
||||||
|
return {
|
||||||
|
contextlevel: contextLevel,
|
||||||
|
contextinstanceid: contextInstanceId,
|
||||||
|
elementid: elementId,
|
||||||
|
extraparams: this.utils.sortAndStringify(extraParams || {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a draft from DB.
|
||||||
|
*
|
||||||
|
* @param contextLevel Context level.
|
||||||
|
* @param contextInstanceId The instance ID related to the context.
|
||||||
|
* @param elementId Element ID.
|
||||||
|
* @param extraParams Object with extra params to identify the draft.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved with the draft data. Undefined if no draft stored.
|
||||||
|
*/
|
||||||
|
async getDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
|
||||||
|
siteId?: string): Promise<CoreEditorDraft> {
|
||||||
|
|
||||||
|
const db = await this.sitesProvider.getSiteDb(siteId);
|
||||||
|
|
||||||
|
const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||||
|
|
||||||
|
return db.getRecord(this.DRAFT_TABLE, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get draft to resume it.
|
||||||
|
*
|
||||||
|
* @param contextLevel Context level.
|
||||||
|
* @param contextInstanceId The instance ID related to the context.
|
||||||
|
* @param elementId Element ID.
|
||||||
|
* @param extraParams Object with extra params to identify the draft.
|
||||||
|
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved with the draft text. Undefined if no draft stored.
|
||||||
|
*/
|
||||||
|
async resumeDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
|
||||||
|
pageInstance: string, siteId?: string): Promise<string> {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if there is a draft stored.
|
||||||
|
const entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId);
|
||||||
|
|
||||||
|
// There is a draft stored. Update its page instance.
|
||||||
|
try {
|
||||||
|
const db = await this.sitesProvider.getSiteDb(siteId);
|
||||||
|
|
||||||
|
entry.pageinstance = pageInstance;
|
||||||
|
entry.timemodified = Date.now();
|
||||||
|
|
||||||
|
await db.insertRecord(this.DRAFT_TABLE, entry);
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors saving the draft. It shouldn't happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.drafttext;
|
||||||
|
} catch (error) {
|
||||||
|
// No draft stored. Store an empty draft to save the pageinstance.
|
||||||
|
await this.saveDraft(contextLevel, contextInstanceId, elementId, extraParams, pageInstance, '', siteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a draft in DB.
|
||||||
|
*
|
||||||
|
* @param contextLevel Context level.
|
||||||
|
* @param contextInstanceId The instance ID related to the context.
|
||||||
|
* @param elementId Element ID.
|
||||||
|
* @param extraParams Object with extra params to identify the draft.
|
||||||
|
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
|
||||||
|
* @param draftText The text to store.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async saveDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
|
||||||
|
pageInstance: string, draftText: string, siteId?: string): Promise<void> {
|
||||||
|
|
||||||
|
let timecreated = Date.now();
|
||||||
|
let entry: CoreEditorDraft;
|
||||||
|
|
||||||
|
// Check if there is a draft already stored.
|
||||||
|
try {
|
||||||
|
entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId);
|
||||||
|
|
||||||
|
timecreated = entry.timecreated;
|
||||||
|
} catch (error) {
|
||||||
|
// No draft already stored.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry && entry.pageinstance != pageInstance) {
|
||||||
|
this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
|
||||||
|
`element '${elementId}'`);
|
||||||
|
throw null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = await this.sitesProvider.getSiteDb(siteId);
|
||||||
|
|
||||||
|
const data: CoreEditorDraft = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||||
|
|
||||||
|
data.drafttext = (draftText || '').trim();
|
||||||
|
data.pageinstance = pageInstance;
|
||||||
|
data.timecreated = timecreated;
|
||||||
|
data.timemodified = Date.now();
|
||||||
|
|
||||||
|
await db.insertRecord(this.DRAFT_TABLE, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary data to identify a stored draft.
|
||||||
|
*/
|
||||||
|
type CoreEditorDraftPrimaryData = {
|
||||||
|
contextlevel: string; // Context level.
|
||||||
|
contextinstanceid: number; // The instance ID related to the context.
|
||||||
|
elementid: string; // Element ID.
|
||||||
|
extraparams: string; // Extra params stringified.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draft data stored.
|
||||||
|
*/
|
||||||
|
type CoreEditorDraft = CoreEditorDraftPrimaryData & {
|
||||||
|
drafttext?: string; // Draft text stored.
|
||||||
|
pageinstance?: string; // Unique identifier to prevent storing data from several sources at the same time.
|
||||||
|
timecreated?: number; // Time created.
|
||||||
|
timemodified?: number; // Time modified.
|
||||||
|
};
|
Loading…
Reference in New Issue