Pull request #292: Workflow

Merge in RED/ui from workflow to master

* commit '1e9071a643f9ed3439540378c5f19e419d8c2f96':
  Empty state, reload fixes
  Tooltip position fix
  Updates
  Disabled items
  Permissions, updated common-ui
  Workflow initial
This commit is contained in:
Adina Teudan 2021-09-28 15:05:51 +02:00
commit 0e8b081fdd
21 changed files with 611 additions and 344 deletions

View File

@ -14,7 +14,7 @@
border-radius: 4px;
width: 100%;
justify-content: flex-start;
padding: 0 8px;
padding: 4px 8px;
margin-left: -8px;
&.link-property {

View File

@ -1,17 +1,26 @@
<div *ngIf="screen === 'dossier-overview'" class="action-buttons">
<div *ngIf="isDossierOverviewList" class="action-buttons">
<ng-container *ngTemplateOutlet="actions"></ng-container>
<iqser-status-bar *ngIf="isWorkable" [configs]="statusBarConfig"></iqser-status-bar>
<iqser-status-bar *ngIf="showStatusBar" [configs]="statusBarConfig"></iqser-status-bar>
</div>
<ng-container *ngIf="screen === 'file-preview'">
<ng-container *ngIf="isFilePreview || isDossierOverviewWorkflow">
<ng-container *ngTemplateOutlet="actions"></ng-container>
</ng-container>
<ng-template #actions redactionLongPress (longPress)="forceReanalysisAction($event)">
<div class="file-actions" *ngIf="fileStatus">
<div *ngIf="fileStatus" class="file-actions">
<iqser-circle-button
(action)="openDocument()"
*ngIf="showOpenDocument"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.open-document' | translate"
[type]="buttonType"
icon="red:collapse"
></iqser-circle-button>
<iqser-circle-button
(action)="openDeleteFileDialog($event)"
*ngIf="canDelete"
*ngIf="showDelete"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.delete.action' | translate"
[type]="buttonType"
@ -20,7 +29,7 @@
<iqser-circle-button
(action)="assign($event)"
*ngIf="canAssign && screen === 'dossier-overview'"
*ngIf="showAssign"
[tooltipPosition]="tooltipPosition"
[tooltip]="assignTooltip | translate"
[type]="buttonType"
@ -29,7 +38,7 @@
<iqser-circle-button
(action)="assignToMe($event)"
*ngIf="canAssignToSelf && screen === 'dossier-overview'"
*ngIf="showAssignToSelf"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.assign-me' | translate"
[type]="buttonType"
@ -47,7 +56,7 @@
<iqser-circle-button
(action)="toggleViewDocumentInfo()"
*ngIf="screen === 'file-preview'"
*ngIf="showDocumentInfo"
[attr.aria-expanded]="activeDocumentInfo"
[tooltip]="'file-preview.document-info' | translate"
icon="red:status-info"
@ -56,7 +65,7 @@
<iqser-circle-button
(action)="toggleExcludePages()"
*ngIf="screen === 'file-preview'"
*ngIf="showExcludePages"
[attr.aria-expanded]="activeExcludePages"
[showDot]="!!fileStatus.excludedPages?.length"
[tooltip]="'file-preview.exclude-pages' | translate"
@ -67,7 +76,7 @@
<!-- Ready for approval-->
<iqser-circle-button
(action)="setFileUnderApproval($event)"
*ngIf="canSetToUnderApproval"
*ngIf="showUnderApproval"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.under-approval' | translate"
[type]="buttonType"
@ -77,7 +86,7 @@
<!-- Back to review -->
<iqser-circle-button
(action)="setFileUnderReview($event, true)"
*ngIf="canSetToUnderReview"
*ngIf="showUnderReview"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.under-review' | translate"
[type]="buttonType"
@ -87,7 +96,7 @@
<!-- Approved-->
<iqser-circle-button
(action)="setFileApproved($event)"
*ngIf="readyForApproval"
*ngIf="showApprove"
[disabled]="!fileStatus.canBeApproved"
[tooltipPosition]="tooltipPosition"
[tooltip]="
@ -100,7 +109,7 @@
<!-- Back to approval -->
<iqser-circle-button
(action)="setFileUnderApproval($event)"
*ngIf="canUndoApproval"
*ngIf="showUndoApproval"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.under-approval' | translate"
[type]="buttonType"
@ -109,7 +118,7 @@
<iqser-circle-button
(action)="ocrFile($event)"
*ngIf="fileStatus.canBeOCRed"
*ngIf="showOCR"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.ocr-file' | translate"
[type]="buttonType"
@ -119,22 +128,22 @@
<!-- reanalyse file preview -->
<iqser-circle-button
(action)="reanalyseFile($event)"
*ngIf="canReanalyse && screen === 'file-preview'"
*ngIf="canReanalyse && isFilePreview"
[tooltip]="'file-preview.reanalyse-notification' | translate"
[type]="circleButtonTypes.warn"
icon="iqser:refresh"
tooltipClass="warn small"
tooltipPosition="below"
[type]="circleButtonTypes.warn"
></iqser-circle-button>
<!-- reanalyse file listing -->
<iqser-circle-button
(action)="reanalyseFile($event)"
*ngIf="canReanalyse && screen === 'dossier-overview'"
*ngIf="canReanalyse && isDossierOverview"
[tooltipPosition]="tooltipPosition"
[tooltip]="'dossier-overview.reanalyse.action' | translate"
icon="iqser:refresh"
[type]="circleButtonTypes.dark"
icon="iqser:refresh"
></iqser-circle-button>
<!-- exclude from redaction -->
<div class="iqser-input-group">
@ -142,7 +151,7 @@
(change)="toggleAnalysis()"
(click)="$event.stopPropagation()"
[checked]="!fileStatus?.excluded"
[class.mr-24]="screen === 'dossier-overview'"
[class.mr-24]="isDossierOverviewList"
[disabled]="!canToggleAnalysis"
[matTooltipPosition]="tooltipPosition"
[matTooltip]="toggleTooltip | translate"

View File

@ -1,5 +1,11 @@
@use 'common-mixins';
@use 'variables';
.file-actions {
display: flex;
overflow-y: auto;
color: variables.$grey-1;
@include common-mixins.no-scroll-bar;
> *:not(:last-child) {
margin-right: 2px;

View File

@ -5,7 +5,7 @@ import { AppStateService } from '@state/app-state.service';
import { FileActionService } from '../../services/file-action.service';
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { ConfirmationDialogInput } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { AutoUnsubscribe, CircleButtonType, CircleButtonTypes, LoadingService, StatusBarConfig, Toaster } from '@iqser/common-ui';
import { AutoUnsubscribe, CircleButtonType, CircleButtonTypes, LoadingService, Required, StatusBarConfig, Toaster } from '@iqser/common-ui';
import { FileManagementControllerService, FileStatus } from '@redaction/red-ui-http';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
@ -25,25 +25,29 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
@Input() fileStatus: FileStatusWrapper;
@Input() activeDocumentInfo: boolean;
@Input() activeExcludePages: boolean;
@Input() @Required() type: 'file-preview' | 'dossier-overview-list' | 'dossier-overview-workflow';
@Output() actionPerformed = new EventEmitter<string>();
screen: 'file-preview' | 'dossier-overview';
statusBarConfig?: readonly StatusBarConfig<FileStatus.StatusEnum>[];
tooltipPosition?: 'below' | 'above';
toggleTooltip?: string;
assignTooltip?: string;
buttonType?: CircleButtonType;
isWorkable: boolean;
canUndoApproval: boolean;
canAssignToSelf: boolean;
canAssign: boolean;
canDelete: boolean;
showUndoApproval: boolean;
showAssignToSelf: boolean;
showAssign: boolean;
showDelete: boolean;
showOCR: boolean;
canReanalyse: boolean;
canSetToUnderReview: boolean;
canSetToUnderApproval: boolean;
readyForApproval: boolean;
showUnderReview: boolean;
showUnderApproval: boolean;
showApprove: boolean;
canToggleAnalysis: boolean;
showExcludePages: boolean;
showDocumentInfo: boolean;
showStatusBar: boolean;
showOpenDocument: boolean;
constructor(
readonly permissionsService: PermissionsService,
@ -59,6 +63,22 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
super();
}
get isDossierOverviewList(): boolean {
return this.type === 'dossier-overview-list';
}
get isDossierOverviewWorkflow(): boolean {
return this.type === 'dossier-overview-workflow';
}
get isFilePreview(): boolean {
return this.type === 'file-preview';
}
get isDossierOverview(): boolean {
return this.type.startsWith('dossier-overview-list');
}
private get _toggleTooltip(): string {
if (!this.currentUser.isManager) {
return _('file-preview.toggle-analysis.only-managers');
@ -68,13 +88,9 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
}
ngOnInit(): void {
if (this.fileStatus) {
this.screen = 'dossier-overview';
} else {
if (!this.fileStatus) {
this.fileStatus = this.appStateService.activeFile;
this.screen = 'file-preview';
}
this._setup();
this.addSubscription = this.appStateService.fileChanged$
.pipe(filter(file => file.fileId === this.fileStatus?.fileId))
@ -96,6 +112,10 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
this.actionPerformed.emit('view-exclude-pages');
}
openDocument() {
this.actionPerformed.emit('navigate');
}
openDeleteFileDialog($event: MouseEvent) {
this._dialogService.openDialog(
'confirm',
@ -223,22 +243,31 @@ export class FileActionsComponent extends AutoUnsubscribe implements OnInit, OnD
private _setup() {
this.statusBarConfig = [{ color: this.fileStatus.status, length: 1 }];
this.tooltipPosition = this.screen === 'file-preview' ? 'below' : 'above';
this.tooltipPosition = this.isFilePreview ? 'below' : 'above';
this.assignTooltip = this.fileStatus.isUnderApproval
? _('dossier-overview.assign-approver')
: _('dossier-overview.assign-reviewer');
this.buttonType = this.screen === 'file-preview' ? CircleButtonTypes.default : CircleButtonTypes.dark;
this.isWorkable = this.fileStatus.isWorkable;
this.buttonType = this.isFilePreview ? CircleButtonTypes.default : CircleButtonTypes.dark;
this.toggleTooltip = this._toggleTooltip;
this.canUndoApproval = this.permissionsService.canUndoApproval(this.fileStatus);
this.canAssignToSelf = this.permissionsService.canAssignToSelf(this.fileStatus);
this.canAssign = this.permissionsService.canAssignUser(this.fileStatus);
this.canDelete = this.permissionsService.canDeleteFile(this.fileStatus);
this.canReanalyse = this.permissionsService.canReanalyseFile(this.fileStatus);
this.canSetToUnderReview = this.permissionsService.canSetUnderReview(this.fileStatus);
this.canSetToUnderApproval = this.permissionsService.canSetUnderApproval(this.fileStatus);
this.readyForApproval = this.permissionsService.isReadyForApproval(this.fileStatus);
this.showUndoApproval = this.permissionsService.canUndoApproval(this.fileStatus) && !this.isDossierOverviewWorkflow;
this.showUnderReview = this.permissionsService.canSetUnderReview(this.fileStatus) && !this.isDossierOverviewWorkflow;
this.showUnderApproval = this.permissionsService.canSetUnderApproval(this.fileStatus) && !this.isDossierOverviewWorkflow;
this.showApprove = this.permissionsService.isReadyForApproval(this.fileStatus) && !this.isDossierOverviewWorkflow;
this.canToggleAnalysis = this.permissionsService.canToggleAnalysis(this.fileStatus);
this.showDelete = this.permissionsService.canDeleteFile(this.fileStatus);
this.showOCR = this.fileStatus.canBeOCRed;
this.canReanalyse = this.permissionsService.canReanalyseFile(this.fileStatus);
this.showStatusBar = this.fileStatus.isWorkable && this.isDossierOverviewList;
this.showAssignToSelf = this.permissionsService.canAssignToSelf(this.fileStatus) && this.isDossierOverview;
this.showAssign = this.permissionsService.canAssignUser(this.fileStatus) && this.isDossierOverview;
this.showOpenDocument = this.fileStatus.canBeOpened && this.isDossierOverviewWorkflow;
this.showExcludePages = this.isFilePreview;
this.showDocumentInfo = this.isFilePreview;
}
}

View File

@ -33,10 +33,10 @@
<ng-template #nameTemplate let-dossier="entity">
<div class="cell">
<div [matTooltip]="dossier.dossierName" class="table-item-title heading" matTooltipPosition="above">
<div [matTooltip]="dossier.dossierName" class="table-item-title heading mb-6" matTooltipPosition="above">
{{ dossier.dossierName }}
</div>
<div class="small-label stats-subtitle">
<div class="small-label stats-subtitle mb-6">
<div>
<mat-icon svgIcon="red:template"></mat-icon>
{{ dossier.dossierTemplateName }}

View File

@ -4,6 +4,7 @@
[actionConfigs]="actionConfigs"
[fileAttributeConfigs]="fileAttributeConfigs"
[showCloseButton]="true"
[viewModeSelection]="viewModeSelection"
>
<redaction-file-download-btn
[dossier]="currentDossier"
@ -37,6 +38,7 @@
<div [class.extended]="collapsedDetails" class="content-container">
<iqser-table
(noDataAction)="fileInput.click()"
*ngIf="(listingMode$ | async) === listingModes.table"
[bulkActions]="bulkActions"
[hasScrollButton]="true"
[itemSize]="80"
@ -49,6 +51,23 @@
noDataButtonIcon="red:upload"
noDataIcon="red:document"
></iqser-table>
<iqser-workflow
(addElement)="fileInput.click()"
(noDataAction)="fileInput.click()"
*ngIf="(listingMode$ | async) === listingModes.workflow"
[config]="workflowConfig"
[itemClasses]="{ disabled: disabledFn }"
[itemTemplate]="workflowItemTemplate"
[noDataButtonLabel]="'dossier-overview.no-data.action' | translate"
[noDataText]="'dossier-overview.no-data.title' | translate"
[showNoDataButton]="true"
addElementColumn="UNASSIGNED"
addElementIcon="red:upload"
itemHeight="56px"
noDataButtonIcon="red:upload"
noDataIcon="red:document"
></iqser-workflow>
</div>
<div [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar>
@ -74,34 +93,19 @@
<ng-template #filenameTemplate let-fileStatus="entity">
<div class="cell">
<div class="filename-wrapper">
<div [class.error]="fileStatus.isError" class="table-item-title text-overflow">
<span [matTooltip]="fileStatus.filename" matTooltipPosition="above">
{{ fileStatus.filename }}
</span>
<div>
<div [class.error]="fileStatus.isError" [matTooltip]="fileStatus.filename" class="table-item-title" matTooltipPosition="above">
{{ fileStatus.filename }}
</div>
</div>
<div *ngIf="fileStatus.primaryAttribute" class="small-label">
<div class="primary-attribute text-overflow">
<div class="primary-attribute">
<span [matTooltip]="fileStatus.primaryAttribute" matTooltipPosition="above">
{{ fileStatus.primaryAttribute }}
</span>
</div>
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
</div>
<div>
<mat-icon svgIcon="red:exclude-pages"></mat-icon>
{{ fileStatus.excludedPagesCount }}
</div>
<div *ngIf="fileStatus.lastOCRTime" [matTooltipPosition]="'above'" [matTooltip]="'dossier-overview.ocr-performed' | translate">
<mat-icon svgIcon="red:ocr"></mat-icon>
{{ fileStatus.lastOCRTime | date: 'mediumDate' }}
</div>
</div>
<ng-container *ngTemplateOutlet="statsTemplate; context: { entity: fileStatus }"></ng-container>
</div>
</ng-template>
@ -167,10 +171,69 @@
></iqser-status-bar>
<redaction-file-actions
(actionPerformed)="calculateData()"
(actionPerformed)="actionPerformed()"
*ngIf="!fileStatus.isProcessing"
[fileStatus]="fileStatus"
class="mr-4"
type="dossier-overview-list"
></redaction-file-actions>
</div>
</ng-template>
<ng-template #viewModeSelection>
<div class="view-mode-selection">
<div class="all-caps-label" translate="view-mode.view-as"></div>
<iqser-circle-button
(action)="listingMode = listingModes.workflow"
[attr.aria-expanded]="(listingMode$ | async) === listingModes.workflow"
[tooltip]="'view-mode.workflow' | translate"
icon="iqser:lanes"
></iqser-circle-button>
<iqser-circle-button
(action)="listingMode = listingModes.table"
[attr.aria-expanded]="(listingMode$ | async) === listingModes.table"
[tooltip]="'view-mode.list' | translate"
icon="iqser:list"
></iqser-circle-button>
</div>
</ng-template>
<ng-template #workflowItemTemplate let-fileStatus="entity">
<div class="workflow-item">
<div>
<div class="details">
<div [matTooltip]="fileStatus.filename" class="filename" matTooltipPosition="above">
{{ fileStatus.filename }}
</div>
<ng-container *ngTemplateOutlet="statsTemplate; context: { entity: fileStatus }"></ng-container>
</div>
<div class="user">
<redaction-initials-avatar [userId]="fileStatus.currentReviewer"></redaction-initials-avatar>
</div>
</div>
<redaction-file-actions
(actionPerformed)="actionPerformed($event, fileStatus)"
*ngIf="!fileStatus.isProcessing"
[fileStatus]="fileStatus"
type="dossier-overview-workflow"
></redaction-file-actions>
</div>
</ng-template>
<ng-template #statsTemplate let-fileStatus="entity">
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
</div>
<div>
<mat-icon svgIcon="red:exclude-pages"></mat-icon>
{{ fileStatus.excludedPagesCount }}
</div>
<div *ngIf="fileStatus.lastOCRTime" [matTooltipPosition]="'above'" [matTooltip]="'dossier-overview.ocr-performed' | translate">
<mat-icon svgIcon="red:ocr"></mat-icon>
{{ fileStatus.lastOCRTime | date: 'mediumDate' }}
</div>
</div>
</ng-template>

View File

@ -1,4 +1,5 @@
@use 'variables';
@use 'common-mixins';
.file-upload-input {
display: none;
@ -27,6 +28,7 @@
.primary-attribute {
padding-top: 6px;
@include common-mixins.line-clamp(1);
}
&.extend-cols {
@ -63,3 +65,56 @@
background-color: inherit;
}
}
.view-mode-selection {
border-right: 1px solid variables.$separator;
padding-right: 16px;
margin-right: 16px !important;
display: flex;
align-items: center;
> iqser-circle-button:not(:last-child) {
margin-right: 2px;
}
> div {
margin-right: 8px;
}
}
.workflow-item {
padding: 10px;
> div {
display: flex;
justify-content: space-between;
.details {
max-width: calc(100% - 28px);
.filename {
font-weight: 600;
line-height: 18px;
@include common-mixins.line-clamp(1);
}
}
.user {
display: flex;
align-items: flex-end;
}
}
redaction-file-actions {
margin-top: 10px;
display: none;
}
&:hover redaction-file-actions {
display: block;
}
}
.stats-subtitle {
margin-top: 4px;
}

View File

@ -10,6 +10,7 @@ import {
TemplateRef,
ViewChild
} from '@angular/core';
import { FileAttributeConfig, FileStatus } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service';
import { FileUploadModel } from '@upload-download/model/file-upload.model';
@ -34,11 +35,13 @@ import {
DefaultListingServices,
keyChecker,
ListingComponent,
ListingModes,
LoadingService,
NestedFilter,
TableColumnConfig,
TableComponent,
Toaster
Toaster,
WorkflowConfig
} from '@iqser/common-ui';
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
@ -49,8 +52,10 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { annotationFilterChecker } from '@utils/filter-utils';
import { PermissionsService } from '@services/permissions.service';
import { RouterHistoryService } from '@services/router-history.service';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { DossierWrapper } from '@state/model/dossier.wrapper';
import { Router } from '@angular/router';
import { FileActionService } from '../../services/file-action.service';
import StatusEnum = FileStatus.StatusEnum;
@Component({
templateUrl: './dossier-overview-screen.component.html',
@ -58,6 +63,7 @@ import { DossierWrapper } from '@state/model/dossier.wrapper';
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }]
})
export class DossierOverviewScreenComponent extends ListingComponent<FileStatusWrapper> implements OnInit, OnDestroy, OnDetach, OnAttach {
readonly listingModes = ListingModes;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
currentDossier = this._appStateService.activeDossier;
@ -81,6 +87,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
@ViewChild('reviewerTemplate', { static: true }) reviewerTemplate: TemplateRef<never>;
@ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef<never>;
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
readonly workflowConfig: WorkflowConfig<FileStatusWrapper, StatusEnum>;
protected readonly _primaryKey = 'filename';
@ViewChild(DossierDetailsComponent, { static: false })
private readonly _dossierDetailsComponent: DossierDetailsComponent;
@ -93,6 +100,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
constructor(
private readonly _toaster: Toaster,
protected readonly _injector: Injector,
private readonly _router: Router,
private readonly _userService: UserService,
readonly permissionsService: PermissionsService,
private readonly _loadingService: LoadingService,
@ -106,13 +114,52 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
private readonly _statusOverlayService: StatusOverlayService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _fileDropOverlayService: FileDropOverlayService,
private readonly _dossierAttributesService: DossierAttributesService
private readonly _dossierAttributesService: DossierAttributesService,
private readonly _fileActionService: FileActionService
) {
super(_injector);
this._loadEntitiesFromState();
this.fileAttributeConfigs = this._appStateService.getFileAttributeConfig(
this.currentDossier.dossierTemplateId
).fileAttributeConfigs;
this.workflowConfig = {
columnIdentifierFn: entity => entity.status,
itemVersionFn: (entity: FileStatusWrapper) => `${entity.lastUpdated}-${entity.numberOfAnalyses}`,
columns: [
{
label: fileStatusTranslations[StatusEnum.UNASSIGNED],
key: StatusEnum.UNASSIGNED,
enterFn: this.unassignFn,
enterPredicate: (entity: FileStatusWrapper) => false,
color: '#D3D5DA'
},
{
label: fileStatusTranslations[StatusEnum.UNDERREVIEW],
enterFn: this.underReviewFn,
enterPredicate: (file: FileStatusWrapper) =>
this.permissionsService.canSetUnderReview(file) ||
this.permissionsService.canAssignToSelf(file) ||
this.permissionsService.canAssignUser(file),
key: StatusEnum.UNDERREVIEW,
color: '#FDBD00'
},
{
label: fileStatusTranslations[StatusEnum.UNDERAPPROVAL],
enterFn: this.underApprovalFn,
enterPredicate: (file: FileStatusWrapper) =>
this.permissionsService.canSetUnderApproval(file) || this.permissionsService.canUndoApproval(file),
key: StatusEnum.UNDERAPPROVAL,
color: '#374C81'
},
{
label: fileStatusTranslations[StatusEnum.APPROVED],
enterFn: this.approveFn,
enterPredicate: (file: FileStatusWrapper) => this.permissionsService.isReadyForApproval(file),
key: StatusEnum.APPROVED,
color: '#48C9F7'
}
]
};
}
get checkedRequiredFilters() {
@ -127,10 +174,46 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
return this.fileAttributeConfigs.filter(config => config.displayedInFileList);
}
unassignFn = async (file: FileStatusWrapper) => {
// TODO
console.log('unassign', file);
};
underReviewFn = (file: FileStatusWrapper) => {
this._fileActionService.assignFile('reviewer', null, file, () => this._loadingService.loadWhile(this.reloadDossiers()), true);
};
underApprovalFn = async (file: FileStatusWrapper) => {
if (this._appStateService.activeDossier.approverIds.length > 1) {
this._fileActionService.assignFile('approver', null, file, () => this._loadingService.loadWhile(this.reloadDossiers()), true);
} else {
this._loadingService.start();
await this._fileActionService.setFileUnderApproval(file).toPromise();
await this.reloadDossiers();
this._loadingService.stop();
}
};
approveFn = async (file: FileStatusWrapper) => {
this._loadingService.start();
await this._fileActionService.setFileApproved(file).toPromise();
await this.reloadDossiers();
this._loadingService.stop();
};
actionPerformed(action?: string, fileStatus?: FileStatusWrapper) {
this._calculateData();
if (action === 'navigate') {
this._router.navigate(this.routerLinkFn(fileStatus));
}
}
routerLinkFn = (fileStatus: FileStatusWrapper) =>
fileStatus.canBeOpened ? [`/main/dossiers/${this.currentDossier.dossierId}/file/${fileStatus.fileId}`] : [];
disabledFn = (fileStatus: FileStatusWrapper) => fileStatus.excluded;
lastOpenedFn = (fileStatus: FileStatusWrapper) => fileStatus.lastOpened;
async ngOnInit(): Promise<void> {
@ -139,7 +222,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
try {
this._fileDropOverlayService.initFileDropHandling();
this.calculateData();
this._calculateData();
this.addSubscription = timer(0, 7500).subscribe(async () => {
await this._appStateService.reloadActiveDossierFilesIfNecessary();
@ -147,7 +230,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
});
this.addSubscription = this._appStateService.fileChanged$.subscribe(() => {
this.calculateData();
this._calculateData();
});
this.addSubscription = this._appStateService.dossierTemplateChanged$.subscribe(() => {
@ -197,10 +280,10 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
async reloadDossiers() {
await this._appStateService.getFiles(this.currentDossier, false);
this.calculateData();
this._calculateData();
}
calculateData(): void {
_calculateData(): void {
if (!this._appStateService.activeDossierId) {
return;
}

View File

@ -92,6 +92,7 @@
(actionPerformed)="fileActionPerformed($event)"
[activeDocumentInfo]="viewDocumentInfo"
[activeExcludePages]="excludePages"
type="file-preview"
></redaction-file-actions>
<iqser-circle-button
@ -107,10 +108,10 @@
(action)="downloadOriginalFile()"
*ngIf="userPreferenceService.areDevFeaturesEnabled"
[tooltip]="'file-preview.download-original-file' | translate"
[type]="circleButtonTypes.primary"
class="ml-8"
icon="red:download"
tooltipPosition="below"
[type]="circleButtonTypes.primary"
></iqser-circle-button>
<!-- End Dev Mode Features-->

View File

@ -8,6 +8,7 @@ import { isArray } from 'rxjs/internal-compatibility';
import { DossiersDialogService } from './dossiers-dialog.service';
import { ConfirmationDialogInput } from '../../shared/dialogs/confirmation-dialog/confirmation-dialog.component';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { Observable } from 'rxjs';
@Injectable()
export class FileActionService {
@ -83,7 +84,7 @@ export class FileActionService {
);
}
setFileApproved(fileStatus: FileStatusWrapper | FileStatusWrapper[]) {
setFileApproved(fileStatus: FileStatusWrapper | FileStatusWrapper[]): Observable<any> {
if (!isArray(fileStatus)) {
fileStatus = [fileStatus];
}
@ -116,11 +117,7 @@ export class FileActionService {
assignFile(mode: 'reviewer' | 'approver', $event: MouseEvent, file?: FileStatusWrapper, callback?: Function, ignoreChanged = false) {
const files = file ? [file] : [this._appStateService.activeFile];
const data = { mode, files, ignoreChanged };
this._dialogService.openDialog('assignFile', $event, data, async () => {
if (callback) {
callback();
}
});
this._dialogService.openDialog('assignFile', $event, data, callback);
}
private async _assignReviewerToCurrentUser(fileStatus: FileStatusWrapper | FileStatusWrapper[], callback?: Function) {

View File

@ -15,7 +15,9 @@
<div (click)="resetFilters()" *ngIf="showResetFilters$ | async" class="reset-filters" translate="reset-filters"></div>
</div>
<div *ngIf="showCloseButton || actionConfigs || buttonConfigs" class="actions">
<div *ngIf="showCloseButton || actionConfigs || buttonConfigs || viewModeSelection" class="actions">
<ng-container [ngTemplateOutlet]="viewModeSelection"></ng-container>
<ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel">
<iqser-icon-button
(action)="config.action($event)"
@ -53,10 +55,10 @@
<ng-template #searchBar>
<iqser-input-with-action
[(value)]="searchService.searchValue"
*ngIf="searchPlaceholder && searchService"
[(value)]="searchService.searchValue"
[class.mr-8]="searchPosition === searchPositions.beforeFilters"
[placeholder]="searchPlaceholder"
[width]="searchWidth"
[class.mr-8]="searchPosition === searchPositions.beforeFilters"
></iqser-input-with-action>
</ng-template>

View File

@ -1,3 +1,5 @@
@import '../../../../../assets/styles/variables';
.ml-6 {
margin-left: 6px;
}

View File

@ -1,7 +1,7 @@
import { Component, EventEmitter, Input, Optional, Output } from '@angular/core';
import { Component, EventEmitter, Input, Optional, Output, TemplateRef } from '@angular/core';
import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
import { ButtonConfig } from '@shared/components/page-header/models/button-config.model';
import { FilterService, SearchService } from '@iqser/common-ui';
import { FilterService, IconButtonTypes, Listable, SearchService } from '@iqser/common-ui';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { combineLatest, Observable, of } from 'rxjs';
import { SearchPosition, SearchPositions } from '@shared/components/page-header/models/search-positions.type';
@ -12,13 +12,15 @@ import { FileAttributeConfig } from '@redaction/red-ui-http';
templateUrl: './page-header.component.html',
styleUrls: ['./page-header.component.scss']
})
export class PageHeaderComponent<T> {
export class PageHeaderComponent<T extends Listable> {
readonly searchPositions = SearchPositions;
readonly iconButtonTypes = IconButtonTypes;
@Input() pageLabel: string;
@Input() showCloseButton: boolean;
@Input() actionConfigs: readonly ActionConfig[];
@Input() buttonConfigs: readonly ButtonConfig[];
@Input() viewModeSelection: TemplateRef<unknown>;
@Input() fileAttributeConfigs: readonly FileAttributeConfig[];
@Input() searchPlaceholder: string;
@Input() searchWidth: number | 'full';

View File

@ -69,5 +69,4 @@ const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, Rea
}
]
})
export class SharedModule {
}
export class SharedModule {}

View File

@ -688,6 +688,7 @@
},
"ocr-file": "OCR Document",
"ocr-performed": "OCR was performed for this file.",
"open-document": "Open Document",
"quick-filters": {
"assigned-to-me": "Assigned to me",
"assigned-to-others": "Assigned to others",
@ -1502,6 +1503,11 @@
"expand": "Show Details",
"title": "Users"
},
"view-mode": {
"list": "List",
"view-as": "View as:",
"workflow": "Workflow"
},
"watermark": "Watermark",
"watermark-screen": {
"action": {

View File

@ -36,7 +36,6 @@ $dark: $black;
$btn-bg-hover: $grey-4;
$btn-bg: $grey-6;
$filter-bg: $grey-2;
$quick-filter-border: $grey-5;
$separator: rgba(226, 228, 233, 0.9);

View File

@ -81,6 +81,8 @@
mat-icon {
width: 10px;
height: 10px;
line-height: 13px;
margin-right: 6px;
}

View File

@ -241,10 +241,18 @@ section.settings {
margin-top: 32px;
}
.mb-6 {
margin-bottom: 6px;
}
.mb-8 {
margin-bottom: 8px !important;
}
.mb-12 {
margin-bottom: 12px !important;
}
.ml-8 {
margin-left: 8px;
}

View File

@ -18,7 +18,11 @@
$iqser-white: vars.$white,
$iqser-separator: vars.$separator,
$iqser-quick-filter-border: vars.$quick-filter-border,
$iqser-filter-bg: vars.$filter-bg,
$iqser-grey-2: vars.$grey-2,
$iqser-grey-3: vars.$grey-3,
$iqser-grey-4: vars.$grey-4,
$iqser-grey-5: vars.$grey-5,
$iqser-grey-6: vars.$grey-6,
$iqser-helpmode-primary: vars.$green-2
);

@ -1 +1 @@
Subproject commit ea82e4351f217045e064ff10ee73ebbd190f330d
Subproject commit 0dc6d05cef931e2682bb9584ad0d0cb31144e5c6

View File

@ -1,388 +1,388 @@
@charset "UTF-8";
@font-face {
font-family: "OpenSans Extrabold";
src: url("../../fonts/OpenSans-ExtraBold.ttf") format("truetype");
font-family: 'OpenSans Extrabold';
src: url('../../fonts/OpenSans-ExtraBold.ttf') format('truetype');
}
@font-face {
font-family: "Inter";
font-weight: 300;
src: url("../../fonts/Inter-VariableFont.ttf") format("truetype");
font-family: 'Inter';
font-weight: 300;
src: url('../../fonts/Inter-VariableFont.ttf') format('truetype');
}
@font-face {
font-family: "Inter";
font-weight: 400;
src: url("../../fonts/Inter-VariableFont.ttf") format("truetype");
font-family: 'Inter';
font-weight: 400;
src: url('../../fonts/Inter-VariableFont.ttf') format('truetype');
}
@font-face {
font-family: "Inter";
font-weight: 500;
src: url("../../fonts/Inter-VariableFont.ttf") format("truetype");
font-family: 'Inter';
font-weight: 500;
src: url('../../fonts/Inter-VariableFont.ttf') format('truetype');
}
@font-face {
font-family: "Inter";
font-weight: 600;
src: url("../../fonts/Inter-VariableFont.ttf") format("truetype");
font-family: 'Inter';
font-weight: 600;
src: url('../../fonts/Inter-VariableFont.ttf') format('truetype');
}
@font-face {
font-family: "Inter";
font-weight: 700;
src: url("../../font/Inter-VariableFont.ttf") format("truetype");
font-family: 'Inter';
font-weight: 700;
src: url('../../font/Inter-VariableFont.ttf') format('truetype');
}
.portal-header {
z-index: 1;
height: 450px;
z-index: 1;
height: 450px;
}
.portal-header::before {
background-color: #283241;
background-color: #283241;
}
.portal-header h1 {
font-size: 64px;
font-weight: 300;
line-height: 87px;
margin-top: 68px;
margin-bottom: 40px;
font-size: 64px;
font-weight: 300;
line-height: 87px;
margin-top: 68px;
margin-bottom: 40px;
}
.portal-header .portal-search {
max-width: 600px;
margin: auto;
position: relative;
max-width: 600px;
margin: auto;
position: relative;
}
.portal-header .portal-search .search-field {
width: 100%;
border: 1px solid #d3d5da;
border-radius: 8px;
background-color: #fff;
width: 100%;
border: 1px solid #d3d5da;
border-radius: 8px;
background-color: #fff;
}
.portal-header .portal-search .search-field::placeholder {
opacity: 0.7;
opacity: 0.7;
}
.portal-header .portal-search .search-field,
.portal-header .portal-search .search-field::placeholder {
color: #283241;
font-size: 14px;
line-height: 18px;
color: #283241;
font-size: 14px;
line-height: 18px;
}
.portal-header .portal-search .search-field {
padding: 12px 17px;
padding: 12px 17px;
}
.portal-header .portal-search .btn {
position: absolute;
right: 0;
padding: 11px 18px;
background-color: transparent;
color: #283241;
cursor: pointer;
border-radius: 0 8px 8px 0;
position: absolute;
right: 0;
padding: 11px 18px;
background-color: transparent;
color: #283241;
cursor: pointer;
border-radius: 0 8px 8px 0;
}
.portal-header .portal-search .btn:hover {
background-color: #dd4d50;
background-color: #dd4d50;
}
@media only screen and (max-width: 768px) {
.portal-header h1 {
font-size: 42px;
font-weight: 300;
line-height: 57px;
}
.portal-header h1 {
font-size: 42px;
font-weight: 300;
line-height: 57px;
}
}
.featured-content-label {
display: none;
display: none;
}
.featured-content {
display: none;
display: none;
}
.portal-single-publication .publication-icon {
background-color: #dd4d50;
background-color: #dd4d50;
}
.portal-contents {
margin-top: 100px;
margin-bottom: 0;
margin-top: 100px;
margin-bottom: 0;
}
.portal-contents .inner {
margin: 0;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 24px;
margin: 0;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 24px;
}
.portal-contents .inner::before {
content: none;
content: none;
}
.publication-contents {
padding: 24px 40px;
border: 1px solid #e2e4e9;
width: 100%;
margin: 0;
background-color: #fff;
border-radius: 4px;
padding: 24px 40px;
border: 1px solid #e2e4e9;
width: 100%;
margin: 0;
background-color: #fff;
border-radius: 4px;
}
.publication-contents:first-child {
grid-column: 1/span 2;
grid-column: 1 / span 2;
}
.publication-contents h4.featured-title,
.publication-contents .section-toc-title {
font-size: 26px;
font-weight: 300;
line-height: 36px;
margin: 0;
font-size: 26px;
font-weight: 300;
line-height: 36px;
margin: 0;
}
.publication-contents h4.featured-title a,
.publication-contents .section-toc-title a {
color: #283241;
color: #283241;
}
.publication-contents h4.featured-title a:hover,
.publication-contents .section-toc-title a:hover {
color: #283241;
text-decoration: underline;
color: #283241;
text-decoration: underline;
}
.publication-contents ul {
margin: 0;
padding: 0;
margin: 0;
padding: 0;
}
.publication-contents li {
margin: 4px 0;
margin: 4px 0;
}
.publication-contents li:first-child {
margin-top: 20px;
margin-top: 20px;
}
.publication-contents li:last-child {
margin-bottom: 40px;
margin-bottom: 40px;
}
.publication-contents li a {
color: #dd4d50;
font-size: 16px;
line-height: 24px;
color: #dd4d50;
font-size: 16px;
line-height: 24px;
}
.publication-contents li a:hover {
color: #dd4d50;
text-decoration: underline;
color: #dd4d50;
text-decoration: underline;
}
.publication-contents h4 span,
.publication-contents li::before {
display: none;
display: none;
}
@media only screen and (max-width: 768px) {
.portal-contents .inner {
grid-template-columns: 1fr;
}
.portal-contents .inner {
grid-template-columns: 1fr;
}
.publication-contents:first-child {
grid-column: auto;
}
.publication-contents:first-child {
grid-column: auto;
}
}
/* Einleitung */
.cat-panel-1:before {
content: "\f277";
content: '\f277';
}
/* Schnellstart */
.cat-panel-2:before {
content: "\f11e";
content: '\f11e';
}
/* Voraussetzungen */
.cat-panel-3:before {
content: "\f109";
content: '\f109';
}
/* Benutzermenü und -profil */
.cat-panel-4:before {
content: "\f007";
content: '\f007';
}
/* Benutzeroberflächen im RedactManager */
.cat-panel-5:before {
content: "\f0d0";
content: '\f0d0';
}
/* Rollen und Berechtigungen */
.cat-panel-6:before {
content: "\f084";
content: '\f084';
}
/* Dossier erstellen und verwalten */
.cat-panel-7:before {
content: "\f07c";
content: '\f07c';
}
/* Dokumente bearbeiten im Editor */
.cat-panel-8:before {
content: "\f15c";
content: '\f15c';
}
/* Dossier abschließen und herunterladen */
.cat-panel-9:before {
content: "\f019";
content: '\f019';
}
/* Funktionsübersicht */
.cat-panel-10:before {
content: "\f03a";
content: '\f03a';
}
/* Glossar */
.cat-panel-11:before {
content: "\f02d";
content: '\f02d';
}
/* FAQs (häufige Fragen) */
.cat-panel-12:before {
content: "\f059";
content: '\f059';
}
.portal-search-result {
background-color: #f5f5f7;
background-color: #f5f5f7;
}
.search-container {
padding-bottom: 100px;
padding-bottom: 100px;
}
.search-container h2 {
font-size: 42px;
font-weight: 300;
line-height: 57px;
font-size: 42px;
font-weight: 300;
line-height: 57px;
}
.portal-search-result {
padding: 0;
margin-top: 80px;
padding: 0;
margin-top: 80px;
}
ul.searchresults {
border: 1px solid #e2e4e9;
background-color: #fff;
border-radius: 4px;
margin-top: 32px;
border: 1px solid #e2e4e9;
background-color: #fff;
border-radius: 4px;
margin-top: 32px;
}
ul.searchresults .search-highlight {
font-style: normal;
font-style: normal;
}
li.searchresultitem {
margin: 0 32px;
border-bottom: 1px solid #e2e4e9;
padding: 32px 8px;
margin: 0 32px;
border-bottom: 1px solid #e2e4e9;
padding: 32px 8px;
}
.searchresultitem.selected-searchresultitem {
background-color: transparent;
border-radius: 0;
background-color: transparent;
border-radius: 0;
}
.searchresulttitle {
font-size: 26px;
font-weight: 300;
line-height: 36px;
color: #283241;
font-size: 26px;
font-weight: 300;
line-height: 36px;
color: #283241;
}
.searchresultsnippet {
margin: 16px 0;
color: #283241;
margin: 16px 0;
color: #283241;
}
.search-result-breadcrumbs {
color: #dd4d50;
color: #dd4d50;
}
.portal-footer,
.site-footer {
border-top: 1px solid #e2e4e9;
padding: 0;
border-top: 1px solid #e2e4e9;
padding: 0;
}
.portal-footer.portal-footer,
.site-footer.portal-footer {
margin-top: 100px;
margin-top: 100px;
}
.portal-footer .inner,
.site-footer .inner {
margin: 0;
padding: 8px 0 64px 0;
font-size: 16px;
line-height: 24px;
margin: 0;
padding: 8px 0 64px 0;
font-size: 16px;
line-height: 24px;
}
.portal-footer .inner > *,
.site-footer .inner > * {
padding: 0;
padding: 0;
}
.toolbar {
box-shadow: none;
padding: 21px 24px;
margin-bottom: 50px;
box-shadow: none;
padding: 21px 24px;
margin-bottom: 50px;
}
.topic-content .breadcrumb-container {
margin-top: 40px;
margin-top: 40px;
}
.topic-content .breadcrumb {
font-size: 14px;
line-height: 18px;
font-weight: 600;
font-size: 14px;
line-height: 18px;
font-weight: 600;
}
.topic-content .breadcrumb a {
color: #283241;
color: #283241;
}
.topic-content .breadcrumb a:hover {
color: #dd4d50;
text-decoration: underline;
color: #dd4d50;
text-decoration: underline;
}
.topic-content .breadcrumb .breadcrumb-node {
color: #dd4d50;
color: #dd4d50;
}
main article {
margin-top: 0;
margin-bottom: 0;
padding: 0;
margin-top: 0;
margin-bottom: 0;
padding: 0;
}
section > .titlepage .title {
margin: 24px 0 16px 0;
margin: 24px 0 16px 0;
}
#topic-content > section > .titlepage h2.title {
margin: 0 0 24px;
font-size: 32px;
font-weight: 300;
line-height: 43px;
margin: 0 0 24px;
font-size: 32px;
font-weight: 300;
line-height: 43px;
}
.image-viewport {
margin: auto;
margin: auto;
}
.image-viewport img {
margin: 16px auto;
box-shadow: 0 3px 12px 5px rgba(40, 50, 65, 0.14);
margin: 16px auto;
box-shadow: 0 3px 12px 5px rgba(40, 50, 65, 0.14);
}
.pager {
margin-top: 30px;
margin-bottom: 30px;
padding: 0;
margin-top: 30px;
margin-bottom: 30px;
padding: 0;
}
.pager li > a,
.pager li > span {
color: #dd4d50;
font-size: 14px;
font-weight: 600;
line-height: 19px;
text-transform: uppercase;
padding: 0;
background-color: transparent;
border: none;
border-radius: 0;
color: #dd4d50;
font-size: 14px;
font-weight: 600;
line-height: 19px;
text-transform: uppercase;
padding: 0;
background-color: transparent;
border: none;
border-radius: 0;
}
.pager li > a:hover,
.pager li > span:hover {
text-decoration: underline;
background-color: transparent;
color: #dd4d50;
text-decoration: underline;
background-color: transparent;
color: #dd4d50;
}
.warning,
@ -390,152 +390,152 @@ section > .titlepage .title {
.important,
.caution,
.tip {
margin-top: 32px;
margin-bottom: 32px;
padding: 16px 24px 16px 68px;
background-color: #fff;
border-left: 4px solid #dd4d50;
margin-top: 32px;
margin-bottom: 32px;
padding: 16px 24px 16px 68px;
background-color: #fff;
border-left: 4px solid #dd4d50;
}
.warning:before,
.note:before,
.important:before,
.caution:before,
.tip:before {
color: #dd4d50;
width: 20px;
height: 20px;
text-align: center;
left: 24px;
top: calc(50% - 15px);
color: #dd4d50;
width: 20px;
height: 20px;
text-align: center;
left: 24px;
top: calc(50% - 15px);
}
.warning h3,
.note h3,
.important h3,
.caution h3,
.tip h3 {
padding: 0;
font-size: 18px;
font-weight: 600;
line-height: 24px;
margin-bottom: 8px;
padding: 0;
font-size: 18px;
font-weight: 600;
line-height: 24px;
margin-bottom: 8px;
}
.warning p,
.note p,
.important p,
.caution p,
.tip p {
line-height: 20px;
line-height: 20px;
}
.topic-content > section > p {
margin: 24px 0;
margin: 24px 0;
}
.mediaobject {
margin-top: 20px;
margin-top: 20px;
}
.mediaobject img {
border-radius: 4px;
margin: 0;
border-radius: 4px;
margin: 0;
}
.mediaobject .caption > p {
font-size: 14px;
text-align: center;
font-style: italic;
margin: 0;
font-size: 14px;
text-align: center;
font-style: italic;
margin: 0;
}
.inlinemediaobject {
vertical-align: unset;
vertical-align: unset;
}
main ol,
main ul {
margin: 0 0 24px;
margin: 0 0 24px;
}
.section-toc {
padding: 24px 40px;
border: 1px solid #e2e4e9;
width: 100%;
margin: 0;
background-color: #fff;
border-radius: 4px;
padding: 24px 40px;
border: 1px solid #e2e4e9;
width: 100%;
margin: 0;
background-color: #fff;
border-radius: 4px;
}
.section-toc:first-child {
grid-column: 1/span 2;
grid-column: 1 / span 2;
}
.section-toc h4.featured-title,
.section-toc .section-toc-title {
font-size: 26px;
font-weight: 300;
line-height: 36px;
margin: 0;
font-size: 26px;
font-weight: 300;
line-height: 36px;
margin: 0;
}
.section-toc h4.featured-title a,
.section-toc .section-toc-title a {
color: #283241;
color: #283241;
}
.section-toc h4.featured-title a:hover,
.section-toc .section-toc-title a:hover {
color: #283241;
text-decoration: underline;
color: #283241;
text-decoration: underline;
}
.section-toc ul {
margin: 0;
padding: 0;
margin: 0;
padding: 0;
}
.section-toc li {
margin: 4px 0;
margin: 4px 0;
}
.section-toc li:first-child {
margin-top: 20px;
margin-top: 20px;
}
.section-toc li:last-child {
margin-bottom: 40px;
margin-bottom: 40px;
}
.section-toc li a {
color: #dd4d50;
font-size: 16px;
line-height: 24px;
color: #dd4d50;
font-size: 16px;
line-height: 24px;
}
.section-toc li a:hover {
color: #dd4d50;
text-decoration: underline;
color: #dd4d50;
text-decoration: underline;
}
.section-toc h4 span,
.section-toc li::before {
display: none;
display: none;
}
.section-toc li:first-child {
margin-top: 16px;
margin-top: 16px;
}
.section-toc li:last-child {
margin-bottom: 8px;
margin-bottom: 8px;
}
.question {
font-weight: 600;
font-weight: 600;
}
.question > td > p {
margin: 32px 0 18px 0;
margin: 32px 0 18px 0;
}
.question > td:first-child {
padding-right: 4px;
padding-right: 4px;
}
.fixed-toolbar article.topic :target.question:before {
content: none;
content: none;
}
body {
color: #283241;
background-color: #f5f5f7;
font-family: "Inter", sans-serif;
scrollbar-color: var(--iqser-quick-filter-border) var(--iqser-filter-bg);
scrollbar-width: thin;
/* Track */
/* Handle */
color: #283241;
background-color: #f5f5f7;
font-family: 'Inter', sans-serif;
scrollbar-color: var(--iqser-quick-filter-border) var(--iqser-grey-2);
scrollbar-width: thin;
/* Track */
/* Handle */
}
body h1,
body h2,
@ -545,31 +545,31 @@ body h5,
body h6,
body p,
body pre {
margin: 0;
font-family: "Inter", sans-serif;
margin: 0;
font-family: 'Inter', sans-serif;
}
body::-webkit-scrollbar {
width: 11px;
width: 11px;
}
body::-webkit-scrollbar-track {
background: var(--iqser-filter-bg);
background: var(--iqser-grey-2);
}
body::-webkit-scrollbar-thumb {
background: var(--iqser-quick-filter-border);
background: var(--iqser-quick-filter-border);
}
body h3 {
font-size: 32px;
font-weight: 300;
line-height: 43px;
font-size: 32px;
font-weight: 300;
line-height: 43px;
}
body p {
font-size: 16px;
line-height: 24px;
font-size: 16px;
line-height: 24px;
}
body a {
color: #dd4d50;
color: #dd4d50;
}
body a:hover {
text-decoration: underline;
color: #dd4d50;
text-decoration: underline;
color: #dd4d50;
}