MOBILE-3627 blog: Add blog functionality
parent
8471b549d1
commit
8097b4e1a8
|
@ -26,11 +26,13 @@ import { AddonMessagesModule } from './messages/messages.module';
|
||||||
import { AddonModModule } from './mod/mod.module';
|
import { AddonModModule } from './mod/mod.module';
|
||||||
import { AddonQbehaviourModule } from './qbehaviour/qbehaviour.module';
|
import { AddonQbehaviourModule } from './qbehaviour/qbehaviour.module';
|
||||||
import { AddonQtypeModule } from './qtype/qtype.module';
|
import { AddonQtypeModule } from './qtype/qtype.module';
|
||||||
|
import { AddonBlogModule } from './blog/blog.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
AddonBlockModule,
|
AddonBlockModule,
|
||||||
AddonBadgesModule,
|
AddonBadgesModule,
|
||||||
|
AddonBlogModule,
|
||||||
AddonCalendarModule,
|
AddonCalendarModule,
|
||||||
AddonMessagesModule,
|
AddonMessagesModule,
|
||||||
AddonPrivateFilesModule,
|
AddonPrivateFilesModule,
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
// (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 { Injector, NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, ROUTES, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { AddonBlogEntriesPage } from './pages/entries/entries';
|
||||||
|
import { CoreCommentsComponentsModule } from '@features/comments/components/components.module';
|
||||||
|
|
||||||
|
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
|
||||||
|
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
|
|
||||||
|
function buildRoutes(injector: Injector): Routes {
|
||||||
|
return [
|
||||||
|
...buildTabMainRoutes(injector, {
|
||||||
|
component: AddonBlogEntriesPage,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CoreSharedModule,
|
||||||
|
CoreCommentsComponentsModule,
|
||||||
|
CoreTagComponentsModule,
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: ROUTES,
|
||||||
|
multi: true,
|
||||||
|
deps: [Injector],
|
||||||
|
useFactory: buildRoutes,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AddonBlogEntriesPage,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonBlogLazyModule {}
|
|
@ -0,0 +1,65 @@
|
||||||
|
// (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 { Routes } from '@angular/router';
|
||||||
|
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
|
import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
|
||||||
|
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
||||||
|
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
|
||||||
|
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
|
import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
|
||||||
|
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
|
||||||
|
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||||
|
import { AddonBlogProvider } from './services/blog';
|
||||||
|
import { AddonBlogCourseOptionHandler } from './services/handlers/course-option';
|
||||||
|
import { AddonBlogIndexLinkHandler } from './services/handlers/index-link';
|
||||||
|
import { AddonBlogMainMenuHandler, AddonBlogMainMenuHandlerService } from './services/handlers/mainmenu';
|
||||||
|
import { AddonBlogTagAreaHandler } from './services/handlers/tag-area';
|
||||||
|
import { AddonBlogUserHandler } from './services/handlers/user';
|
||||||
|
|
||||||
|
export const ADDON_BLOG_SERVICES: Type<unknown>[] = [
|
||||||
|
AddonBlogProvider,
|
||||||
|
];
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: AddonBlogMainMenuHandlerService.PAGE_NAME,
|
||||||
|
loadChildren: () => import('@addons/blog/blog-lazy.module').then(m => m.AddonBlogLazyModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||||
|
CoreMainMenuRoutingModule.forChild({ children: routes }),
|
||||||
|
CoreCourseIndexRoutingModule.forChild({ children: routes }),
|
||||||
|
],
|
||||||
|
exports: [CoreMainMenuRoutingModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
multi: true,
|
||||||
|
deps: [],
|
||||||
|
useFactory: () => async () => {
|
||||||
|
CoreContentLinksDelegate.registerHandler(AddonBlogIndexLinkHandler.instance);
|
||||||
|
CoreMainMenuDelegate.registerHandler(AddonBlogMainMenuHandler.instance);
|
||||||
|
CoreUserDelegate.registerHandler(AddonBlogUserHandler.instance);
|
||||||
|
CoreTagAreaDelegate.registerHandler(AddonBlogTagAreaHandler.instance);
|
||||||
|
CoreCourseOptionsDelegate.registerHandler(AddonBlogCourseOptionHandler.instance);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonBlogModule {}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"blog": "Blog",
|
||||||
|
"blogentries": "Blog entries",
|
||||||
|
"errorloadentries": "Error loading blog entries.",
|
||||||
|
"linktooriginalentry": "Link to original blog entry",
|
||||||
|
"noentriesyet": "No visible entries here",
|
||||||
|
"publishtonoone": "Yourself (draft)",
|
||||||
|
"publishtosite": "Anyone on this site",
|
||||||
|
"publishtoworld": "Anyone in the world",
|
||||||
|
"showonlyyourentries": "Show only your entries",
|
||||||
|
"siteblogheading": "Site blog"
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>{{ title | translate }}</ion-title>
|
||||||
|
<ion-buttons slot="end"></ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refresh($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<ion-item *ngIf="showMyEntriesToggle">
|
||||||
|
<ion-label>{{ 'addon.blog.showonlyyourentries' | translate }}</ion-label>
|
||||||
|
<ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)"></ion-toggle>
|
||||||
|
</ion-item>
|
||||||
|
<core-empty-box *ngIf="entries && entries.length == 0" icon="far-newspaper"
|
||||||
|
[message]="'addon.blog.noentriesyet' | translate">
|
||||||
|
</core-empty-box>
|
||||||
|
<ng-container *ngFor="let entry of entries">
|
||||||
|
<ion-card *ngIf="!onlyMyEntries || entry.userid == currentUserId">
|
||||||
|
<ion-item class="ion-text-wrap">
|
||||||
|
<core-user-avatar [user]="entry.user" slot="start" [courseId]="entry.courseid"></core-user-avatar>
|
||||||
|
<ion-label>
|
||||||
|
<h2>
|
||||||
|
<core-format-text [text]="entry.subject" [contextLevel]="contextLevel"
|
||||||
|
[contextInstanceId]="contextInstanceId">
|
||||||
|
</core-format-text>
|
||||||
|
<ion-note class="ion-float-end ion-padding-left ion-text-end">
|
||||||
|
{{ 'addon.blog.' + entry.publishTranslated! | translate}}
|
||||||
|
</ion-note>
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<ion-note class="ion-float-end ion-padding-left ion-text-end">
|
||||||
|
{{entry.created | coreDateDayOrTime}}
|
||||||
|
</ion-note>
|
||||||
|
{{entry.user && entry.user!.fullname}}
|
||||||
|
</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-card-content>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>
|
||||||
|
<core-format-text [text]="entry.summary" [component]="this.component" [componentId]="entry.id"
|
||||||
|
[contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="tagsEnabled && entry.tags && entry.tags!.length > 0">
|
||||||
|
<ion-label>
|
||||||
|
<div slot="start">{{ 'core.tag.tags' | translate }}:</div>
|
||||||
|
<core-tag-list [tags]="entry.tags"></core-tag-list>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="commentsEnabled" detail>
|
||||||
|
<ion-label>
|
||||||
|
<core-comments [component]="this.component" [itemId]="entry.id" area="format_blog"
|
||||||
|
[instanceId]="entry.userid" contextLevel="user">
|
||||||
|
</core-comments>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<core-file *ngFor="let file of entry.attachmentfiles" [file]="file" [component]="this.component"
|
||||||
|
[componentId]="entry.id">
|
||||||
|
</core-file>
|
||||||
|
<ion-item *ngIf="entry.uniquehash" [href]="entry.uniquehash" core-link>
|
||||||
|
<ion-label>{{ 'addon.blog.linktooriginalentry' | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card-content>
|
||||||
|
<ion-row class="ion-text-center">
|
||||||
|
<ion-col *ngIf="entry.lastmodified > entry.created">
|
||||||
|
<ion-note>
|
||||||
|
<ion-icon name="fas-clock"></ion-icon> {{entry.lastmodified | coreTimeAgo}}
|
||||||
|
</ion-note>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-card>
|
||||||
|
</ng-container>
|
||||||
|
<core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError"></core-infinite-loading>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,288 @@
|
||||||
|
// (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 { ContextLevel } from '@/core/constants';
|
||||||
|
import { AddonBlog, AddonBlogFilter, AddonBlogPost, AddonBlogProvider } from '@addons/blog/services/blog';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { CoreComments } from '@features/comments/services/comments';
|
||||||
|
import { CoreTag } from '@features/tag/services/tag';
|
||||||
|
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||||
|
import { 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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the list of blog entries.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-blog-entries',
|
||||||
|
templateUrl: 'entries.html',
|
||||||
|
})
|
||||||
|
export class AddonBlogEntriesPage implements OnInit {
|
||||||
|
|
||||||
|
title = '';
|
||||||
|
|
||||||
|
protected filter: AddonBlogFilter = {};
|
||||||
|
protected pageLoaded = 0;
|
||||||
|
protected userPageLoaded = 0;
|
||||||
|
protected canLoadMoreEntries = false;
|
||||||
|
protected canLoadMoreUserEntries = true;
|
||||||
|
protected siteHomeId: number;
|
||||||
|
|
||||||
|
loaded = false;
|
||||||
|
canLoadMore = false;
|
||||||
|
loadMoreError = false;
|
||||||
|
entries: AddonBlogPostFormatted[] = [];
|
||||||
|
currentUserId: number;
|
||||||
|
showMyEntriesToggle = false;
|
||||||
|
onlyMyEntries = false;
|
||||||
|
component = AddonBlogProvider.COMPONENT;
|
||||||
|
commentsEnabled = false;
|
||||||
|
tagsEnabled = false;
|
||||||
|
contextLevel: ContextLevel = ContextLevel.SYSTEM;
|
||||||
|
contextInstanceId = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.currentUserId = CoreSites.getCurrentSiteUserId();
|
||||||
|
this.siteHomeId = CoreSites.getCurrentSiteHomeId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View loaded.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||||
|
const courseId = CoreNavigator.getRouteNumberParam('courseId');
|
||||||
|
const cmId = CoreNavigator.getRouteNumberParam('cmId');
|
||||||
|
const entryId = CoreNavigator.getRouteNumberParam('entryId');
|
||||||
|
const groupId = CoreNavigator.getRouteNumberParam('groupId');
|
||||||
|
const tagId = CoreNavigator.getRouteNumberParam('tagId');
|
||||||
|
|
||||||
|
if (!userId && !courseId && !cmId && !entryId && !groupId && !tagId) {
|
||||||
|
this.title = 'addon.blog.siteblogheading';
|
||||||
|
} else {
|
||||||
|
this.title = 'addon.blog.blogentries';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
this.filter.userid = userId;
|
||||||
|
}
|
||||||
|
this.showMyEntriesToggle = !userId;
|
||||||
|
|
||||||
|
if (courseId) {
|
||||||
|
this.filter.courseid = courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmId) {
|
||||||
|
this.filter.cmid = cmId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entryId) {
|
||||||
|
this.filter.entryid = entryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupId) {
|
||||||
|
this.filter.groupid = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagId) {
|
||||||
|
this.filter.tagid = tagId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the context level.
|
||||||
|
if (userId && !courseId && !cmId) {
|
||||||
|
this.contextLevel = ContextLevel.USER;
|
||||||
|
this.contextInstanceId = userId;
|
||||||
|
} else if (courseId && courseId != this.siteHomeId) {
|
||||||
|
this.contextLevel = ContextLevel.COURSE;
|
||||||
|
this.contextInstanceId = courseId;
|
||||||
|
} else {
|
||||||
|
this.contextLevel = ContextLevel.SYSTEM;
|
||||||
|
this.contextInstanceId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.commentsEnabled = !CoreComments.areCommentsDisabledInSite();
|
||||||
|
this.tagsEnabled = CoreTag.areTagsAvailableInSite();
|
||||||
|
|
||||||
|
await this.fetchEntries();
|
||||||
|
|
||||||
|
CoreUtils.ignoreErrors(AddonBlog.logView(this.filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch blog entries.
|
||||||
|
*
|
||||||
|
* @param refresh Empty events array first.
|
||||||
|
* @return Promise with the entries.
|
||||||
|
*/
|
||||||
|
protected async fetchEntries(refresh: boolean = false): Promise<void> {
|
||||||
|
this.loadMoreError = false;
|
||||||
|
|
||||||
|
if (refresh) {
|
||||||
|
this.pageLoaded = 0;
|
||||||
|
this.userPageLoaded = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadPage = this.onlyMyEntries ? this.userPageLoaded : this.pageLoaded;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await AddonBlog.getEntries(this.filter, loadPage);
|
||||||
|
|
||||||
|
const promises = result.entries.map(async (entry: AddonBlogPostFormatted) => {
|
||||||
|
switch (entry.publishstate) {
|
||||||
|
case 'draft':
|
||||||
|
entry.publishTranslated = 'publishtonoone';
|
||||||
|
break;
|
||||||
|
case 'site':
|
||||||
|
entry.publishTranslated = 'publishtosite';
|
||||||
|
break;
|
||||||
|
case 'public':
|
||||||
|
entry.publishTranslated = 'publishtoworld';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
entry.publishTranslated = 'privacy:unknown';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the context. This code was inspired by calendar events, Moodle doesn't do this for blogs.
|
||||||
|
if (entry.moduleid || entry.coursemoduleid) {
|
||||||
|
entry.contextLevel = ContextLevel.MODULE;
|
||||||
|
entry.contextInstanceId = entry.moduleid || entry.coursemoduleid;
|
||||||
|
} else if (entry.courseid) {
|
||||||
|
entry.contextLevel = ContextLevel.COURSE;
|
||||||
|
entry.contextInstanceId = entry.courseid;
|
||||||
|
} else {
|
||||||
|
entry.contextLevel = ContextLevel.USER;
|
||||||
|
entry.contextInstanceId = entry.userid;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.summary = CoreTextUtils.instance.replacePluginfileUrls(entry.summary, entry.summaryfiles || []);
|
||||||
|
|
||||||
|
return CoreUser.getProfile(entry.userid, entry.courseid, true).then((user) => {
|
||||||
|
entry.user = user;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (refresh) {
|
||||||
|
this.entries = result.entries;
|
||||||
|
} else {
|
||||||
|
this.entries = CoreUtils.uniqueArray(this.entries
|
||||||
|
.concat(result.entries), 'id')
|
||||||
|
.sort((a, b) => b.created - a.created);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.onlyMyEntries) {
|
||||||
|
const count = this.entries.filter((entry) => entry.userid == this.currentUserId).length;
|
||||||
|
this.canLoadMoreUserEntries = result.totalentries > count;
|
||||||
|
this.canLoadMore = this.canLoadMoreUserEntries;
|
||||||
|
this.userPageLoaded++;
|
||||||
|
} else {
|
||||||
|
this.canLoadMoreEntries = result.totalentries > this.entries.length;
|
||||||
|
this.canLoadMore = this.canLoadMoreEntries;
|
||||||
|
this.pageLoaded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'addon.blog.errorloadentries', true);
|
||||||
|
this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
|
||||||
|
} finally {
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle between showing only my entries or not.
|
||||||
|
*
|
||||||
|
* @param enabled If true, filter my entries. False otherwise.
|
||||||
|
*/
|
||||||
|
onlyMyEntriesToggleChanged(enabled: boolean): void {
|
||||||
|
this.canLoadMore = enabled ? this.canLoadMoreUserEntries : this.canLoadMoreEntries;
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
delete this.filter.userid;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = this.entries.filter((entry) => entry.userid == this.currentUserId).length;
|
||||||
|
this.userPageLoaded = Math.floor(count / AddonBlogProvider.ENTRIES_PER_PAGE);
|
||||||
|
this.filter.userid = this.currentUserId;
|
||||||
|
|
||||||
|
if (count == 0 && this.canLoadMoreUserEntries) {
|
||||||
|
// First time but no entry loaded. Try to load some.
|
||||||
|
this.loadMore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to load more entries.
|
||||||
|
*
|
||||||
|
* @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
|
||||||
|
* @return Resolved when done.
|
||||||
|
*/
|
||||||
|
loadMore(infiniteComplete?: () => void): Promise<void> {
|
||||||
|
return this.fetchEntries().finally(() => {
|
||||||
|
infiniteComplete && infiniteComplete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh blog entries on PTR.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher instance.
|
||||||
|
*/
|
||||||
|
refresh(refresher?: CustomEvent<IonRefresher>): void {
|
||||||
|
const promises = this.entries.map((entry) =>
|
||||||
|
CoreComments.invalidateCommentsData('user', entry.userid, this.component, entry.id, 'format_blog'));
|
||||||
|
|
||||||
|
promises.push(AddonBlog.invalidateEntries(this.filter));
|
||||||
|
|
||||||
|
if (this.showMyEntriesToggle) {
|
||||||
|
this.filter['userid'] = this.currentUserId;
|
||||||
|
promises.push(AddonBlog.invalidateEntries(this.filter));
|
||||||
|
|
||||||
|
if (!this.onlyMyEntries) {
|
||||||
|
delete this.filter['userid'];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreUtils.allPromises(promises).finally(() => {
|
||||||
|
this.fetchEntries(true).finally(() => {
|
||||||
|
if (refresher) {
|
||||||
|
refresher?.detail.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blog post with some calculated data.
|
||||||
|
*/
|
||||||
|
type AddonBlogPostFormatted = AddonBlogPost & {
|
||||||
|
publishTranslated?: string; // Calculated in the app. Key of the string to translate the publish state of the post.
|
||||||
|
user?: CoreUserProfile; // Calculated in the app. Data of the user that wrote the post.
|
||||||
|
contextLevel?: string; // Calculated in the app. The context level of the entry.
|
||||||
|
contextInstanceId?: number; // Calculated in the app. The context instance id.
|
||||||
|
};
|
|
@ -0,0 +1,204 @@
|
||||||
|
// (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 { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||||
|
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
|
||||||
|
import { CoreTagItem } from '@features/tag/services/tag';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
const ROOT_CACHE_KEY = 'addonBlog:';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to handle blog entries.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonBlogProvider {
|
||||||
|
|
||||||
|
static readonly ENTRIES_PER_PAGE = 10;
|
||||||
|
static readonly COMPONENT = 'blog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the blog 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.wsAvailable('core_blog_get_entries') &&site.canUseAdvancedFeature('enableblogs');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cache key for the blog entries.
|
||||||
|
*
|
||||||
|
* @param filter Filter to apply on search.
|
||||||
|
* @return Cache key.
|
||||||
|
*/
|
||||||
|
getEntriesCacheKey(filter: AddonBlogFilter = {}): string {
|
||||||
|
return ROOT_CACHE_KEY + CoreUtils.sortAndStringify(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get blog entries.
|
||||||
|
*
|
||||||
|
* @param filter Filter to apply on search.
|
||||||
|
* @param page Page of the blog entries to fetch.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise to be resolved when the entries are retrieved.
|
||||||
|
*/
|
||||||
|
async getEntries(filter: AddonBlogFilter = {}, page: number = 0, siteId?: string): Promise<CoreBlogGetEntriesWSResponse> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
const data: CoreBlogGetEntriesWSParams = {
|
||||||
|
filters: CoreUtils.objectToArrayOfObjects(filter, 'name', 'value'),
|
||||||
|
page: page,
|
||||||
|
perpage: AddonBlogProvider.ENTRIES_PER_PAGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const preSets: CoreSiteWSPreSets = {
|
||||||
|
cacheKey: this.getEntriesCacheKey(filter),
|
||||||
|
updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('core_blog_get_entries', data, preSets);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate blog entries WS call.
|
||||||
|
*
|
||||||
|
* @param filter Filter to apply on search
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when data is invalidated.
|
||||||
|
*/
|
||||||
|
async invalidateEntries(filter: AddonBlogFilter = {}, siteId?: string): Promise<void> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
await site.invalidateWsCacheForKey(this.getEntriesCacheKey(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger the blog_entries_viewed event.
|
||||||
|
*
|
||||||
|
* @param filter Filter to apply on search.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise to be resolved when done.
|
||||||
|
*/
|
||||||
|
async logView(filter: AddonBlogFilter = {}, siteId?: string): Promise<CoreStatusWithWarningsWSResponse> {
|
||||||
|
CorePushNotifications.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId);
|
||||||
|
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
const data: AddonBlogViewEntriesWSParams = {
|
||||||
|
filters: CoreUtils.objectToArrayOfObjects(filter, 'name', 'value'),
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.write('core_blog_view_entries', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonBlog = makeSingleton(AddonBlogProvider);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of core_blog_get_entries WS.
|
||||||
|
*/
|
||||||
|
type CoreBlogGetEntriesWSParams = {
|
||||||
|
filters?: { // Parameters to filter blog listings.
|
||||||
|
name: string; // The expected keys (value format) are:
|
||||||
|
// tag PARAM_NOTAGS blog tag
|
||||||
|
// tagid PARAM_INT blog tag id
|
||||||
|
// userid PARAM_INT blog author (userid)
|
||||||
|
// cmid PARAM_INT course module id
|
||||||
|
// entryid PARAM_INT entry id
|
||||||
|
// groupid PARAM_INT group id
|
||||||
|
// courseid PARAM_INT course id
|
||||||
|
// search PARAM_RAW search term.
|
||||||
|
value: string; // The value of the filter.
|
||||||
|
}[];
|
||||||
|
page?: number; // The blog page to return.
|
||||||
|
perpage?: number; // The number of posts to return per page.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by core_blog_get_entries WS.
|
||||||
|
*/
|
||||||
|
export type CoreBlogGetEntriesWSResponse = {
|
||||||
|
entries: AddonBlogPost[];
|
||||||
|
totalentries: number; // The total number of entries found.
|
||||||
|
warnings?: CoreWSExternalWarning[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by blog's post_exporter.
|
||||||
|
*/
|
||||||
|
export type AddonBlogPost = {
|
||||||
|
id: number; // Post/entry id.
|
||||||
|
module: string; // Where it was published the post (blog, blog_external...).
|
||||||
|
userid: number; // Post author.
|
||||||
|
courseid: number; // Course where the post was created.
|
||||||
|
groupid: number; // Group post was created for.
|
||||||
|
moduleid: number; // Module id where the post was created (not used anymore).
|
||||||
|
coursemoduleid: number; // Course module id where the post was created.
|
||||||
|
subject: string; // Post subject.
|
||||||
|
summary: string; // Post summary.
|
||||||
|
summaryformat?: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
||||||
|
content: string; // Post content.
|
||||||
|
uniquehash: string; // Post unique hash.
|
||||||
|
rating: number; // Post rating.
|
||||||
|
format: number; // Post content format.
|
||||||
|
attachment: string; // Post atachment.
|
||||||
|
publishstate: string; // Post publish state.
|
||||||
|
lastmodified: number; // When it was last modified.
|
||||||
|
created: number; // When it was created.
|
||||||
|
usermodified: number; // User that updated the post.
|
||||||
|
summaryfiles: CoreWSExternalFile[]; // Summaryfiles.
|
||||||
|
attachmentfiles?: CoreWSExternalFile[]; // Attachmentfiles.
|
||||||
|
tags?: CoreTagItem[]; // @since 3.7. Tags.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of core_blog_view_entries WS.
|
||||||
|
*/
|
||||||
|
type AddonBlogViewEntriesWSParams = {
|
||||||
|
filters?: { // Parameters used in the filter of view_entries.
|
||||||
|
name: string; // The expected keys (value format) are:
|
||||||
|
// tag PARAM_NOTAGS blog tag
|
||||||
|
// tagid PARAM_INT blog tag id
|
||||||
|
// userid PARAM_INT blog author (userid)
|
||||||
|
// cmid PARAM_INT course module id
|
||||||
|
// entryid PARAM_INT entry id
|
||||||
|
// groupid PARAM_INT group id
|
||||||
|
// courseid PARAM_INT course id
|
||||||
|
// search PARAM_RAW search term.
|
||||||
|
value: string; // The value of the filter.
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AddonBlogFilter = {
|
||||||
|
tag?: string; // Blog tag
|
||||||
|
tagid?: number; // Blog tag id
|
||||||
|
userid?: number; // Blog author (userid)
|
||||||
|
cmid?: number; // Course module id
|
||||||
|
entryid?: number; // Entry id
|
||||||
|
groupid?: number; // Group id
|
||||||
|
courseid?: number; // Course id
|
||||||
|
search?: string; // Search term.
|
||||||
|
};
|
|
@ -0,0 +1,109 @@
|
||||||
|
// (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 { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||||
|
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 { CoreFilepool } from '@services/filepool';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreWSExternalFile } from '@services/ws';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonBlog } from '../blog';
|
||||||
|
import { AddonBlogMainMenuHandlerService } from './mainmenu';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Course nav handler.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonBlogCourseOptionHandlerService implements CoreCourseOptionsHandler {
|
||||||
|
|
||||||
|
name = 'AddonBlog';
|
||||||
|
priority = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
invalidateEnabledForCourse(courseId: number): Promise<void> {
|
||||||
|
return CoreCourse.invalidateCourseBlocks(courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
isEnabled(): Promise<boolean> {
|
||||||
|
return AddonBlog.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabledForCourse(
|
||||||
|
courseId: number,
|
||||||
|
accessData: CoreCourseAccess,
|
||||||
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const enabled = await CoreCourseHelper.hasABlockNamed(courseId, 'blog_menu');
|
||||||
|
|
||||||
|
if (enabled && navOptions && typeof navOptions.blogs != 'undefined') {
|
||||||
|
return navOptions.blogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getDisplayData(): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
|
||||||
|
return {
|
||||||
|
title: 'addon.blog.blog',
|
||||||
|
class: 'addon-blog-handler',
|
||||||
|
page: AddonBlogMainMenuHandlerService.PAGE_NAME,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async prefetch(course: CoreEnrolledCourseDataWithExtraInfoAndOptions): Promise<void> {
|
||||||
|
const siteId = CoreSites.getCurrentSiteId();
|
||||||
|
|
||||||
|
const result = await AddonBlog.getEntries({ courseid: course.id });
|
||||||
|
|
||||||
|
await Promise.all(result.entries.map(async (entry) => {
|
||||||
|
let files: CoreWSExternalFile[] = [];
|
||||||
|
|
||||||
|
if (entry.attachmentfiles && entry.attachmentfiles.length) {
|
||||||
|
files = entry.attachmentfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.summaryfiles && entry.summaryfiles.length) {
|
||||||
|
files = files.concat(entry.summaryfiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.length > 0) {
|
||||||
|
await CoreFilepool.addFilesToQueue(siteId, files, entry.module, entry.id);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonBlogCourseOptionHandler = makeSingleton(AddonBlogCourseOptionHandlerService);
|
|
@ -0,0 +1,61 @@
|
||||||
|
// (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 { Params } from '@angular/router';
|
||||||
|
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||||
|
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonBlog } from '../blog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to treat links to blog page.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonBlogIndexLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||||
|
|
||||||
|
name = 'AddonBlogIndexLinkHandler';
|
||||||
|
featureName = 'CoreUserDelegate_AddonBlog:blogs';
|
||||||
|
pattern = /\/blog\/index\.php/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
|
||||||
|
const pageParams: Params = {};
|
||||||
|
|
||||||
|
params.userid ? pageParams['userId'] = parseInt(params.userid, 10) : null;
|
||||||
|
params.modid ? pageParams['cmId'] = parseInt(params.modid, 10) : null;
|
||||||
|
params.courseid ? pageParams['courseId'] = parseInt(params.courseid, 10) : null;
|
||||||
|
params.entryid ? pageParams['entryId'] = parseInt(params.entryid, 10) : null;
|
||||||
|
params.groupid ? pageParams['groupId'] = parseInt(params.groupid, 10) : null;
|
||||||
|
params.tagid ? pageParams['tagId'] = parseInt(params.tagid, 10) : null;
|
||||||
|
|
||||||
|
return [{
|
||||||
|
action: (siteId: string): void => {
|
||||||
|
CoreNavigator.navigateToSitePath('/blog', { params: pageParams, siteId });
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
isEnabled(siteId: string): Promise<boolean> {
|
||||||
|
return AddonBlog.isPluginEnabled(siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonBlogIndexLinkHandler = makeSingleton(AddonBlogIndexLinkHandlerService);
|
|
@ -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 { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonBlog } from '../blog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to inject an option into main menu.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonBlogMainMenuHandlerService implements CoreMainMenuHandler {
|
||||||
|
|
||||||
|
static readonly PAGE_NAME = 'blog';
|
||||||
|
|
||||||
|
name = 'AddonBlog';
|
||||||
|
priority = 450;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return AddonBlog.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getDisplayData(): CoreMainMenuHandlerData {
|
||||||
|
return {
|
||||||
|
icon: 'far-newspaper',
|
||||||
|
title: 'addon.blog.siteblogheading',
|
||||||
|
page: AddonBlogMainMenuHandlerService.PAGE_NAME,
|
||||||
|
class: 'addon-blog-handler',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonBlogMainMenuHandler = makeSingleton(AddonBlogMainMenuHandlerService);
|
|
@ -0,0 +1,53 @@
|
||||||
|
// (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, Type } from '@angular/core';
|
||||||
|
import { CoreTagFeedComponent } from '@features/tag/components/feed/feed';
|
||||||
|
import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate';
|
||||||
|
import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonBlog } from '../blog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to support tags.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonBlogTagAreaHandlerService implements CoreTagAreaHandler {
|
||||||
|
|
||||||
|
name = 'AddonBlogTagAreaHandler';
|
||||||
|
type = 'core/post';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
isEnabled(): Promise<boolean> {
|
||||||
|
return AddonBlog.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async parseContent(content: string): Promise<CoreTagFeedElement[]> {
|
||||||
|
return CoreTagHelper.parseFeedContent(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getComponent(): Type<unknown> | Promise<Type<unknown>> {
|
||||||
|
return CoreTagFeedComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonBlogTagAreaHandler = makeSingleton(AddonBlogTagAreaHandlerService);
|
|
@ -0,0 +1,64 @@
|
||||||
|
// (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 { CoreUserProfileHandler, CoreUserProfileHandlerData, CoreUserDelegateService } from '@features/user/services/user-delegate';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonBlog } from '../blog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile item handler.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonBlogUserHandlerService implements CoreUserProfileHandler {
|
||||||
|
|
||||||
|
name = 'AddonBlog:blogs';
|
||||||
|
priority = 300;
|
||||||
|
type = CoreUserDelegateService.TYPE_NEW_PAGE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
isEnabled(): Promise<boolean> {
|
||||||
|
return AddonBlog.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabledForUser(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getDisplayData(): CoreUserProfileHandlerData {
|
||||||
|
return {
|
||||||
|
icon: 'far-newspaper',
|
||||||
|
title: 'addon.blog.blogentries',
|
||||||
|
class: 'addon-blog-handler',
|
||||||
|
action: (event, user, courseId): void => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
CoreNavigator.navigateToSitePath('/blog', {
|
||||||
|
params: { courseId, userId: user.id },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonBlogUserHandler = makeSingleton(AddonBlogUserHandlerService);
|
|
@ -25,7 +25,6 @@ export class AddonCalendarMainMenuHandlerService implements CoreMainMenuHandler
|
||||||
|
|
||||||
static readonly PAGE_NAME = 'calendar';
|
static readonly PAGE_NAME = 'calendar';
|
||||||
|
|
||||||
|
|
||||||
name = 'AddonCalendar';
|
name = 'AddonCalendar';
|
||||||
priority = 900;
|
priority = 900;
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
[priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'">
|
[priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="blog"
|
<core-context-menu-item *ngIf="blog"
|
||||||
[priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'fa-newspaper-o'" (action)="gotoBlog()">
|
[priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'far-newspaper'" (action)="gotoBlog()">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="discussions.loaded && !(hasOffline || hasOfflineRatings) && isOnline"
|
<core-context-menu-item *ngIf="discussions.loaded && !(hasOffline || hasOfflineRatings) && isOnline"
|
||||||
[priority]="700" [content]="'addon.mod_forum.refreshdiscussions' | translate" [iconAction]="refreshIcon" [closeOnClick]="false"
|
[priority]="700" [content]="'addon.mod_forum.refreshdiscussions' | translate" [iconAction]="refreshIcon" [closeOnClick]="false"
|
||||||
|
|
|
@ -77,6 +77,10 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
|
||||||
|
|
||||||
// Calculate distance from edge.
|
// Calculate distance from edge.
|
||||||
const content = this.element.nativeElement.closest('ion-content') as IonContent;
|
const content = this.element.nativeElement.closest('ion-content') as IonContent;
|
||||||
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const scrollElement = await content.getScrollElement();
|
const scrollElement = await content.getScrollElement();
|
||||||
|
|
||||||
const infiniteHeight = this.element.nativeElement.getBoundingClientRect().height;
|
const infiniteHeight = this.element.nativeElement.getBoundingClientRect().height;
|
||||||
|
|
|
@ -13,9 +13,13 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
|
import { AddonBlog } from '@addons/blog/services/blog';
|
||||||
|
import { AddonBlogMainMenuHandlerService } from '@addons/blog/services/handlers/mainmenu';
|
||||||
import { OnInit, OnDestroy, Input, Output, EventEmitter, Component, Optional, Inject } from '@angular/core';
|
import { OnInit, OnDestroy, Input, Output, EventEmitter, Component, Optional, Inject } from '@angular/core';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
|
||||||
|
@ -90,7 +94,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
this.componentId = this.module?.id;
|
this.componentId = this.module?.id;
|
||||||
this.externalUrl = this.module?.url;
|
this.externalUrl = this.module?.url;
|
||||||
this.courseId = this.courseId || this.module?.course;
|
this.courseId = this.courseId || this.module?.course;
|
||||||
// @todo this.blog = await this.blogProvider.isPluginEnabled();
|
this.blog = await AddonBlog.isPluginEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -223,8 +227,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
* Go to blog posts.
|
* Go to blog posts.
|
||||||
*/
|
*/
|
||||||
async gotoBlog(): Promise<void> {
|
async gotoBlog(): Promise<void> {
|
||||||
// const params: Params = { cmId: this.module?.id };
|
const params: Params = { cmId: this.module?.id };
|
||||||
// @todo return CoreNavigator.navigateToSitePath('AddonBlogEntriesPage', { params });
|
|
||||||
|
CoreNavigator.navigateToSitePath(AddonBlogMainMenuHandlerService.PAGE_NAME, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue