commit
b10b5781cf
|
@ -53,7 +53,7 @@
|
||||||
</ion-row>
|
</ion-row>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col class="addon-block-timeline-activity-action ion-no-padding" *ngIf="event.action?.actionable">
|
<ion-col class="addon-block-timeline-activity-action ion-no-padding" *ngIf="event.action?.actionable">
|
||||||
<ion-button fill="outline" (click)="action($event, event.action.url)" [title]="event.action.name">
|
<ion-button fill="outline" (click)="action($event, event.action.url)" [title]="event.action.name" class="chip">
|
||||||
{{event.action.name}}
|
{{event.action.name}}
|
||||||
<ion-badge slot="end" class="ion-margin-start" *ngIf="event.action.showitemcount">
|
<ion-badge slot="end" class="ion-margin-start" *ngIf="event.action.showitemcount">
|
||||||
{{event.action.itemcount}}
|
{{event.action.itemcount}}
|
||||||
|
|
|
@ -4,13 +4,21 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item-divider>
|
</ion-item-divider>
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
<ion-row class="ion-no-padding ion-justify-content-between ion-align-items-center">
|
<ion-row class="ion-hide-md-up addon-block-timeline-filter" *ngIf="searchEnabled">
|
||||||
<ion-col size="auto" class="ion-no-padding">
|
<ion-col>
|
||||||
<core-combobox [selection]="filter" (onChange)="switchFilter($event)" icon="fas-filter">
|
<!-- Filter courses. -->
|
||||||
|
<core-search-box (onSubmit)="searchTextChanged($event)" (onClear)="searchTextChanged()"
|
||||||
|
[placeholder]="'addon.block_timeline.searchevents' | translate" autocorrect="off" spellcheck="false" lengthCheck="2"
|
||||||
|
searchArea="AddonBlockTimeline"></core-search-box>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row class="ion-justify-content-between ion-align-items-center addon-block-timeline-filter">
|
||||||
|
<ion-col size="auto">
|
||||||
|
<core-combobox [selection]="filter" (onChange)="switchFilter($event)">
|
||||||
<ion-select-option class="ion-text-wrap" value="all">
|
<ion-select-option class="ion-text-wrap" value="all">
|
||||||
{{ 'core.all' | translate }}
|
{{ 'core.all' | translate }}
|
||||||
</ion-select-option>
|
</ion-select-option>
|
||||||
<ion-select-option class="ion-text-wrap" value="overdue">
|
<ion-select-option class="ion-text-wrap core-select-option-border-bottom" value="overdue">
|
||||||
{{ 'addon.block_timeline.overdue' | translate }}
|
{{ 'addon.block_timeline.overdue' | translate }}
|
||||||
</ion-select-option>
|
</ion-select-option>
|
||||||
<ion-select-option class="ion-text-wrap core-select-option-title" disabled value="disabled">
|
<ion-select-option class="ion-text-wrap core-select-option-title" disabled value="disabled">
|
||||||
|
@ -30,13 +38,13 @@
|
||||||
</ion-select-option>
|
</ion-select-option>
|
||||||
</core-combobox>
|
</core-combobox>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col class="ion-no-padding ion-hide-md-down" *ngIf="searchEnabled">
|
<ion-col class="ion-hide-md-down" *ngIf="searchEnabled">
|
||||||
<!-- Filter courses. -->
|
<!-- Filter courses. -->
|
||||||
<core-search-box (onSubmit)="searchTextChanged($event)" (onClear)="searchTextChanged()"
|
<core-search-box (onSubmit)="searchTextChanged($event)" (onClear)="searchTextChanged()"
|
||||||
[placeholder]="'addon.block_timeline.searchevents' | translate" autocorrect="off" spellcheck="false" lengthCheck="2"
|
[placeholder]="'addon.block_timeline.searchevents' | translate" autocorrect="off" spellcheck="false" lengthCheck="2"
|
||||||
searchArea="AddonBlockTimeline"></core-search-box>
|
searchArea="AddonBlockTimeline"></core-search-box>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col size="auto" class="ion-no-padding">
|
<ion-col size="auto">
|
||||||
<core-combobox [label]="'core.sortby' | translate" [selection]="sort" (onChange)="switchSort($event)"
|
<core-combobox [label]="'core.sortby' | translate" [selection]="sort" (onChange)="switchSort($event)"
|
||||||
icon="fas-sort-amount-down-alt">
|
icon="fas-sort-amount-down-alt">
|
||||||
<ion-select-option class="ion-text-wrap" value="sortbydates">
|
<ion-select-option class="ion-text-wrap" value="sortbydates">
|
||||||
|
@ -48,14 +56,7 @@
|
||||||
</core-combobox>
|
</core-combobox>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
<ion-row class="ion-no-padding ion-hide-md-up" *ngIf="searchEnabled">
|
|
||||||
<ion-col class="ion-no-padding">
|
|
||||||
<!-- Filter courses. -->
|
|
||||||
<core-search-box (onSubmit)="searchTextChanged($event)" (onClear)="searchTextChanged()"
|
|
||||||
[placeholder]="'addon.block_timeline.searchevents' | translate" autocorrect="off" spellcheck="false" lengthCheck="2"
|
|
||||||
searchArea="AddonBlockTimeline"></core-search-box>
|
|
||||||
</ion-col>
|
|
||||||
</ion-row>
|
|
||||||
|
|
||||||
<core-loading [hideUntil]="timeline.loaded" [hidden]="sort != 'sortbydates'">
|
<core-loading [hideUntil]="timeline.loaded" [hidden]="sort != 'sortbydates'">
|
||||||
<addon-block-timeline-events [events]="timeline.events" [canLoadMore]="timeline.canLoadMore" (loadMore)="loadMore()"
|
<addon-block-timeline-events [events]="timeline.events" [canLoadMore]="timeline.canLoadMore" (loadMore)="loadMore()"
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
:host {
|
||||||
|
ion-row.addon-block-timeline-filter {
|
||||||
|
margin: 8px;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
ion-col {
|
||||||
|
padding: 0;
|
||||||
|
margin-right: 2px;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-button,
|
||||||
|
core-combobox ::ng-deep ion-button {
|
||||||
|
--border-width: 0;
|
||||||
|
--a11y-min-target-size: 40px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.select-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
ion-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core-combobox ::ng-deep ion-select {
|
||||||
|
margin: 0;
|
||||||
|
--a11y-min-target-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
core-search-box {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
--height: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'addon-block-timeline',
|
selector: 'addon-block-timeline',
|
||||||
templateUrl: 'addon-block-timeline.html',
|
templateUrl: 'addon-block-timeline.html',
|
||||||
|
styleUrls: ['timeline.scss'],
|
||||||
})
|
})
|
||||||
export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implements OnInit {
|
export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implements OnInit {
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@ export class AddonModLabelPrefetchHandlerService extends CoreCourseResourcePrefe
|
||||||
modName = 'label';
|
modName = 'label';
|
||||||
component = AddonModLabelProvider.COMPONENT;
|
component = AddonModLabelProvider.COMPONENT;
|
||||||
updatesNames = /^.*files$/;
|
updatesNames = /^.*files$/;
|
||||||
skipListStatus = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content class="limited-width">
|
<ion-content class="limited-width">
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="loaded">
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item class="ion-text-wrap" *ngIf="downloadCourseEnabled">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p>{{ 'addon.storagemanager.courseinfo' | translate }}</p>
|
<p>{{ 'addon.storagemanager.courseinfo' | translate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
@ -47,8 +47,8 @@
|
||||||
<ng-container *ngFor="let section of sections">
|
<ng-container *ngFor="let section of sections">
|
||||||
<ion-card class="section" *ngIf="section.modules.length > 0">
|
<ion-card class="section" *ngIf="section.modules.length > 0">
|
||||||
<ion-card-header>
|
<ion-card-header>
|
||||||
<ion-item class="ion-no-padding" [lines]="section.expanded ? 'full' : 'none'" button detail="false"
|
<ion-item [lines]="section.expanded ? 'full' : 'none'" button detail="false" (click)="toggleExpand($event, section)"
|
||||||
(click)="toggleExpand($event, section)" [class.core-course-storage-section-expanded]="section.expanded"
|
[class.core-course-storage-section-expanded]="section.expanded"
|
||||||
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
|
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
|
||||||
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-storage-section-' + section.id">
|
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-storage-section-' + section.id">
|
||||||
<ion-icon name="fas-chevron-right" flip-rtl slot="start" class="expandable-status-icon"
|
<ion-icon name="fas-chevron-right" flip-rtl slot="start" class="expandable-status-icon"
|
||||||
|
@ -99,10 +99,10 @@
|
||||||
</div>
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card-header>
|
</ion-card-header>
|
||||||
<ion-card-content id="core-course-storage-section-{{section.id}}" [ngClass]="{'hidden-content': !section.expanded}">
|
<ion-card-content id="core-course-storage-section-{{section.id}}">
|
||||||
<ng-container *ngIf="section.expanded">
|
<ng-container *ngIf="section.expanded">
|
||||||
<ng-container *ngFor="let module of section.modules">
|
<ng-container *ngFor="let module of section.modules">
|
||||||
<ion-item class="ion-no-padding core-course-storage-activity"
|
<ion-item class="core-course-storage-activity"
|
||||||
*ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)">
|
*ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)">
|
||||||
<core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon"
|
<core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon"
|
||||||
[modname]="module.modname" [componentId]="module.instance">
|
[modname]="module.modname" [componentId]="module.instance">
|
||||||
|
|
|
@ -3,36 +3,41 @@
|
||||||
:host {
|
:host {
|
||||||
--course-storage-max-activity-height: 120px;
|
--course-storage-max-activity-height: 120px;
|
||||||
|
|
||||||
ion-card ion-item {
|
ion-card ion-item.size {
|
||||||
--inner-padding-end: 0px;
|
--inner-padding-end: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-card.section ion-card-header {
|
ion-card.section {
|
||||||
padding-top: 8px;
|
ion-card-header {
|
||||||
padding-bottom: 8px;
|
padding: 0;
|
||||||
}
|
}
|
||||||
ion-card.section .item-heading {
|
ion-card-content {
|
||||||
font-weight: bold;
|
padding: 0;
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-card-content.hidden-content {
|
.core-course-storage-activity ion-label {
|
||||||
padding: 0;
|
h3 {
|
||||||
}
|
position: relative;
|
||||||
|
max-height: var(--course-storage-max-activity-height);
|
||||||
|
overflow: hidden;
|
||||||
|
::ng-deep * {
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.core-course-storage-activity ion-label h3 {
|
&:before {
|
||||||
position: relative;
|
content: '';
|
||||||
max-height: var(--course-storage-max-activity-height);
|
height: var(--course-storage-max-activity-height);
|
||||||
|
position: absolute;
|
||||||
&:before {
|
@include position(0, 0, null, 0);
|
||||||
content: '';
|
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--background-gradient-rgb), 1) calc(100% - 20px));
|
||||||
height: 100%;
|
z-index: 6;
|
||||||
min-height: var(--course-storage-max-activity-height);
|
pointer-events: none;
|
||||||
position: absolute;
|
}
|
||||||
@include position(0, 0, null, 0);
|
}
|
||||||
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--background-gradient-rgb), 1) calc(100% - 20px));
|
}
|
||||||
z-index: 6;
|
}
|
||||||
pointer-events: none;
|
.item-heading {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,12 +47,6 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
||||||
*/
|
*/
|
||||||
updatesNames = /^.*files$/;
|
updatesNames = /^.*files$/;
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, this module will be ignored when determining the status of a list of modules. The module will
|
|
||||||
* still be downloaded when downloading the section/course, it only affects whether the button should be displayed.
|
|
||||||
*/
|
|
||||||
skipListStatus = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of download promises to prevent downloading the module twice at the same time.
|
* List of download promises to prevent downloading the module twice at the same time.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1361,8 +1361,10 @@ export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler {
|
||||||
/**
|
/**
|
||||||
* If true, this module will be treated as not downloadable when determining the status of a list of modules. The module will
|
* If true, this module will be treated as not downloadable when determining the status of a list of modules. The module will
|
||||||
* still be downloaded when downloading the section/course, it only affects whether the button should be displayed.
|
* still be downloaded when downloading the section/course, it only affects whether the button should be displayed.
|
||||||
|
*
|
||||||
|
* @depracated since app 4.0.
|
||||||
*/
|
*/
|
||||||
skipListStatus: boolean;
|
skipListStatus?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the download size of a module.
|
* Get the download size of a module.
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
<ion-card>
|
<form (ngSubmit)="submitForm($event)" role="search" #searchForm>
|
||||||
<form (ngSubmit)="submitForm($event)" role="search" #searchForm>
|
<ion-item class="search-box">
|
||||||
<ion-item>
|
<ion-label class="sr-only">{{ placeholder }}</ion-label>
|
||||||
<ion-label class="sr-only">{{ placeholder }}</ion-label>
|
<ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect"
|
||||||
<ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect"
|
[spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox" (ionFocus)="focus($event)">
|
||||||
[spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox" (ionFocus)="focus($event)">
|
</ion-input>
|
||||||
</ion-input>
|
<ion-button slot="end" fill="clear" type="submit" [attr.aria-label]="searchLabel"
|
||||||
<ion-button slot="end" fill="clear" type="submit" [attr.aria-label]="searchLabel"
|
[disabled]="disabled || !searchText || (searchText.length < lengthCheck)">
|
||||||
[disabled]="disabled || !searchText || (searchText.length < lengthCheck)">
|
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
</ion-button>
|
||||||
</ion-button>
|
<ion-button *ngIf="showClear" slot="end" fill="clear" [attr.aria-label]="'core.clearsearch' | translate"
|
||||||
<ion-button *ngIf="showClear" slot="end" fill="clear" [attr.aria-label]="'core.clearsearch' | translate"
|
[disabled]="searched == '' || disabled" (click)="clearForm()">
|
||||||
[disabled]="searched == '' || disabled" (click)="clearForm()">
|
<ion-icon name="fas-backspace" slot="icon-only" aria-hidden="true" flip-rtl></ion-icon>
|
||||||
<ion-icon name="fas-backspace" slot="icon-only" aria-hidden="true" flip-rtl></ion-icon>
|
</ion-button>
|
||||||
</ion-button>
|
</ion-item>
|
||||||
|
<ion-list class="core-search-history" [hidden]="!historyShown">
|
||||||
|
<ion-item button class="ion-text-wrap" *ngFor="let item of history" (click)="historyClicked($event, item.searchedtext)" tabindex="0"
|
||||||
|
detail="true">
|
||||||
|
<ion-icon name="fas-history" slot="start" aria-hidden="true">
|
||||||
|
</ion-icon>
|
||||||
|
<ion-label>{{item.searchedtext}}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-list class="core-search-history" [hidden]="!historyShown">
|
</ion-list>
|
||||||
<ion-item button class="ion-text-wrap" *ngFor="let item of history" (click)="historyClicked($event, item.searchedtext)"
|
</form>
|
||||||
tabindex="0" detail="true">
|
|
||||||
<ion-icon name="fas-history" slot="start" aria-hidden="true">
|
|
||||||
</ion-icon>
|
|
||||||
<ion-label>{{item.searchedtext}}</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
</ion-list>
|
|
||||||
</form>
|
|
||||||
</ion-card>
|
|
||||||
|
|
|
@ -1,29 +1,40 @@
|
||||||
:host {
|
:host {
|
||||||
min-height: var(--a11y-min-target-size);
|
--height: var(--a11y-min-target-size);
|
||||||
|
--search-height: calc(var(--height) - 2px);
|
||||||
|
min-height: var(--height);
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
|
|
||||||
ion-card {
|
form {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
--border-color: var(--core-search-box-border-color);
|
border: 1px solid var(--core-search-box-border-color);
|
||||||
--border-radius: var(--core-search-box-border-radius);
|
border-radius: var(--core-search-box-border-radius);
|
||||||
--background: var(--core-search-box-background);
|
background: var(--core-search-box-background);
|
||||||
--color: var(--core-search-box-border-color);
|
color: var(--core-search-box-border-color);
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
--min-height: var(--search-height);
|
||||||
|
border-radius: var(--core-search-box-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-button.button {
|
||||||
|
margin: 0;
|
||||||
|
--a11y-min-target-size: var(--search-height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-button.button {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.core-search-history {
|
.core-search-history {
|
||||||
max-height: calc(-120px + 80vh);
|
max-height: calc(-120px + 80vh);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: var(--core-search-box-background);
|
background: var(--core-search-box-background);
|
||||||
|
border-radius: 0 0 var(--core-search-box-border-radius) var(--core-search-box-border-radius);
|
||||||
--ion-item-background: var(--core-search-box-background);
|
--ion-item-background: var(--core-search-box-background);
|
||||||
|
|
||||||
.item:hover {
|
.item:hover {
|
||||||
|
|
|
@ -501,10 +501,6 @@ ion-toast {
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
&::part(container) {
|
&::part(container) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
&::part(message) {
|
|
||||||
align-self: flex-start;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -848,6 +844,10 @@ ion-card {
|
||||||
ion-item:only-child {
|
ion-item:only-child {
|
||||||
--inner-border-width: 0px;
|
--inner-border-width: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-card-title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-course-module-handler:not(.addon-mod-label-handler) .item-heading .filter_mathjaxloader_equation div {
|
.core-course-module-handler:not(.addon-mod-label-handler) .item-heading .filter_mathjaxloader_equation div {
|
||||||
|
@ -1054,9 +1054,16 @@ ion-button.chip {
|
||||||
ion-icon {
|
ion-icon {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
min-width: 16px;
|
min-width: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-icon[slot=start] {
|
||||||
@include margin(0, 8px, 0, 0);
|
@include margin(0, 8px, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-icon[slot=end] {
|
||||||
|
@include margin(0, 0, 0, 8px);
|
||||||
|
}
|
||||||
|
|
||||||
ion-label {
|
ion-label {
|
||||||
white-space: normal !important;
|
white-space: normal !important;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue