commit
691ad40490
|
@ -524,15 +524,13 @@
|
||||||
"addon.mod_choice.cannotsubmit": "choice",
|
"addon.mod_choice.cannotsubmit": "choice",
|
||||||
"addon.mod_choice.choiceoptions": "choice",
|
"addon.mod_choice.choiceoptions": "choice",
|
||||||
"addon.mod_choice.errorgetchoice": "local_moodlemobileapp",
|
"addon.mod_choice.errorgetchoice": "local_moodlemobileapp",
|
||||||
"addon.mod_choice.expired": "choice",
|
|
||||||
"addon.mod_choice.full": "choice",
|
"addon.mod_choice.full": "choice",
|
||||||
"addon.mod_choice.limita": "choice",
|
"addon.mod_choice.limita": "choice",
|
||||||
"addon.mod_choice.modulenameplural": "choice",
|
"addon.mod_choice.modulenameplural": "choice",
|
||||||
"addon.mod_choice.noresultsviewable": "choice",
|
"addon.mod_choice.noresultsviewable": "choice",
|
||||||
"addon.mod_choice.notopenyet": "choice",
|
|
||||||
"addon.mod_choice.numberofuser": "choice",
|
"addon.mod_choice.numberofuser": "choice",
|
||||||
"addon.mod_choice.numberofuserinpercentage": "choice",
|
"addon.mod_choice.numberofuserinpercentage": "choice",
|
||||||
"addon.mod_choice.previewonly": "choice",
|
"addon.mod_choice.previewing": "choice",
|
||||||
"addon.mod_choice.publishinfoanonafter": "choice",
|
"addon.mod_choice.publishinfoanonafter": "choice",
|
||||||
"addon.mod_choice.publishinfoanonclose": "choice",
|
"addon.mod_choice.publishinfoanonclose": "choice",
|
||||||
"addon.mod_choice.publishinfofullafter": "choice",
|
"addon.mod_choice.publishinfofullafter": "choice",
|
||||||
|
|
|
@ -9,9 +9,7 @@
|
||||||
|
|
||||||
.ion-text-wrap ion-label {
|
.ion-text-wrap ion-label {
|
||||||
.item-heading, h2, p {
|
.item-heading, h2, p {
|
||||||
white-space: nowrap;
|
@include ellipsis();
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,8 +52,7 @@ h4.core-bold {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
overflow: hidden;
|
@include ellipsis();
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.addon-message-last-message-text {
|
.addon-message-last-message-text {
|
||||||
overflow: hidden;
|
@include ellipsis();
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,9 +51,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
core-format-text {
|
core-format-text {
|
||||||
overflow: hidden;
|
@include ellipsis();
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,13 @@ import { CoreCourseModulePrefetchDelegate } from '@features/course/services/modu
|
||||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
import { CoreCronDelegate } from '@services/cron';
|
import { CoreCronDelegate } from '@services/cron';
|
||||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
||||||
import { AddonModChoiceProvider } from './services/choice';
|
|
||||||
import { OFFLINE_SITE_SCHEMA } from './services/database/choice';
|
import { OFFLINE_SITE_SCHEMA } from './services/database/choice';
|
||||||
import { AddonModChoiceIndexLinkHandler } from './services/handlers/index-link';
|
import { AddonModChoiceIndexLinkHandler } from './services/handlers/index-link';
|
||||||
import { AddonModChoiceListLinkHandler } from './services/handlers/list-link';
|
import { AddonModChoiceListLinkHandler } from './services/handlers/list-link';
|
||||||
import { AddonModChoiceModuleHandler, AddonModChoiceModuleHandlerService } from './services/handlers/module';
|
import { AddonModChoiceModuleHandler, AddonModChoiceModuleHandlerService } from './services/handlers/module';
|
||||||
import { AddonModChoicePrefetchHandler } from './services/handlers/prefetch';
|
import { AddonModChoicePrefetchHandler } from './services/handlers/prefetch';
|
||||||
import { AddonModChoiceSyncCronHandler } from './services/handlers/sync-cron';
|
import { AddonModChoiceSyncCronHandler } from './services/handlers/sync-cron';
|
||||||
|
import { ADDON_MOD_CHOICE_COMPONENT } from './constants';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -57,7 +57,7 @@ const routes: Routes = [
|
||||||
CoreContentLinksDelegate.registerHandler(AddonModChoiceIndexLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(AddonModChoiceIndexLinkHandler.instance);
|
||||||
CoreContentLinksDelegate.registerHandler(AddonModChoiceListLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(AddonModChoiceListLinkHandler.instance);
|
||||||
|
|
||||||
CoreCourseHelper.registerModuleReminderClick(AddonModChoiceProvider.COMPONENT);
|
CoreCourseHelper.registerModuleReminderClick(ADDON_MOD_CHOICE_COMPONENT);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -12,32 +12,17 @@
|
||||||
[courseId]="courseId" [hasDataToSync]="hasOffline" (completionChanged)="onCompletionChange()" />
|
[courseId]="courseId" [hasDataToSync]="hasOffline" (completionChanged)="onCompletionChange()" />
|
||||||
|
|
||||||
<!-- Activity availability messages -->
|
<!-- Activity availability messages -->
|
||||||
<ion-card class="core-info-card" *ngIf="choiceNotOpenYet">
|
<ion-card class="core-info-card" *ngIf="showPreview && options.length">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p *ngIf="options.length">{{ 'addon.mod_choice.previewonly' | translate:{$a: openTimeReadable} }}</p>
|
<p>{{ 'addon.mod_choice.previewing' | translate }}</p>
|
||||||
<p *ngIf="!options.length">{{ 'addon.mod_choice.notopenyet' | translate:{$a: openTimeReadable} }}</p>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ion-card>
|
|
||||||
|
|
||||||
<ion-card class="core-info-card" *ngIf="choiceClosed">
|
|
||||||
<ion-item>
|
|
||||||
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
|
||||||
<ion-label>
|
|
||||||
<p *ngIf="options.length">
|
|
||||||
{{ 'addon.mod_choice.yourselection' | translate }}
|
|
||||||
<core-format-text [text]="options[0].text" contextLevel="module" [contextInstanceId]="module.id"
|
|
||||||
[courseId]="courseId" />
|
|
||||||
</p>
|
|
||||||
<p>{{ 'addon.mod_choice.expired' | translate:{$a: closeTimeReadable} }}</p>
|
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
<!-- Inform what will happen with the choices. -->
|
<!-- Inform what will happen with the choices. -->
|
||||||
<ion-card class="core-info-card" *ngIf="canEdit && publishInfo && options.length">
|
<ion-card class="core-info-card" *ngIf="publishInfo && options.length">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
||||||
<ion-label>{{ publishInfo | translate }}</ion-label>
|
<ion-label>{{ publishInfo | translate }}</ion-label>
|
||||||
|
@ -45,24 +30,40 @@
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
<!-- Choice options -->
|
<!-- Choice options -->
|
||||||
<ion-card *ngIf="options.length && choice">
|
<ion-card *ngIf="options.length && choice && (canEdit || showPreview)">
|
||||||
<ng-container *ngIf="choice.allowmultiple">
|
<ng-container *ngIf="choice.allowmultiple">
|
||||||
<ion-item class="ion-text-wrap" *ngFor="let option of options">
|
<ion-item class="ion-text-wrap" *ngFor="let option of options">
|
||||||
<ion-checkbox [(ngModel)]="option.checked" [disabled]="option.disabled || !canEdit">
|
<ion-checkbox [(ngModel)]="option.checked" [disabled]="option.disabled || showPreview">
|
||||||
<ng-container *ngTemplateOutlet="optionLabelTemplate; context: {option: option}" />
|
<ng-container *ngTemplateOutlet="optionLabelTemplate; context: {option: option}" />
|
||||||
</ion-checkbox>
|
</ion-checkbox>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ion-radio-group *ngIf="!choice.allowmultiple" [(ngModel)]="selectedOption.id">
|
<ion-radio-group *ngIf="!choice.allowmultiple" [(ngModel)]="selectedOption.id">
|
||||||
<ion-item class="ion-text-wrap" *ngFor="let option of options">
|
<ion-item class="ion-text-wrap" *ngFor="let option of options">
|
||||||
<ion-radio [value]="option.id" [disabled]="option.disabled || !canEdit">
|
<ion-radio [value]="option.id" [disabled]="option.disabled || showPreview">
|
||||||
<ng-container *ngTemplateOutlet="optionLabelTemplate; context: {option: option}" />
|
<ng-container *ngTemplateOutlet="optionLabelTemplate; context: {option: option}" />
|
||||||
</ion-radio>
|
</ion-radio>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-radio-group>
|
</ion-radio-group>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-card *ngIf="options.length && choice && !canEdit && !showPreview">
|
||||||
|
<ion-item-divider>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'addon.mod_choice.yourselection' | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item-divider>
|
||||||
|
<ng-container *ngFor="let option of options">
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="option.checked">
|
||||||
|
<ion-label>
|
||||||
|
<core-format-text [text]="option.text" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" />
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
<!-- Choice results -->
|
<!-- Choice results -->
|
||||||
<div *ngIf="canSeeResults && choice">
|
<ng-container *ngIf="canSeeResults && choice">
|
||||||
<ion-item-divider>
|
<ion-item-divider>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'addon.mod_choice.responses' | translate }}</h2>
|
<h2>{{ 'addon.mod_choice.responses' | translate }}</h2>
|
||||||
|
@ -115,9 +116,9 @@
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
</div>
|
</ng-container>
|
||||||
|
|
||||||
<ion-card class="core-info-card" *ngIf="!canSeeResults && !choiceNotOpenYet">
|
<ion-card class="core-info-card" *ngIf="!canSeeResults && showResultsMessage">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
<ion-icon name="fas-circle-info" slot="start" aria-hidden="true" />
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
|
|
@ -26,7 +26,6 @@ import {
|
||||||
AddonModChoice,
|
AddonModChoice,
|
||||||
AddonModChoiceChoice,
|
AddonModChoiceChoice,
|
||||||
AddonModChoiceOption,
|
AddonModChoiceOption,
|
||||||
AddonModChoiceProvider,
|
|
||||||
AddonModChoiceResult,
|
AddonModChoiceResult,
|
||||||
} from '../../services/choice';
|
} from '../../services/choice';
|
||||||
import { AddonModChoiceOffline } from '../../services/choice-offline';
|
import { AddonModChoiceOffline } from '../../services/choice-offline';
|
||||||
|
@ -37,6 +36,7 @@ import {
|
||||||
AddonModChoiceSyncResult,
|
AddonModChoiceSyncResult,
|
||||||
} from '../../services/choice-sync';
|
} from '../../services/choice-sync';
|
||||||
import { AddonModChoicePrefetchHandler } from '../../services/handlers/prefetch';
|
import { AddonModChoicePrefetchHandler } from '../../services/handlers/prefetch';
|
||||||
|
import { ADDON_MOD_CHOICE_COMPONENT, ADDON_MOD_CHOICE_PUBLISH_ANONYMOUS, AddonModChoiceShowResults } from '../../constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays a choice.
|
* Component that displays a choice.
|
||||||
|
@ -47,14 +47,14 @@ import { AddonModChoicePrefetchHandler } from '../../services/handlers/prefetch'
|
||||||
})
|
})
|
||||||
export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
|
export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit {
|
||||||
|
|
||||||
component = AddonModChoiceProvider.COMPONENT;
|
component = ADDON_MOD_CHOICE_COMPONENT;
|
||||||
pluginName = 'choice';
|
pluginName = 'choice';
|
||||||
|
|
||||||
choice?: AddonModChoiceChoice;
|
choice?: AddonModChoiceChoice;
|
||||||
options: AddonModChoiceOption[] = [];
|
options: AddonModChoiceOption[] = [];
|
||||||
selectedOption: {id: number} = { id: -1 };
|
selectedOption: {id: number} = { id: -1 };
|
||||||
choiceNotOpenYet = false;
|
showPreview = false;
|
||||||
choiceClosed = false;
|
showResultsMessage = false;
|
||||||
canEdit = false;
|
canEdit = false;
|
||||||
canDelete = false;
|
canDelete = false;
|
||||||
canSeeResults = false;
|
canSeeResults = false;
|
||||||
|
@ -62,13 +62,11 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
labels: string[] = [];
|
labels: string[] = [];
|
||||||
results: AddonModChoiceResultFormatted[] = [];
|
results: AddonModChoiceResultFormatted[] = [];
|
||||||
publishInfo?: string; // Message explaining the user what will happen with his choices.
|
publishInfo?: string; // Message explaining the user what will happen with his choices.
|
||||||
openTimeReadable?: string;
|
|
||||||
closeTimeReadable?: string;
|
|
||||||
|
|
||||||
protected userId?: number;
|
protected userId?: number;
|
||||||
protected syncEventName = AddonModChoiceSyncProvider.AUTO_SYNCED;
|
protected syncEventName = AddonModChoiceSyncProvider.AUTO_SYNCED;
|
||||||
protected hasAnsweredOnline = false;
|
protected hasAnsweredOnline = false;
|
||||||
protected now = Date.now();
|
protected now = CoreTimeUtils.timestamp();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected content?: IonContent,
|
protected content?: IonContent,
|
||||||
|
@ -121,7 +119,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
this.now = Date.now();
|
this.now = CoreTimeUtils.timestamp();
|
||||||
|
|
||||||
this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
|
this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
|
||||||
|
|
||||||
|
@ -135,14 +133,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.choice.timeopen = (this.choice.timeopen || 0) * 1000;
|
|
||||||
this.choice.timeclose = (this.choice.timeclose || 0) * 1000;
|
|
||||||
this.openTimeReadable = CoreTimeUtils.userDate(this.choice.timeopen);
|
|
||||||
this.closeTimeReadable = CoreTimeUtils.userDate(this.choice.timeclose);
|
|
||||||
|
|
||||||
this.description = this.choice.intro;
|
this.description = this.choice.intro;
|
||||||
this.choiceNotOpenYet = !!this.choice.timeopen && this.choice.timeopen > this.now;
|
|
||||||
this.choiceClosed = !!this.choice.timeclose && this.choice.timeclose <= this.now;
|
|
||||||
|
|
||||||
this.dataRetrieved.emit(this.choice);
|
this.dataRetrieved.emit(this.choice);
|
||||||
|
|
||||||
|
@ -171,7 +162,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
options = await this.getOfflineResponses(choice, options);
|
options = await this.getOfflineResponses(choice, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOpen = this.isChoiceOpen(choice);
|
const isOpen = AddonModChoice.choiceHasBeenOpened(choice, this.now) &&
|
||||||
|
!AddonModChoice.choiceHasBeenClosed(choice, this.now);
|
||||||
|
|
||||||
this.selectedOption = { id: -1 }; // Single choice model.
|
this.selectedOption = { id: -1 }; // Single choice model.
|
||||||
const hasAnswered = options.some((option) => {
|
const hasAnswered = options.some((option) => {
|
||||||
|
@ -186,30 +178,49 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.canEdit = isOpen && (choice.allowupdate! || !hasAnswered);
|
this.canEdit = isOpen && (!!choice.allowupdate || !hasAnswered);
|
||||||
this.canDelete = isOpen && choice.allowupdate! && hasAnswered;
|
this.canDelete = isOpen && !!choice.allowupdate && hasAnswered;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
if (!this.canEdit) {
|
this.setPublishInfo(choice, hasAnswered);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set publish info message.
|
||||||
|
*
|
||||||
|
* @param choice Choice data.
|
||||||
|
*/
|
||||||
|
protected setPublishInfo(choice: AddonModChoiceChoice, hasAnswered: boolean): void {
|
||||||
|
const choiceOpen = !AddonModChoice.choiceHasBeenOpened(choice, this.now) &&
|
||||||
|
!AddonModChoice.choiceHasBeenClosed(choice, this.now);
|
||||||
|
|
||||||
|
if ((!choice.allowupdate && hasAnswered) || !choiceOpen) {
|
||||||
|
this.showPreview = false;
|
||||||
|
this.showResultsMessage = true;
|
||||||
|
this.publishInfo = '';
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.showResultsMessage = false;
|
||||||
|
this.showPreview = !!choice.showpreview;
|
||||||
|
|
||||||
// Calculate the publish info message.
|
// Calculate the publish info message.
|
||||||
switch (choice.showresults) {
|
switch (choice.showresults) {
|
||||||
case AddonModChoiceProvider.RESULTS_NOT:
|
case AddonModChoiceShowResults.SHOWRESULTS_NOT:
|
||||||
this.publishInfo = 'addon.mod_choice.publishinfonever';
|
this.publishInfo = 'addon.mod_choice.publishinfonever';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AddonModChoiceProvider.RESULTS_AFTER_ANSWER:
|
case AddonModChoiceShowResults.SHOWRESULTS_AFTER_ANSWER:
|
||||||
if (choice.publish == AddonModChoiceProvider.PUBLISH_ANONYMOUS) {
|
if (choice.publish === ADDON_MOD_CHOICE_PUBLISH_ANONYMOUS) {
|
||||||
this.publishInfo = 'addon.mod_choice.publishinfoanonafter';
|
this.publishInfo = 'addon.mod_choice.publishinfoanonafter';
|
||||||
} else {
|
} else {
|
||||||
this.publishInfo = 'addon.mod_choice.publishinfofullafter';
|
this.publishInfo = 'addon.mod_choice.publishinfofullafter';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AddonModChoiceProvider.RESULTS_AFTER_CLOSE:
|
case AddonModChoiceShowResults.SHOWRESULTS_AFTER_CLOSE:
|
||||||
if (choice.publish == AddonModChoiceProvider.PUBLISH_ANONYMOUS) {
|
if (choice.publish === ADDON_MOD_CHOICE_PUBLISH_ANONYMOUS) {
|
||||||
this.publishInfo = 'addon.mod_choice.publishinfoanonclose';
|
this.publishInfo = 'addon.mod_choice.publishinfoanonclose';
|
||||||
} else {
|
} else {
|
||||||
this.publishInfo = 'addon.mod_choice.publishinfofullclose';
|
this.publishInfo = 'addon.mod_choice.publishinfofullclose';
|
||||||
|
@ -288,7 +299,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* @returns Resolved when done.
|
* @returns Resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async fetchResults(choice: AddonModChoiceChoice): Promise<void> {
|
protected async fetchResults(choice: AddonModChoiceChoice): Promise<void> {
|
||||||
if (this.choiceNotOpenYet) {
|
if (!AddonModChoice.choiceHasBeenOpened(choice, this.now)) {
|
||||||
// Cannot see results yet.
|
// Cannot see results yet.
|
||||||
this.canSeeResults = false;
|
this.canSeeResults = false;
|
||||||
|
|
||||||
|
@ -310,7 +321,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
|
|
||||||
return Object.assign(result, { percentageamountfixed: result.percentageamount.toFixed(1) });
|
return Object.assign(result, { percentageamountfixed: result.percentageamount.toFixed(1) });
|
||||||
});
|
});
|
||||||
this.canSeeResults = hasVotes || AddonModChoice.canStudentSeeResults(choice, this.hasAnsweredOnline);
|
|
||||||
|
this.canSeeResults = hasVotes || AddonModChoice.canStudentSeeResults(choice, this.hasAnsweredOnline, this.now);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -326,16 +338,6 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
this.analyticsLogEvent('mod_choice_view_choice');
|
this.analyticsLogEvent('mod_choice_view_choice');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a choice is open.
|
|
||||||
*
|
|
||||||
* @param choice Choice data.
|
|
||||||
* @returns True if choice is open, false otherwise.
|
|
||||||
*/
|
|
||||||
protected isChoiceOpen(choice: AddonModChoiceChoice): boolean {
|
|
||||||
return (!choice.timeopen || choice.timeopen <= this.now) && (!choice.timeclose || choice.timeclose > this.now);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the user has selected at least one option.
|
* Return true if the user has selected at least one option.
|
||||||
*
|
*
|
||||||
|
@ -357,15 +359,17 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* Save options selected.
|
* Save options selected.
|
||||||
*/
|
*/
|
||||||
async save(): Promise<void> {
|
async save(): Promise<void> {
|
||||||
const choice = this.choice!;
|
if (!this.choice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Only show confirm if choice doesn't allow update.
|
// Only show confirm if choice doesn't allow update.
|
||||||
if (!choice.allowupdate) {
|
if (!this.choice.allowupdate) {
|
||||||
await CoreDomUtils.showConfirm(Translate.instant('core.areyousure'));
|
await CoreDomUtils.showConfirm(Translate.instant('core.areyousure'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const responses: number[] = [];
|
const responses: number[] = [];
|
||||||
if (choice.allowmultiple) {
|
if (this.choice.allowmultiple) {
|
||||||
this.options.forEach((option) => {
|
this.options.forEach((option) => {
|
||||||
if (option.checked) {
|
if (option.checked) {
|
||||||
responses.push(option.id);
|
responses.push(option.id);
|
||||||
|
@ -378,7 +382,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const online = await AddonModChoice.submitResponse(choice.id, choice.name, this.courseId, responses);
|
const online = await AddonModChoice.submitResponse(this.choice.id, this.choice.name, this.courseId, responses);
|
||||||
|
|
||||||
this.content?.scrollToTop();
|
this.content?.scrollToTop();
|
||||||
|
|
||||||
|
@ -402,6 +406,10 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* Delete options selected.
|
* Delete options selected.
|
||||||
*/
|
*/
|
||||||
async delete(): Promise<void> {
|
async delete(): Promise<void> {
|
||||||
|
if (!this.choice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await CoreDomUtils.showDeleteConfirm();
|
await CoreDomUtils.showDeleteConfirm();
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -412,7 +420,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await AddonModChoice.deleteResponses(this.choice!.id, this.choice!.name, this.courseId);
|
await AddonModChoice.deleteResponses(this.choice.id, this.choice.name, this.courseId);
|
||||||
|
|
||||||
this.content?.scrollToTop();
|
this.content?.scrollToTop();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
// (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.
|
||||||
|
|
||||||
|
export const ADDON_MOD_CHOICE_COMPONENT = 'mmaModChoice';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Possible show results values.
|
||||||
|
*/
|
||||||
|
export const enum AddonModChoiceShowResults {
|
||||||
|
SHOWRESULTS_NOT = 0,
|
||||||
|
SHOWRESULTS_AFTER_ANSWER = 1,
|
||||||
|
SHOWRESULTS_AFTER_CLOSE = 2,
|
||||||
|
SHOWRESULTS_ALWAYS = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Possible choice publish values.
|
||||||
|
export const ADDON_MOD_CHOICE_PUBLISH_ANONYMOUS = false;
|
||||||
|
export const ADDON_MOD_CHOICE_PUBLISH_NAMES = true;
|
|
@ -2,15 +2,13 @@
|
||||||
"cannotsubmit": "Sorry, there was a problem submitting your choice. Please try again.",
|
"cannotsubmit": "Sorry, there was a problem submitting your choice. Please try again.",
|
||||||
"choiceoptions": "Choice options",
|
"choiceoptions": "Choice options",
|
||||||
"errorgetchoice": "Error getting choice data.",
|
"errorgetchoice": "Error getting choice data.",
|
||||||
"expired": "This activity closed on {{$a}}.",
|
|
||||||
"full": "(Full)",
|
"full": "(Full)",
|
||||||
"limita": "Limit: {{$a}}",
|
"limita": "Limit: {{$a}}",
|
||||||
"modulenameplural": "Choices",
|
"modulenameplural": "Choices",
|
||||||
"noresultsviewable": "The results are not currently viewable.",
|
"noresultsviewable": "The results are not currently viewable.",
|
||||||
"notopenyet": "This activity is not available until {{$a}}.",
|
|
||||||
"numberofuser": "Number of responses",
|
"numberofuser": "Number of responses",
|
||||||
"numberofuserinpercentage": "Percentage of responses",
|
"numberofuserinpercentage": "Percentage of responses",
|
||||||
"previewonly": "This is just a preview of the available options for this activity. You will not be able to submit your choice until {{$a}}.",
|
"previewing": "This is just a preview of the available options for this activity. You will be able to make a choice when it opens.",
|
||||||
"publishinfoanonafter": "Anonymous results will be published after you answer.",
|
"publishinfoanonafter": "Anonymous results will be published after you answer.",
|
||||||
"publishinfoanonclose": "Anonymous results will be published after the activity is closed.",
|
"publishinfoanonclose": "Anonymous results will be published after the activity is closed.",
|
||||||
"publishinfofullafter": "Full results, showing everyone's choices, will be published after you answer.",
|
"publishinfofullafter": "Full results, showing everyone's choices, will be published after you answer.",
|
||||||
|
|
|
@ -23,9 +23,10 @@ import { CoreSites } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { AddonModChoice, AddonModChoiceProvider } from './choice';
|
import { AddonModChoice } from './choice';
|
||||||
import { AddonModChoiceOffline } from './choice-offline';
|
import { AddonModChoiceOffline } from './choice-offline';
|
||||||
import { AddonModChoicePrefetchHandler } from './handlers/prefetch';
|
import { AddonModChoicePrefetchHandler } from './handlers/prefetch';
|
||||||
|
import { ADDON_MOD_CHOICE_COMPONENT } from '../constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to sync choices.
|
* Service to sync choices.
|
||||||
|
@ -150,7 +151,7 @@ export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvid
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sync offline logs.
|
// Sync offline logs.
|
||||||
await CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(AddonModChoiceProvider.COMPONENT, choiceId, siteId));
|
await CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(ADDON_MOD_CHOICE_COMPONENT, choiceId, siteId));
|
||||||
|
|
||||||
const data = await CoreUtils.ignoreErrors(AddonModChoiceOffline.getResponse(choiceId, siteId, userId));
|
const data = await CoreUtils.ignoreErrors(AddonModChoiceOffline.getResponse(choiceId, siteId, userId));
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,7 @@ import { makeSingleton, Translate } from '@singletons';
|
||||||
import { AddonModChoiceOffline } from './choice-offline';
|
import { AddonModChoiceOffline } from './choice-offline';
|
||||||
import { AddonModChoiceAutoSyncData, AddonModChoiceSyncProvider } from './choice-sync';
|
import { AddonModChoiceAutoSyncData, AddonModChoiceSyncProvider } from './choice-sync';
|
||||||
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
|
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
|
||||||
|
import { ADDON_MOD_CHOICE_COMPONENT, AddonModChoiceShowResults } from '../constants';
|
||||||
const ROOT_CACHE_KEY = 'mmaModChoice:';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides some features for choices.
|
* Service that provides some features for choices.
|
||||||
|
@ -36,15 +35,7 @@ const ROOT_CACHE_KEY = 'mmaModChoice:';
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AddonModChoiceProvider {
|
export class AddonModChoiceProvider {
|
||||||
|
|
||||||
static readonly COMPONENT = 'mmaModChoice';
|
protected static readonly ROOT_CACHE_KEY = 'mmaModChoice:';
|
||||||
|
|
||||||
static readonly RESULTS_NOT = 0;
|
|
||||||
static readonly RESULTS_AFTER_ANSWER = 1;
|
|
||||||
static readonly RESULTS_AFTER_CLOSE = 2;
|
|
||||||
static readonly RESULTS_ALWAYS = 3;
|
|
||||||
|
|
||||||
static readonly PUBLISH_ANONYMOUS = false;
|
|
||||||
static readonly PUBLISH_NAMES = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if results can be seen by a student. The student can see the results if:
|
* Check if results can be seen by a student. The student can see the results if:
|
||||||
|
@ -54,14 +45,41 @@ export class AddonModChoiceProvider {
|
||||||
*
|
*
|
||||||
* @param choice Choice to check.
|
* @param choice Choice to check.
|
||||||
* @param hasAnswered True if user has answered the choice, false otherwise.
|
* @param hasAnswered True if user has answered the choice, false otherwise.
|
||||||
|
* @param timeNow Current time in seconds.
|
||||||
* @returns True if the students can see the results.
|
* @returns True if the students can see the results.
|
||||||
*/
|
*/
|
||||||
canStudentSeeResults(choice: AddonModChoiceChoice, hasAnswered: boolean): boolean {
|
canStudentSeeResults(choice: AddonModChoiceChoice, hasAnswered: boolean, timeNow: number): boolean {
|
||||||
const now = Date.now();
|
if (!this.choiceHasBeenOpened(choice, timeNow)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return choice.showresults === AddonModChoiceProvider.RESULTS_ALWAYS ||
|
const choiceClosed = this.choiceHasBeenClosed(choice, timeNow);
|
||||||
choice.showresults === AddonModChoiceProvider.RESULTS_AFTER_CLOSE && choice.timeclose && choice.timeclose <= now ||
|
|
||||||
choice.showresults === AddonModChoiceProvider.RESULTS_AFTER_ANSWER && hasAnswered;
|
return choice.showresults === AddonModChoiceShowResults.SHOWRESULTS_ALWAYS ||
|
||||||
|
choice.showresults === AddonModChoiceShowResults.SHOWRESULTS_AFTER_ANSWER && hasAnswered ||
|
||||||
|
choice.showresults === AddonModChoiceShowResults.SHOWRESULTS_AFTER_CLOSE && choiceClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a choice has been opened.
|
||||||
|
*
|
||||||
|
* @param choice Choice to check.
|
||||||
|
* @param timeNow Current time in seconds.
|
||||||
|
* @returns True if the choice open dated has passed, false otherwise.
|
||||||
|
*/
|
||||||
|
choiceHasBeenOpened(choice: AddonModChoiceChoice, timeNow: number): boolean {
|
||||||
|
return !choice.timeopen || timeNow > choice.timeopen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a choice has been closed.
|
||||||
|
*
|
||||||
|
* @param choice Choice to check.
|
||||||
|
* @param timeNow Current time in seconds.
|
||||||
|
* @returns True if the choice close dated has passed, false otherwise.
|
||||||
|
*/
|
||||||
|
choiceHasBeenClosed(choice: AddonModChoiceChoice, timeNow: number): boolean {
|
||||||
|
return !!choice.timeclose && timeNow > choice.timeclose;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -154,7 +172,7 @@ export class AddonModChoiceProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getChoiceDataCacheKey(courseId: number): string {
|
protected getChoiceDataCacheKey(courseId: number): string {
|
||||||
return ROOT_CACHE_KEY + 'choice:' + courseId;
|
return AddonModChoiceProvider.ROOT_CACHE_KEY + 'choice:' + courseId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,7 +182,7 @@ export class AddonModChoiceProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getChoiceOptionsCacheKey(choiceId: number): string {
|
protected getChoiceOptionsCacheKey(choiceId: number): string {
|
||||||
return ROOT_CACHE_KEY + 'options:' + choiceId;
|
return AddonModChoiceProvider.ROOT_CACHE_KEY + 'options:' + choiceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,7 +192,7 @@ export class AddonModChoiceProvider {
|
||||||
* @returns Cache key.
|
* @returns Cache key.
|
||||||
*/
|
*/
|
||||||
protected getChoiceResultsCacheKey(choiceId: number): string {
|
protected getChoiceResultsCacheKey(choiceId: number): string {
|
||||||
return ROOT_CACHE_KEY + 'results:' + choiceId;
|
return AddonModChoiceProvider.ROOT_CACHE_KEY + 'results:' + choiceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,7 +218,7 @@ export class AddonModChoiceProvider {
|
||||||
const preSets: CoreSiteWSPreSets = {
|
const preSets: CoreSiteWSPreSets = {
|
||||||
cacheKey: this.getChoiceDataCacheKey(courseId),
|
cacheKey: this.getChoiceDataCacheKey(courseId),
|
||||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||||
component: AddonModChoiceProvider.COMPONENT,
|
component: ADDON_MOD_CHOICE_COMPONENT,
|
||||||
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
|
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -259,7 +277,7 @@ export class AddonModChoiceProvider {
|
||||||
const preSets: CoreSiteWSPreSets = {
|
const preSets: CoreSiteWSPreSets = {
|
||||||
cacheKey: this.getChoiceOptionsCacheKey(choiceId),
|
cacheKey: this.getChoiceOptionsCacheKey(choiceId),
|
||||||
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||||
component: AddonModChoiceProvider.COMPONENT,
|
component: ADDON_MOD_CHOICE_COMPONENT,
|
||||||
componentId: options.cmId,
|
componentId: options.cmId,
|
||||||
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
|
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
|
||||||
};
|
};
|
||||||
|
@ -288,7 +306,7 @@ export class AddonModChoiceProvider {
|
||||||
};
|
};
|
||||||
const preSets: CoreSiteWSPreSets = {
|
const preSets: CoreSiteWSPreSets = {
|
||||||
cacheKey: this.getChoiceOptionsCacheKey(choiceId),
|
cacheKey: this.getChoiceOptionsCacheKey(choiceId),
|
||||||
component: AddonModChoiceProvider.COMPONENT,
|
component: ADDON_MOD_CHOICE_COMPONENT,
|
||||||
componentId: options.cmId,
|
componentId: options.cmId,
|
||||||
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
|
...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
|
||||||
};
|
};
|
||||||
|
@ -332,7 +350,7 @@ export class AddonModChoiceProvider {
|
||||||
this.invalidateChoiceData(courseId),
|
this.invalidateChoiceData(courseId),
|
||||||
this.invalidateOptions(choice.id),
|
this.invalidateOptions(choice.id),
|
||||||
this.invalidateResults(choice.id),
|
this.invalidateResults(choice.id),
|
||||||
CoreFilepool.invalidateFilesByComponent(siteId, AddonModChoiceProvider.COMPONENT, moduleId),
|
CoreFilepool.invalidateFilesByComponent(siteId, ADDON_MOD_CHOICE_COMPONENT, moduleId),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,7 +395,7 @@ export class AddonModChoiceProvider {
|
||||||
return CoreCourseLogHelper.log(
|
return CoreCourseLogHelper.log(
|
||||||
'mod_choice_view_choice',
|
'mod_choice_view_choice',
|
||||||
params,
|
params,
|
||||||
AddonModChoiceProvider.COMPONENT,
|
ADDON_MOD_CHOICE_COMPONENT,
|
||||||
id,
|
id,
|
||||||
siteId,
|
siteId,
|
||||||
);
|
);
|
||||||
|
@ -482,7 +500,7 @@ export type AddonModChoiceChoice = {
|
||||||
introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
||||||
introfiles?: CoreWSExternalFile[];
|
introfiles?: CoreWSExternalFile[];
|
||||||
publish?: boolean; // If choice is published.
|
publish?: boolean; // If choice is published.
|
||||||
showresults?: number; // 0 never, 1 after answer, 2 after close, 3 always.
|
showresults?: AddonModChoiceShowResults; // 0 never, 1 after answer, 2 after close, 3 always.
|
||||||
display?: number; // Display mode (vertical, horizontal).
|
display?: number; // Display mode (vertical, horizontal).
|
||||||
allowupdate?: boolean; // Allow update.
|
allowupdate?: boolean; // Allow update.
|
||||||
allowmultiple?: boolean; // Allow multiple choices.
|
allowmultiple?: boolean; // Allow multiple choices.
|
||||||
|
@ -531,7 +549,7 @@ export type AddonModChoiceOption = {
|
||||||
id: number; // Option id.
|
id: number; // Option id.
|
||||||
text: string; // Text of the choice.
|
text: string; // Text of the choice.
|
||||||
maxanswers: number; // Maximum number of answers.
|
maxanswers: number; // Maximum number of answers.
|
||||||
displaylayout: boolean; // True for orizontal, otherwise vertical.
|
displaylayout: boolean; // True for horizontal, otherwise vertical.
|
||||||
countanswers: number; // Number of answers.
|
countanswers: number; // Number of answers.
|
||||||
checked: boolean; // We already answered.
|
checked: boolean; // We already answered.
|
||||||
disabled: boolean; // Option disabled.
|
disabled: boolean; // Option disabled.
|
||||||
|
|
|
@ -21,8 +21,9 @@ import { CoreSitesReadingStrategy } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreWSFile } from '@services/ws';
|
import { CoreWSFile } from '@services/ws';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { AddonModChoice, AddonModChoiceProvider } from '../choice';
|
import { AddonModChoice } from '../choice';
|
||||||
import { AddonModChoiceSync, AddonModChoiceSyncResult } from '../choice-sync';
|
import { AddonModChoiceSync, AddonModChoiceSyncResult } from '../choice-sync';
|
||||||
|
import { ADDON_MOD_CHOICE_COMPONENT } from '../../constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler to prefetch choices.
|
* Handler to prefetch choices.
|
||||||
|
@ -32,7 +33,7 @@ export class AddonModChoicePrefetchHandlerService extends CoreCourseActivityPref
|
||||||
|
|
||||||
name = 'AddonModChoice';
|
name = 'AddonModChoice';
|
||||||
modName = 'choice';
|
modName = 'choice';
|
||||||
component = AddonModChoiceProvider.COMPONENT;
|
component = ADDON_MOD_CHOICE_COMPONENT;
|
||||||
updatesNames = /^configuration$|^.*files$|^answers$/;
|
updatesNames = /^configuration$|^.*files$|^answers$/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,7 +75,7 @@ export class AddonModChoicePrefetchHandlerService extends CoreCourseActivityPref
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
AddonModChoice.getOptions(choice.id, modOptions),
|
AddonModChoice.getOptions(choice.id, modOptions),
|
||||||
this.prefetchResults(choice.id, courseId, modOptions),
|
this.prefetchResults(choice.id, courseId, modOptions),
|
||||||
CoreFilepool.addFilesToQueue(siteId, introFiles, AddonModChoiceProvider.COMPONENT, module.id),
|
CoreFilepool.addFilesToQueue(siteId, introFiles, ADDON_MOD_CHOICE_COMPONENT, module.id),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
@use "theme/globals" as *;
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
.addon-mod_h5pactivity-table-header {
|
.addon-mod_h5pactivity-table-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
ion-col {
|
ion-col {
|
||||||
overflow: hidden;
|
@include ellipsis();
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
@use "theme/globals" as *;
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
.addon-mod_h5pactivity-table-header ion-col {
|
.addon-mod_h5pactivity-table-header ion-col {
|
||||||
overflow: hidden;
|
@include ellipsis();
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,7 @@ ion-item.addon-notification-item {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
p.item-heading {
|
p.item-heading {
|
||||||
font-size: var(--text-size);
|
font-size: var(--text-size);
|
||||||
-webkit-line-clamp: 3;
|
@include ellipsis(3);
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
display: -webkit-box;
|
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
|
@ -63,11 +63,11 @@
|
||||||
--padding-bottom: 8px;
|
--padding-bottom: 8px;
|
||||||
|
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
|
|
||||||
|
--color: var(--core-combobox-color);
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-height: var(--a11y-sizing-minTargetSize);
|
min-height: var(--a11y-sizing-minTargetSize);
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: var(--box-shadow);
|
box-shadow: var(--box-shadow);
|
||||||
|
|
||||||
--highlight-color: transparent !important;
|
--highlight-color: transparent !important;
|
||||||
|
@ -121,7 +121,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-button {
|
ion-button {
|
||||||
--color: var(--core-combobox-color);
|
|
||||||
--color-activated: var(--core-combobox-color);
|
--color-activated: var(--core-combobox-color);
|
||||||
--color-focused: currentcolor;
|
--color-focused: currentcolor;
|
||||||
--color-hover: currentcolor;
|
--color-hover: currentcolor;
|
||||||
|
@ -130,9 +129,7 @@
|
||||||
|
|
||||||
.select-text {
|
.select-text {
|
||||||
@include margin-horizontal(null, auto);
|
@include margin-horizontal(null, auto);
|
||||||
white-space: nowrap;
|
@include ellipsis();
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
font: var(--mdl-typography-label-font-lg);
|
font: var(--mdl-typography-label-font-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,9 +67,7 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-left: .5rem;
|
padding-left: .5rem;
|
||||||
padding-right: .5rem;
|
padding-right: .5rem;
|
||||||
overflow: hidden;
|
@include ellipsis();
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,9 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
@Input() showAlt = true; // Show alt otherwise it's only presentation icon.
|
@Input() showAlt = true; // Show alt otherwise it's only presentation icon.
|
||||||
@Input() purpose: ModPurpose = ModPurpose.MOD_PURPOSE_OTHER; // Purpose of the module.
|
@Input() purpose: ModPurpose = ModPurpose.MOD_PURPOSE_OTHER; // Purpose of the module.
|
||||||
@Input() @HostBinding('class.colorize') colorize = true; // Colorize the icon. Only applies on 4.0 onwards.
|
@Input() @HostBinding('class.colorize') colorize = true; // Colorize the icon. Only applies on 4.0 onwards.
|
||||||
@Input() @HostBinding('class.branded') isBranded?: boolean; // If icon is branded and no colorize will be applied.
|
@Input() isBranded?: boolean; // If icon is branded and no colorize will be applied.
|
||||||
|
|
||||||
|
@HostBinding('class.branded') brandedClass?: boolean;
|
||||||
|
|
||||||
@HostBinding('attr.role')
|
@HostBinding('attr.role')
|
||||||
get getRole(): string | null {
|
get getRole(): string | null {
|
||||||
|
@ -112,9 +114,9 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
/**
|
/**
|
||||||
* Sets the isBranded property when undefined.
|
* Sets the isBranded property when undefined.
|
||||||
*/
|
*/
|
||||||
protected async setIsBranded(): Promise<void> {
|
protected async setBrandedClass(): Promise<void> {
|
||||||
if (!this.colorize) {
|
if (!this.colorize) {
|
||||||
this.isBranded = false;
|
this.brandedClass = false;
|
||||||
|
|
||||||
// It doesn't matter.
|
// It doesn't matter.
|
||||||
return;
|
return;
|
||||||
|
@ -122,37 +124,36 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
// Earlier 4.0, icons were never colorized.
|
// Earlier 4.0, icons were never colorized.
|
||||||
if (this.iconVersion === IconVersion.LEGACY_VERSION) {
|
if (this.iconVersion === IconVersion.LEGACY_VERSION) {
|
||||||
this.isBranded = false;
|
this.brandedClass = false;
|
||||||
this.colorize = false;
|
this.colorize = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the branded class to the original value.
|
||||||
|
this.brandedClass = this.isBranded;
|
||||||
|
|
||||||
// No icon or local icon (not legacy), colorize it.
|
// No icon or local icon (not legacy), colorize it.
|
||||||
if (!this.iconUrl || this.isLocalUrl) {
|
if (!this.iconUrl || this.isLocalUrl) {
|
||||||
// Exception for bigbluebuttonbn, it's the only one that has a branded icon.
|
// Exception for bigbluebuttonbn, it's the only one that has a branded icon.
|
||||||
if (this.iconVersion === IconVersion.VERSION_4_0 && this.modname === 'bigbluebuttonbn') {
|
if (this.iconVersion === IconVersion.VERSION_4_0 && this.modname === 'bigbluebuttonbn') {
|
||||||
this.isBranded = true;
|
this.brandedClass = true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isBranded ??= false;
|
this.brandedClass ??= false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.iconUrl = CoreTextUtils.decodeHTMLEntities(this.iconUrl);
|
this.iconUrl = CoreTextUtils.decodeHTMLEntities(this.iconUrl);
|
||||||
|
|
||||||
if (this.isBranded !== undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's an Moodle Theme icon, check if filtericon is set and use it.
|
// If it's an Moodle Theme icon, check if filtericon is set and use it.
|
||||||
if (this.iconUrl && CoreUrlUtils.isThemeImageUrl(this.iconUrl)) {
|
if (this.iconUrl && CoreUrlUtils.isThemeImageUrl(this.iconUrl)) {
|
||||||
const filter = CoreUrlUtils.getThemeImageUrlParam(this.iconUrl, 'filtericon');
|
const filter = CoreUrlUtils.getThemeImageUrlParam(this.iconUrl, 'filtericon');
|
||||||
if (filter === '1') {
|
if (filter === '1') {
|
||||||
this.isBranded = false;
|
this.brandedClass = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -161,7 +162,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
if (this.modname && !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan(['4.0.8', '4.1.3', '4.2'])) {
|
if (this.modname && !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan(['4.0.8', '4.1.3', '4.2'])) {
|
||||||
// If version is prior to that, check if the url is a module icon and filter it.
|
// If version is prior to that, check if the url is a module icon and filter it.
|
||||||
if (this.getComponentNameFromIconUrl(this.iconUrl) === this.modname) {
|
if (this.getComponentNameFromIconUrl(this.iconUrl) === this.modname) {
|
||||||
this.isBranded = false;
|
this.brandedClass = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -169,7 +170,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
}
|
}
|
||||||
|
|
||||||
// External icons, or non monologo, do not filter.
|
// External icons, or non monologo, do not filter.
|
||||||
this.isBranded = true;
|
this.brandedClass = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -180,7 +181,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
if (!this.iconUrl) {
|
if (!this.iconUrl) {
|
||||||
this.loadFallbackIcon();
|
this.loadFallbackIcon();
|
||||||
this.setIsBranded();
|
this.setBrandedClass();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -196,7 +197,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
!this.isLocalUrl &&
|
!this.isLocalUrl &&
|
||||||
this.getComponentNameFromIconUrl(this.iconUrl) != this.modname;
|
this.getComponentNameFromIconUrl(this.iconUrl) != this.modname;
|
||||||
|
|
||||||
this.setIsBranded();
|
this.setBrandedClass();
|
||||||
|
|
||||||
await this.setSVGIcon();
|
await this.setSVGIcon();
|
||||||
}
|
}
|
||||||
|
@ -212,7 +213,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
this.isLocalUrl = true;
|
this.isLocalUrl = true;
|
||||||
this.linkIconWithComponent = false;
|
this.linkIconWithComponent = false;
|
||||||
|
|
||||||
const moduleName = !this.modname || CoreCourse.CORE_MODULES.indexOf(this.modname) < 0
|
const moduleName = !this.modname || !CoreCourse.isCoreModule(this.modname)
|
||||||
? fallbackModName
|
? fallbackModName
|
||||||
: this.modname;
|
: this.modname;
|
||||||
|
|
||||||
|
@ -370,7 +371,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
// Has own styles, do not apply colors.
|
// Has own styles, do not apply colors.
|
||||||
if (doc.documentElement.getElementsByTagName('style').length > 0) {
|
if (doc.documentElement.getElementsByTagName('style').length > 0) {
|
||||||
this.isBranded = true;
|
this.brandedClass = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively remove attributes starting with on.
|
// Recursively remove attributes starting with on.
|
||||||
|
|
|
@ -61,9 +61,7 @@
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
||||||
ion-label {
|
ion-label {
|
||||||
text-overflow: ellipsis;
|
@include ellipsis();
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
line-height: 1.2em;
|
line-height: 1.2em;
|
||||||
|
|
|
@ -31,12 +31,12 @@
|
||||||
<div collapsible-footer appearOnBottom *ngIf="displayCourseIndex && (previousSection || nextSection)" slot="fixed">
|
<div collapsible-footer appearOnBottom *ngIf="displayCourseIndex && (previousSection || nextSection)" slot="fixed">
|
||||||
<div class="core-course-section-nav-buttons safe-area-padding-horizontal list-item-limited-width">
|
<div class="core-course-section-nav-buttons safe-area-padding-horizontal list-item-limited-width">
|
||||||
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" expand="block"
|
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" expand="block"
|
||||||
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name">
|
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name" class="ion-text-nowrap">
|
||||||
<ion-icon name="fas-arrow-left" slot="start" aria-hidden="true" />
|
<ion-icon name="fas-arrow-left" slot="start" aria-hidden="true" />
|
||||||
<core-format-text [text]="previousSection.name" contextLevel="course" [contextInstanceId]="course.id" />
|
<core-format-text [text]="previousSection.name" contextLevel="course" [contextInstanceId]="course.id" />
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button *ngIf="nextSection" (click)="sectionChanged(nextSection)" expand="block"
|
<ion-button *ngIf="nextSection" (click)="sectionChanged(nextSection)" expand="block"
|
||||||
[attr.aria-label]="('core.next' | translate) + ': ' + nextSection.name">
|
[attr.aria-label]="('core.next' | translate) + ': ' + nextSection.name" class="ion-text-nowrap">
|
||||||
<core-format-text [text]="nextSection.name" contextLevel="course" [contextInstanceId]="course.id" />
|
<core-format-text [text]="nextSection.name" contextLevel="course" [contextInstanceId]="course.id" />
|
||||||
<ion-icon name="fas-arrow-right" slot="end" aria-hidden="true" />
|
<ion-icon name="fas-arrow-right" slot="end" aria-hidden="true" />
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
|
|
||||||
ion-button {
|
ion-button {
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
|
|
@ -137,7 +137,7 @@ export class CoreCourseProvider {
|
||||||
|
|
||||||
static readonly COMPONENT = 'CoreCourse';
|
static readonly COMPONENT = 'CoreCourse';
|
||||||
|
|
||||||
readonly CORE_MODULES = [
|
static readonly CORE_MODULES = [
|
||||||
'assign', 'bigbluebuttonbn', 'book', 'chat', 'choice', 'data', 'feedback', 'folder', 'forum', 'glossary', 'h5pactivity',
|
'assign', 'bigbluebuttonbn', 'book', 'chat', 'choice', 'data', 'feedback', 'folder', 'forum', 'glossary', 'h5pactivity',
|
||||||
'imscp', 'label', 'lesson', 'lti', 'page', 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop',
|
'imscp', 'label', 'lesson', 'lti', 'page', 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop',
|
||||||
];
|
];
|
||||||
|
@ -848,7 +848,7 @@ export class CoreCourseProvider {
|
||||||
return mimetypeIcon;
|
return mimetypeIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.CORE_MODULES.indexOf(moduleName) < 0) {
|
if (!CoreCourse.isCoreModule(moduleName)) {
|
||||||
if (modicon) {
|
if (modicon) {
|
||||||
return modicon;
|
return modicon;
|
||||||
}
|
}
|
||||||
|
@ -1324,6 +1324,17 @@ export class CoreCourseProvider {
|
||||||
return !!module.url;
|
return !!module.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the module is a core module.
|
||||||
|
*
|
||||||
|
* @param moduleName The module name.
|
||||||
|
* @returns Whether it's a core module.
|
||||||
|
*/
|
||||||
|
isCoreModule(moduleName: string): boolean {
|
||||||
|
// If core modules are removed for a certain version we should check the version of the site.
|
||||||
|
return CoreCourseProvider.CORE_MODULES.includes(moduleName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for any course format plugin to load, and open the course page.
|
* Wait for any course format plugin to load, and open the course page.
|
||||||
*
|
*
|
||||||
|
|
|
@ -87,9 +87,7 @@ ion-card {
|
||||||
.core-course-shortname {
|
.core-course-shortname {
|
||||||
font-size: var(--shortname-size);
|
font-size: var(--shortname-size);
|
||||||
color: var(--dark);
|
color: var(--dark);
|
||||||
white-space: nowrap;
|
@include ellipsis();
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-chip {
|
ion-chip {
|
||||||
|
@ -226,18 +224,13 @@ ion-card.core-course-list-card {
|
||||||
|
|
||||||
// Clamp one line with ellipsis on tablet view, and 2 in mobile.
|
// Clamp one line with ellipsis on tablet view, and 2 in mobile.
|
||||||
.item-heading {
|
.item-heading {
|
||||||
white-space: nowrap;
|
@include ellipsis();
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
.item-heading {
|
.item-heading {
|
||||||
// Addition lines for 2 line or multiline ellipsis
|
// Addition lines for 2 line or multiline ellipsis
|
||||||
display: -webkit-box !important;
|
@include ellipsis(2);
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@use "theme/globals" as *;
|
||||||
|
|
||||||
:host ion-item {
|
:host ion-item {
|
||||||
--core-global-search-result-image-size: 40px;
|
--core-global-search-result-image-size: 40px;
|
||||||
--core-global-search-result-title-color: var(--text);
|
--core-global-search-result-title-color: var(--text);
|
||||||
|
@ -52,14 +54,7 @@
|
||||||
core-format-text {
|
core-format-text {
|
||||||
color: var(--core-global-search-result-content-color);
|
color: var(--core-global-search-result-content-color);
|
||||||
|
|
||||||
@supports (-webkit-line-clamp: 2) {
|
@include ellipsis(2);
|
||||||
white-space: normal;
|
|
||||||
overflow: hidden;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-context-wrapper {
|
.result-context-wrapper {
|
||||||
|
|
|
@ -620,9 +620,7 @@ core-rich-text-editor .core-rte-editor {
|
||||||
.text-wrap { white-space: normal !important; }
|
.text-wrap { white-space: normal !important; }
|
||||||
.text-nowrap { white-space: nowrap !important; }
|
.text-nowrap { white-space: nowrap !important; }
|
||||||
.text-truncate {
|
.text-truncate {
|
||||||
overflow: hidden;
|
@include ellipsis();
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
.text-left { text-align: left !important; }
|
.text-left { text-align: left !important; }
|
||||||
.text-right { text-align: right !important; }
|
.text-right { text-align: right !important; }
|
||||||
|
|
|
@ -5,13 +5,21 @@ ion-button {
|
||||||
line-height: 120%;
|
line-height: 120%;
|
||||||
|
|
||||||
core-format-text {
|
core-format-text {
|
||||||
white-space: normal;
|
|
||||||
display: contents;
|
display: contents;
|
||||||
line-height: 120%;
|
line-height: 120%;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > * {
|
&.ion-text-nowrap {
|
||||||
white-space: normal;
|
@include ellipsis();
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
@include ellipsis();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
core-format-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-spinner {
|
ion-spinner {
|
||||||
|
|
|
@ -61,9 +61,7 @@ ion-header.header-md {
|
||||||
@include padding(0, 16px);
|
@include padding(0, 16px);
|
||||||
|
|
||||||
h1, h2, .subheading {
|
h1, h2, .subheading {
|
||||||
text-overflow: ellipsis;
|
@include ellipsis();
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -193,9 +193,7 @@ ion-toggle::part(label),
|
||||||
ion-input > label {
|
ion-input > label {
|
||||||
core-format-text,
|
core-format-text,
|
||||||
core-format-text > *:not(pre) {
|
core-format-text > *:not(pre) {
|
||||||
white-space: nowrap;
|
@include ellipsis();
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,24 @@
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin ellipsis($lines: 1) {
|
||||||
|
@if ($lines == 1) {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
} @else {
|
||||||
|
// Only supported on Android 124+, iOs 11+. https://caniuse.com/css-line-clamp
|
||||||
|
@supports (-webkit-line-clamp: 2) {
|
||||||
|
-webkit-line-clamp: $lines;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as item-push-svg-url but admits flip-rtl
|
* Same as item-push-svg-url but admits flip-rtl
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue