MOBILE-4456 course: Load icon svg as inline and style them properly
| @ -1,7 +1,7 @@ | ||||
| :host { | ||||
|     core-mod-icon { | ||||
|         background: transparent; | ||||
|         margin: 0; | ||||
|         --filter: var(--module-icon-filter); | ||||
|         --padding-start: 0px; | ||||
|         --module-icon-size: 24px; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -5,7 +5,7 @@ | ||||
| </ion-item-divider> | ||||
| <core-loading [hideUntil]="loaded"> | ||||
|     <ion-item class="ion-text-wrap" *ngFor="let entry of entries" [detail]="true" button (click)="gotoCoureListModType(entry)"> | ||||
|         <core-mod-icon slot="start" [modicon]="entry.icon" [modname]="entry.iconModName" [showAlt]="false" /> | ||||
|         <core-mod-icon slot="start" [modicon]="entry.icon" [modname]="entry.iconModName" [showAlt]="false" [colorize]="false" /> | ||||
|         <ion-label>{{ entry.name }}</ion-label> | ||||
|     </ion-item> | ||||
| </core-loading> | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
|                 <ion-card> | ||||
|                     <ion-item class="core-course-module-handler ion-text-wrap" [detail]="false" (click)="action($event, item)" button> | ||||
|                         <core-mod-icon slot="start" *ngIf="item.iconUrl" [modicon]="item.iconUrl" [modname]="item.modname" | ||||
|                             [componentId]="item.cmid" [showAlt]="false" [purpose]="item.purpose" /> | ||||
|                             [componentId]="item.cmid" [showAlt]="false" [purpose]="item.purpose" [isBranded]="item.branded" /> | ||||
|                         <ion-label> | ||||
|                             <!-- Add the icon title so accessibility tools read it. --> | ||||
|                             <span class="sr-only" *ngIf="item.iconTitle">{{ item.iconTitle }}</span> | ||||
|  | ||||
| @ -22,7 +22,7 @@ | ||||
|                             <ion-col class="addon-block-timeline-activity-time ion-no-padding ion-text-nowrap"> | ||||
|                                 <small>{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</small> | ||||
|                                 <core-mod-icon *ngIf="event.iconUrl" [modicon]="event.iconUrl" [componentId]="event.instance" | ||||
|                                     [modname]="event.modulename" [purpose]="event.purpose" /> | ||||
|                                     [modname]="event.modulename" [purpose]="event.purpose" [isBranded]="event.branded" /> | ||||
|                             </ion-col> | ||||
|                             <ion-col class="addon-block-timeline-activity-name ion-no-padding"> | ||||
|                                 <p class="item-heading"> | ||||
|  | ||||
| @ -23,7 +23,7 @@ h4.core-bold { | ||||
|     } | ||||
| 
 | ||||
|     core-mod-icon { | ||||
|         --padding: 8px; | ||||
|         --module-legacy-icon-padding: 8px; | ||||
|         --margin-end: 0.5rem; | ||||
|         --margin-vertical: 0; | ||||
|     } | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
|                 <ion-item class="ion-text-wrap addon-calendar-event" [attr.aria-label]="event.name" (click)="eventClicked(event)" button | ||||
|                     [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" [detail]="false"> | ||||
|                     <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [modname]="event.modulename" | ||||
|                         [componentId]="event.instance" [showAlt]="false" [purpose]="event.purpose" /> | ||||
|                         [componentId]="event.instance" [showAlt]="false" [purpose]="event.purpose" [isBranded]="event.branded" /> | ||||
|                     <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" aria-hidden="true" /> | ||||
|                     <ion-label> | ||||
|                         <!-- Add the icon title so accessibility tools read it. --> | ||||
|  | ||||
| @ -67,7 +67,8 @@ | ||||
|                                         (click)="gotoEvent(event.id, day)" [class.item-dimmed]="event.ispast" | ||||
|                                         [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button [detail]="false"> | ||||
|                                         <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [showAlt]="false" | ||||
|                                             [modname]="event.modulename" [componentId]="event.instance" [purpose]="event.purpose" /> | ||||
|                                             [modname]="event.modulename" [componentId]="event.instance" [purpose]="event.purpose" | ||||
|                                             [isBranded]="event.branded" /> | ||||
|                                         <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" | ||||
|                                             aria-hidden="true" /> | ||||
|                                         <ion-label> | ||||
|  | ||||
| @ -32,6 +32,7 @@ | ||||
|             <ion-item class="ion-text-wrap addon-calendar-event" collapsible [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> | ||||
|                 <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" [showAlt]="false" [modname]="event.modulename" | ||||
|                     [componentId]="event.instance" slot="start" [purpose]="event.purpose" /> | ||||
|                 <!-- TODO MOBILE-4530 Add isBranded when available --> | ||||
|                 <ion-icon *ngIf=" event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" aria-hidden="true" slot="start" /> | ||||
|                 <ion-label> | ||||
|                     <!-- Add the icon title so accessibility tools read it. --> | ||||
|  | ||||
| @ -84,7 +84,8 @@ | ||||
|                     </p> | ||||
|                     <ion-item class="ion-text-wrap" *ngFor="let activity of coursemodules" [href]="activity.url" | ||||
|                         [attr.aria-label]="activity.name" core-link capture="true"> | ||||
|                         <core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl" /> | ||||
|                         <core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl" | ||||
|                             [colorize]="false" /> | ||||
|                         <ion-label> | ||||
|                             <core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id" | ||||
|                                 [courseId]="courseId" /> | ||||
|  | ||||
| @ -119,7 +119,8 @@ | ||||
|                             </p> | ||||
|                             <ion-item class="ion-text-wrap core-course-module-handler" [attr.aria-label]="activity.name" core-link | ||||
|                                 *ngFor="let activity of competency.coursemodules" [href]="activity.url" capture="true"> | ||||
|                                 <core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl" /> | ||||
|                                 <core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl" | ||||
|                                     [colorize]="false" /> | ||||
|                                 <ion-label> | ||||
|                                     <core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id" | ||||
|                                         [courseId]="courseId" /> | ||||
|  | ||||
| Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB | 
| @ -1,17 +1,17 @@ | ||||
| @use "theme/globals" as *; | ||||
| 
 | ||||
| :host { | ||||
|     --extra-icon-size: 16px; | ||||
|     --icon-size: 24px; | ||||
|     --icon-size: 32px; | ||||
|     --core-avatar-size: var(--icon-size); | ||||
|     --extra-icon-size: 12px; | ||||
| 
 | ||||
|     ::ng-deep core-user-avatar { | ||||
|         .core-avatar-extra-img, | ||||
|         core-mod-icon { | ||||
|         .core-avatar-extra-img { | ||||
|             margin: 0 !important; | ||||
|             position: absolute; | ||||
|             right: -4px; | ||||
|             bottom: -4px; | ||||
|             --padding: 0.2rem; | ||||
|             --module-icon-padding: 0.2rem; | ||||
|         } | ||||
| 
 | ||||
|         .core-avatar-extra-img { | ||||
| @ -20,16 +20,16 @@ | ||||
|             img { | ||||
|                 max-width: var(--extra-icon-size); | ||||
|                 max-height: var(--extra-icon-size); | ||||
|                 width: var(--extra-icon-size); | ||||
|                 height: var(--extra-icon-size); | ||||
|                 display: block; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         core-mod-icon  { | ||||
|             --size: var(--extra-icon-size); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     div.core-notification-icon { | ||||
|         max-width: var(--icon-size); | ||||
|         max-height:  var(--icon-size); | ||||
|         img { | ||||
|             width: var(--icon-size); | ||||
|             height:  var(--icon-size); | ||||
| @ -37,20 +37,17 @@ | ||||
|         ion-icon { | ||||
|             font-size: var(--icon-size); | ||||
|         } | ||||
|         padding: 0.7rem; | ||||
|         padding: 0px; | ||||
|         background: var(--background-color); | ||||
|         border-radius: var(--radius-xs); | ||||
|     } | ||||
| 
 | ||||
|     .core-notification-icon { | ||||
|         --module-icon-size: var(--icon-size); | ||||
|         @include margin(6px, 8px, 6px, 0px); | ||||
|     } | ||||
| 
 | ||||
|     .core-notification-img { | ||||
|         @include margin(6px, 8px, 6px, 0px); | ||||
|         width: var(--core-avatar-size); | ||||
|         height: var(--core-avatar-size); | ||||
|         width: var(--icon-size); | ||||
|         height: var(--icon-size); | ||||
|         object-fit: cover; | ||||
|         border-radius: var(--core-avatar-radius); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -41,7 +41,7 @@ | ||||
|                     [profileUrl]="notification.profileimageurlfrom" [fullname]="notification.userfromfullname" | ||||
|                     [userId]="notification.useridfrom"> | ||||
|                     <div class="core-avatar-extra-img" *ngIf="notification.iconurl"> | ||||
|                         <img [src]="notification.iconurl" alt="" role="presentation"> | ||||
|                         <img [src]="notification.iconurl" alt="" role="presentation" core-external-content> | ||||
|                     </div> | ||||
|                 </core-user-avatar> | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| @use "theme/globals" as *; | ||||
| 
 | ||||
| ion-item.addon-notification-item { | ||||
| 
 | ||||
|     ion-label { | ||||
|         margin-top: 8px; | ||||
|         margin-bottom: 8px; | ||||
| @ -32,17 +33,6 @@ ion-item.addon-notification-item { | ||||
|         align-self: start; | ||||
|         margin-top: 16px; | ||||
|     } | ||||
| 
 | ||||
|     --icon-size: 16px; | ||||
|     --extra-icon-size: 12px; | ||||
|     --core-avatar-size: 32px; | ||||
| 
 | ||||
|     div.core-notification-icon, | ||||
|     core-mod-icon.core-notification-icon { | ||||
|         --padding: 8px; | ||||
|         max-width: var(--core-avatar-size); | ||||
|         max-height:  var(--core-avatar-size); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| .mark-all-as-read { | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
|                 <core-user-avatar *ngIf="notification.useridfrom > 0" slot="start" [userId]="notification.useridfrom" | ||||
|                     [profileUrl]="notification.profileimageurlfrom" [fullname]="notification.userfromfullname"> | ||||
|                     <div class="core-avatar-extra-img" *ngIf="notification.iconurl"> | ||||
|                         <img [src]="notification.iconurl" alt="" role="presentation"> | ||||
|                         <img [src]="notification.iconurl" alt="" role="presentation" core-external-content> | ||||
|                     </div> | ||||
|                 </core-user-avatar> | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| @use "theme/globals" as *; | ||||
| 
 | ||||
| :host { | ||||
|     --icon-size: 44px; | ||||
|     --extra-icon-size: 16px; | ||||
| 
 | ||||
|     .core-notification-title { | ||||
|         [slot=start] { | ||||
|  | ||||
| @ -100,7 +100,8 @@ | ||||
|                             <ion-item class="core-course-storage-activity" | ||||
|                                 *ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)"> | ||||
|                                 <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" | ||||
|                                     [modname]="module.modname" [componentId]="module.instance" [fallbackTranslation]="module.modplural" /> | ||||
|                                     [modname]="module.modname" [componentId]="module.instance" [fallbackTranslation]="module.modplural" | ||||
|                                     [isBranded]="module.branded" /> | ||||
|                                 <ion-label class="ion-text-wrap"> | ||||
|                                     <p class="item-heading {{module.handlerData!.class}} addon-storagemanager-module-size"> | ||||
|                                         <core-format-text [text]="module.handlerData.title" [courseId]="module.course" contextLevel="module" | ||||
|  | ||||
| @ -50,8 +50,9 @@ ion-badge { | ||||
| } | ||||
| 
 | ||||
| ion-item core-mod-icon { | ||||
|     --size: 18px; | ||||
|     --padding: 9px; | ||||
|     --module-icon-size: 24px; | ||||
|     --module-legacy-icon-size: 16px; | ||||
| 
 | ||||
|     --margin-vertical: 8px; | ||||
|     --margin-end: 8px; | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,5 @@ | ||||
| <img *ngIf="!isLocalUrl && !iconSVG" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null" | ||||
|     class="core-module-icon" core-external-content [component]="linkIconWithComponent ? modname : null" | ||||
| <ng-container *ngIf="loaded"> | ||||
|     <img *ngIf="!isLocalUrl" [src]="iconUrl" core-external-content alt="" [component]="linkIconWithComponent ? modname : null" | ||||
|         [componentId]="linkIconWithComponent ? componentId : null" (error)="loadFallbackIcon()"> | ||||
| <img *ngIf="isLocalUrl && !iconSVG" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null" | ||||
|     class="core-module-icon" (error)="loadFallbackIcon()"> | ||||
| <div *ngIf="iconSVG" class="core-module-icon" [innerHTML]="iconSVG" [attr.aria-label]="showAlt ? modNameTranslated : ''" | ||||
|     [attr.role]="!showAlt ? 'presentation' : null"></div> | ||||
|     <img *ngIf="isLocalUrl" [src]="iconUrl" (error)="loadFallbackIcon()" alt=""> | ||||
| </ng-container> | ||||
|  | ||||
| @ -3,58 +3,68 @@ | ||||
| :host { | ||||
|     display: inline-block; | ||||
|     --size: var(--module-icon-size, 32px); | ||||
|     --padding: var(--module-icon-padding, 8px); | ||||
|     --padding-start: var(--module-icon-padding, 4px); | ||||
|     --padding-top: var(--module-icon-padding, 4px); | ||||
|     --padding-end: var(--module-icon-padding, 4px); | ||||
|     --padding-bottom: var(--module-icon-padding, 4px); | ||||
|     --icon-radius: var(--module-icon-radius, var(--radius-xs)); | ||||
|     --margin-end: 0px; | ||||
|     --margin-vertical: 0px; | ||||
| 
 | ||||
|     min-width: calc(var(--size) + var(--padding-start) + var(--padding-end)); | ||||
|     min-height: calc(var(--size) + var(--padding-top) + var(--padding-bottom)); | ||||
| 
 | ||||
|     margin-top: var(--margin-vertical); | ||||
|     margin-bottom: var(--margin-vertical); | ||||
|     @include margin-horizontal(null, var(--margin-end)); | ||||
| 
 | ||||
|     border-radius: var(--icon-radius); | ||||
|     padding: var(--padding); | ||||
|     @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start)); | ||||
|     background-color: transparent; | ||||
|     line-height: var(--size); | ||||
| 
 | ||||
|     &.version_current { | ||||
|         @each $type, $value in $activity-icon-color-filters { | ||||
|             &.#{$type} { | ||||
|                 --filter: var(--activity#{$type}); | ||||
|             } | ||||
|         } | ||||
|     --color: var(--text-color); | ||||
| 
 | ||||
|         img { | ||||
|             filter: var(--filter, brightness(0) invert(1)); | ||||
|     &.colorize { | ||||
|         &.version_current { | ||||
|             @each $type, $value in $activity-icon-colors { | ||||
|                 &.#{$type} { | ||||
|                     --color: var(--activity#{$type}); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         &.version_40 { | ||||
|         --size: var(--module-icon-size, 24px); | ||||
|         background-color: $gray-100; | ||||
|             background-color: var(--gray-100); | ||||
| 
 | ||||
|             --color: white; | ||||
| 
 | ||||
|             @each $type, $value in $activity-icon-background-colors { | ||||
|                 &.#{$type} { | ||||
|                     background-color: var(--activity-40-#{$type}); | ||||
|                 img { | ||||
|                     filter: var(--filter, brightness(0) invert(1)); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     &.version_40, | ||||
|     &.version_legacy { | ||||
|         --size: var(--module-icon-size, 24px); | ||||
|         --filter: none; | ||||
|         background-color: $gray-100; | ||||
|         --size: var(--module-legacy-icon-size, 24px); | ||||
|         --padding-start: var(--module-legacy-icon-padding, 8px); | ||||
|         --padding-top: var(--module-legacy-icon-padding, 8px); | ||||
|         --padding-end: var(--module-legacy-icon-padding, 8px); | ||||
|         --padding-bottom: var(--module-legacy-icon-padding, 8px); | ||||
|     } | ||||
| 
 | ||||
|     &.no-filter { | ||||
|         --filter: none !important; | ||||
|     &:not(.branded) { | ||||
|         ::ng-deep svg, | ||||
|         ::ng-deep svg * { | ||||
|             fill: var(--color); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| img { | ||||
|     img, | ||||
|     ::ng-deep svg { | ||||
|         width: var(--size); | ||||
|         height: var(--size); | ||||
|         max-width: var(--size); | ||||
| @ -62,11 +72,6 @@ img { | ||||
|         min-width: var(--size); | ||||
|         min-height: var(--size); | ||||
|         vertical-align: top; | ||||
| 
 | ||||
|     &[alt] { | ||||
|         text-indent: -999999px; | ||||
|         white-space: nowrap; | ||||
|         overflow: hidden; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -16,9 +16,14 @@ import { CoreConstants, ModPurpose } from '@/core/constants'; | ||||
| import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange } from '@angular/core'; | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | ||||
| import { CoreFile } from '@services/file'; | ||||
| import { CoreFileHelper } from '@services/file-helper'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { Http } from '@singletons'; | ||||
| import { firstValueFrom } from 'rxjs'; | ||||
| 
 | ||||
| const assetsPath = 'assets/img/'; | ||||
| const fallbackModName = 'external-tool'; | ||||
| @ -39,30 +44,46 @@ const enum IconVersion { | ||||
| }) | ||||
| export class CoreModIconComponent implements OnInit, OnChanges { | ||||
| 
 | ||||
|     @HostBinding('class.no-filter') noFilter = false; | ||||
| 
 | ||||
|     @Input() modname = ''; // The module name. Used also as component if set.
 | ||||
|     @Input() fallbackTranslation = ''; // Fallback translation string if cannot auto translate.
 | ||||
|     @Input() componentId?: number; // Component Id for external icons.
 | ||||
|     @Input() modicon?: string; // Module icon url or local url.
 | ||||
|     @Input() showAlt = true; // Show alt otherwise it's only presentation icon.
 | ||||
|     @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.branded') isBranded?: boolean; // If icon is branded and no colorize will be applied.
 | ||||
| 
 | ||||
|     icon = ''; | ||||
|     @HostBinding('attr.role') | ||||
|     get getRole(): string | null { | ||||
|         return !this.showAlt ? 'presentation' : null; | ||||
|     } | ||||
| 
 | ||||
|     @HostBinding('attr.aria-label') | ||||
|     get getAriaLabel(): string { | ||||
|         return this.showAlt ? this.modNameTranslated : ''; | ||||
|     } | ||||
| 
 | ||||
|     iconUrl = ''; | ||||
| 
 | ||||
|     modNameTranslated = ''; | ||||
|     isLocalUrl = true; | ||||
|     isLocalUrl = false; | ||||
|     linkIconWithComponent = false; | ||||
|     loaded = false; | ||||
| 
 | ||||
|     @HostBinding('class') iconVersion = 'legacy'; | ||||
|     protected iconVersion: IconVersion = IconVersion.LEGACY_VERSION; | ||||
|     protected purposeClass = ''; | ||||
|     protected element: HTMLElement; | ||||
| 
 | ||||
|     constructor(protected el: ElementRef) { } | ||||
|     constructor(element: ElementRef) { | ||||
|         this.element = element.nativeElement; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         this.iconVersion = this.getIconVersion(); | ||||
|         this.element.classList.add(this.iconVersion); | ||||
| 
 | ||||
|         if (!this.modname && this.modicon) { | ||||
|             // Guess module from the icon url.
 | ||||
| @ -70,32 +91,9 @@ export class CoreModIconComponent implements OnInit, OnChanges { | ||||
|         } | ||||
| 
 | ||||
|         this.modNameTranslated = CoreCourse.translateModuleName(this.modname, this.fallbackTranslation); | ||||
|         if (this.iconVersion !== IconVersion.LEGACY_VERSION) { | ||||
| 
 | ||||
|             let purposeClass = | ||||
|                 CoreCourseModuleDelegate.supportsFeature<ModPurpose>( | ||||
|                     this.modname || '', | ||||
|                     CoreConstants.FEATURE_MOD_PURPOSE, | ||||
|                     this.purpose, | ||||
|                 ); | ||||
| 
 | ||||
|             if (this.iconVersion === IconVersion.VERSION_4_0) { | ||||
|                 if (purposeClass === ModPurpose.MOD_PURPOSE_INTERACTIVECONTENT) { | ||||
|                     // Interactive content was introduced on 4.4, on previous versions CONTENT is used instead.
 | ||||
|                     purposeClass = ModPurpose.MOD_PURPOSE_CONTENT; | ||||
|                 } | ||||
| 
 | ||||
|                 if (this.modname === 'lti') { | ||||
|                     // LTI had content purpose with 4.0 icons.
 | ||||
|                     purposeClass = ModPurpose.MOD_PURPOSE_CONTENT; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (purposeClass) { | ||||
|                 const element: HTMLElement = this.el.nativeElement; | ||||
|                 element.classList.add(purposeClass); | ||||
|             } | ||||
|         } | ||||
|         this.setIsBranded(); | ||||
|         this.setPurposeClass(); | ||||
| 
 | ||||
|         await this.setIcon(); | ||||
|     } | ||||
| @ -109,12 +107,70 @@ export class CoreModIconComponent implements OnInit, OnChanges { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the isBranded property when undefined. | ||||
|      * | ||||
|      * @returns wether the icon does not need to be filtered. | ||||
|      */ | ||||
|     protected async setIsBranded(): Promise<void> { | ||||
|         if (!this.colorize || this.isBranded !== undefined) { | ||||
|             // It doesn't matter.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Earlier 4.0, icons were never colorized.
 | ||||
|         if (this.iconVersion === IconVersion.LEGACY_VERSION) { | ||||
|             this.colorize = false; | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // No icon or local icon (not legacy), colorize it.
 | ||||
|         if (!this.iconUrl || this.isLocalUrl) { | ||||
|             this.isBranded = false; | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.iconUrl = CoreTextUtils.decodeHTMLEntities(this.iconUrl); | ||||
| 
 | ||||
|         // If it's an Moodle Theme icon, check if filtericon is set and use it.
 | ||||
|         if (this.iconUrl && CoreUrlUtils.isThemeImageUrl(this.iconUrl)) { | ||||
|             const iconParams = CoreUrlUtils.extractUrlParams(this.iconUrl); | ||||
|             if (iconParams['filtericon'] === '1') { | ||||
|                 this.isBranded =  false; | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // filtericon was introduced in 4.2 and backported to 4.1.3 and 4.0.8.
 | ||||
|             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 (this.getComponentNameFromIconUrl(this.iconUrl) === this.modname) { | ||||
|                     this.isBranded =  false; | ||||
| 
 | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // External icons, or non monologo, do not filter.
 | ||||
|         this.isBranded =  true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set icon. | ||||
|      */ | ||||
|     async setIcon(): Promise<void> { | ||||
|         this.icon = this.modicon || this.icon; | ||||
|         this.isLocalUrl = this.icon.startsWith(assetsPath); | ||||
|         this.iconUrl = this.modicon || this.iconUrl; | ||||
| 
 | ||||
|         if (!this.iconUrl) { | ||||
|             this.loadFallbackIcon(); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.isLocalUrl = this.iconUrl.startsWith(assetsPath); | ||||
| 
 | ||||
|         // Cache icon if the url is not the theme generic one.
 | ||||
|         // If modname is not set icon won't be cached.
 | ||||
| @ -123,62 +179,31 @@ export class CoreModIconComponent implements OnInit, OnChanges { | ||||
|             !!this.modname && | ||||
|             !!this.componentId && | ||||
|             !this.isLocalUrl && | ||||
|             this.getComponentNameFromIconUrl(this.icon) != this.modname; | ||||
|             this.getComponentNameFromIconUrl(this.iconUrl) != this.modname; | ||||
| 
 | ||||
|         this.noFilter = await this.getIconNoFilter(); | ||||
|         await this.setSVGIcon(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Icon to load on error. | ||||
|      */ | ||||
|     async loadFallbackIcon(): Promise<void> { | ||||
|         if (this.isLocalUrl) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.isLocalUrl = true; | ||||
|         this.linkIconWithComponent = false; | ||||
| 
 | ||||
|         const moduleName = !this.modname || CoreCourse.CORE_MODULES.indexOf(this.modname) < 0 | ||||
|             ? fallbackModName | ||||
|             : this.modname; | ||||
| 
 | ||||
|         const path = CoreCourse.getModuleIconsPath(); | ||||
| 
 | ||||
|         this.icon = path + moduleName + '.svg'; | ||||
|         this.noFilter = await this.getIconNoFilter(); | ||||
|     } | ||||
|         this.iconUrl = path + moduleName + '.svg'; | ||||
| 
 | ||||
|     /** | ||||
|      * Returns if the icon does not need to be filtered. | ||||
|      * | ||||
|      * @returns wether the icon does not need to be filtered. | ||||
|      */ | ||||
|     protected async getIconNoFilter(): Promise<boolean> { | ||||
|         // Earlier 4.0, icons were never filtered.
 | ||||
|         if (this.iconVersion === IconVersion.LEGACY_VERSION) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         // No icon or local icon (not legacy), filter it.
 | ||||
|         if (!this.icon || this.isLocalUrl) { | ||||
|             return await CoreCourseModuleDelegate.moduleIconIsBranded(this.modname); | ||||
|         } | ||||
| 
 | ||||
|         this.icon = CoreTextUtils.decodeHTMLEntities(this.icon); | ||||
| 
 | ||||
|         // If it's an Moodle Theme icon, check if filtericon is set and use it.
 | ||||
|         if (this.icon && CoreUrlUtils.isThemeImageUrl(this.icon)) { | ||||
|             const iconParams = CoreUrlUtils.extractUrlParams(this.icon); | ||||
|             if (iconParams['filtericon'] === '1') { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             // filtericon was introduced in 4.2 and backported to 4.1.3 and 4.0.8.
 | ||||
|             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 (this.getComponentNameFromIconUrl(this.icon) === this.modname) { | ||||
|                     return false; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // External icons, or non monologo, do not filter.
 | ||||
|         return true; | ||||
|         await this.setSVGIcon(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -188,7 +213,7 @@ export class CoreModIconComponent implements OnInit, OnChanges { | ||||
|      * @returns Guessed modname. | ||||
|      */ | ||||
|     protected getComponentNameFromIconUrl(iconUrl: string): string { | ||||
|         if (!CoreUrlUtils.isThemeImageUrl(this.icon)) { | ||||
|         if (!CoreUrlUtils.isThemeImageUrl(this.iconUrl)) { | ||||
|             // Cannot be guessed.
 | ||||
|             return ''; | ||||
|         } | ||||
| @ -213,6 +238,38 @@ export class CoreModIconComponent implements OnInit, OnChanges { | ||||
|         return component; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the purpose class. | ||||
|      */ | ||||
|     protected setPurposeClass(): void { | ||||
|         if (this.iconVersion === IconVersion.LEGACY_VERSION) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.purposeClass = | ||||
|             CoreCourseModuleDelegate.supportsFeature<ModPurpose>( | ||||
|                 this.modname || '', | ||||
|                 CoreConstants.FEATURE_MOD_PURPOSE, | ||||
|                 this.purpose, | ||||
|             ); | ||||
| 
 | ||||
|         if (this.iconVersion === IconVersion.VERSION_4_0) { | ||||
|             if (this.purposeClass === ModPurpose.MOD_PURPOSE_INTERACTIVECONTENT) { | ||||
|                 // Interactive content was introduced on 4.4, on previous versions CONTENT is used instead.
 | ||||
|                 this.purposeClass = ModPurpose.MOD_PURPOSE_CONTENT; | ||||
|             } | ||||
| 
 | ||||
|             if (this.modname === 'lti') { | ||||
|                 // LTI had content purpose with 4.0 icons.
 | ||||
|                 this.purposeClass = ModPurpose.MOD_PURPOSE_CONTENT; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.purposeClass) { | ||||
|             this.element.classList.add(this.purposeClass); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the icon version depending on site version. | ||||
|      * | ||||
| @ -232,4 +289,103 @@ export class CoreModIconComponent implements OnInit, OnChanges { | ||||
|         return IconVersion.CURRENT_VERSION; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets SVG markup for the icon (if the URL is an SVG). | ||||
|      * | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async setSVGIcon(): Promise<void> { | ||||
|         if (this.iconVersion === IconVersion.LEGACY_VERSION) { | ||||
|             this.loaded = true; | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.loaded = false; | ||||
| 
 | ||||
|         let mimetype = ''; | ||||
|         let fileContents = ''; | ||||
| 
 | ||||
|         // Download the icon if it's not local to cache it.
 | ||||
|         if (!this.isLocalUrl) { | ||||
|             try { | ||||
|                 const iconUrl = await CoreFileHelper.downloadFile( | ||||
|                     this.iconUrl, | ||||
|                     this.linkIconWithComponent ? this.modname : undefined, | ||||
|                     this.linkIconWithComponent ? this.componentId : undefined, | ||||
|                 ); | ||||
|                 if (iconUrl) { | ||||
|                     mimetype = await CoreUtils.getMimeTypeFromUrl(iconUrl); | ||||
|                     fileContents = await CoreFile.readFile(iconUrl); | ||||
|                 } | ||||
|             } catch { | ||||
|                 // Ignore errors.
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             if (!fileContents) { | ||||
|                 // Try to download the icon directly (also for local files).
 | ||||
|                 const response = await firstValueFrom(Http.get( | ||||
|                     this.iconUrl, | ||||
|                     { | ||||
|                         observe: 'response', | ||||
|                         responseType: 'text', | ||||
|                     }, | ||||
|                 )); | ||||
|                 mimetype = response.headers.get('content-type') || mimetype; | ||||
|                 fileContents = response.body || ''; | ||||
|             } | ||||
| 
 | ||||
|             if (mimetype !== 'image/svg+xml' || !fileContents) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // Clean the DOM to avoid security issues.
 | ||||
|             const parser = new DOMParser(); | ||||
|             const doc = parser.parseFromString(fileContents, 'image/svg+xml'); | ||||
| 
 | ||||
|             // Safety check.
 | ||||
|             if (doc.documentElement.nodeName !== 'svg') { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // Remove scripts tags.
 | ||||
|             const scripts = doc.documentElement.getElementsByTagName('script'); | ||||
|             for (let i = scripts.length - 1; i >= 0; i--) { | ||||
|                 scripts[i].parentNode?.removeChild(scripts[i]); | ||||
|             } | ||||
| 
 | ||||
|             // Recursively remove attributes starting with on.
 | ||||
|             const removeAttributes = (element: Element): void => { | ||||
|                 Array.from(element.attributes).forEach((attr) => { | ||||
|                     if (attr.name.startsWith('on')) { | ||||
|                         element.removeAttribute(attr.name); | ||||
|                     } | ||||
|                 }); | ||||
| 
 | ||||
|                 Array.from(element.children).forEach((child) => { | ||||
|                     removeAttributes(child); | ||||
|                 }); | ||||
|             }; | ||||
|             removeAttributes(doc.documentElement); | ||||
| 
 | ||||
|             // Add viewBox to avoid scaling issues.
 | ||||
|             if (!doc.documentElement.getAttribute('viewBox')) { | ||||
|                 const width = doc.documentElement.getAttribute('width'); | ||||
|                 const height = doc.documentElement.getAttribute('height'); | ||||
|                 if (width && height) { | ||||
|                     doc.documentElement.setAttribute('viewBox', '0 0 '+ width + ' ' + height); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             this.element.replaceChildren(doc.documentElement); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } finally { | ||||
|             this.loaded = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| <ion-item class="ion-text-wrap" collapsible> | ||||
|     <core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance" [purpose]="module.purpose" /> | ||||
|     <ion-label> | ||||
|     <core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance" [purpose]="module.purpose" | ||||
|         [isBranded]="module.branded" /> | ||||
|     <ion-label class="core-module-info-activity-title"> | ||||
|         <h1> | ||||
|             <core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId" | ||||
|                 [contextInstanceId]="module.id" [courseId]="courseId" /> | ||||
|  | ||||
| @ -10,8 +10,16 @@ | ||||
| 
 | ||||
|     core-mod-icon { | ||||
|         align-self: flex-start; | ||||
|         --padding: 4px; | ||||
|         @include margin-horizontal(null, 8px); | ||||
| 
 | ||||
|         --module-icon-padding: 0px; | ||||
|         --module-legacy-icon-padding: 4px; | ||||
|     } | ||||
| 
 | ||||
|     ion-label.core-module-info-activity-title { | ||||
|         margin-top: 12px; | ||||
|         margin-bottom: 12px; | ||||
|         line-height: 32px; | ||||
|     } | ||||
| 
 | ||||
|     h1 ion-icon { | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
|             <ion-label> | ||||
|                 <p *ngIf="moduleNameTranslated" class="core-modulename"> | ||||
|                     <core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance" | ||||
|                         [fallbackTranslation]="module.modplural" [purpose]="module.purpose" /> | ||||
|                         [fallbackTranslation]="module.modplural" [purpose]="module.purpose" [isBranded]="module.branded" /> | ||||
|                     {{moduleNameTranslated}} | ||||
|                 </p> | ||||
|                 <h1> | ||||
|  | ||||
| @ -10,16 +10,20 @@ h1 { | ||||
| 
 | ||||
| .core-modulename { | ||||
|     text-transform: uppercase; | ||||
| 
 | ||||
|     core-mod-icon { | ||||
|         --padding: 3px; | ||||
|         --size: 10px; | ||||
|         margin: 0; | ||||
|         --module-icon-padding: 0px; | ||||
|         --module-legacy-icon-padding: 2px; | ||||
|         --module-icon-size: 16px; | ||||
|         --module-legacy-icon-size: 12px; | ||||
| 
 | ||||
|         margin: 0px; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| ion-item ion-label ion-icon { | ||||
|     @include margin-horizontal(0, 4px); | ||||
|     @include margin-horizontal(0px, 4px); | ||||
|     vertical-align: text-top; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -8,7 +8,8 @@ | ||||
|             <ion-label> | ||||
|                 <div class="activity-main"> | ||||
|                     <core-mod-icon *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" [modname]="module.modname" | ||||
|                         [componentId]="module.instance" [fallbackTranslation]="module.modplural" [purpose]="module.purpose" /> | ||||
|                         [componentId]="module.instance" [fallbackTranslation]="module.modplural" [purpose]="module.purpose" | ||||
|                         [isBranded]="module.branded" /> | ||||
|                     <div class="activity-title"> | ||||
|                         <p class="item-heading"> | ||||
|                             <core-format-text [text]="module.handlerData.title" contextLevel="module" [contextInstanceId]="module.id" | ||||
|  | ||||
| @ -33,7 +33,8 @@ | ||||
|         core-mod-icon { | ||||
|             margin-top: 0px; | ||||
|             margin-bottom: 0px; | ||||
|             --module-icon-padding: 4px; | ||||
|             --module-icon-padding: 0px; | ||||
|             --module-legacy-icon-padding: 4px; | ||||
|             --module-icon-radius: var(--radius-xs); | ||||
| 
 | ||||
|             @include margin-horizontal(null, 8px); | ||||
|  | ||||
| @ -1751,6 +1751,7 @@ export type CoreCourseGetContentsWSModule = { | ||||
|     modicon: string; // Activity icon url.
 | ||||
|     modname: string; // Activity module type.
 | ||||
|     purpose?: string; // @since 4.4 The module purpose.
 | ||||
|     branded?: boolean; // @since 4.4 Whether the module is branded or not.
 | ||||
|     modplural: string; // Activity module plural name.
 | ||||
|     availability?: string; // Module availability settings.
 | ||||
|     indent: number; // Number of identation in the site.
 | ||||
|  | ||||
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB | 
| @ -45,7 +45,7 @@ | ||||
|                                     <img *ngIf="row.image && !row.itemmodule" [src]="row.image" slot="start" class="core-module-icon" | ||||
|                                         [alt]="row.iconAlt" /> | ||||
|                                     <core-mod-icon *ngIf="row.image && row.itemmodule" [modicon]="row.image" slot="start" | ||||
|                                         [modname]="row.itemmodule" /> | ||||
|                                         [modname]="row.itemmodule" [colorize]="false" /> | ||||
|                                     <span [innerHTML]="row.gradeitem"></span> | ||||
|                                 </th> | ||||
|                                 <ng-container *ngIf="row.itemtype !== 'category'"> | ||||
|  | ||||
| @ -43,10 +43,10 @@ table.core-table.core-grades-table { | ||||
|         } | ||||
| 
 | ||||
|         core-mod-icon { | ||||
|             --padding: 0px; | ||||
|             --size: 16px; | ||||
|             background: transparent; | ||||
|             --filter: var(--module-icon-filter); | ||||
|             --module-icon-padding: 0px; | ||||
|             --module-legacy-icon-padding: 0px; | ||||
|             --module-icon-size: 16px; | ||||
|             --module-legacy-icon-size: 16px; | ||||
|         } | ||||
| 
 | ||||
|         ion-icon { | ||||
|  | ||||
| @ -4,8 +4,8 @@ | ||||
|     <ion-label> | ||||
|         <h3 *ngIf="result.title"> | ||||
|             <ion-icon *ngIf="renderedIcon" [name]="renderedIcon" aria-hidden="true" /> | ||||
|             <core-mod-icon *ngIf="!renderedIcon && result.module" [modicon]="result.module.iconurl" | ||||
|                 [modname]="result.module.name" /> | ||||
|             <core-mod-icon *ngIf="!renderedIcon && result.module" [modicon]="result.module.iconurl" [modname]="result.module.name" | ||||
|                 [colorize]="false" /> | ||||
|             <img *ngIf="!renderedIcon && !result.module && result.component" [src]="result.component.iconurl" alt="" class="result-icon" | ||||
|                 core-external-content [component]="result.component.name"> | ||||
|             <core-format-text [text]="result.title" /> | ||||
|  | ||||
| @ -12,14 +12,14 @@ | ||||
|         color: var(--core-global-search-result-title-color); | ||||
| 
 | ||||
|         core-mod-icon { | ||||
|             --size: var(--core-global-search-result-icon-size); | ||||
|             --filter: var(--module-icon-filter); | ||||
|             --module-icon-padding: 0px; | ||||
|             --module-legacy-icon-padding: 0px; | ||||
|             --module-icon-size: var(--core-global-search-result-icon-size); | ||||
|             --module-legacy-icon-size: var(--core-global-search-result-icon-size); | ||||
| 
 | ||||
|             margin-inline-end: var(--spacing-2); | ||||
|             margin-top: 0px; | ||||
|             margin-bottom: 0px; | ||||
|             --padding: 0px; | ||||
|             background: transparent; | ||||
|             flex-shrink: 0; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|     <ion-item class="ion-text-wrap" [href]="item.url" core-link [capture]="true"> | ||||
|         <core-user-avatar *ngIf="item.avatarUrl" [profileUrl]="item.avatarUrl" slot="start" [linkProfile]="false" /> | ||||
| 
 | ||||
|         <core-mod-icon *ngIf="item.iconUrl" [modicon]="item.iconUrl" slot="start" [showAlt]="false" /> | ||||
|         <core-mod-icon *ngIf="item.iconUrl" [modicon]="item.iconUrl" slot="start" [showAlt]="false" [isBranded]="item.branded" /> | ||||
|         <ion-label> | ||||
|             <p class="item-heading">{{ item.heading }}</p> | ||||
|             <p *ngFor="let text of item.details">{{ text }}</p> | ||||
|  | ||||
| Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB | 
| @ -135,15 +135,6 @@ $activity-icon-colors: ( | ||||
|     interactivecontent: #8d3d1b | ||||
| ) !default; | ||||
| 
 | ||||
| $activity-icon-color-filters: ( | ||||
|     administration: invert(45%) sepia(46%) saturate(3819%) hue-rotate(260deg) brightness(101%) contrast(87%), | ||||
|     assessment: invert(36%) sepia(98%) saturate(6969%) hue-rotate(315deg) brightness(90%) contrast(119%), | ||||
|     collaboration: invert(25%) sepia(54%) saturate(6226%) hue-rotate(245deg) brightness(100%) contrast(102%), | ||||
|     communication: invert(48%) sepia(74%) saturate(4887%) hue-rotate(11deg) brightness(102%) contrast(101%), | ||||
|     content: invert(49%) sepia(52%) saturate(4675%) hue-rotate(156deg) brightness(89%) contrast(102%), | ||||
|     interactivecontent: invert(25%) sepia(63%) saturate(1152%) hue-rotate(344deg) brightness(94%) contrast(91%) | ||||
| ) !default; | ||||
| 
 | ||||
| $calendar-event-category-category: #8e24aa !default; | ||||
| $calendar-event-category-course: $red !default; | ||||
| $calendar-event-category-group: $yellow !default; | ||||
|  | ||||
| @ -130,8 +130,6 @@ html.dark { | ||||
|     --core-login-input-background: var(--core-login-background); | ||||
|     --core-login-input-color: var(--core-login-text-color); | ||||
| 
 | ||||
|     --module-icon-filter: brightness(0) invert(1); | ||||
| 
 | ||||
|     --core-question-correct-color: var(--success-tint); | ||||
|     --core-question-correct-color-bg: var(--success-shade); | ||||
|     --core-question-incorrect-color: var(--danger); | ||||
|  | ||||
| @ -354,8 +354,6 @@ html { | ||||
|     --core-messages-discussion-badge: var(--primary); | ||||
|     --core-messages-discussion-badge-text: var(--white); | ||||
| 
 | ||||
|     --module-icon-filter: brightness(0); | ||||
| 
 | ||||
|     --addon-forum-avatar-size: var(--core-avatar-size); | ||||
|     --addon-forum-border-color: var(--stroke); | ||||
|     --addon-forum-highlight-color: var(--light); | ||||
| @ -404,7 +402,7 @@ html { | ||||
|     } | ||||
| 
 | ||||
|     // Make activtity colours available for custom modules. | ||||
|     @each $type, $value in $activity-icon-color-filters { | ||||
|     @each $type, $value in $activity-icon-colors { | ||||
|         --activity#{$type}: #{$value}; | ||||
|     } | ||||
| 
 | ||||
|  | ||||