MOBILE-3643 forum: Migrate sync
parent
d41523d4bb
commit
41259a66c9
|
@ -3,7 +3,7 @@
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" iconAction="open"></core-context-menu-item>
|
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" iconAction="open"></core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></core-context-menu-item>
|
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'fa-newspaper-o'" (action)="gotoBlog($event)"></core-context-menu-item>
|
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'fa-newspaper-o'" (action)="gotoBlog()"></core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="discussions.loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700" [content]="'addon.mod_forum.refreshdiscussions' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
|
<core-context-menu-item *ngIf="discussions.loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700" [content]="'addon.mod_forum.refreshdiscussions' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="discussions.loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
|
<core-context-menu-item *ngIf="discussions.loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
|
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||||
|
@ -103,6 +103,9 @@
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<core-infinite-loading [enabled]="discussions.onlineLoaded && !discussions.completed" (action)="fetchMoreDiscussions($event)" [error]="fetchMoreDiscussionsFailed">
|
||||||
|
</core-infinite-loading>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,12 @@ import { CoreCourseContentsPage } from '@features/course/pages/contents/contents
|
||||||
import { AddonModForumHelper } from '@addons/mod/forum/services/helper.service';
|
import { AddonModForumHelper } from '@addons/mod/forum/services/helper.service';
|
||||||
import { CoreGroups, CoreGroupsProvider } from '@services/groups';
|
import { CoreGroups, CoreGroupsProvider } from '@services/groups';
|
||||||
import { CoreEvents, CoreEventObserver } from '@singletons/events';
|
import { CoreEvents, CoreEventObserver } from '@singletons/events';
|
||||||
import { AddonModForumSyncProvider } from '@addons/mod/forum/services/sync.service';
|
import {
|
||||||
|
AddonModForumAutoSyncData,
|
||||||
|
AddonModForumManualSyncData,
|
||||||
|
AddonModForumSyncProvider,
|
||||||
|
AddonModForumSyncResult,
|
||||||
|
} from '@addons/mod/forum/services/sync.service';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreUser } from '@features/user/services/user';
|
import { CoreUser } from '@features/user/services/user';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
@ -41,6 +46,7 @@ import { AddonModForumDiscussionOptionsMenuComponent } from '../discussion-optio
|
||||||
import { AddonModForumSortOrderSelectorComponent } from '../sort-order-selector/sort-order-selector';
|
import { AddonModForumSortOrderSelectorComponent } from '../sort-order-selector/sort-order-selector';
|
||||||
import { CoreScreen } from '@services/screen';
|
import { CoreScreen } from '@services/screen';
|
||||||
import { CoreArray } from '@singletons/array';
|
import { CoreArray } from '@singletons/array';
|
||||||
|
import { AddonModForumPrefetchHandler } from '../../services/handlers/prefetch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays a forum entry page.
|
* Component that displays a forum entry page.
|
||||||
|
@ -58,8 +64,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
moduleName = 'forum';
|
moduleName = 'forum';
|
||||||
descriptionNote?: string;
|
descriptionNote?: string;
|
||||||
forum?: AddonModForumData;
|
forum?: AddonModForumData;
|
||||||
canLoadMore = false;
|
fetchMoreDiscussionsFailed = false;
|
||||||
loadMoreError = false;
|
|
||||||
discussions: AddonModForumDiscussionsManager;
|
discussions: AddonModForumDiscussionsManager;
|
||||||
canAddDiscussion = false;
|
canAddDiscussion = false;
|
||||||
addDiscussionText!: string;
|
addDiscussionText!: string;
|
||||||
|
@ -201,12 +206,12 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
* @param sync If the refresh needs syncing.
|
* @param sync If the refresh needs syncing.
|
||||||
* @param showErrors Wether to show errors to the user or hide them.
|
* @param showErrors Wether to show errors to the user or hide them.
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false): Promise<void> {
|
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
||||||
this.loadMoreError = false;
|
this.fetchMoreDiscussionsFailed = false;
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
promises.push(this.fetchForum());
|
promises.push(this.fetchForum(sync, showErrors));
|
||||||
promises.push(this.fetchSortOrderPreference());
|
promises.push(this.fetchSortOrderPreference());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -219,7 +224,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_forum.errorgetforum', true);
|
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_forum.errorgetforum', true);
|
||||||
|
|
||||||
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
this.fetchMoreDiscussionsFailed = true; // Set to prevent infinite calls with infinite-loading.
|
||||||
} else {
|
} else {
|
||||||
// Get forum failed, retry without using cache since it might be a new activity.
|
// Get forum failed, retry without using cache since it might be a new activity.
|
||||||
await this.refreshContent(sync);
|
await this.refreshContent(sync);
|
||||||
|
@ -229,136 +234,102 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
this.fillContextMenu(refresh);
|
this.fillContextMenu(refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchForum(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
private async fetchForum(sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
||||||
if (!this.courseId || !this.module) {
|
if (!this.courseId || !this.module) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadMoreError = false;
|
const forum = await AddonModForum.instance.getForum(this.courseId, this.module.id);
|
||||||
|
|
||||||
|
this.forum = forum;
|
||||||
|
this.description = forum.intro || this.description;
|
||||||
|
this.availabilityMessage = AddonModForumHelper.instance.getAvailabilityMessage(forum);
|
||||||
|
this.descriptionNote = Translate.instant('addon.mod_forum.numdiscussions', {
|
||||||
|
numdiscussions: forum.numdiscussions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof forum.istracked != 'undefined') {
|
||||||
|
this.trackPosts = forum.istracked;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataRetrieved.emit(forum);
|
||||||
|
|
||||||
|
switch (forum.type) {
|
||||||
|
case 'news':
|
||||||
|
case 'blog':
|
||||||
|
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewtopic');
|
||||||
|
break;
|
||||||
|
case 'qanda':
|
||||||
|
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewquestion');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewdiscussion');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
// Try to synchronize the forum.
|
||||||
|
const updated = await this.syncActivity(showErrors);
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
// Sync successful, send event.
|
||||||
|
CoreEvents.trigger<AddonModForumManualSyncData>(AddonModForumSyncProvider.MANUAL_SYNCED, {
|
||||||
|
forumId: forum.id,
|
||||||
|
userId: CoreSites.instance.getCurrentSiteUserId(),
|
||||||
|
source: 'index',
|
||||||
|
}, CoreSites.instance.getCurrentSiteId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
// Check if the activity uses groups.
|
||||||
promises.push(
|
promises.push(
|
||||||
AddonModForum.instance
|
CoreGroups.instance
|
||||||
.getForum(this.courseId, this.module.id)
|
.getActivityGroupMode(this.forum.cmid)
|
||||||
.then(async (forum) => {
|
.then(async mode => {
|
||||||
this.forum = forum;
|
this.usesGroups = mode === CoreGroupsProvider.SEPARATEGROUPS
|
||||||
this.description = forum.intro || this.description;
|
|
||||||
this.availabilityMessage = AddonModForumHelper.instance.getAvailabilityMessage(forum);
|
|
||||||
this.descriptionNote = Translate.instant('addon.mod_forum.numdiscussions', {
|
|
||||||
numdiscussions: forum.numdiscussions,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof forum.istracked != 'undefined') {
|
|
||||||
this.trackPosts = forum.istracked;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dataRetrieved.emit(forum);
|
|
||||||
|
|
||||||
switch (forum.type) {
|
|
||||||
case 'news':
|
|
||||||
case 'blog':
|
|
||||||
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewtopic');
|
|
||||||
break;
|
|
||||||
case 'qanda':
|
|
||||||
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewquestion');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.addDiscussionText = Translate.instant('addon.mod_forum.addanewdiscussion');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sync) {
|
|
||||||
// Try to synchronize the forum.
|
|
||||||
const updated = await this.syncActivity(showErrors);
|
|
||||||
|
|
||||||
if (updated) {
|
|
||||||
// Sync successful, send event.
|
|
||||||
CoreEvents.trigger(AddonModForumSyncProvider.MANUAL_SYNCED, {
|
|
||||||
forumId: forum.id,
|
|
||||||
userId: CoreSites.instance.getCurrentSiteUserId(),
|
|
||||||
source: 'index',
|
|
||||||
}, CoreSites.instance.getCurrentSiteId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
|
|
||||||
// Check if the activity uses groups.
|
|
||||||
promises.push(
|
|
||||||
// eslint-disable-next-line promise/no-nesting
|
|
||||||
CoreGroups.instance
|
|
||||||
.getActivityGroupMode(this.forum.cmid)
|
|
||||||
.then(async mode => {
|
|
||||||
this.usesGroups = mode === CoreGroupsProvider.SEPARATEGROUPS
|
|
||||||
|| mode === CoreGroupsProvider.VISIBLEGROUPS;
|
|| mode === CoreGroupsProvider.VISIBLEGROUPS;
|
||||||
|
|
||||||
return;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
promises.push(
|
|
||||||
// eslint-disable-next-line promise/no-nesting
|
|
||||||
AddonModForum.instance
|
|
||||||
.getAccessInformation(this.forum.id, { cmId: this.module!.id })
|
|
||||||
.then(async accessInfo => {
|
|
||||||
// Disallow adding discussions if cut-off date is reached and the user has not the
|
|
||||||
// capability to override it.
|
|
||||||
// Just in case the forum was fetched from WS when the cut-off date was not reached but it is now.
|
|
||||||
const cutoffDateReached = AddonModForumHelper.instance.isCutoffDateReached(this.forum!)
|
|
||||||
&& !accessInfo.cancanoverridecutoff;
|
|
||||||
this.canAddDiscussion = !!this.forum?.cancreatediscussions && !cutoffDateReached;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (AddonModForum.instance.isSetPinStateAvailableForSite()) {
|
|
||||||
// Use the canAddDiscussion WS to check if the user can pin discussions.
|
|
||||||
promises.push(
|
|
||||||
// eslint-disable-next-line promise/no-nesting
|
|
||||||
AddonModForum.instance
|
|
||||||
.canAddDiscussionToAll(this.forum.id, { cmId: this.module!.id })
|
|
||||||
.then(async response => {
|
|
||||||
this.canPin = !!response.canpindiscussions;
|
|
||||||
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
.catch(async () => {
|
|
||||||
this.canPin = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.canPin = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
promises.push(this.fetchSortOrderPreference());
|
promises.push(
|
||||||
|
AddonModForum.instance
|
||||||
|
.getAccessInformation(this.forum.id, { cmId: this.module!.id })
|
||||||
|
.then(async accessInfo => {
|
||||||
|
// Disallow adding discussions if cut-off date is reached and the user has not the
|
||||||
|
// capability to override it.
|
||||||
|
// Just in case the forum was fetched from WS when the cut-off date was not reached but it is now.
|
||||||
|
const cutoffDateReached = AddonModForumHelper.instance.isCutoffDateReached(this.forum!)
|
||||||
|
&& !accessInfo.cancanoverridecutoff;
|
||||||
|
this.canAddDiscussion = !!this.forum?.cancreatediscussions && !cutoffDateReached;
|
||||||
|
|
||||||
try {
|
return;
|
||||||
await Promise.all(promises);
|
}),
|
||||||
await Promise.all([
|
);
|
||||||
this.fetchOfflineDiscussions(),
|
|
||||||
this.fetchDiscussions(refresh),
|
|
||||||
]);
|
|
||||||
} catch (message) {
|
|
||||||
if (!refresh) {
|
|
||||||
// Get forum failed, retry without using cache since it might be a new activity.
|
|
||||||
return this.refreshContent(sync);
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreDomUtils.instance.showErrorModalDefault(message, 'addon.mod_forum.errorgetforum', true);
|
if (AddonModForum.instance.isSetPinStateAvailableForSite()) {
|
||||||
|
// Use the canAddDiscussion WS to check if the user can pin discussions.
|
||||||
|
promises.push(
|
||||||
|
AddonModForum.instance
|
||||||
|
.canAddDiscussionToAll(this.forum.id, { cmId: this.module!.id })
|
||||||
|
.then(async response => {
|
||||||
|
this.canPin = !!response.canpindiscussions;
|
||||||
|
|
||||||
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
return;
|
||||||
|
})
|
||||||
|
.catch(async () => {
|
||||||
|
this.canPin = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.canPin = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fillContextMenu(refresh);
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -414,7 +385,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
*/
|
*/
|
||||||
protected async fetchDiscussions(refresh: boolean): Promise<void> {
|
protected async fetchDiscussions(refresh: boolean): Promise<void> {
|
||||||
const forum = this.forum!;
|
const forum = this.forum!;
|
||||||
this.loadMoreError = false;
|
this.fetchMoreDiscussionsFailed = false;
|
||||||
|
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
this.page = 0;
|
this.page = 0;
|
||||||
|
@ -435,7 +406,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
if (forum.type === 'single') {
|
if (forum.type === 'single') {
|
||||||
for (const discussion of discussions) {
|
for (const discussion of discussions) {
|
||||||
if (discussion.userfullname && discussion.parent === 0) {
|
if (discussion.userfullname && discussion.parent === 0) {
|
||||||
(discussion as any).userfullname = false;
|
discussion.userfullname = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -452,12 +423,11 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.page === 0) {
|
if (this.page === 0) {
|
||||||
this.discussions.setOnlineDiscussions(discussions);
|
this.discussions.setOnlineDiscussions(discussions, response.canLoadMore);
|
||||||
} else {
|
} else {
|
||||||
this.discussions.setItems(this.discussions.items.concat(discussions));
|
this.discussions.setItems(this.discussions.items.concat(discussions), response.canLoadMore);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canLoadMore = response.canLoadMore;
|
|
||||||
this.page++;
|
this.page++;
|
||||||
|
|
||||||
// Check if there are replies for discussions stored in offline.
|
// Check if there are replies for discussions stored in offline.
|
||||||
|
@ -467,7 +437,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
|
|
||||||
if (hasOffline) {
|
if (hasOffline) {
|
||||||
// Only update new fetched discussions.
|
// Only update new fetched discussions.
|
||||||
const promises = discussions.map(async (discussion: any) => {
|
const promises = discussions.map(async (discussion) => {
|
||||||
// Get offline discussions.
|
// Get offline discussions.
|
||||||
const replies = await AddonModForumOffline.instance.getDiscussionReplies(discussion.discussion);
|
const replies = await AddonModForumOffline.instance.getDiscussionReplies(discussion.discussion);
|
||||||
|
|
||||||
|
@ -484,14 +454,16 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
|
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
fetchMoreDiscussions(infiniteComplete?: any): Promise<any> {
|
async fetchMoreDiscussions(complete: () => void): Promise<void> {
|
||||||
return this.fetchDiscussions(false).catch((message) => {
|
try {
|
||||||
CoreDomUtils.instance.showErrorModalDefault(message, 'addon.mod_forum.errorgetforum', true);
|
await this.fetchDiscussions(false);
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(error, 'addon.mod_forum.errorgetforum', true);
|
||||||
|
|
||||||
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
this.fetchMoreDiscussionsFailed = true;
|
||||||
}).finally(() => {
|
} finally {
|
||||||
infiniteComplete && infiniteComplete();
|
complete();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -522,7 +494,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
*
|
*
|
||||||
* @return Resolved when done.
|
* @return Resolved when done.
|
||||||
*/
|
*/
|
||||||
protected invalidateContent(): Promise<any> {
|
protected async invalidateContent(): Promise<void> {
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
promises.push(AddonModForum.instance.invalidateForumData(this.courseId!));
|
promises.push(AddonModForum.instance.invalidateForumData(this.courseId!));
|
||||||
|
@ -537,7 +509,16 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
promises.push(CoreUser.instance.invalidateUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER));
|
promises.push(CoreUser.instance.invalidateUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the sync of the activity.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected sync(): Promise<AddonModForumSyncResult> {
|
||||||
|
return AddonModForumPrefetchHandler.instance.sync(this.module!, this.courseId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -546,10 +527,23 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
* @param result Data returned on the sync function.
|
* @param result Data returned on the sync function.
|
||||||
* @return Whether it succeed or not.
|
* @return Whether it succeed or not.
|
||||||
*/
|
*/
|
||||||
protected hasSyncSucceed(result: any): boolean {
|
protected hasSyncSucceed(result: AddonModForumSyncResult): boolean {
|
||||||
return result.updated;
|
return result.updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares sync event data with current data to check if refresh content is needed.
|
||||||
|
*
|
||||||
|
* @param syncEventData Data receiven on sync observer.
|
||||||
|
* @return True if refresh is needed, false otherwise.
|
||||||
|
*/
|
||||||
|
protected isRefreshSyncNeeded(syncEventData: AddonModForumAutoSyncData | AddonModForumManualSyncData): boolean {
|
||||||
|
return !!this.forum
|
||||||
|
&& (!('source' in syncEventData) || syncEventData.source != 'index')
|
||||||
|
&& syncEventData.forumId == this.forum.id
|
||||||
|
&& syncEventData.userId == CoreSites.instance.getCurrentSiteUserId();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called when we receive an event of new discussion or reply to discussion.
|
* Function called when we receive an event of new discussion or reply to discussion.
|
||||||
*
|
*
|
||||||
|
@ -685,6 +679,8 @@ type DiscussionItem = AddonModForumDiscussion | AddonModForumOfflineDiscussion |
|
||||||
*/
|
*/
|
||||||
class AddonModForumDiscussionsManager extends CorePageItemsListManager<DiscussionItem> {
|
class AddonModForumDiscussionsManager extends CorePageItemsListManager<DiscussionItem> {
|
||||||
|
|
||||||
|
onlineLoaded = false;
|
||||||
|
|
||||||
private discussionsPathPrefix: string;
|
private discussionsPathPrefix: string;
|
||||||
private component: AddonModForumIndexComponent;
|
private component: AddonModForumIndexComponent;
|
||||||
|
|
||||||
|
@ -747,10 +743,10 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<Discussio
|
||||||
*
|
*
|
||||||
* @param onlineDiscussions Online discussions
|
* @param onlineDiscussions Online discussions
|
||||||
*/
|
*/
|
||||||
setOnlineDiscussions(onlineDiscussions: AddonModForumDiscussion[]): void {
|
setOnlineDiscussions(onlineDiscussions: AddonModForumDiscussion[], hasMoreItems: boolean = false): void {
|
||||||
const otherDiscussions = this.items.filter(discussion => !this.isOnlineDiscussion(discussion));
|
const otherDiscussions = this.items.filter(discussion => !this.isOnlineDiscussion(discussion));
|
||||||
|
|
||||||
this.setItems(otherDiscussions.concat(onlineDiscussions));
|
this.setItems(otherDiscussions.concat(onlineDiscussions), hasMoreItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -761,7 +757,16 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<Discussio
|
||||||
setOfflineDiscussions(offlineDiscussions: AddonModForumOfflineDiscussion[]): void {
|
setOfflineDiscussions(offlineDiscussions: AddonModForumOfflineDiscussion[]): void {
|
||||||
const otherDiscussions = this.items.filter(discussion => !this.isOfflineDiscussion(discussion));
|
const otherDiscussions = this.items.filter(discussion => !this.isOfflineDiscussion(discussion));
|
||||||
|
|
||||||
this.setItems((offlineDiscussions as DiscussionItem[]).concat(otherDiscussions));
|
this.setItems((offlineDiscussions as DiscussionItem[]).concat(otherDiscussions), this.hasMoreItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
setItems(discussions: DiscussionItem[], hasMoreItems: boolean = false): void {
|
||||||
|
super.setItems(discussions, hasMoreItems);
|
||||||
|
|
||||||
|
this.onlineLoaded = this.onlineLoaded || discussions.some(discussion => this.isOnlineDiscussion(discussion));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,6 +25,10 @@ import { CoreScreen } from '@services/screen';
|
||||||
import { AddonModForumComponentsModule } from './components/components.module';
|
import { AddonModForumComponentsModule } from './components/components.module';
|
||||||
import { AddonModForumModuleHandler, AddonModForumModuleHandlerService } from './services/handlers/module';
|
import { AddonModForumModuleHandler, AddonModForumModuleHandlerService } from './services/handlers/module';
|
||||||
import { SITE_SCHEMA } from './services/offline-db';
|
import { SITE_SCHEMA } from './services/offline-db';
|
||||||
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
|
import { AddonModForumPrefetchHandler } from './services/handlers/prefetch';
|
||||||
|
import { CoreCronDelegate } from '@services/cron';
|
||||||
|
import { AddonModForumSyncCronHandler } from './services/handlers/sync-cron';
|
||||||
|
|
||||||
const mainMenuRoutes: Routes = [
|
const mainMenuRoutes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -77,7 +81,11 @@ const courseContentsRoutes: Routes = conditionalRoutes(
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
multi: true,
|
multi: true,
|
||||||
useValue: () => CoreCourseModuleDelegate.instance.registerHandler(AddonModForumModuleHandler.instance),
|
useValue: () => {
|
||||||
|
CoreCourseModuleDelegate.instance.registerHandler(AddonModForumModuleHandler.instance);
|
||||||
|
CoreCourseModulePrefetchDelegate.instance.registerHandler(AddonModForumPrefetchHandler.instance);
|
||||||
|
CoreCronDelegate.instance.register(AddonModForumSyncCronHandler.instance);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
@ -37,7 +37,7 @@ import {
|
||||||
} from '../../services/forum.service';
|
} from '../../services/forum.service';
|
||||||
import { AddonModForumHelper } from '../../services/helper.service';
|
import { AddonModForumHelper } from '../../services/helper.service';
|
||||||
import { AddonModForumOffline } from '../../services/offline.service';
|
import { AddonModForumOffline } from '../../services/offline.service';
|
||||||
import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/sync.service';
|
import { AddonModForumManualSyncData, AddonModForumSync, AddonModForumSyncProvider } from '../../services/sync.service';
|
||||||
|
|
||||||
type SortType = 'flat-newest' | 'flat-oldest' | 'nested';
|
type SortType = 'flat-newest' | 'flat-oldest' | 'nested';
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
||||||
}, CoreSites.instance.getCurrentSiteId());
|
}, CoreSites.instance.getCurrentSiteId());
|
||||||
|
|
||||||
// Refresh data if this forum discussion is synchronized from discussions list.
|
// Refresh data if this forum discussion is synchronized from discussions list.
|
||||||
this.syncManualObserver = CoreEvents.on(AddonModForumSyncProvider.MANUAL_SYNCED, (data: any) => {
|
this.syncManualObserver = CoreEvents.on(AddonModForumSyncProvider.MANUAL_SYNCED, (data: AddonModForumManualSyncData) => {
|
||||||
if (data.source != 'discussion' && data.forumId == this.forumId &&
|
if (data.source != 'discussion' && data.forumId == this.forumId &&
|
||||||
data.userId == CoreSites.instance.getCurrentSiteUserId()) {
|
data.userId == CoreSites.instance.getCurrentSiteUserId()) {
|
||||||
// Refresh the data.
|
// Refresh the data.
|
||||||
|
@ -541,7 +541,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
||||||
|
|
||||||
if (result && result.updated) {
|
if (result && result.updated) {
|
||||||
// Sync successful, send event.
|
// Sync successful, send event.
|
||||||
CoreEvents.trigger(AddonModForumSyncProvider.MANUAL_SYNCED, {
|
CoreEvents.trigger<AddonModForumManualSyncData>(AddonModForumSyncProvider.MANUAL_SYNCED, {
|
||||||
forumId: this.forumId,
|
forumId: this.forumId,
|
||||||
userId: CoreSites.instance.getCurrentSiteUserId(),
|
userId: CoreSites.instance.getCurrentSiteUserId(),
|
||||||
source: 'discussion',
|
source: 'discussion',
|
||||||
|
|
|
@ -1401,7 +1401,7 @@ export type AddonModForumDiscussion = {
|
||||||
attachments?: CoreWSExternalFile[];
|
attachments?: CoreWSExternalFile[];
|
||||||
totalscore: number; // The post message total score.
|
totalscore: number; // The post message total score.
|
||||||
mailnow: number; // Mail now?.
|
mailnow: number; // Mail now?.
|
||||||
userfullname: string; // Post author full name.
|
userfullname: string | boolean; // Post author full name.
|
||||||
usermodifiedfullname: string; // Post modifier full name.
|
usermodifiedfullname: string; // Post modifier full name.
|
||||||
userpictureurl: string; // Post author picture.
|
userpictureurl: string; // Post author picture.
|
||||||
usermodifiedpictureurl: string; // Post modifier picture.
|
usermodifiedpictureurl: string; // Post modifier picture.
|
||||||
|
@ -1452,6 +1452,7 @@ export type AddonModForumPost = {
|
||||||
};
|
};
|
||||||
attachment?: 0 | 1;
|
attachment?: 0 | 1;
|
||||||
attachments?: (CoreFileEntry | AddonModForumWSPostAttachment)[];
|
attachments?: (CoreFileEntry | AddonModForumWSPostAttachment)[];
|
||||||
|
messageinlinefiles?: CoreWSExternalFile[];
|
||||||
haswordcount?: boolean; // Haswordcount.
|
haswordcount?: boolean; // Haswordcount.
|
||||||
wordcount?: number; // Wordcount.
|
wordcount?: number; // Wordcount.
|
||||||
tags?: { // Tags.
|
tags?: { // Tags.
|
||||||
|
|
|
@ -0,0 +1,353 @@
|
||||||
|
// (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 { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
|
||||||
|
import { AddonModForum, AddonModForumData, AddonModForumPost, AddonModForumProvider } from '../forum.service';
|
||||||
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||||
|
import { CoreFilepool } from '@services/filepool';
|
||||||
|
import { CoreWSExternalFile } from '@services/ws';
|
||||||
|
import { CoreCourse, CoreCourseAnyModuleData, CoreCourseCommonModWSOptions } from '@features/course/services/course';
|
||||||
|
import { CoreUser } from '@features/user/services/user';
|
||||||
|
import { CoreGroups, CoreGroupsProvider } from '@services/groups';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { AddonModForumSync } from '../sync.service';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to prefetch forums.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase {
|
||||||
|
|
||||||
|
name = 'AddonModForum';
|
||||||
|
modName = 'forum';
|
||||||
|
component = AddonModForumProvider.COMPONENT;
|
||||||
|
updatesNames = /^configuration$|^.*files$|^discussions$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of files. If not defined, we'll assume they're in module.contents.
|
||||||
|
*
|
||||||
|
* @param module Module.
|
||||||
|
* @param courseId Course ID the module belongs to.
|
||||||
|
* @param single True if we're downloading a single module, false if we're downloading a whole section.
|
||||||
|
* @return Promise resolved with the list of files.
|
||||||
|
*/
|
||||||
|
async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
|
||||||
|
try {
|
||||||
|
const forum = await AddonModForum.instance.getForum(courseId, module.id);
|
||||||
|
|
||||||
|
const files = this.getIntroFilesFromInstance(module, forum);
|
||||||
|
|
||||||
|
// Get posts.
|
||||||
|
const posts = await this.getPostsForPrefetch(forum, { cmId: module.id });
|
||||||
|
|
||||||
|
// Add posts attachments and embedded files.
|
||||||
|
files.concat(this.getPostsFiles(posts));
|
||||||
|
|
||||||
|
return files;
|
||||||
|
} catch (error) {
|
||||||
|
// Forum not found, return empty list.
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of forum posts, return a list with all the files (attachments and embedded files).
|
||||||
|
*
|
||||||
|
* @param posts Forum posts.
|
||||||
|
* @return Files.
|
||||||
|
*/
|
||||||
|
protected getPostsFiles(posts: AddonModForumPost[]): CoreWSExternalFile[] {
|
||||||
|
let files: CoreWSExternalFile[] = [];
|
||||||
|
const getInlineFiles = CoreSites.instance.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
|
||||||
|
|
||||||
|
posts.forEach((post) => {
|
||||||
|
if (post.attachments && post.attachments.length) {
|
||||||
|
files = files.concat(post.attachments as CoreWSExternalFile[]);
|
||||||
|
}
|
||||||
|
if (getInlineFiles && post.messageinlinefiles && post.messageinlinefiles.length) {
|
||||||
|
files = files.concat(post.messageinlinefiles);
|
||||||
|
} else if (post.message && !getInlineFiles) {
|
||||||
|
files = files.concat(CoreFilepool.instance.extractDownloadableFilesFromHtmlAsFakeFileObjects(post.message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the posts to be prefetched.
|
||||||
|
*
|
||||||
|
* @param forum Forum instance.
|
||||||
|
* @param options Other options.
|
||||||
|
* @return Promise resolved with array of posts.
|
||||||
|
*/
|
||||||
|
protected getPostsForPrefetch(
|
||||||
|
forum: AddonModForumData,
|
||||||
|
options: CoreCourseCommonModWSOptions = {},
|
||||||
|
): Promise<AddonModForumPost[]> {
|
||||||
|
const promises = AddonModForum.instance.getAvailableSortOrders().map((sortOrder) => {
|
||||||
|
// Get discussions in first 2 pages.
|
||||||
|
const discussionsOptions = {
|
||||||
|
sortOrder: sortOrder.value,
|
||||||
|
numPages: 2,
|
||||||
|
...options, // Include all options.
|
||||||
|
};
|
||||||
|
|
||||||
|
return AddonModForum.instance.getDiscussionsInPages(forum.id, discussionsOptions).then((response) => {
|
||||||
|
if (response.error) {
|
||||||
|
throw new Error('Failed getting discussions');
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises: Promise<{ posts: AddonModForumPost[] }>[] = [];
|
||||||
|
|
||||||
|
response.discussions.forEach((discussion) => {
|
||||||
|
promises.push(AddonModForum.instance.getDiscussionPosts(discussion.discussion, options));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then((results) => {
|
||||||
|
// Each order has returned its own list of posts. Merge all the lists, preventing duplicates.
|
||||||
|
const posts: AddonModForumPost[] = [];
|
||||||
|
const postIds = {}; // To make the array unique.
|
||||||
|
|
||||||
|
results.forEach((orderResults) => {
|
||||||
|
orderResults.forEach((orderResult) => {
|
||||||
|
orderResult.posts.forEach((post) => {
|
||||||
|
if (!postIds[post.id]) {
|
||||||
|
postIds[post.id] = true;
|
||||||
|
posts.push(post);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return posts;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the prefetched content.
|
||||||
|
*
|
||||||
|
* @param moduleId The module ID.
|
||||||
|
* @param courseId The course ID the module belongs to.
|
||||||
|
* @return Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateContent(moduleId: number, courseId: number): Promise<void> {
|
||||||
|
return AddonModForum.instance.invalidateContent(moduleId, courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate WS calls needed to determine module status (usually, to check if module is downloadable).
|
||||||
|
* It doesn't need to invalidate check updates. It should NOT invalidate files nor all the prefetched data.
|
||||||
|
*
|
||||||
|
* @param module Module.
|
||||||
|
* @param courseId Course ID the module belongs to.
|
||||||
|
* @return Promise resolved when invalidated.
|
||||||
|
*/
|
||||||
|
async invalidateModule(module: CoreCourseAnyModuleData, courseId: number): Promise<void> {
|
||||||
|
// Invalidate forum data to recalculate unread message count badge.
|
||||||
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
|
promises.push(AddonModForum.instance.invalidateForumData(courseId));
|
||||||
|
promises.push(CoreCourse.instance.invalidateModule(module.id));
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch a module.
|
||||||
|
*
|
||||||
|
* @param module Module.
|
||||||
|
* @param courseId Course ID the module belongs to.
|
||||||
|
* @param single True if we're downloading a single module, false if we're downloading a whole section.
|
||||||
|
* @param dirPath Path of the directory where to store all the content files.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
prefetch(module: CoreCourseAnyModuleData, courseId?: number, single?: boolean): Promise<void> {
|
||||||
|
return this.prefetchPackage(module, courseId, this.prefetchForum.bind(this, module, courseId, single));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch a forum.
|
||||||
|
*
|
||||||
|
* @param module The module object returned by WS.
|
||||||
|
* @param courseId Course ID the module belongs to.
|
||||||
|
* @param single True if we're downloading a single module, false if we're downloading a whole section.
|
||||||
|
* @param siteId Site ID.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async prefetchForum(
|
||||||
|
module: CoreCourseAnyModuleData,
|
||||||
|
courseId: number,
|
||||||
|
single: boolean,
|
||||||
|
siteId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const commonOptions = {
|
||||||
|
readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
|
||||||
|
siteId,
|
||||||
|
};
|
||||||
|
const modOptions = {
|
||||||
|
cmId: module.id,
|
||||||
|
...commonOptions, // Include all common options.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the forum data.
|
||||||
|
const forum = await AddonModForum.instance.getForum(courseId, module.id, commonOptions);
|
||||||
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
|
// Prefetch the posts.
|
||||||
|
promises.push(this.getPostsForPrefetch(forum, modOptions).then((posts) => {
|
||||||
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
|
const files = this.getIntroFilesFromInstance(module, forum).concat(this.getPostsFiles(posts));
|
||||||
|
promises.push(CoreFilepool.instance.addFilesToQueue(siteId, files, this.component, module.id));
|
||||||
|
|
||||||
|
// Prefetch groups data.
|
||||||
|
promises.push(this.prefetchGroupsInfo(forum, courseId, !!forum.cancreatediscussions, siteId));
|
||||||
|
|
||||||
|
// Prefetch avatars.
|
||||||
|
promises.push(CoreUser.instance.prefetchUserAvatars(posts, 'userpictureurl', siteId));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Prefetch access information.
|
||||||
|
promises.push(AddonModForum.instance.getAccessInformation(forum.id, modOptions));
|
||||||
|
|
||||||
|
// Prefetch sort order preference.
|
||||||
|
if (AddonModForum.instance.isDiscussionListSortingAvailable()) {
|
||||||
|
promises.push(CoreUser.instance.getUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER, siteId));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch groups info for a forum.
|
||||||
|
*
|
||||||
|
* @param module The module object returned by WS.
|
||||||
|
* @param courseI Course ID the module belongs to.
|
||||||
|
* @param canCreateDiscussions Whether the user can create discussions in the forum.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when group data has been prefetched.
|
||||||
|
*/
|
||||||
|
protected async prefetchGroupsInfo(
|
||||||
|
forum: AddonModForumData,
|
||||||
|
courseId: number,
|
||||||
|
canCreateDiscussions: boolean,
|
||||||
|
siteId?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const options = {
|
||||||
|
cmId: forum.cmid,
|
||||||
|
siteId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check group mode.
|
||||||
|
try {
|
||||||
|
const mode = await CoreGroups.instance.getActivityGroupMode(forum.cmid, siteId);
|
||||||
|
|
||||||
|
if (mode !== CoreGroupsProvider.SEPARATEGROUPS && mode !== CoreGroupsProvider.VISIBLEGROUPS) {
|
||||||
|
// Activity doesn't use groups. Prefetch canAddDiscussionToAll to determine if user can pin/attach.
|
||||||
|
await CoreUtils.instance.ignoreErrors(AddonModForum.instance.canAddDiscussionToAll(forum.id, options));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activity uses groups, prefetch allowed groups.
|
||||||
|
const result = await CoreGroups.instance.getActivityAllowedGroups(forum.cmid, undefined, siteId);
|
||||||
|
if (mode === CoreGroupsProvider.SEPARATEGROUPS) {
|
||||||
|
// Groups are already filtered by WS. Prefetch canAddDiscussionToAll to determine if user can pin/attach.
|
||||||
|
await CoreUtils.instance.ignoreErrors(AddonModForum.instance.canAddDiscussionToAll(forum.id, options));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canCreateDiscussions) {
|
||||||
|
// Prefetch data to check the visible groups when creating discussions.
|
||||||
|
const response = await CoreUtils.instance.ignoreErrors(
|
||||||
|
AddonModForum.instance.canAddDiscussionToAll(forum.id, options),
|
||||||
|
{ status: false },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status) {
|
||||||
|
// User can post to all groups, nothing else to prefetch.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user can't post to all groups, let's check which groups he can post to.
|
||||||
|
await Promise.all(
|
||||||
|
result.groups.map(
|
||||||
|
async (group) => CoreUtils.instance.ignoreErrors(
|
||||||
|
AddonModForum.instance.canAddDiscussion(forum.id, group.id, options),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore errors if cannot create discussions.
|
||||||
|
if (canCreateDiscussions) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync a module.
|
||||||
|
*
|
||||||
|
* @param module Module.
|
||||||
|
* @param courseId Course ID the module belongs to
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async sync(
|
||||||
|
module: CoreCourseAnyModuleData,
|
||||||
|
courseId: number,
|
||||||
|
siteId?: string,
|
||||||
|
): Promise<AddonModForumSyncResult> {
|
||||||
|
const promises: Promise<AddonModForumSyncResult>[] = [];
|
||||||
|
|
||||||
|
promises.push(AddonModForumSync.instance.syncForumDiscussions(module.instance!, undefined, siteId));
|
||||||
|
promises.push(AddonModForumSync.instance.syncForumReplies(module.instance!, undefined, siteId));
|
||||||
|
promises.push(AddonModForumSync.instance.syncRatings(module.id, undefined, true, siteId));
|
||||||
|
|
||||||
|
const results = await Promise.all(promises);
|
||||||
|
|
||||||
|
return results.reduce(
|
||||||
|
(a, b) => ({
|
||||||
|
updated: a.updated || b.updated,
|
||||||
|
warnings: (a.warnings || []).concat(b.warnings || []),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
updated: false,
|
||||||
|
warnings: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddonModForumPrefetchHandler extends makeSingleton(AddonModForumPrefetchHandlerService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by a forum sync.
|
||||||
|
*/
|
||||||
|
export type AddonModForumSyncResult = {
|
||||||
|
warnings: string[]; // List of warnings.
|
||||||
|
updated: boolean; // Whether some data was sent to the server or offline data was updated.
|
||||||
|
};
|
|
@ -0,0 +1,51 @@
|
||||||
|
// (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 { AddonModForumSync } from '../sync.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronization cron handler.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonModForumSyncCronHandlerService implements CoreCronHandler {
|
||||||
|
|
||||||
|
name = 'AddonModForumSyncCronHandler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the process.
|
||||||
|
* Receives the ID of the site affected, undefined for all sites.
|
||||||
|
*
|
||||||
|
* @param siteId ID of the site affected, undefined for all sites.
|
||||||
|
* @param force Wether the execution is forced (manual sync).
|
||||||
|
* @return Promise resolved when done, rejected if failure.
|
||||||
|
*/
|
||||||
|
execute(siteId?: string, force?: boolean): Promise<void> {
|
||||||
|
return AddonModForumSync.instance.syncAllForums(siteId, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time between consecutive executions.
|
||||||
|
*
|
||||||
|
* @return Time between consecutive executions (in ms).
|
||||||
|
*/
|
||||||
|
getInterval(): number {
|
||||||
|
return AddonModForumSync.instance.syncInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AddonModForumSyncCronHandler extends makeSingleton(AddonModForumSyncCronHandlerService) {}
|
|
@ -25,7 +25,7 @@ import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { makeSingleton, Translate } from '@singletons';
|
import { makeSingleton, Translate } from '@singletons';
|
||||||
import { CoreArray } from '@singletons/array';
|
import { CoreArray } from '@singletons/array';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents, CoreEventSiteData } from '@singletons/events';
|
||||||
import {
|
import {
|
||||||
AddonModForum,
|
AddonModForum,
|
||||||
AddonModForumAddDiscussionPostWSOptionsObject,
|
AddonModForumAddDiscussionPostWSOptionsObject,
|
||||||
|
@ -95,7 +95,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForu
|
||||||
|
|
||||||
if (result && result.updated) {
|
if (result && result.updated) {
|
||||||
// Sync successful, send event.
|
// Sync successful, send event.
|
||||||
CoreEvents.trigger(AddonModForumSyncProvider.AUTO_SYNCED, {
|
CoreEvents.trigger<AddonModForumAutoSyncData>(AddonModForumSyncProvider.AUTO_SYNCED, {
|
||||||
forumId: discussion.forumid,
|
forumId: discussion.forumid,
|
||||||
userId: discussion.userid,
|
userId: discussion.userid,
|
||||||
warnings: result.warnings,
|
warnings: result.warnings,
|
||||||
|
@ -127,7 +127,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForu
|
||||||
|
|
||||||
if (result && result.updated) {
|
if (result && result.updated) {
|
||||||
// Sync successful, send event.
|
// Sync successful, send event.
|
||||||
CoreEvents.trigger(AddonModForumSyncProvider.AUTO_SYNCED, {
|
CoreEvents.trigger<AddonModForumAutoSyncData>(AddonModForumSyncProvider.AUTO_SYNCED, {
|
||||||
forumId: reply.forumid,
|
forumId: reply.forumid,
|
||||||
discussionId: reply.discussionid,
|
discussionId: reply.discussionid,
|
||||||
userId: reply.userid,
|
userId: reply.userid,
|
||||||
|
@ -619,3 +619,23 @@ export type AddonModForumSyncResult = {
|
||||||
updated: boolean;
|
updated: boolean;
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data passed to AUTO_SYNCED event.
|
||||||
|
*/
|
||||||
|
export type AddonModForumAutoSyncData = CoreEventSiteData & {
|
||||||
|
forumId: number;
|
||||||
|
userId: number;
|
||||||
|
warnings: string[];
|
||||||
|
discussionId?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data passed to MANUAL_SYNCED event.
|
||||||
|
*/
|
||||||
|
export type AddonModForumManualSyncData = CoreEventSiteData & {
|
||||||
|
forumId: number;
|
||||||
|
userId: number;
|
||||||
|
source: string;
|
||||||
|
discussionId?: number;
|
||||||
|
};
|
||||||
|
|
|
@ -147,7 +147,10 @@ export abstract class CorePageItemsListManager<Item> {
|
||||||
const params = this.getItemQueryParams(item);
|
const params = this.getItemQueryParams(item);
|
||||||
const pathPrefix = selectedItemPath ? selectedItemPath.split('/').fill('../').join('') : '';
|
const pathPrefix = selectedItemPath ? selectedItemPath.split('/').fill('../').join('') : '';
|
||||||
|
|
||||||
await CoreNavigator.instance.navigate(pathPrefix + itemPath, { params, reset: true });
|
await CoreNavigator.instance.navigate(pathPrefix + itemPath, {
|
||||||
|
params,
|
||||||
|
reset: CoreScreen.instance.isTablet,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue