Merge pull request #2128 from dpalou/MOBILE-2491

Mobile 2491
main
Juan Leyva 2019-10-16 11:01:32 +02:00 committed by GitHub
commit eb19e61a2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
300 changed files with 3315 additions and 740 deletions

View File

@ -24,9 +24,7 @@
</ion-item-divider>
<ion-item text-wrap>
<h2>{{ 'core.name' | translate}}</h2>
<p>
<core-format-text clean="true" [text]="user.fullname"></core-format-text>
</p>
<p>{{ user.fullname }}</p>
</ion-item>
</ion-item-group>
@ -36,14 +34,12 @@
</ion-item-divider>
<ion-item text-wrap *ngIf="badge.issuername">
<h2>{{ 'addon.badges.issuername' | translate}}</h2>
<p>
<core-format-text clean="true" [text]="badge.issuername"></core-format-text>
</p>
<p>{{ badge.issuername }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.issuercontact">
<h2>{{ 'addon.badges.contact' | translate}}</h2>
<p><a href="mailto:{{badge.issuercontact}}" core-link auto-login="no">
<core-format-text [text]="badge.issuercontact"></core-format-text>
{{ badge.issuercontact }}
</a></p>
</ion-item>
</ion-item-group>
@ -66,9 +62,7 @@
</ion-item>
<ion-item text-wrap *ngIf="badge.description">
<h2>{{ 'core.description' | translate}}</h2>
<p>
<core-format-text clean="true" [text]="badge.description"></core-format-text>
</p>
<p>{{ badge.description }}</p>
</ion-item>
<ion-item text-wrap *ngIf="badge.imageauthorname">
<h2>{{ 'addon.badges.imageauthorname' | translate}}</h2>
@ -77,23 +71,23 @@
<ion-item text-wrap *ngIf="badge.imageauthoremail">
<h2>{{ 'addon.badges.imageauthoremail' | translate}}</h2>
<p><a href="mailto:{{badge.imageauthoremail}}" core-link auto-login="no">
<core-format-text [text]="badge.imageauthoremail"></core-format-text>
{{ badge.imageauthoremail }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.imageauthorurl">
<h2>{{ 'addon.badges.imageauthorurl' | translate}}</h2>
<p><a [href]="badge.imageauthorurl" core-link auto-login="no">
<core-format-text [text]="badge.imageauthorurl"></core-format-text>
{{ badge.imageauthorurl }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.imagecaption">
<h2>{{ 'addon.badges.imagecaption' | translate}}</h2>
<p><core-format-text [text]="badge.imagecaption"></core-format-text></p>
<p>{{ badge.imagecaption }}</p>
</ion-item>
<ion-item text-wrap *ngIf="course.fullname">
<h2>{{ 'core.course' | translate}}</h2>
<p>
<core-format-text [text]="course.fullname"></core-format-text>
<core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="courseId"></core-format-text>
</p>
</ion-item>
<!-- Criteria (not yet avalaible) -->
@ -131,13 +125,13 @@
<ion-item text-wrap *ngIf="badge.endorsement.issueremail">
<h2>{{ 'addon.badges.issueremail' | translate}}</h2>
<p><a href="mailto:{{badge.endorsement.issueremail}}" core-link auto-login="no">
<core-format-text [text]="badge.endorsement.issueremail"></core-format-text>
{{ badge.endorsement.issueremail }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.endorsement.issuerurl">
<h2>{{ 'addon.badges.issuerurl' | translate}}</h2>
<p><a [href]="badge.endorsement.issuerurl" core-link auto-login="no">
<core-format-text [text]="badge.endorsement.issuerurl"></core-format-text>
{{ badge.endorsement.issuerurl }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.endorsement.dateissued">
@ -147,14 +141,12 @@
<ion-item text-wrap *ngIf="badge.endorsement.claimid">
<h2>{{ 'addon.badges.claimid' | translate}}</h2>
<p><a [href]="badge.endorsement.claimid" core-link auto-login="no">
<core-format-text [text]="badge.endorsement.claimid"></core-format-text>
{{ badge.endorsement.claimid }}
</a></p>
</ion-item>
<ion-item text-wrap *ngIf="badge.endorsement.claimcomment">
<h2>{{ 'addon.badges.claimcomment' | translate}}</h2>
<p>
<core-format-text [text]="badge.endorsement.claimcomment"></core-format-text>
</p>
<p>{{ badge.endorsement.claimcomment }}</p>
</ion-item>
</ion-item-group>
@ -164,7 +156,7 @@
<h2>{{ 'addon.badges.relatedbages' | translate}}</h2>
</ion-item-divider>
<ion-item text-wrap *ngFor="let relatedBadge of badge.relatedbadges">
<h2><core-format-text [text]="relatedBadge.name"></core-format-text></h2>
<h2><{{ relatedBadge.name }}</h2>
</ion-item>
<ion-item text-wrap *ngIf="badge.relatedbadges.length == 0">
<h2>{{ 'addon.badges.norelated' | translate}}</h2>
@ -177,7 +169,7 @@
<h2>{{ 'addon.badges.alignment' | translate}}</h2>
</ion-item-divider>
<a ion-item text-wrap *ngFor="let alignment of badge.alignment" [href]="alignment.targeturl" core-link auto-login="no">
<h2><core-format-text [text]="alignment.targetname"></core-format-text></h2>
<h2>{{ alignment.targetname }}</h2>
</a>
<ion-item text-wrap *ngIf="badge.alignment.length == 0">
<h2>{{ 'addon.badges.noalignment' | translate}}</h2>

View File

@ -17,7 +17,7 @@
<ion-avatar item-start>
<img [src]="badge.badgeurl" [alt]="badge.name" item-start core-external-content>
</ion-avatar>
<h2><core-format-text [text]="badge.name"></core-format-text></h2>
<h2>{{ badge.name }}</h2>
<p>{{ badge.dateissued * 1000 | coreFormatDate :'strftimedatetimeshort' }}</p>
<ion-badge item-end color="danger" *ngIf="badge.dateexpire && currentTime >= badge.dateexpire">
{{ 'addon.badges.expired' | translate }}

View File

@ -4,6 +4,6 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<a ion-item text-wrap *ngFor="let entry of entries" class="item-media" detail-none [navPush]="'CoreCourseListModTypePage'" [navParams]="{title: entry.name, courseId: instanceId, modName: entry.modName}">
<img item-start [src]="entry.icon" alt="" role="presentation" class="core-module-icon">
<core-format-text [text]="entry.name"></core-format-text>
{{ entry.name }}
</a>
</core-loading>

View File

@ -7,8 +7,8 @@
<ion-card>
<a ion-item text-wrap detail-none class="core-course-module-handler item-media" (click)="action($event, item)" [title]="item.name">
<img item-start [src]="item.iconUrl" alt="" role="presentation" *ngIf="item.iconUrl" class="core-module-icon">
<h2><core-format-text [text]="item.name"></core-format-text></h2>
<p><core-format-text [text]="item.coursename"></core-format-text></p>
<h2><core-format-text [text]="item.name" contextLevel="module" [contextInstanceId]="item.cmid" [courseId]="item.courseid"></core-format-text></h2>
<p><core-format-text [text]="item.coursename" contextLevel="course" [contextInstanceId]="item.courseid"></core-format-text></p>
</a>
</ion-card>
</div>

View File

@ -4,7 +4,7 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<ng-container *ngIf="mainMenuBlock">
<ion-item text-wrap *ngIf="mainMenuBlock.summary">
<core-format-text [text]="mainMenuBlock.summary"></core-format-text>
<core-format-text [text]="mainMenuBlock.summary" [component]="component" [componentId]="siteHomeId" contextLevel="course" [contextInstanceId]="siteHomeId"></core-format-text>
</ion-item>
<core-course-module *ngFor="let module of mainMenuBlock.modules" [module]="module" [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled" [section]="mainMenuBlock"></core-course-module>

View File

@ -30,6 +30,7 @@ import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component
export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent implements OnInit {
@Input() downloadEnabled: boolean;
component = 'AddonBlockSiteMainMenu';
mainMenuBlock: any;
siteHomeId: number;

View File

@ -5,9 +5,9 @@
<ng-container *ngFor="let event of dayEvents.events">
<a ion-item text-wrap detail-none class="core-course-module-handler item-media" (click)="action($event, event.url)" [title]="event.name">
<img item-start [src]="event.iconUrl" alt="" role="presentation" *ngIf="event.iconUrl" class="core-module-icon">
<h2><core-format-text [text]="event.name"></core-format-text></h2>
<h2><core-format-text [text]="event.name" contextLevel="module" [contextInstanceId]="event.id" [courseId]="event.course.id"></core-format-text></h2>
<p *ngIf="showCourse">
<core-format-text [text]="event.course.fullnamedisplay"></core-format-text>
<core-format-text [text]="event.course.fullnamedisplay" contextLevel="course" [contextInstanceId]="event.course.id"></core-format-text>
</p>
<button ion-button clear class="hidden-tablet" (click)="action($event, event.action.url)" [title]="event.action.name" [disabled]="!event.action.actionable" *ngIf="event.action">

View File

@ -13,7 +13,7 @@
<ion-item text-wrap>
<ion-avatar core-user-avatar [user]="entry.user" item-start [courseId]="entry.courseid"></ion-avatar>
<h2>
<core-format-text [text]="entry.subject"></core-format-text>
<core-format-text [text]="entry.subject" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"></core-format-text>
<ion-note float-end padding-left text-end>
{{ 'addon.blog.' + entry.publishTranslated | translate}}
</ion-note>
@ -27,7 +27,7 @@
</ion-item>
<ion-card-content>
<ion-item>
<core-format-text [text]="entry.summary" [component]="this.component" [componentId]="entry.id"></core-format-text>
<core-format-text [text]="entry.summary" [component]="this.component" [componentId]="entry.id" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"></core-format-text>
</ion-item>
<ion-item text-wrap *ngIf="tagsEnabled && entry.tags && entry.tags.length > 0">
<div item-start>{{ 'core.tag.tags' | translate }}:</div>

View File

@ -42,6 +42,7 @@ export class AddonBlogEntriesComponent implements OnInit {
protected userPageLoaded = 0;
protected canLoadMoreEntries = false;
protected canLoadMoreUserEntries = true;
protected siteHomeId: number;
@ViewChild(Content) content: Content;
@ -55,11 +56,14 @@ export class AddonBlogEntriesComponent implements OnInit {
component = AddonBlogProvider.COMPONENT;
commentsEnabled: boolean;
tagsEnabled: boolean;
contextLevel: string;
contextInstanceId: number;
constructor(protected blogProvider: AddonBlogProvider, protected domUtils: CoreDomUtilsProvider,
protected userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider, protected utils: CoreUtilsProvider,
protected commentsProvider: CoreCommentsProvider, private tagProvider: CoreTagProvider) {
this.currentUserId = sitesProvider.getCurrentSiteUserId();
this.siteHomeId = sitesProvider.getCurrentSiteHomeId();
}
/**
@ -91,6 +95,18 @@ export class AddonBlogEntriesComponent implements OnInit {
this.filter['tagid'] = this.tagId;
}
// Calculate the context level.
if (this.userId && !this.courseId && !this.cmId) {
this.contextLevel = 'user';
this.contextInstanceId = this.userId;
} else if (this.courseId && this.courseId != this.siteHomeId) {
this.contextLevel = 'course';
this.contextInstanceId = this.courseId;
} else {
this.contextLevel = 'system';
this.contextInstanceId = 0;
}
this.commentsEnabled = !this.commentsProvider.areCommentsDisabledInSite();
this.tagsEnabled = this.tagProvider.areTagsAvailableInSite();
@ -134,6 +150,18 @@ export class AddonBlogEntriesComponent implements OnInit {
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 = 'module';
entry.contextInstanceId = entry.moduleid || entry.coursemoduleid;
} else if (entry.courseid) {
entry.contextLevel = 'course';
entry.contextInstanceId = entry.courseid;
} else {
entry.contextLevel = 'user';
entry.contextInstanceId = entry.userid;
}
return this.userProvider.getProfile(entry.userid, entry.courseid, true).then((user) => {
entry.user = user;
}).catch(() => {
@ -245,4 +273,6 @@ export class AddonBlogEntriesComponent implements OnInit {
type AddonBlogPostFormatted = AddonBlogPost & {
publishTranslated?: string; // Calculated in the app. Key of the string to translate the publish state of the post.
user?: any; // 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.
};

View File

@ -7,8 +7,8 @@
<a ion-item text-wrap [title]="event.name" (click)="eventClicked(event)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon">
<core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<h2><core-format-text [text]="event.name"></core-format-text></h2>
<p><core-format-text [text]="event.formattedtime"></core-format-text></p>
<h2><core-format-text [text]="event.name" [contextLevel]="event.contextLevel" [contextInstanceId]="event.contextInstanceId"></core-format-text></h2>
<p [innerHTML]="event.formattedtime"></p>
<ion-note *ngIf="event.offline && !event.deleted" item-end>
<ion-icon name="time"></ion-icon>
<span text-wrap>{{ 'core.notsent' | translate }}</span>

View File

@ -51,8 +51,8 @@
<ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.item-dimmed]="event.ispast" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon">
<core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<h2><core-format-text [text]="event.name"></core-format-text></h2>
<p><core-format-text [text]="event.formattedtime"></core-format-text></p>
<h2><core-format-text [text]="event.name" [contextLevel]="event.contextLevel" [contextInstanceId]="event.contextInstanceId"></core-format-text></h2>
<p [innerHTML]="event.formattedtime"></p>
<ion-note *ngIf="event.offline && !event.deleted" item-end>
<ion-icon name="time"></ion-icon>
<span text-wrap>{{ 'core.notsent' | translate }}</span>

View File

@ -21,7 +21,6 @@ import { CoreGroupsProvider } from '@providers/groups';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncProvider } from '@providers/sync';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
@ -32,6 +31,7 @@ import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
import { AddonCalendarHelperProvider } from '../../providers/helper';
import { AddonCalendarSyncProvider } from '../../providers/calendar-sync';
import { CoreSite } from '@classes/site';
import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
/**
* Page that displays a form to create/edit an event.
@ -81,7 +81,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
private navCtrl: NavController,
private translate: TranslateService,
private domUtils: CoreDomUtilsProvider,
private textUtils: CoreTextUtilsProvider,
private timeUtils: CoreTimeUtilsProvider,
private eventsProvider: CoreEventsProvider,
private groupsProvider: CoreGroupsProvider,
@ -94,6 +93,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
private calendarSync: AddonCalendarSyncProvider,
private fb: FormBuilder,
private syncProvider: CoreSyncProvider,
private filterHelper: CoreFilterHelperProvider,
@Optional() private svComponent: CoreSplitViewComponent) {
this.eventId = navParams.get('eventId');
@ -244,7 +244,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
// Format the name of the courses.
const subPromises = [];
courses.forEach((course) => {
subPromises.push(this.textUtils.formatText(course.fullname).then((text) => {
subPromises.push(this.filterHelper.getFiltersAndFormatText(course.fullname, 'course', course.id)
.then((text) => {
course.fullname = text;
}).catch(() => {
// Ignore errors.

View File

@ -3,7 +3,7 @@
<ion-title>
<img *ngIf="event && event.moduleIcon" src="{{event.moduleIcon}}" alt="" role="presentation" class="core-module-icon">
<core-icon *ngIf="event && event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<core-format-text *ngIf="event" [text]="event.name"></core-format-text>
<core-format-text *ngIf="event" [text]="event.name" [contextLevel]="event.contextLevel" [contextInstanceId]="event.contextInstanceId"></core-format-text>
</ion-title>
<ion-buttons end>
<!-- The context menu will be added in here. -->
@ -34,14 +34,14 @@
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start alt="" role="presentation" class="core-module-icon">
<core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<h2>{{ 'addon.calendar.eventname' | translate }}</h2>
<p><core-format-text [text]="event.name"></core-format-text></p>
<p><core-format-text [text]="event.name" [contextLevel]="event.contextLevel" [contextInstanceId]="event.contextInstanceId"></core-format-text></p>
<ion-note item-end *ngIf="event.deleted">
<ion-icon name="trash"></ion-icon> {{ 'core.deletedoffline' | translate }}
</ion-note>
</ion-item>
<ion-item>
<h2>{{ 'addon.calendar.when' | translate }}</h2>
<p><core-format-text [text]="event.formattedtime"></core-format-text></p>
<p [innerHTML]="event.formattedtime"></p>
<ion-note item-end *ngIf="!isSplitViewOn && event.deleted">
<ion-icon name="trash"></ion-icon> {{ 'core.deletedoffline' | translate }}
</ion-note>
@ -52,7 +52,7 @@
</ion-item>
<a ion-item text-wrap *ngIf="courseName" [href]="courseUrl" core-link capture="true">
<h2>{{ 'core.course' | translate}}</h2>
<p><core-format-text [text]="courseName"></core-format-text></p>
<p><core-format-text [text]="courseName" contextLevel="course" [contextInstanceId]="courseId"></core-format-text></p>
</a>
<ion-item text-wrap *ngIf="groupName">
<h2>{{ 'core.group' | translate}}</h2>
@ -60,19 +60,19 @@
</ion-item>
<a ion-item text-wrap *ngIf="categoryPath">
<h2>{{ 'core.category' | translate}}</h2>
<p><core-format-text [text]="categoryPath"></core-format-text></p>
<p><core-format-text [text]="categoryPath" contextLevel="category" [contextInstanceId]="event.category.id"></core-format-text></p>
</a>
<ion-item text-wrap *ngIf="event.description">
<h2>{{ 'core.description' | translate}}</h2>
<p>
<core-format-text [text]="event.description"></core-format-text>
<core-format-text [text]="event.description" [contextLevel]="event.contextLevel" [contextInstanceId]="event.contextInstanceId"></core-format-text>
</p>
</ion-item>
<ion-item text-wrap *ngIf="event.location">
<h2>{{ 'core.location' | translate}}</h2>
<p>
<a [href]="event.encodedLocation" core-link auto-login="no">
<core-format-text [text]="event.location"></core-format-text>
<core-format-text [text]="event.location" [contextLevel]="event.contextLevel" [contextInstanceId]="event.contextInstanceId"></core-format-text>
</a>
</p>
</ion-item>

View File

@ -57,6 +57,7 @@ export class AddonCalendarEventPage implements OnDestroy {
notificationMax: string;
notificationTimeText: string;
event: any = {};
courseId: number;
courseName: string;
groupName: string;
courseUrl = '';
@ -250,11 +251,13 @@ export class AddonCalendarEventPage implements OnDestroy {
// If the event belongs to a course, get the course name and the URL to view it.
if (canGetById && event.course && event.course.id != this.siteHomeId) {
this.courseId = event.course.id;
this.courseName = event.course.fullname;
this.courseUrl = event.course.viewurl;
} else if (event.courseid && event.courseid != this.siteHomeId) {
// Retrieve the course.
promises.push(this.coursesProvider.getUserCourse(event.courseid, true).then((course) => {
this.courseId = course.id;
this.courseName = course.fullname;
this.courseUrl = currentSite ? this.textUtils.concatenatePaths(currentSite.siteUrl,
'/course/view.php?id=' + event.courseid) : '';

View File

@ -35,7 +35,7 @@
<a ion-item text-wrap [title]="event.name" (click)="gotoEvent(event.id)" [class.core-split-item-selected]="event.id == eventId" class="addon-calendar-event" [ngClass]="['addon-calendar-eventtype-'+event.eventtype]">
<img *ngIf="event.moduleIcon" src="{{event.moduleIcon}}" item-start class="core-module-icon">
<core-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" item-start></core-icon>
<h2><core-format-text [text]="event.name"></core-format-text></h2>
<h2><core-format-text [text]="event.name" [contextLevel]="event.contextLevel" [contextInstanceId]="event.contextInstanceId"></core-format-text></h2>
<p>
{{ event.timestart * 1000 | coreFormatDate: "strftimetime" }}
<span *ngIf="event.timeduration && event.endsSameDay"> - {{ (event.timestart + event.timeduration) * 1000 | coreFormatDate: "strftimetime" }}</span>

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
import { CoreCourseProvider } from '@core/course/providers/course';
import { AddonCalendarProvider, AddonCalendarCalendarEvent } from './calendar';
import { AddonCalendarProvider } from './calendar';
import { CoreConstants } from '@core/constants';
import { CoreConfigProvider } from '@providers/config';
import { CoreUtilsProvider } from '@providers/utils/utils';
@ -130,7 +130,7 @@ export class AddonCalendarHelperProvider {
*
* @param e Event to format.
*/
formatEventData(e: AddonCalendarCalendarEvent): void {
formatEventData(e: any): void {
e.eventIcon = this.EVENTICONS[e.eventtype] || '';
if (!e.eventIcon) {
e.eventIcon = this.courseProvider.getModuleIconSrc(e.modulename);
@ -139,6 +139,21 @@ export class AddonCalendarHelperProvider {
e.formattedType = this.calendarProvider.getEventType(e);
// Calculate context.
const categoryId = e.category ? e.category.id : e.categoryid,
courseId = e.course ? e.course.id : e.courseid;
if (categoryId > 0) {
e.contextLevel = 'category';
e.contextInstanceId = categoryId;
} else if (courseId > 0) {
e.contextLevel = 'course';
e.contextInstanceId = courseId;
} else {
e.contextLevel = 'user';
e.contextInstanceId = e.userid;
}
if (typeof e.duration != 'undefined') {
// It's an offline event, add some calculated data.
e.format = 1;

View File

@ -30,7 +30,7 @@
<ion-card *ngIf="user">
<ion-item text-wrap>
<ion-avatar core-user-avatar [user]="user" item-start></ion-avatar>
<h2><core-format-text [text]="user.fullname"></core-format-text></h2>
<h2>{{ user.fullname }}</h2>
</ion-item>
</ion-card>
<core-empty-box *ngIf="competencies && competencies.statistics.competencycount == 0" icon="ribbon" message="{{ 'addon.competency.nocompetenciesincourse' | translate }}"></core-empty-box>
@ -43,7 +43,7 @@
</ion-item>
<ion-item text-wrap>
<p *ngIf="competency.competency.description">
<core-format-text [text]=" competency.competency.description "></core-format-text>
<core-format-text [text]="competency.competency.description" contextLevel="course" [contextInstanceId]="courseId"></core-format-text>
</p>
<div>
<strong>{{ 'addon.competency.path' | translate }}</strong>
@ -59,9 +59,7 @@
<div *ngIf="competencies.statistics.canmanagecoursecompetencies">
<strong>{{ 'addon.competency.uponcoursecompletion' | translate }}</strong>
<ng-container *ngFor="let ruleoutcome of competency.ruleoutcomeoptions">
<span *ngIf="ruleoutcome.selected">
<core-format-text [text]="ruleoutcome.text"></core-format-text>
</span>
<span *ngIf="ruleoutcome.selected">{{ ruleoutcome.text }}</span>
</ng-container>
</div>
<div>
@ -71,7 +69,7 @@
</p>
<a ion-item text-wrap *ngFor="let activity of competency.coursemodules" [href]="activity.url" [title]="activity.name" core-link capture="true" class="core-course-module-handler item-media">
<img item-start [src]="activity.iconurl" core-external-content alt="" role="presentation" *ngIf="activity.iconurl" class="core-module-icon">
<core-format-text [text]="activity.name"></core-format-text>
<core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id" [courseId]="courseId"></core-format-text>
</a>
</div>
<div *ngIf="competency.plans">
@ -80,7 +78,7 @@
{{ 'addon.competency.nouserplanswithcompetency' | translate }}
</p>
<a ion-item text-wrap *ngFor="let plan of competency.plans" [href]="plan.url" [title]="plan.name" core-link capture="true">
<core-format-text [text]="plan.name"></core-format-text>
<core-format-text [text]="plan.name" contextLevel="user" [contextInstanceId]="plan.userid"></core-format-text>
</a>
</div>
</ion-item>

View File

@ -86,7 +86,11 @@ export class AddonCompetencyCourseComponent {
* @param competencyId
*/
openCompetencySummary(competencyId: number): void {
this.navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId});
this.navCtrl.push('AddonCompetencyCompetencySummaryPage', {
competencyId,
contextLevel: 'course',
contextInstanceId: this.courseId
});
}
/**

View File

@ -11,13 +11,13 @@
<ion-card *ngIf="user">
<ion-item text-wrap>
<ion-avatar core-user-avatar [user]="user" item-start></ion-avatar>
<h2><core-format-text [text]="user.fullname"></core-format-text></h2>
<h2>{{ user.fullname }}</h2>
</ion-item>
</ion-card>
<ion-card *ngIf="competency">
<ion-item text-wrap *ngIf="competency.competency.competency.description">
<core-format-text [text]="competency.competency.competency.description"></core-format-text>
<core-format-text [text]="competency.competency.competency.description" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"></core-format-text>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.path' | translate }}</strong>
@ -48,7 +48,7 @@
</p>
<a ion-item text-wrap *ngFor="let activity of coursemodules" [href]="activity.url" [title]="activity.name" core-link capture="true">
<img item-start core-external-content [src]="activity.iconurl" alt="" role="presentation" *ngIf="activity.iconurl" class="core-module-icon">
<core-format-text [text]="activity.name"></core-format-text>
<core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id" [courseId]="courseId"></core-format-text>
</a>
</ion-item>
<ion-item text-wrap *ngIf="userCompetency.status">
@ -84,7 +84,7 @@
<ion-item text-wrap>
<p><ion-badge color="dark">{{ evidence.gradename }}</ion-badge></p>
<p margin-top *ngIf="evidence.description">{{ evidence.description }}</p>
<blockquote *ngIf="evidence.note"><core-format-text [text]="evidence.note"></core-format-text></blockquote>
<blockquote *ngIf="evidence.note">{{ evidence.note }}</blockquote>
</ion-item>
</ion-card>
</div>

View File

@ -46,6 +46,8 @@ export class AddonCompetencyCompetencyPage {
user: CoreUserSummary;
competency: AddonCompetencyUserCompetencySummary;
userCompetency: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse;
contextLevel: string;
contextInstanceId: number;
constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService,
private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
@ -99,6 +101,15 @@ export class AddonCompetencyCompetencyPage {
return promise.then((competency) => {
// Calculate the context.
if (this.courseId) {
this.contextLevel = 'course';
this.contextInstanceId = this.courseId;
} else {
this.contextLevel = 'user';
this.contextInstanceId = this.userId || competency.usercompetencysummary.user.id;
}
this.competency = competency.usercompetencysummary;
this.userCompetency = this.competency.usercompetencyplan || this.competency.usercompetency;
@ -155,6 +166,10 @@ export class AddonCompetencyCompetencyPage {
openCompetencySummary(competencyId: number): void {
// Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav.
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId});
navCtrl.push('AddonCompetencyCompetencySummaryPage', {
competencyId,
contextLevel: this.contextLevel,
contextInstanceId: this.contextInstanceId
});
}
}

View File

@ -10,7 +10,7 @@
<core-loading [hideUntil]="competencyLoaded">
<ion-card *ngIf="competency">
<ion-item text-wrap *ngIf="competency.competency.description">
<core-format-text [text]="competency.competency.description"></core-format-text>
<core-format-text [text]="competency.competency.description" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId"></core-format-text>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.path' | translate }}</strong>

View File

@ -30,10 +30,14 @@ export class AddonCompetencyCompetencySummaryPage {
competencyLoaded = false;
competencyId: number;
competency: AddonCompetencySummary;
contextLevel: string;
contextInstanceId: number;
constructor(private navCtrl: NavController, navParams: NavParams, private domUtils: CoreDomUtilsProvider,
@Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider) {
this.competencyId = navParams.get('competencyId');
this.contextLevel = navParams.get('contextLevel');
this.contextInstanceId = navParams.get('contextInstanceId');
}
/**
@ -57,8 +61,14 @@ export class AddonCompetencyCompetencySummaryPage {
* @return Promise resolved when done.
*/
protected fetchCompetency(): Promise<void> {
return this.competencyProvider.getCompetencySummary(this.competencyId).then((competency) => {
this.competency = competency;
return this.competencyProvider.getCompetencySummary(this.competencyId).then((result) => {
if (!this.contextLevel || typeof this.contextInstanceId == 'undefined') {
// Context not specified, use user context.
this.contextLevel = 'user';
this.contextInstanceId = result.usercompetency.userid;
}
this.competency = result.competency;
}).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'Error getting competency summary data.');
});
@ -85,6 +95,10 @@ export class AddonCompetencyCompetencySummaryPage {
openCompetencySummary(competencyId: number): void {
// Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav.
const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl;
navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId});
navCtrl.push('AddonCompetencyCompetencySummaryPage', {
competencyId,
contextLevel: this.contextLevel,
contextInstanceId: this.contextInstanceId
});
}
}

View File

@ -11,13 +11,13 @@
<ion-card *ngIf="user">
<ion-item text-wrap>
<ion-avatar core-user-avatar [user]="user" item-start></ion-avatar>
<h2><core-format-text [text]="user.fullname"></core-format-text></h2>
<h2>{{ user.fullname }}</h2>
</ion-item>
</ion-card>
<ion-card *ngIf="plan">
<ion-list>
<ion-item text-wrap *ngIf="plan.plan.description">
<core-format-text [text]="plan.plan.description"></core-format-text>
<core-format-text [text]="plan.plan.description" contextLevel="user" [contextInstanceId]="plan.plan.userid"></core-format-text>
</ion-item>
<ion-item text-wrap>
<strong>{{ 'addon.competency.status' | translate }}</strong>:

View File

@ -299,7 +299,7 @@ export class AddonCompetencyProvider {
* @return Promise to be resolved when the competency summary is retrieved.
*/
getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean)
: Promise<AddonCompetencySummary> {
: Promise<AddonCompetencyUserCompetencySummary> {
return this.sitesProvider.getSite(siteId).then((site) => {
userId = userId || site.getUserId();
@ -324,7 +324,7 @@ export class AddonCompetencyProvider {
.then((response: AddonCompetencyUserCompetencySummary): any => {
if (response.competency) {
return response.competency;
return response;
}
return Promise.reject(null);

View File

@ -17,6 +17,7 @@ import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '@core/co
import { CoreCourseProvider } from '@core/course/providers/course';
import { AddonCompetencyCourseComponent } from '../components/course/course';
import { AddonCompetencyProvider } from '../providers/competency';
import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
/**
* Course nav handler.
@ -26,7 +27,7 @@ export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHand
name = 'AddonCompetency';
priority = 300;
constructor(private competencyProvider: AddonCompetencyProvider) {}
constructor(private competencyProvider: AddonCompetencyProvider, protected filterHelper: CoreFilterHelperProvider) {}
/**
* Whether or not the handler is enabled ona site level.
@ -110,6 +111,18 @@ export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHand
promises.push(this.competencyProvider.getCompetencySummary(competency.competency.id, undefined, undefined,
true));
if (competency.coursemodules) {
competency.coursemodules.forEach((module) => {
promises.push(this.filterHelper.getFilters('module', module.id, {courseId: course.id}));
});
}
if (competency.plans) {
competency.plans.forEach((plan) => {
promises.push(this.filterHelper.getFilters('user', plan.userid));
});
}
});
}

View File

@ -17,8 +17,8 @@
<ion-card *ngIf="completion && tracked">
<ion-item-divider>{{ 'addon.coursecompletion.requiredcriteria' | translate }}</ion-item-divider>
<ion-item class="hidden-tablet" text-wrap *ngFor="let criteria of completion.completions">
<h2><core-format-text clean="true" [text]="criteria.details.criteria"></core-format-text></h2>
<p><core-format-text clean="true" [text]="criteria.details.requirement"></core-format-text></p>
<h2><core-format-text clean="true" [text]="criteria.details.criteria" [filter]="false"></core-format-text></h2>
<p><core-format-text clean="true" [text]="criteria.details.requirement" [filter]="false"></core-format-text></p>
<strong item-end>{{ criteria.status }}</strong>
</ion-item>
<ion-item class="hidden-phone" text-wrap>
@ -31,10 +31,10 @@
<ion-col><strong>{{ 'addon.coursecompletion.completiondate' | translate }}</strong></ion-col>
</ion-row>
<ion-row *ngFor="let criteria of completion.completions">
<ion-col><core-format-text clean="true" [text]="criteria.title"></core-format-text></ion-col>
<ion-col><core-format-text clean="true" [text]="criteria.details.criteria"></core-format-text></ion-col>
<ion-col><core-format-text clean="true" [text]="criteria.details.requirement"></core-format-text></ion-col>
<ion-col><core-format-text [text]="criteria.details.status"></core-format-text></ion-col>
<ion-col><core-format-text clean="true" [text]="criteria.title" [filter]="false"></core-format-text></ion-col>
<ion-col><core-format-text clean="true" [text]="criteria.details.criteria" [filter]="false"></core-format-text></ion-col>
<ion-col><core-format-text clean="true" [text]="criteria.details.requirement" [filter]="false"></core-format-text></ion-col>
<ion-col><core-format-text [text]="criteria.details.status" [filter]="false"></core-format-text></ion-col>
<ion-col>{{ criteria.status }}</ion-col>
<ion-col *ngIf="criteria.timecompleted">{{ criteria.timecompleted * 1000 | coreFormatDate :'strftimedatetimeshort' }}</ion-col>
<ion-col *ngIf="!criteria.timecompleted"></ion-col>

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-title>{{ title }}</ion-title>
</ion-navbar>
</ion-header>
<ion-content>

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterActivityNamesHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterActivityNamesHandler
]
})
export class AddonFilterActivityNamesModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterActivityNamesHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the Activity names filter.
*/
@Injectable()
export class AddonFilterActivityNamesHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterActivityNamesHandler';
filterName = 'activitynames';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterAlgebraHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterAlgebraHandler
]
})
export class AddonFilterAlgebraModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterAlgebraHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the Algebra notation filter.
*/
@Injectable()
export class AddonFilterAlgebraHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterAlgebraHandler';
filterName = 'algebra';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterCensorHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterCensorHandler
]
})
export class AddonFilterCensorModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterCensorHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the Word censorship filter.
*/
@Injectable()
export class AddonFilterCensorHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterCensorHandler';
filterName = 'censor';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterDataHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterDataHandler
]
})
export class AddonFilterDataModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterDataHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the Database auto-link filter.
*/
@Injectable()
export class AddonFilterDataHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterDataHandler';
filterName = 'data';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterEmailProtectHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterEmailProtectHandler
]
})
export class AddonFilterEmailProtectModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterEmailProtectHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the Email protection filter.
*/
@Injectable()
export class AddonFilterEmailProtectHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterEmailProtectHandler';
filterName = 'emailprotect';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterEmoticonHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterEmoticonHandler
]
})
export class AddonFilterEmoticonModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterEmoticonHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the Emoticon filter.
*/
@Injectable()
export class AddonFilterEmoticonHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterEmoticonHandler';
filterName = 'emoticon';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,49 @@
// (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 { NgModule } from '@angular/core';
import { AddonFilterActivityNamesModule } from './activitynames/activitynames.module';
import { AddonFilterAlgebraModule } from './algebra/algebra.module';
import { AddonFilterCensorModule } from './censor/censor.module';
import { AddonFilterDataModule } from './data/data.module';
import { AddonFilterEmailProtectModule } from './emailprotect/emailprotect.module';
import { AddonFilterEmoticonModule } from './emoticon/emoticon.module';
import { AddonFilterGlossaryModule } from './glossary/glossary.module';
import { AddonFilterMediaPluginModule } from './mediaplugin/mediaplugin.module';
import { AddonFilterMultilangModule } from './multilang/multilang.module';
import { AddonFilterTexModule } from './tex/tex.module';
import { AddonFilterTidyModule } from './tidy/tidy.module';
import { AddonFilterUrlToLinkModule } from './urltolink/urltolink.module';
@NgModule({
declarations: [],
imports: [
AddonFilterActivityNamesModule,
AddonFilterAlgebraModule,
AddonFilterCensorModule,
AddonFilterDataModule,
AddonFilterEmailProtectModule,
AddonFilterEmoticonModule,
AddonFilterGlossaryModule,
AddonFilterMediaPluginModule,
AddonFilterMultilangModule,
AddonFilterTexModule,
AddonFilterTidyModule,
AddonFilterUrlToLinkModule
],
providers: [
],
exports: []
})
export class AddonFilterModule { }

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterGlossaryHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterGlossaryHandler
]
})
export class AddonFilterGlossaryModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterGlossaryHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the Glossary auto-link filter.
*/
@Injectable()
export class AddonFilterGlossaryHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterGlossaryHandler';
filterName = 'glossary';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterMediaPluginHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterMediaPluginHandler
]
})
export class AddonFilterMediaPluginModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterMediaPluginHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,139 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreTextUtilsProvider } from '@providers/utils/text';
/**
* Handler to support the Multimedia filter.
*/
@Injectable()
export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterMediaPluginHandler';
filterName = 'mediaplugin';
constructor(private textUtils: CoreTextUtilsProvider) {
super();
}
/**
* Filter some text.
*
* @param text The text to filter.
* @param filter The filter.
* @param options Options passed to the filters.
* @param siteId Site ID. If not defined, current site.
* @return Filtered text (or promise resolved with the filtered text).
*/
filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string)
: string | Promise<string> {
const div = document.createElement('div');
div.innerHTML = text;
const videos = Array.from(div.querySelectorAll('video'));
videos.forEach((video) => {
this.treatVideoFilters(video);
});
return div.innerHTML;
}
/**
* Treat video filters. Currently only treating youtube video using video JS.
*
* @param el Video element.
* @param navCtrl NavController to use.
*/
protected treatVideoFilters(video: HTMLElement): void {
// Treat Video JS Youtube video links and translate them to iframes.
if (!video.classList.contains('video-js')) {
return;
}
const data = this.textUtils.parseJSON(video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'),
youtubeData = data.techOrder && data.techOrder[0] && data.techOrder[0] == 'youtube' &&
this.parseYoutubeUrl(data.sources && data.sources[0] && data.sources[0].src);
if (!youtubeData || !youtubeData.videoId) {
return;
}
const iframe = document.createElement('iframe');
iframe.id = video.id;
iframe.src = 'https://www.youtube.com/embed/' + youtubeData.videoId; // Don't apply other params to align with Moodle web.
iframe.setAttribute('frameborder', '0');
iframe.setAttribute('allowfullscreen', '1');
iframe.width = '100%';
iframe.height = '300';
// Replace video tag by the iframe.
video.parentNode.replaceChild(iframe, video);
}
/**
* Parse a YouTube URL.
* Based on Youtube.parseUrl from Moodle media/player/videojs/amd/src/Youtube-lazy.js
*
* @param url URL of the video.
* @return Data of the video.
*/
protected parseYoutubeUrl(url: string): {videoId: string, listId?: string, start?: number} {
const result = {
videoId: null,
listId: null,
start: null
};
if (!url) {
return result;
}
url = this.textUtils.decodeHTML(url);
// Get the video ID.
let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/);
if (match && match[2].length === 11) {
result.videoId = match[2];
}
// Now get the playlist (if any).
match = url.match(/[?&]list=([^#\&\?]+)/);
if (match && match[1]) {
result.listId = match[1];
}
// Now get the start time (if any).
match = url.match(/[?&]start=(\d+)/);
if (match && match[1]) {
result.start = parseInt(match[1], 10);
} else {
// No start param, but it could have a time param.
match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/);
if (match) {
result.start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) + (match[2] ? parseInt(match[2], 10) * 60 : 0) +
(match[3] ? parseInt(match[3], 10) : 0);
}
}
return result;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterMultilangHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterMultilangHandler
]
})
export class AddonFilterMultilangModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterMultilangHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,93 @@
// (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 { CoreSitesProvider } from '@providers/sites';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreLangProvider } from '@providers/lang';
import { CoreSite } from '@classes/site';
/**
* Handler to support the Multilang filter.
*/
@Injectable()
export class AddonFilterMultilangHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterMultilangHandler';
filterName = 'multilang';
constructor(private langProvider: CoreLangProvider,
private sitesProvider: CoreSitesProvider) {
super();
}
/**
* Filter some text.
*
* @param text The text to filter.
* @param filter The filter.
* @param options Options passed to the filters.
* @param siteId Site ID. If not defined, current site.
* @return Filtered text (or promise resolved with the filtered text).
*/
filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string)
: string | Promise<string> {
return this.sitesProvider.getSite(siteId).then((site) => {
// Don't apply this filter if Moodle is 3.7 or higher and the WS already filtered the content.
if (!this.shouldBeApplied(options, site)) {
return text;
}
return this.langProvider.getCurrentLanguage().then((language) => {
// Match the current language.
const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g;
let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g');
if (!text.match(currentLangRegEx)) {
// Current lang not found. Try to find the first language.
const matches = text.match(anyLangRegEx);
if (matches && matches[0]) {
language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1];
currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>',
'g');
} else {
// No multi-lang tag found, stop.
return text;
}
}
// Extract contents of current language.
text = text.replace(currentLangRegEx, '$1');
// Delete the rest of languages
text = text.replace(anyLangRegEx, '');
return text;
});
});
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
// The filter should be applied if site is older than 3.7 or the WS didn't filter the text.
return options.wsNotFiltered || !site.isVersionGreaterEqualThan('3.7');
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the TeX notation filter.
*/
@Injectable()
export class AddonFilterTexHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterTexHandler';
filterName = 'tex';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterTexHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterTexHandler
]
})
export class AddonFilterTexModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterTexHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the HTML tidy filter.
*/
@Injectable()
export class AddonFilterTidyHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterTidyHandler';
filterName = 'tidy';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterTidyHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterTidyHandler
]
})
export class AddonFilterTidyModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterTidyHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -0,0 +1,45 @@
// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter';
import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter';
import { CoreSite } from '@classes/site';
/**
* Handler to support the URL to link and images filter.
*/
@Injectable()
export class AddonFilterUrlToLinkHandler extends CoreFilterDefaultHandler {
name = 'AddonFilterUrlToLinkHandler';
filterName = 'urltolink';
constructor() {
super();
// This filter is handled by Moodle, nothing to do in the app.
}
/**
* Check if the filter should be applied in a certain site based on some filter options.
*
* @param options Options.
* @param site Site.
* @return Whether filter should be applied.
*/
shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
return false;
}
}

View File

@ -0,0 +1,34 @@
// (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 { NgModule } from '@angular/core';
import { IonicModule } from 'ionic-angular';
import { CoreFilterDelegate } from '@core/filter/providers/delegate';
import { AddonFilterUrlToLinkHandler } from './providers/handler';
@NgModule({
declarations: [
],
imports: [
IonicModule
],
providers: [
AddonFilterUrlToLinkHandler
]
})
export class AddonFilterUrlToLinkModule {
constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterUrlToLinkHandler) {
filterDelegate.registerHandler(handler);
}
}

View File

@ -7,7 +7,7 @@
<a ion-item text-wrap *ngFor="let contact of contacts" [title]="contact.fullname" (click)="selectUser(contact.id)" [class.core-split-item-selected]="contact.id == selectedUserId" class="addon-messages-conversation-item">
<ion-avatar item-start core-user-avatar [user]="contact" [checkOnline]="contact.showonlinestatus" [linkProfile]="false"></ion-avatar>
<h2>
<core-format-text [text]="contact.fullname"></core-format-text>
{{ contact.fullname }}
<core-icon *ngIf="contact.isblocked" name="fa-ban" item-end></core-icon>
</h2>
</a>

View File

@ -6,7 +6,7 @@
<ion-list no-margin>
<a ion-item text-wrap *ngFor="let request of requests" [title]="request.fullname" (click)="selectUser(request.id)" [class.core-split-item-selected]="request.id == selectedUserId" class="addon-messages-conversation-item">
<ion-avatar item-start core-user-avatar [user]="request" [linkProfile]="false"></ion-avatar>
<h2><core-format-text [text]="request.fullname"></core-format-text></h2>
<h2>{{ request.fullname }}</h2>
<p *ngIf="!request.iscontact && !request.confirmedOrDeclined">{{ 'addon.messages.wouldliketocontactyou' | translate }}</p>
</a>
</ion-list>

View File

@ -20,7 +20,7 @@
<!-- Don't show deleted users -->
<a ion-item text-wrap *ngIf="contact.profileimageurl || contact.profileimageurlsmall" [title]="contact.fullname" (click)="gotoDiscussion(contact.id)" [class.core-split-item-selected]="contact.id == discussionUserId" class="addon-messages-conversation-item">
<ion-avatar core-user-avatar [user]="contact" item-start [checkOnline]="contact.showonlinestatus"></ion-avatar>
<h2><core-format-text [text]="contact.fullname"></core-format-text></h2>
<h2>{{ contact.fullname }}</h2>
</a>
</ng-container>
</ng-container>

View File

@ -14,22 +14,20 @@
</ion-item-divider>
<a ion-item text-wrap *ngFor="let result of search.results" [title]="result.fullname" (click)="gotoDiscussion(result.userid, result.messageid)" [class.core-split-item-selected]="result.userid == discussionUserId" class="addon-message-discussion">
<ion-avatar core-user-avatar [user]="result" item-start [checkOnline]="result.showonlinestatus"></ion-avatar>
<h2><core-format-text [text]="result.fullname"></core-format-text></h2>
<p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage"></core-format-text></p>
<h2>{{ result.fullname }}</h2>
<p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage" contextLevel="system" [contextInstanceId]="0"></core-format-text></p>
</a>
</ion-list>
<ion-list *ngIf="!search.showResults" no-margin>
<a ion-item text-wrap *ngFor="let discussion of discussions" [title]="discussion.fullname" (click)="gotoDiscussion(discussion.message.user)" [class.core-split-item-selected]="discussion.message.user == discussionUserId" class="addon-message-discussion">
<ion-avatar core-user-avatar [user]="discussion" item-start [checkOnline]="discussion.showonlinestatus"></ion-avatar>
<h2>
<core-format-text [text]="discussion.fullname"></core-format-text>
</h2>
<h2>{{ discussion.fullname }}</h2>
<ion-note *ngIf="discussion.message.timecreated > 0 || discussion.unread">
<span *ngIf="discussion.unread" class="core-primary-circle"></span>
<span *ngIf="discussion.message.timecreated > 0">{{discussion.message.timecreated / 1000 | coreDateDayOrTime}}</span>
</ion-note>
<p><core-format-text clean="true" singleLine="true" [text]="discussion.message.message"></core-format-text></p>
<p><core-format-text clean="true" singleLine="true" [text]="discussion.message.message" contextLevel="system" [contextInstanceId]="0"></core-format-text></p>
</a>
</ion-list>

View File

@ -18,15 +18,15 @@
<div class="item-avatar-center">
<img class="avatar" [src]="conversation.imageurl" core-external-content [alt]="conversation.name" role="presentation" onError="this.src='assets/img/group-avatar.png'">
</div>
<h2><core-format-text [text]="conversation.name"></core-format-text></h2>
<p><core-format-text *ngIf="conversation.subname" [text]="conversation.subname"></core-format-text></p>
<h2><core-format-text [text]="conversation.name" contextLevel="system" [contextInstanceId]="0"></core-format-text></h2>
<p><core-format-text *ngIf="conversation.subname" [text]="conversation.subname" contextLevel="system" [contextInstanceId]="0"></core-format-text></p>
<p>{{ 'addon.messages.numparticipants' | translate:{$a: conversation.membercount} }}</p>
</ion-item>
<a ion-item text-wrap *ngFor="let member of members" (click)="closeModal(member.id)" class="addon-messages-conversation-item">
<ion-avatar core-user-avatar [user]="member" [linkProfile]="false" [checkOnline]="member.showonlinestatus" item-start></ion-avatar>
<h2>
<core-format-text [text]="member.fullname"></core-format-text>
{{ member.fullname }}
<core-icon name="fa-ban" *ngIf="member.isblocked" [label]="'addon.messages.contactblocked' | translate"></core-icon>
</h2>
</a>

View File

@ -18,6 +18,7 @@ import {
AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMember
} from '../../providers/messages';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreSitesProvider } from '@providers/sites';
/**
* Page that displays the list of conversations, including group conversations.
@ -38,7 +39,7 @@ export class AddonMessagesConversationInfoPage implements OnInit {
protected conversationId: number;
constructor(private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams,
protected viewCtrl: ViewController) {
protected viewCtrl: ViewController, sitesProvider: CoreSitesProvider) {
this.conversationId = navParams.get('conversationId');
}

View File

@ -3,7 +3,7 @@
<ion-title>
<img *ngIf="loaded && !otherMember && conversationImage" class="core-bar-button-image" [src]="conversationImage" [alt]="title" onError="this.src='assets/img/group-avatar.png'" core-external-content role="presentation" [siteId]="siteId || null">
<ion-avatar *ngIf="loaded && otherMember" class="core-bar-button-image" core-user-avatar [user]="otherMember" [linkProfile]="false" [checkOnline]="otherMember.showonlinestatus" item-start (click)="showInfo && viewInfo()"></ion-avatar>
<core-format-text [text]="title" (click)="showInfo && !isGroup && viewInfo()"></core-format-text>
<core-format-text [text]="title" contextLevel="system" [contextInstanceId]="0" (click)="showInfo && !isGroup && viewInfo()"></core-format-text>
<core-icon *ngIf="conversation && conversation.isfavourite" name="fa-star" [label]="'core.favourites' | translate"></core-icon>
<core-icon *ngIf="conversation && conversation.ismuted" name="volume-off" [label]="'addon.messages.mutedconversation' | translate"></core-icon>
</ion-title>
@ -58,7 +58,7 @@
<!-- Some messages have <p> and some others don't. Add a <p> so they all have same styles. -->
<p class="addon-message-text">
<core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text"></core-format-text>
<core-format-text (afterRender)="last && scrollToBottom()" [text]="message.text" contextLevel="system" [contextInstanceId]="0"></core-format-text>
</p>
<button ion-button icon-only clear="true" *ngIf="!message.sending && showDelete" (click)="deleteMessage(message, index)" class="addon-messages-delete-button" [@coreSlideInOut]="'fromRight'" [attr.aria-label]=" 'addon.messages.deletemessage' | translate">

View File

@ -99,7 +99,7 @@
<ion-avatar *ngIf="conversation.type != typeGroup" core-user-avatar [user]="conversation.otherUser" [linkProfile]="false" [checkOnline]="conversation.showonlinestatus" item-start></ion-avatar>
<h2>
<core-format-text [text]="conversation.name"></core-format-text>
<core-format-text [text]="conversation.name" contextLevel="system" [contextInstanceId]="0"></core-format-text>
<core-icon name="fa-ban" *ngIf="conversation.isblocked" [label]="'addon.messages.contactblocked' | translate"></core-icon>
<core-icon *ngIf="conversation.ismuted" name="volume-off" [label]="'addon.messages.mutedconversation' | translate"></core-icon>
</h2>
@ -107,11 +107,11 @@
<ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
<span *ngIf="conversation.lastmessagedate > 0">{{conversation.lastmessagedate | coreDateDayOrTime}}</span>
</ion-note>
<p *ngIf="conversation.subname"><core-format-text [text]="conversation.subname"></core-format-text></p>
<p *ngIf="conversation.subname"><core-format-text [text]="conversation.subname" contextLevel="system" [contextInstanceId]="0"></core-format-text></p>
<p class="addon-message-last-message">
<span *ngIf="conversation.sentfromcurrentuser" class="addon-message-last-message-user">{{ 'addon.messages.you' | translate }}</span>
<core-format-text *ngIf="!conversation.sentfromcurrentuser && conversation.type == typeGroup && conversation.members[0]" [text]="conversation.members[0].fullname + ':'" class="addon-message-last-message-user"></core-format-text>
<core-format-text clean="true" singleLine="true" [text]="conversation.lastmessage" class="addon-message-last-message-text"></core-format-text>
<span *ngIf="!conversation.sentfromcurrentuser && conversation.type == typeGroup && conversation.members[0]" class="addon-message-last-message-user">{{ conversation.members[0].fullname + ':' }}</span>
<core-format-text clean="true" singleLine="true" [text]="conversation.lastmessage" class="addon-message-last-message-text" contextLevel="system" [contextInstanceId]="0"></core-format-text>
</p>
</a>
</ng-template>

View File

@ -33,7 +33,7 @@
<a ion-item text-wrap *ngFor="let result of item.results" [title]="result.fullname" (click)="openConversation(result)" [class.core-split-item-selected]="result == selectedResult" class="addon-message-discussion">
<ion-avatar item-start core-user-avatar [user]="result" [checkOnline]="true" [linkProfile]="false"></ion-avatar>
<h2>
<core-format-text [text]="result.fullname" [highlight]="result.highlightName"></core-format-text>
<core-format-text [text]="result.fullname" [highlight]="result.highlightName" [filter]="false"></core-format-text>
<core-icon name="fa-ban" *ngIf="result.isblocked" [label]="'addon.messages.contactblocked' | translate"></core-icon>
</h2>
<ion-note *ngIf="result.lastmessagedate > 0">
@ -41,7 +41,7 @@
</ion-note>
<p class="addon-message-last-message">
<span *ngIf="result.sentfromcurrentuser" class="addon-message-last-message-user">{{ 'addon.messages.you' | translate }}</span>
<core-format-text clean="true" singleLine="true" [text]="result.lastmessage" [highlight]="result.highlightMessage" class="addon-message-last-message-text"></core-format-text>
<core-format-text clean="true" singleLine="true" [text]="result.lastmessage" [highlight]="result.highlightMessage" contextLevel="system" [contextInstanceId]="0" class="addon-message-last-message-text"></core-format-text>
</p>
</a>

View File

@ -19,12 +19,12 @@ import { CoreCronHandler } from '@providers/cron';
import { CoreSitesProvider } from '@providers/sites';
import { CoreEventsProvider } from '@providers/events';
import { CoreAppProvider } from '@providers/app';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreLocalNotificationsProvider } from '@providers/local-notifications';
import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications';
import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate';
import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper';
import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
/**
* Handler to inject an option into main menu.
@ -49,7 +49,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
constructor(private messagesProvider: AddonMessagesProvider, private sitesProvider: CoreSitesProvider,
eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider,
private localNotificationsProvider: CoreLocalNotificationsProvider, private textUtils: CoreTextUtilsProvider,
private localNotificationsProvider: CoreLocalNotificationsProvider, private filterHelper: CoreFilterHelperProvider,
private pushNotificationsProvider: CorePushNotificationsProvider, utils: CoreUtilsProvider,
pushNotificationsDelegate: CorePushNotificationsDelegate, private emulatorHelper: CoreEmulatorHelperProvider) {
@ -297,7 +297,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr
title: message.name || message.userfromfullname,
};
return this.textUtils.formatText(message.text, true, true).catch(() => {
return this.filterHelper.getFiltersAndFormatText(message.text, 'system', 0, {clean: true, singleLine: true}).catch(() => {
return message.text;
}).then((formattedText) => {
data['text'] = formattedText;

View File

@ -8,7 +8,7 @@
{{ 'addon.mod_assign.feedbacknotsupported' | translate }}
</ion-badge>
<p *ngIf="text">
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true" [fullTitle]="plugin.name" [text]="text"></core-format-text>
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true" [fullTitle]="plugin.name" [text]="text" contextLevel="module" [contextInstanceId]="assign.cmid" [courseId]="assign.course"></core-format-text>
</p>
<core-file *ngFor="let file of files" [file]="file" [component]="component" [componentId]="assign.cmid" [alwaysDownload]="true"></core-file>
</ion-item>

View File

@ -17,7 +17,7 @@
<!-- Description and intro attachments. -->
<ion-card *ngIf="description" (click)="expandDescription($event)">
<ion-item text-wrap>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120" (click)="expandDescription($event)"></core-format-text>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" (click)="expandDescription($event)"></core-format-text>
</ion-item>
</ion-card>

View File

@ -134,7 +134,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
if (this.assign && (this.description || this.assign.introattachments)) {
this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component,
this.module.id, this.assign.introattachments);
this.module.id, this.assign.introattachments, true, 'module', this.module.id, this.courseId);
}
}

View File

@ -8,7 +8,7 @@
{{ 'addon.mod_assign.submissionnotsupported' | translate }}
</ion-badge>
<p *ngIf="text">
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true" [fullTitle]="plugin.name" [text]="text"></core-format-text>
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true" [fullTitle]="plugin.name" [text]="text" contextLevel="module" [contextInstanceId]="assign.cmid" [courseId]="assign.course"></core-format-text>
</p>
<core-file *ngFor="let file of files" [file]="file" [component]="component" [componentId]="assign.cmid" [alwaysDownload]="true"></core-file>
</ion-item>

View File

@ -34,7 +34,7 @@
<ion-item text-wrap *ngIf="timeRemaining" [ngClass]="[timeRemainingClass]">
<h2>{{ 'addon.mod_assign.timeremaining' | translate }}</h2>
<p><core-format-text [text]="timeRemaining"></core-format-text></p>
<p [innerHTML]="timeRemaining"></p>
</ion-item>
<ion-item text-wrap *ngIf="fromDate && !isSubmittedForGrading">
@ -91,7 +91,7 @@
<!-- Submit for grading form. -->
<div *ngIf="canSubmit">
<ion-item text-wrap *ngIf="submissionStatement">
<ion-label><core-format-text [text]="submissionStatement"></core-format-text></ion-label>
<ion-label><core-format-text [text]="submissionStatement" [filter]="false"></core-format-text></ion-label>
<ion-checkbox item-end name="submissionstatement" [(ngModel)]="submitModel.submissionStatement">
</ion-checkbox>
</ion-item>
@ -115,7 +115,7 @@
<h2>{{ user.fullname }}</h2>
</a>
<ion-item text-wrap *ngIf="!user.fullname">
{{ 'addon.mod_assign.hiddenuser' | translate }} <core-format-text [text]="user"></core-format-text>
{{ 'addon.mod_assign.hiddenuser' | translate }} {{ user }}
</ion-item>
</div>
</ion-item>
@ -140,7 +140,7 @@
<!-- Current grade if method is advanced. -->
<ion-item text-wrap *ngIf="feedback.gradefordisplay && (!isGrading || grade.method != 'simple')" class="core-grading-summary">
<h2>{{ 'addon.mod_assign.currentgrade' | translate }}</h2>
<p><core-format-text [text]="feedback.gradefordisplay"></core-format-text></p>
<p><core-format-text [text]="feedback.gradefordisplay" [filter]="false"></core-format-text></p>
<a ion-button item-end icon-only *ngIf="feedback.advancedgrade" (click)="showAdvancedGrade()">
<ion-icon name="search"></ion-icon>
</a>

View File

@ -2,7 +2,7 @@
<ion-item text-wrap *ngIf="(text || canEdit) && !edit">
<h2>{{ plugin.name }}</h2>
<p>
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true" [fullTitle]="plugin.name" [text]="text"></core-format-text>
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true" [fullTitle]="plugin.name" [text]="text" contextLevel="module" [contextInstanceId]="assign.cmid" [courseId]="assign.course"></core-format-text>
</p>
<div item-end>
<div text-end>

View File

@ -65,7 +65,8 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb
if (this.text) {
// Open a new state with the text.
this.textUtils.expandText(this.plugin.name, this.text, this.component, this.assign.cmid);
this.textUtils.expandText(this.plugin.name, this.text, this.component, this.assign.cmid, undefined, true,
'module', this.assign.cmid, this.assign.course);
}
});
} else if (this.edit) {

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="plugin.name"></core-format-text></ion-title>
<ion-title>{{ plugin.name }}</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
<ion-icon name="close"></ion-icon>

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-title><core-format-text [text]="title" contextLevel="module" [contextInstanceId]="moduleId" [courseId]="courseId"></core-format-text></ion-title>
<ion-buttons end>
<button ion-button clear (click)="save()" [attr.aria-label]="'core.save' | translate">
@ -16,7 +16,7 @@
<form name="addon-mod_assign-edit-form" *ngIf="userSubmission && userSubmission.plugins && userSubmission.plugins.length">
<!-- Submission statement. -->
<ion-item text-wrap *ngIf="submissionStatement">
<ion-label><core-format-text [text]="submissionStatement"></core-format-text></ion-label>
<ion-label><core-format-text [text]="submissionStatement" [filter]="false"></core-format-text></ion-label>
<ion-checkbox item-end name="submissionstatement" [(ngModel)]="submissionStatementAccepted"></ion-checkbox>
<!-- ion-checkbox doesn't use an input. Create a hidden input to hold the value. -->

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-title><core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text></ion-title>
<ion-buttons end>
<!-- The buttons defined by the component will be added in here. -->

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-title><core-format-text [text]="title" contextLevel="module" [contextInstanceId]="moduleId" [courseId]="courseId"></core-format-text></ion-title>
<ion-buttons end></ion-buttons>
</ion-navbar>

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-title><core-format-text [text]="title" contextLevel="module" [contextInstanceId]="moduleId" [courseId]="courseId"></core-format-text></ion-title>
<ion-buttons end></ion-buttons>
</ion-navbar>

View File

@ -31,6 +31,7 @@ import { AddonModAssignHelperProvider, AddonModAssignSubmissionFormatted } from
import { AddonModAssignSyncProvider } from './assign-sync';
import { AddonModAssignFeedbackDelegate } from './feedback-delegate';
import { AddonModAssignSubmissionDelegate } from './submission-delegate';
import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
/**
* Handler to prefetch assigns.
@ -42,16 +43,26 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan
component = AddonModAssignProvider.COMPONENT;
updatesNames = /^configuration$|^.*files$|^submissions$|^grades$|^gradeitems$|^outcomes$|^comments$/;
constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider,
courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider,
domUtils: CoreDomUtilsProvider, protected assignProvider: AddonModAssignProvider,
protected textUtils: CoreTextUtilsProvider, protected feedbackDelegate: AddonModAssignFeedbackDelegate,
protected submissionDelegate: AddonModAssignSubmissionDelegate, protected courseHelper: CoreCourseHelperProvider,
protected groupsProvider: CoreGroupsProvider, protected gradesHelper: CoreGradesHelperProvider,
protected userProvider: CoreUserProvider, protected assignHelper: AddonModAssignHelperProvider,
constructor(translate: TranslateService,
appProvider: CoreAppProvider,
utils: CoreUtilsProvider,
courseProvider: CoreCourseProvider,
filepoolProvider: CoreFilepoolProvider,
sitesProvider: CoreSitesProvider,
domUtils: CoreDomUtilsProvider,
filterHelper: CoreFilterHelperProvider,
protected assignProvider: AddonModAssignProvider,
protected textUtils: CoreTextUtilsProvider,
protected feedbackDelegate: AddonModAssignFeedbackDelegate,
protected submissionDelegate: AddonModAssignSubmissionDelegate,
protected courseHelper: CoreCourseHelperProvider,
protected groupsProvider: CoreGroupsProvider,
protected gradesHelper: CoreGradesHelperProvider,
protected userProvider: CoreUserProvider,
protected assignHelper: AddonModAssignHelperProvider,
protected syncProvider: AddonModAssignSyncProvider) {
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils);
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper);
}
/**

View File

@ -1,4 +1,4 @@
<a *ngIf="commentsEnabled" ion-item text-wrap (click)="showComments($event)" detail-none>
<h2>{{plugin.name}}</h2>
<core-comments contextLevel="module" [instanceId]="assign.cmid" component="assignsubmission_comments" [itemId]="submission.id" area="submission_comments" [title]="plugin.name"></core-comments>
<core-comments contextLevel="module" [instanceId]="assign.cmid" component="assignsubmission_comments" [itemId]="submission.id" area="submission_comments" [title]="plugin.name" [courseId]="assign.course"></core-comments>
</a>

View File

@ -3,7 +3,7 @@
<h2>{{ plugin.name }}</h2>
<p *ngIf="words">{{ 'addon.mod_assign.numwords' | translate: {'$a': words} }}</p>
<p>
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true" [fullTitle]="plugin.name" [text]="text"></core-format-text>
<core-format-text [component]="component" [componentId]="assign.cmid" [maxHeight]="80" [fullOnClick]="true" [fullTitle]="plugin.name" [text]="text" contextLevel="module" [contextInstanceId]="assign.cmid" [courseId]="assign.course"></core-format-text>
</p>
</ion-item>

View File

@ -75,7 +75,8 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
if (text) {
// Open a new state with the interpolated contents.
this.textUtils.expandText(this.plugin.name, text, this.component, this.assign.cmid);
this.textUtils.expandText(this.plugin.name, text, this.component, this.assign.cmid, undefined, true,
'module', this.assign.cmid, this.assign.course);
}
});
} else {

View File

@ -16,11 +16,11 @@
<!-- Content. -->
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description>
<div padding class="safe-padding-horizontal">
<core-navigation-bar [previous]="previousChapter > 0 && previousChapter" [next]="nextChapter > 0 && nextChapter" (action)="changeChapter($event)"></core-navigation-bar>
<core-format-text [component]="component" [componentId]="componentId" [text]="chapterContent"></core-format-text>
<core-format-text [component]="component" [componentId]="componentId" [text]="chapterContent" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text>
<div margin-top *ngIf="tagsEnabled && contentsMap && contentsMap[currentChapter] && contentsMap[currentChapter].tags && contentsMap[currentChapter].tags.length > 0">
<b>{{ 'core.tag.tags' | translate }}:</b>
<core-tag-list [tags]="contentsMap[currentChapter].tags"></core-tag-list>

View File

@ -66,8 +66,10 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
showToc(event: MouseEvent): void {
// Create the toc modal.
const modal = this.modalCtrl.create('AddonModBookTocPage', {
moduleId: this.module.id,
chapters: this.chapters,
selected: this.currentChapter
selected: this.currentChapter,
courseId: this.courseId
}, { cssClass: 'core-modal-lateral',
showBackdrop: true,
enableBackdropDismiss: true,

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-title><core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text></ion-title>
<ion-buttons end>
<!-- The buttons defined by the component will be added in here. -->

View File

@ -12,7 +12,7 @@
<nav>
<ion-list>
<a ion-item text-wrap *ngFor="let chapter of chapters" (click)="loadChapter(chapter.id)" [class.core-nav-item-selected]="selected == chapter.id" [class.item-dimmed]="chapter.hidden">
<p [attr.padding-left]="chapter.level == 1 ? true : null">{{chapter.number}} {{chapter.title}}</p>
<p [attr.padding-left]="chapter.level == 1 ? true : null">{{chapter.number}} <core-format-text [text]="chapter.title" contextLevel="module" [contextInstanceId]="moduleId" [courseId]="courseId"></core-format-text></p>
</a>
</ion-list>
</nav>

View File

@ -25,12 +25,16 @@ import { AddonModBookTocChapter } from '../../providers/book';
templateUrl: 'toc.html'
})
export class AddonModBookTocPage {
moduleId: number;
chapters: AddonModBookTocChapter[];
selected: number;
courseId: number;
constructor(navParams: NavParams, private viewCtrl: ViewController) {
this.moduleId = navParams.get('moduleId');
this.chapters = navParams.get('chapters') || [];
this.selected = navParams.get('selected');
this.courseId = navParams.get('courseId');
}
/**

View File

@ -22,6 +22,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler';
import { AddonModBookProvider } from './book';
import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
/**
* Handler to prefetch books.
@ -33,11 +34,17 @@ export class AddonModBookPrefetchHandler extends CoreCourseResourcePrefetchHandl
component = AddonModBookProvider.COMPONENT;
updatesNames = /^configuration$|^.*files$|^entries$/;
constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider,
courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider,
domUtils: CoreDomUtilsProvider, protected bookProvider: AddonModBookProvider) {
constructor(translate: TranslateService,
appProvider: CoreAppProvider,
utils: CoreUtilsProvider,
courseProvider: CoreCourseProvider,
filepoolProvider: CoreFilepoolProvider,
sitesProvider: CoreSitesProvider,
domUtils: CoreDomUtilsProvider,
filterHelper: CoreFilterHelperProvider,
protected bookProvider: AddonModBookProvider) {
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils);
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper);
}
/**

View File

@ -12,7 +12,7 @@
<!-- Content. -->
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description>
<ion-card icon-start class="core-info-card" *ngIf="chatInfo">
<ion-icon name="time"></ion-icon> {{ 'addon.mod_chat.sessionstart' | translate:{$a: chatInfo} }}

View File

@ -95,7 +95,12 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp
*/
enterChat(): void {
const title = this.chat.name || this.moduleName;
this.navCtrl.push('AddonModChatChatPage', {chatId: this.chat.id, courseId: this.courseId, title: title });
this.navCtrl.push('AddonModChatChatPage', {
chatId: this.chat.id,
courseId: this.courseId,
title: title,
cmId: this.module.id
});
}
/**

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-title><core-format-text [text]="title" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId"></core-format-text></ion-title>
<ion-buttons end>
<button *ngIf="loaded" ion-button icon-only (click)="showChatUsers()">
<ion-icon name="people"></ion-icon>
@ -43,7 +43,7 @@
<ion-badge text-wrap color="info" *ngIf="!message.system && !message.beep">
<span><core-icon name="fa-asterisk"></core-icon> {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }}
<strong>{{ message.userfullname }} <core-format-text [text]="message.message" (afterRender)="last && scrollToBottom()"></core-format-text></strong></span>
<strong>{{ message.userfullname }} <core-format-text [text]="message.message" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId" (afterRender)="last && scrollToBottom()"></core-format-text></strong></span>
</ion-badge>
</div>
@ -56,7 +56,7 @@
</h2>
<p class="addon-message-text">
<core-format-text [text]="message.message" (afterRender)="last && scrollToBottom()"></core-format-text>
<core-format-text [text]="message.message" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId" (afterRender)="last && scrollToBottom()"></core-format-text>
</p>
<div class="tail" *ngIf="message.showTail"></div>
</ion-item>

View File

@ -46,6 +46,7 @@ export class AddonModChatChatPage {
isOnline: boolean;
currentUserId: number;
sending: boolean;
cmId: number;
protected logger;
protected courseId: number;
@ -67,6 +68,8 @@ export class AddonModChatChatPage {
this.chatId = navParams.get('chatId');
this.courseId = navParams.get('courseId');
this.title = navParams.get('title');
this.cmId = navParams.get('cmId');
this.logger = logger.getInstance('AddonModChoiceChoicePage');
this.currentUserId = sitesProvider.getCurrentSiteUserId();
this.isOnline = this.appProvider.isOnline();

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-title><core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text></ion-title>
<ion-buttons end>
<!-- The buttons defined by the component will be added in here. -->

View File

@ -40,7 +40,7 @@
<ion-badge text-wrap color="info" *ngIf="!message.issystem && !message.beep">
<span><core-icon name="fa-asterisk"></core-icon> {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }}
<strong>{{ message.userfullname }} <core-format-text [text]="message.message"></core-format-text></strong></span>
<strong>{{ message.userfullname }} <core-format-text [text]="message.message" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId"></core-format-text></strong></span>
</ion-badge>
</div>
@ -53,7 +53,7 @@
</h2>
<p class="addon-message-text">
<core-format-text [text]="message.message"></core-format-text>
<core-format-text [text]="message.message" contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId"></core-format-text>
</p>
<div class="tail" *ngIf="message.showTail"></div>
</ion-item>

View File

@ -31,14 +31,15 @@ import { AddonModChatHelperProvider, AddonModChatSessionMessageForView } from '.
export class AddonModChatSessionMessagesPage {
currentUserId: number;
cmId: number;
messages: AddonModChatSessionMessageForView[] = [];
loaded = false;
protected courseId: number;
protected chatId: number;
protected sessionStart: number;
protected sessionEnd: number;
protected groupId: number;
protected loaded = false;
protected messages: AddonModChatSessionMessageForView[] = [];
constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private chatProvider: AddonModChatProvider,
sitesProvider: CoreSitesProvider, private chatHelper: AddonModChatHelperProvider, private userProvider: CoreUserProvider) {
@ -47,6 +48,7 @@ export class AddonModChatSessionMessagesPage {
this.groupId = navParams.get('groupId');
this.sessionStart = navParams.get('sessionStart');
this.sessionEnd = navParams.get('sessionEnd');
this.cmId = navParams.get('cmId');
this.currentUserId = sitesProvider.getCurrentSiteUserId();
this.fetchMessages();

View File

@ -140,7 +140,8 @@ export class AddonModChatSessionsPage {
chatId: this.chatId,
groupId: this.groupId,
sessionStart: session.sessionstart,
sessionEnd: session.sessionend
sessionEnd: session.sessionend,
cmId: this.cmId
};
this.splitviewCtrl.push('AddonModChatSessionMessagesPage', params);
}

View File

@ -12,7 +12,7 @@
<core-loading [hideUntil]="usersLoaded">
<ion-item text-wrap *ngFor="let user of users" [class.addon-mod-chat-user]="currentUserId != user.id && isOnline">
<ion-avatar core-user-avatar [user]="user" item-start></ion-avatar>
<h2><core-format-text [text]="user.fullname"></core-format-text></h2>
<h2>{{ user.fullname }}</h2>
<ng-container *ngIf="currentUserId != user.id && isOnline">
<button ion-button clear icon-left (click)="talkTo(user)">
<ion-icon name="chatboxes"></ion-icon>

View File

@ -24,6 +24,7 @@ import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler';
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonModChatProvider, AddonModChatChat } from './chat';
import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
/**
* Handler to prefetch chats.
@ -41,11 +42,12 @@ export class AddonModChatPrefetchHandler extends CoreCourseActivityPrefetchHandl
filepoolProvider: CoreFilepoolProvider,
sitesProvider: CoreSitesProvider,
domUtils: CoreDomUtilsProvider,
filterHelper: CoreFilterHelperProvider,
private groupsProvider: CoreGroupsProvider,
private userProvider: CoreUserProvider,
private chatProvider: AddonModChatProvider) {
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils);
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper);
}
/**

View File

@ -14,7 +14,7 @@
<!-- Content. -->
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description>
<!-- Activity availability messages -->
<ion-card class="core-info-card" icon-start *ngIf="choiceNotOpenYet">
@ -24,7 +24,7 @@
</ion-card>
<ion-card class="core-info-card" icon-start *ngIf="choiceClosed">
<ion-icon name="information-circle"></ion-icon>
<p *ngIf="options && options.length">{{ 'addon.mod_choice.yourselection' | translate }} <core-format-text [text]="options[0].text"></core-format-text></p>
<p *ngIf="options && options.length">{{ 'addon.mod_choice.yourselection' | translate }} <core-format-text [text]="options[0].text" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text></p>
<p>{{ 'addon.mod_choice.expired' | translate:{$a: closeTimeReadable} }}</p>
</ion-card>
@ -43,13 +43,13 @@
<ion-card *ngIf="options && options.length">
<ng-container *ngIf="choice.allowmultiple">
<ion-item text-wrap *ngFor="let option of options">
<ion-label><core-format-text [text]="option.text"></core-format-text> <span *ngIf="choice.limitanswers && option.countanswers >= option.maxanswers">{{ 'addon.mod_choice.full' | translate }}</span></ion-label>
<ion-label><core-format-text [text]="option.text" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text> <span *ngIf="choice.limitanswers && option.countanswers >= option.maxanswers">{{ 'addon.mod_choice.full' | translate }}</span></ion-label>
<ion-checkbox item-end [(ngModel)]="option.checked" [disabled]="option.disabled || !canEdit"></ion-checkbox>
</ion-item>
</ng-container>
<ng-container *ngIf="!choice.allowmultiple">
<ion-item text-wrap *ngFor="let option of options" radio-group [(ngModel)]="selectedOption.id">
<ion-label><core-format-text [text]="option.text"></core-format-text> <span *ngIf="choice.limitanswers && option.countanswers >= option.maxanswers">{{ 'addon.mod_choice.full' | translate }}</span></ion-label>
<ion-label><core-format-text [text]="option.text" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text> <span *ngIf="choice.limitanswers && option.countanswers >= option.maxanswers">{{ 'addon.mod_choice.full' | translate }}</span></ion-label>
<ion-radio color="primary" [value]="option.id" [disabled]="option.disabled || !canEdit"></ion-radio>
</ion-item>
</ng-container>
@ -73,13 +73,13 @@
<ion-icon item-start name="warning" color="warning"></ion-icon> {{ 'addon.mod_choice.resultsnotsynced' | translate }}
</ion-item>
<ion-item>
<core-chart type="pie" [data]="data" [labels]="labels" height="300"></core-chart>
<core-chart type="pie" [data]="data" [labels]="labels" height="300" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-chart>
</ion-item>
</ion-col>
<ion-col *ngIf="choice.publish && results" col-12 col-lg-7>
<ion-item-group *ngFor="let result of results">
<ion-item-divider text-wrap>
<h2><core-format-text [text]="result.text"></core-format-text></h2>
<h2><core-format-text [text]="result.text" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text></h2>
<p>{{ 'addon.mod_choice.numberofuser' | translate }}: {{ result.numberofuser }} ({{ 'core.percentagenumber' | translate: {$a: result.percentageamountfixed} }})</p>
</ion-item-divider>
<a ion-item *ngFor="let user of result.userresponses" core-user-link [courseId]="courseid" [userId]="user.userid" [title]="user.fullname" text-wrap>

View File

@ -1,6 +1,6 @@
<ion-header>
<ion-navbar core-back-button>
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
<ion-title><core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text></ion-title>
<ion-buttons end>
<!-- The buttons defined by the component will be added in here. -->

View File

@ -24,6 +24,7 @@ import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/acti
import { CoreUserProvider } from '@core/user/providers/user';
import { AddonModChoiceSyncProvider } from './sync';
import { AddonModChoiceProvider } from './choice';
import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
/**
* Handler to prefetch choices.
@ -37,12 +38,19 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan
protected syncProvider: AddonModChoiceSyncProvider; // It will be injected later to prevent circular dependencies.
constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider,
courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider,
domUtils: CoreDomUtilsProvider, protected choiceProvider: AddonModChoiceProvider,
protected userProvider: CoreUserProvider, protected injector: Injector) {
constructor(translate: TranslateService,
appProvider: CoreAppProvider,
utils: CoreUtilsProvider,
courseProvider: CoreCourseProvider,
filepoolProvider: CoreFilepoolProvider,
sitesProvider: CoreSitesProvider,
domUtils: CoreDomUtilsProvider,
filterHelper: CoreFilterHelperProvider,
protected choiceProvider: AddonModChoiceProvider,
protected userProvider: CoreUserProvider,
protected injector: Injector) {
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils);
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper);
}
/**

View File

@ -22,15 +22,15 @@
<ion-icon name="thumbs-down"></ion-icon>
</a>
<core-comments *ngIf="action == 'comments' && mode == 'list'" contextLevel="module" [instanceId]="database.coursemodule" component="mod_data" [itemId]="entry.id" area="database_entry"></core-comments>
<core-comments *ngIf="action == 'comments' && mode == 'list'" contextLevel="module" [instanceId]="database.coursemodule" component="mod_data" [itemId]="entry.id" area="database_entry" [courseId]="database.course"></core-comments>
<span *ngIf="action == 'timeadded'">{{ entry.timecreated * 1000 | coreFormatDate }}</span>
<span *ngIf="action == 'timemodified'">{{ entry.timemodified * 1000 | coreFormatDate }}</span>
<a *ngIf="action == 'userpicture'" core-user-link [courseId]="database.courseid" [userId]="entry.userid" [title]="entry.fullname">
<a *ngIf="action == 'userpicture'" core-user-link [courseId]="database.course" [userId]="entry.userid" [title]="entry.fullname">
<img class="avatar-round" [src]="userPicture" [alt]="'core.pictureof' | translate:{$a: entry.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'" role="presentation">
</a>
<a *ngIf="action == 'user' && entry" core-user-link [courseId]="database.courseid" [userId]="entry.userid" [title]="entry.fullname">{{entry.fullname}}</a>
<a *ngIf="action == 'user' && entry" core-user-link [courseId]="database.course" [userId]="entry.userid" [title]="entry.fullname">{{entry.fullname}}</a>
<core-tag-list *ngIf="tagsEnabled && action == 'tags' && entry" [tags]="entry.tags"></core-tag-list>

View File

@ -19,7 +19,7 @@
<!-- Content. -->
<core-loading [hideUntil]="loaded" class="core-loading-center">
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description>
<!-- Data done in offline but not synchronized -->
<div class="core-warning-card" icon-start *ngIf="hasOffline || hasOfflineRatings">

View File

@ -12,4 +12,4 @@
</ion-item>
</span>
<core-format-text *ngIf="isShowOrListMode() && value && value.content" [text]="value.content"></core-format-text>
<core-format-text *ngIf="isShowOrListMode() && value && value.content" [text]="value.content" [filter]="false"></core-format-text>

Some files were not shown because too many files have changed in this diff Show More