MOBILE-2338 data: Entry page

main
Pau Ferrer Ocaña 2018-05-16 16:12:54 +02:00
parent b7769ec2a4
commit c626bee407
8 changed files with 609 additions and 35 deletions

View File

@ -1,28 +0,0 @@
addon-mod-data-index {
.addon-data-contents {
overflow: visible;
white-space: normal;
word-break: break-word;
padding: $content-padding;
background-color: white;
border-top-width: 1px;
border-bottom-width: 1px;
border-right-width: 0;
border-left-width: 0;
border-style: solid;
border-color: $list-border-color;
table, tbody {
display: block;
}
tr {
@extend .row;
padding: 0;
}
td, th {
@extend .col;
}
}
}

View File

@ -75,11 +75,11 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
protected fieldsArray: any; protected fieldsArray: any;
constructor(injector: Injector, private dataProvider: AddonModDataProvider, private dataHelper: AddonModDataHelperProvider, constructor(injector: Injector, private dataProvider: AddonModDataProvider, private dataHelper: AddonModDataHelperProvider,
private dataOffline: AddonModDataOfflineProvider, @Optional() @Optional() content: Content, private dataOffline: AddonModDataOfflineProvider, @Optional() content: Content,
private dataSync: AddonModDataSyncProvider, private timeUtils: CoreTimeUtilsProvider, private dataSync: AddonModDataSyncProvider, private timeUtils: CoreTimeUtilsProvider,
private groupsProvider: CoreGroupsProvider, private commentsProvider: CoreCommentsProvider, private groupsProvider: CoreGroupsProvider, private commentsProvider: CoreCommentsProvider,
private modalCtrl: ModalController, private utils: CoreUtilsProvider, protected navCtrl: NavController) { private modalCtrl: ModalController, private utils: CoreUtilsProvider, protected navCtrl: NavController) {
super(injector); super(injector, content);
// Refresh entries on change. // Refresh entries on change.
this.entryChangedObserver = this.eventsProvider.on(AddonModDataProvider.ENTRY_CHANGED, (eventData) => { this.entryChangedObserver = this.eventsProvider.on(AddonModDataProvider.ENTRY_CHANGED, (eventData) => {
@ -424,9 +424,9 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
gotoEntry(entryId: number): void { gotoEntry(entryId: number): void {
const stateParams = { const stateParams = {
module: this.module, module: this.module,
moduleid: this.module.id, moduleId: this.module.id,
courseid: this.courseId, courseId: this.courseId,
entryid: entryId, entryId: entryId,
group: this.selectedGroup group: this.selectedGroup
}; };

View File

@ -0,0 +1,26 @@
.addon-data-contents {
overflow: visible;
white-space: normal;
word-break: break-word;
padding: $content-padding;
background-color: white;
border-top-width: 1px;
border-bottom-width: 1px;
border-right-width: 0;
border-left-width: 0;
border-style: solid;
border-color: $list-border-color;
table, tbody {
display: block;
}
tr {
@extend .row;
padding: 0;
}
td, th {
@extend .col;
}
}

View File

@ -0,0 +1,54 @@
<ion-header>
<ion-navbar>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
</ion-navbar>
</ion-header>
<ion-content>
<ion-refresher [enabled]="entryLoaded" (ionRefresh)="refreshDatabase($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="entryLoaded">
<!-- Database entries found to be synchronized -->
<div class="core-warning-card" icon-start *ngIf="hasOffline">
<ion-icon name="warning"></ion-icon>
{{ 'core.hasdatatosync' | translate: {$a: moduleName} }}
</div>
<ion-item text-wrap *ngIf="groupInfo && (groupInfo.separateGroups || groupInfo.visibleGroups)">
<ion-label id="addon-data-groupslabel" *ngIf="groupInfo.separateGroups">{{ 'core.groupsseparate' | translate }}</ion-label>
<ion-label id="addon-data-groupslabel" *ngIf="groupInfo.visibleGroups">{{ 'core.groupsvisible' | translate }}</ion-label>
<ion-select [(ngModel)]="selectedGroup" (ionChange)="setGroup(selectedGroup)" aria-labelledby="addon-data-groupslabel">
<ion-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">{{groupOpt.name}}</ion-option>
</ion-select>
</ion-item>
<div class="addon-data-contents {{cssClass}}">
<style *ngIf="cssTemplate">
{{ cssTemplate }}
</style>
<core-compile-html [text]="entryRendered" [jsData]="jsData" [extraImports]="extraImports"></core-compile-html>
</div>
<ion-item *ngIf="data && entry">
<core-comments contextLevel="module" [instanceId]="data.coursemodule" component="mod_data" [itemId]="entry.id" area="database_entry"></core-comments>
</ion-item>
<ion-grid *ngIf="previousId || nextId">
<ion-row align-items-center>
<ion-col *ngIf="previousId">
<button ion-button block outline icon-start (click)="gotoEntry(previousId)">
<ion-icon name="arrow-back"></ion-icon>
{{ 'core.previous' | translate }}
</button>
</ion-col>
<ion-col *ngIf="nextId">
<button ion-button block icon-end (click)="gotoEntry(nextId)">
{{ 'core.next' | translate }}
<ion-icon name="arrow-forward"></ion-icon>
</button>
</ion-col>
</ion-row>
</ion-grid>
</core-loading>
</ion-content>

View File

@ -0,0 +1,39 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { IonicPageModule } from 'ionic-angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreComponentsModule } from '@components/components.module';
import { CoreCommentsComponentsModule } from '@core/comments/components/components.module';
import { CoreCompileHtmlComponentModule } from '@core/compile/components/compile-html/compile-html.module';
import { AddonModDataComponentsModule } from '../../components/components.module';
import { AddonModDataEntryPage } from './entry';
@NgModule({
declarations: [
AddonModDataEntryPage,
],
imports: [
CoreDirectivesModule,
CoreComponentsModule,
AddonModDataComponentsModule,
CoreCompileHtmlComponentModule,
CoreCommentsComponentsModule,
IonicPageModule.forChild(AddonModDataEntryPage),
TranslateModule.forChild()
],
})
export class AddonModDataEntryPageModule {}

View File

@ -0,0 +1,307 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, ViewChild } from '@angular/core';
import { Content, IonicPage, NavParams, NavController } from 'ionic-angular';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSitesProvider } from '@providers/sites';
import { CoreGroupsProvider } from '@providers/groups';
import { CoreEventsProvider } from '@providers/events';
import { CoreCourseProvider } from '@core/course/providers/course';
import { AddonModDataProvider } from '../../providers/data';
import { AddonModDataHelperProvider } from '../../providers/helper';
import { AddonModDataOfflineProvider } from '../../providers/offline';
import { AddonModDataSyncProvider } from '../../providers/sync';
import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
import { AddonModDataComponentsModule } from '../../components/components.module';
/**
* Page that displays the view entry page.
*/
@IonicPage({ segment: 'addon-mod-data-entry' })
@Component({
selector: 'page-addon-mod-data-entry',
templateUrl: 'entry.html',
})
export class AddonModDataEntryPage {
@ViewChild(Content) content: Content;
protected module: any;
protected entryId: number;
protected courseId: number;
protected page: number;
protected syncObserver: any; // It will observe the sync auto event.
protected entryChangedObserver: any; // It will observe the changed entry event.
protected fields = {};
title = '';
moduleName = 'data';
component = AddonModDataProvider.COMPONENT;
entryLoaded = false;
selectedGroup = 0;
entry: any;
offlineActions = [];
hasOffline = false;
cssTemplate = '';
previousId: number;
nextId: number;
access: any;
data: any;
groupInfo: any;
showComments: any;
entryRendered = '';
siteId: string;
cssClass = '';
extraImports = [AddonModDataComponentsModule];
jsData;
constructor(params: NavParams, protected utils: CoreUtilsProvider, protected groupsProvider: CoreGroupsProvider,
protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate,
protected courseProvider: CoreCourseProvider, protected dataProvider: AddonModDataProvider,
protected dataOffline: AddonModDataOfflineProvider, protected dataHelper: AddonModDataHelperProvider,
sitesProvider: CoreSitesProvider, protected navCtrl: NavController,
protected eventsProvider: CoreEventsProvider) {
this.module = params.get('module') || {};
this.entryId = params.get('entryId') || null;
this.courseId = params.get('courseId');
this.selectedGroup = params.get('group') || 0;
this.page = params.get('page') || null;
this.siteId = sitesProvider.getCurrentSiteId();
this.title = this.module.name;
this.moduleName = this.courseProvider.translateModuleName('data');
}
/**
* View loaded.
*/
ionViewDidLoad(): void {
this.fetchEntryData();
// Refresh data if this discussion is synchronized automatically.
this.syncObserver = this.eventsProvider.on(AddonModDataSyncProvider.AUTO_SYNCED, (data) => {
if ((data.entryId == this.entryId || data.offlineEntryId == this.entryId) && this.data.id == data.dataId) {
if (data.deleted) {
// If deleted, go back.
this.navCtrl.pop();
} else {
this.entryId = data.entryid;
this.entryLoaded = false;
this.fetchEntryData(true);
}
}
}, this.siteId);
// Refresh entry on change.
this.entryChangedObserver = this.eventsProvider.on(AddonModDataProvider.ENTRY_CHANGED, (data) => {
if (data.entryId == this.entryId && data.id == data.dataId) {
if (data.deleted) {
// If deleted, go back.
this.navCtrl.pop();
} else {
this.entryLoaded = false;
this.fetchEntryData(true);
}
}
}, this.siteId);
}
/**
* Fetch the entry data.
*
* @param {boolean} refresh If refresh the current data or not.
* @return {Promise<any>} Resolved when done.
*/
protected fetchEntryData(refresh?: boolean): Promise<any> {
return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => {
this.title = data.name || this.title;
this.data = data;
this.cssClass = 'addon-data-entries-' + data.id;
return this.setEntryIdFromPage(data.id, this.page, this.selectedGroup).then(() => {
return this.dataProvider.getDatabaseAccessInformation(data.id);
});
}).then((accessData) => {
this.access = accessData;
return this.groupsProvider.getActivityGroupInfo(this.data.coursemodule, accessData.canmanageentries)
.then((groupInfo) => {
this.groupInfo = groupInfo;
// Check selected group is accessible.
if (groupInfo && groupInfo.groups && groupInfo.groups.length > 0) {
if (!groupInfo.groups.some((group) => this.selectedGroup == group.id)) {
this.selectedGroup = groupInfo.groups[0].id;
}
}
return this.dataOffline.getEntryActions(this.data.id, this.entryId);
});
}).then((actions) => {
this.offlineActions = actions;
this.hasOffline = !!actions.length;
return this.dataProvider.getFields(this.data.id).then((fieldsData) => {
this.fields = {};
fieldsData.forEach((field) => {
this.fields[field.id] = field;
});
return this.dataHelper.getEntry(this.data, this.entryId, this.offlineActions);
});
}).then((entry) => {
entry = entry.entry;
this.cssTemplate = this.dataHelper.prefixCSS(this.data.csstemplate, '.' + this.cssClass);
// Index contents by fieldid.
const contents = {};
entry.contents.forEach((field) => {
contents[field.fieldid] = field;
});
entry.contents = contents;
const fieldsArray = this.utils.objectToArray(this.fields);
return this.dataHelper.applyOfflineActions(entry, this.offlineActions, fieldsArray);
}).then((entryData) => {
this.entry = entryData;
const actions = this.dataHelper.getActions(this.data, this.access, this.entry),
fieldsArray = this.utils.objectToArray(this.fields);
this.entryRendered = this.dataHelper.displayShowFields(this.data.singletemplate, fieldsArray,
this.entry, 'show', actions);
this.showComments = actions.comments;
const entries = {};
entries[this.entryId] = this.entry;
// Pass the input data to the component.
this.jsData = {
fields: this.fields,
entries: entries,
data: this.data
};
return this.dataHelper.getPageInfoByEntry(this.data.id, this.entryId, this.selectedGroup).then((result) => {
this.previousId = result.previousId;
this.nextId = result.nextId;
});
}).catch((message) => {
if (!refresh) {
// Some call failed, retry without using cache since it might be a new activity.
return this.refreshAllData();
}
this.domUtils.showErrorModalDefault(message, 'core.course.errorgetmodule', true);
return Promise.reject(null);
}).finally(() => {
this.content && this.content.scrollToTop();
this.entryLoaded = true;
});
}
/**
* Go to selected entry without changing state.
*
* @param {number} entry Entry Id where to go.
* @return {Promise<any>} Resolved when done.
*/
gotoEntry(entry: number): Promise<any> {
this.entryId = entry;
this.page = null;
this.entryLoaded = false;
return this.fetchEntryData();
}
/**
* Refresh all the data.
*
* @return {Promise<any>} Promise resolved when done.
*/
protected refreshAllData(): Promise<any> {
const promises = [];
promises.push(this.dataProvider.invalidateDatabaseData(this.courseId));
if (this.data) {
promises.push(this.dataProvider.invalidateEntryData(this.data.id, this.entryId));
promises.push(this.groupsProvider.invalidateActivityGroupInfo(this.data.coursemodule));
promises.push(this.dataProvider.invalidateEntriesData(this.data.id));
}
return Promise.all(promises).finally(() => {
return this.fetchEntryData(true);
});
}
/**
* Refresh the data.
*
* @param {any} [refresher] Refresher.
* @return {Promise<any>} Promise resolved when done.
*/
refreshDatabase(refresher?: any): Promise<any> {
if (this.entryLoaded) {
return this.refreshAllData().finally(() => {
refresher && refresher.complete();
});
}
}
/**
* Set group to see the database.
*
* @param {number} groupId Group identifier to set.
* @return {Promise<any>} Resolved when done.
*/
setGroup(groupId: number): Promise<any> {
this.selectedGroup = groupId;
this.entryLoaded = false;
return this.setEntryIdFromPage(this.data.id, 0, this.selectedGroup).then(() => {
return this.fetchEntryData();
});
}
/**
* Convenience function to translate page number to entry identifier.
*
* @param {number} dataId Data Id.
* @param {number} [pageNumber] Page number where to go
* @param {number} group Group Id to get the entry.
* @return {Promise<any>} Resolved when done.
*/
protected setEntryIdFromPage(dataId: number, pageNumber?: number, group?: number): Promise<any> {
if (typeof pageNumber == 'number') {
return this.dataHelper.getPageInfoByPage(dataId, pageNumber, group).then((result) => {
this.entryId = result.entryId;
this.page = null;
});
}
return Promise.resolve();
}
/**
* Component being destroyed.
*/
ngOnDestroy(): void {
this.syncObserver && this.syncObserver.off();
this.entryChangedObserver && this.entryChangedObserver.off();
}
}

View File

@ -218,6 +218,59 @@ export class AddonModDataProvider {
}); });
} }
/**
* Performs the whole fetch of the entries in the database.
*
* @param {number} dataId Data ID.
* @param {number} [groupId] Group ID.
* @param {string} [sort] Sort the records by this field id. See AddonModDataProvider#getEntries for more info.
* @param {string} [order] The direction of the sorting. See AddonModDataProvider#getEntries for more info.
* @param {number} [perPage] Records per page to fetch. It has to match with the prefetch.
* Default on AddonModDataProvider.PER_PAGE.
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
fetchAllEntries(dataId: number, groupId: number = 0, sort: string = '0', order: string = 'DESC',
perPage: number = AddonModDataProvider.PER_PAGE, forceCache: boolean = false, ignoreCache: boolean = false,
siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
return this.fetchEntriesRecursive(dataId, groupId, sort, order, perPage, forceCache, ignoreCache, [], 0, siteId);
}
/**
* Recursive call on fetch all entries.
*
* @param {number} dataId Data ID.
* @param {number} groupId Group ID.
* @param {string} sort Sort the records by this field id. See AddonModDataProvider#getEntries for more info.
* @param {string} order The direction of the sorting. See AddonModDataProvider#getEntries for more info.
* @param {number} perPage Records per page to fetch. It has to match with the prefetch.
* @param {boolean} forceCache True to always get the value from cache, false otherwise. Default false.
* @param {boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
* @param {any} entries Entries already fetch (just to concatenate them).
* @param {number} page Page of records to return.
* @param {string} siteId Site ID.
* @return {Promise<any>} Promise resolved when done.
*/
protected fetchEntriesRecursive(dataId: number, groupId: number, sort: string, order: string, perPage: number,
forceCache: boolean, ignoreCache: boolean, entries: any, page: number, siteId: string): Promise<any> {
return this.getEntries(dataId, groupId, sort, order, page, perPage, forceCache, ignoreCache, siteId)
.then((result) => {
entries = entries.concat(result.entries);
const canLoadMore = perPage > 0 && ((page + 1) * perPage) < result.totalcount;
if (canLoadMore) {
return this.fetchEntriesRecursive(dataId, groupId, sort, order, perPage, forceCache, ignoreCache, entries, page + 1,
siteId);
}
return entries;
});
}
/** /**
* Get cache key for data data WS calls. * Get cache key for data data WS calls.
* *

View File

@ -15,7 +15,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { AddonModDataFieldsDelegate } from './fields-delegate'; import { AddonModDataFieldsDelegate } from './fields-delegate';
import { AddonModDataOfflineProvider } from './offline'; import { AddonModDataOfflineProvider } from './offline';
@ -27,7 +26,7 @@ import { AddonModDataProvider } from './data';
@Injectable() @Injectable()
export class AddonModDataHelperProvider { export class AddonModDataHelperProvider {
constructor(private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, constructor(private sitesProvider: CoreSitesProvider, protected dataProvider: AddonModDataProvider,
private translate: TranslateService, private fieldsDelegate: AddonModDataFieldsDelegate, private translate: TranslateService, private fieldsDelegate: AddonModDataFieldsDelegate,
private dataOffline: AddonModDataOfflineProvider, private fileUploaderProvider: CoreFileUploaderProvider) { } private dataOffline: AddonModDataOfflineProvider, private fileUploaderProvider: CoreFileUploaderProvider) { }
@ -175,6 +174,130 @@ export class AddonModDataHelperProvider {
}; };
} }
/**
* Fetch all entries and return it's Id
*
* @param {number} dataId Data ID.
* @param {number} groupId Group ID.
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. Current if not defined.
* @return {Promise<any>} Resolved with an array of entry ID.
*/
getAllEntriesIds(dataId: number, groupId: number, forceCache: boolean = false, ignoreCache: boolean = false, siteId?: string):
Promise<any> {
return this.dataProvider.fetchAllEntries(dataId, groupId, undefined, undefined, undefined, forceCache, ignoreCache, siteId)
.then((entries) => {
return entries.map((entry) => {
return entry.id;
});
});
}
/**
* Get an online or offline entry.
*
* @param {any} data Database.
* @param {number} entryId Entry ID.
* @param {any} [offlineActions] Offline data with the actions done. Required for offline entries.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with the entry.
*/
getEntry(data: any, entryId: number, offlineActions?: any, siteId?: string): Promise<any> {
if (entryId > 0) {
// It's an online entry, get it from WS.
return this.dataProvider.getEntry(data.id, entryId, siteId);
}
// It's an offline entry, search it in the offline actions.
return this.sitesProvider.getSite(siteId).then((site) => {
const offlineEntry = offlineActions.find((offlineAction) => {
return offlineAction.action == 'add';
});
if (offlineEntry) {
const siteInfo = site.getInfo();
return {entry: {
id: offlineEntry.entryid,
canmanageentry: true,
approved: !data.approval || data.manageapproved,
dataid: offlineEntry.dataid,
groupid: offlineEntry.groupid,
timecreated: -offlineEntry.entryid,
timemodified: -offlineEntry.entryid,
userid: siteInfo.userid,
fullname: siteInfo.fullname,
contents: {}
}
};
}
});
}
/**
* Get page info related to an entry.
*
* @param {number} dataId Data ID.
* @param {number} entryId Entry ID.
* @param {number} groupId Group ID.
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. Current if not defined.
* @return {Promise<any>} Containing page number, if has next and have following page.
*/
getPageInfoByEntry(dataId: number, entryId: number, groupId: number, forceCache: boolean = false,
ignoreCache: boolean = false, siteId?: string): Promise<any> {
return this.getAllEntriesIds(dataId, groupId, forceCache, ignoreCache, siteId).then((entries) => {
const index = entries.findIndex((entry) => {
return entry == entryId;
});
if (index >= 0) {
return {
previousId: entries[index - 1] || false,
nextId: entries[index + 1] || false,
entryId: entryId,
page: index + 1, // Parsed to natural language.
numEntries: entries.length
};
}
return false;
});
}
/**
* Get page info related to an entry by page number.
*
* @param {number} dataId Data ID.
* @param {number} page Page number.
* @param {number} groupId Group ID.
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. Current if not defined.
* @return {Promise<any>} Containing page number, if has next and have following page.
*/
getPageInfoByPage(dataId: number, page: number, groupId: number, forceCache: boolean = false,
ignoreCache: boolean = false, siteId?: string): Promise<any> {
return this.getAllEntriesIds(dataId, groupId, forceCache, ignoreCache, siteId).then((entries) => {
const index = page - 1,
entryId = entries[index];
if (entryId) {
return {
previousId: entries[index - 1] || null,
nextId: entries[index + 1] || null,
entryId: entryId,
page: page, // Parsed to natural language.
numEntries: entries.length
};
}
return false;
});
}
/** /**
* Get a list of stored attachment files for a new entry. See $mmaModDataHelper#storeFiles. * Get a list of stored attachment files for a new entry. See $mmaModDataHelper#storeFiles.
* *