Merge pull request #4339 from MaximilianKrambach/finalVersion

editor for final modified version of a motion
This commit is contained in:
Emanuel Schütze 2019-05-27 14:38:02 +02:00 committed by GitHub
commit 441e10ed59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 243 additions and 100 deletions

View File

@ -42,6 +42,14 @@ export enum ChangeRecoMode {
ModifiedFinal = 'modified_final_version'
}
export const verboseChangeRecoMode = {
original: 'Original version',
changed: 'Changed version',
diff: 'Diff version',
agreed: 'Final version',
modified_final_version: 'Final print template'
};
export interface MotionTitleInformation extends TitleInformationWithAgendaItem {
title: string;
identifier?: string;

View File

@ -38,6 +38,8 @@
<div *ngIf="changes.length === 0" class="no-changes">{{ 'No change recommendations yet' | translate }}</div>
</section>
<span class="text-prefix-label">{{ preamble | translate }}</span>
<!-- The actual diff view -->
<div class="motion-text-with-diffs">
<div *ngFor="let change of changes; let i = index">

View File

@ -58,7 +58,7 @@
background-color: #eee;
border: solid 1px #ddd;
border-radius: 3px;
margin-bottom: 5px;
margin-bottom: 15px;
padding: 5px 10px;
a,

View File

@ -64,6 +64,8 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
*/
public lineLength: number;
public preamble: string;
/**
* @param title
* @param translate
@ -92,6 +94,7 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
) {
super(title, translate, matSnackBar);
this.configService.get<number>('motions_line_length').subscribe(lineLength => (this.lineLength = lineLength));
this.configService.get<string>('motions_preamble').subscribe(preamble => (this.preamble = preamble));
}
/**

View File

@ -436,83 +436,115 @@
<ng-template #contentTemplate>
<form class="motion-content" [formGroup]="contentForm" (keydown)="onKeyDown($event)">
<!-- Line Number and Diff buttons -->
<div *ngIf="!editMotion && !motion.isStatuteAmendment()" class="motion-text-controls">
<mat-form-field class="motion-goto-line" *ngIf="highlightedLineOpened">
<input
type="number"
min="1"
matInput
placeholder="{{ 'Go to line' | translate }}"
osAutofocus
[(ngModel)]="highlightedLineTyping"
[ngModelOptions]="{ standalone: true }"
[errorStateMatcher]="highlightedLineMatcher"
/>
<mat-error *ngIf="highlightedLineTyping > 10" translate>Invalid line number</mat-error>
<button
type="submit"
mat-button
matSuffix
mat-icon-button
aria-label="Go to line"
*ngIf="highlightedLineTyping"
(click)="gotoHighlightedLine(highlightedLineTyping); highlightedLineTyping = ''"
>
<mat-icon>redo</mat-icon>
<!-- Toolbar with text controls and buttonf for managing the (modified) final version-->
<div class="motion-text-toolbar-wrapper outline-border-bottom">
<!-- Line Number and Diff buttons -->
<div *ngIf="!editMotion && !motion.isStatuteAmendment()" class="motion-text-controls">
<mat-form-field class="motion-goto-line" *ngIf="highlightedLineOpened">
<input
type="number"
min="1"
matInput
placeholder="{{ 'Go to line' | translate }}"
osAutofocus
[(ngModel)]="highlightedLineTyping"
[ngModelOptions]="{ standalone: true }"
[errorStateMatcher]="highlightedLineMatcher"
/>
<mat-error *ngIf="highlightedLineTyping > 10" translate>Invalid line number</mat-error>
<button
type="submit"
mat-button
matSuffix
mat-icon-button
aria-label="Go to line"
*ngIf="highlightedLineTyping"
(click)="gotoHighlightedLine(highlightedLineTyping); highlightedLineTyping = ''"
>
<mat-icon>redo</mat-icon>
</button>
</mat-form-field>
<button mat-icon-button (click)="highlightedLineOpened = false" *ngIf="highlightedLineOpened">
<mat-icon>cancel</mat-icon>
</button>
</mat-form-field>
<button
mat-icon-button
matTooltip="{{ 'Go to line' | translate }}"
*ngIf="!highlightedLineOpened"
(click)="highlightedLineOpened = true"
>
<mat-icon>redo</mat-icon>
</button>
<button mat-icon-button (click)="highlightedLineOpened = false" *ngIf="highlightedLineOpened">
<mat-icon>cancel</mat-icon>
</button>
<button
type="button"
mat-icon-button
[matMenuTriggerFor]="lineNumberingMenu"
matTooltip="{{ 'Line numbering' | translate }}"
>
<mat-icon>format_list_numbered</mat-icon>
</button>
<button
type="button"
mat-icon-button
[matMenuTriggerFor]="changeRecoMenu"
matTooltip="{{ 'Change recommendations' | translate }}"
*ngIf="
motion && !motion.isParagraphBasedAmendment() && allChangingObjects && allChangingObjects.length > 0
"
>
<mat-icon>rate_review</mat-icon>
</button>
<button
type="button"
mat-button
[matMenuTriggerFor]="lineNumberingMenu"
>
<mat-icon>format_list_numbered</mat-icon>
&nbsp;<span translate>Line numbering</span>
<span *ngIf="lnMode === LineNumberingMode.None">
&nbsp;(<span translate>none</span>)
</span>
</button>
<button
type="button"
mat-button
[matMenuTriggerFor]="changeRecoMenu"
*ngIf="
motion && !motion.isParagraphBasedAmendment() && allChangingObjects && allChangingObjects.length > 0
"
>
<mat-icon>rate_review</mat-icon>
&nbsp;<span>{{ verboseChangeRecoMode[crMode] | translate }}</span>
</button>
</div>
<button
type="button"
mat-icon-button
matTooltip="{{ 'Create final print template' | translate }}"
*osPerms="'motions.can_manage'; and: isRecoMode(ChangeRecoMode.Final)"
(click)="createModifiedFinalVersion()"
>
<mat-icon>description</mat-icon>
</button>
<button
type="button"
class="red-warning-text"
mat-button
matTooltip="{{ 'Delete final print template' | translate }}"
*ngIf="isRecoMode(ChangeRecoMode.ModifiedFinal)"
(click)="deleteModifiedFinalVersion()"
>
<mat-icon>description</mat-icon>
<!-- Final edit buttons -->
<div *ngIf="isRecoMode(ChangeRecoMode.ModifiedFinal) || isRecoMode(ChangeRecoMode.Final)">
<!-- create final version -->
<button
type="button"
mat-icon-button
matTooltip="{{ 'Copy to final print template' | translate }}"
*osPerms="'motions.can_manage'; and: isRecoMode(ChangeRecoMode.Final)"
(click)="createModifiedFinalVersion()"
>
<mat-icon>file_copy</mat-icon>
</button>
<!-- edit final version -->
<button
type="button"
mat-icon-button
*ngIf="isRecoMode(ChangeRecoMode.ModifiedFinal) && !isFinalEdit"
(click)="editModifiedFinal()"
>
<mat-icon>edit</mat-icon>
</button>
<!-- save final version -->
<button
type="button"
*ngIf="isRecoMode(ChangeRecoMode.ModifiedFinal) && isFinalEdit"
mat-icon-button
[disabled]="!finalVersionEdited"
(click)="onSubmitFinalVersion()"
>
<mat-icon>save</mat-icon>
</button>
<!-- cancel final version edit -->
<button
type="button"
*ngIf="isRecoMode(ChangeRecoMode.ModifiedFinal) && isFinalEdit"
mat-icon-button
(click)="cancelFinalVersionEdit()"
>
<mat-icon>close</mat-icon>
</button>
<!-- delete final version edit -->
<button
type="button"
class="red-warning-text"
mat-icon-button
matTooltip="{{ 'Delete final print template' | translate }}"
*ngIf="isRecoMode(ChangeRecoMode.ModifiedFinal) && !isFinalEdit"
(click)="deleteModifiedFinalVersion()"
>
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
<!-- Selecting statute paragraphs for amendment -->
@ -579,13 +611,13 @@
</div>
</div>
<!-- Text -->
<span class="text-prefix-label">{{ preamble | translate }}</span>
<!-- Text (hide preamble, if diff mode. The preample is included in the motion-detail-diff component) -->
<span *ngIf="!isRecoMode(ChangeRecoMode.Diff)" class="text-prefix-label">{{ preamble | translate }}</span>
<!-- Regular motions or traditional amendments -->
<ng-container *ngIf="!editMotion && !motion.isStatuteAmendment() && !motion.isParagraphBasedAmendment()">
<div
*ngIf="!isRecoMode(ChangeRecoMode.Diff)"
*ngIf="!isRecoMode(ChangeRecoMode.Diff) && !isFinalEdit"
class="motion-text"
[class.line-numbers-none]="isLineNumberingNone()"
[class.line-numbers-inline]="isLineNumberingInline()"
@ -612,7 +644,23 @@
[lineNumberingMode]="lnMode"
(createChangeRecommendation)="createChangeRecommendation($event)"
></os-motion-detail-diff>
<div *ngIf="isFinalEdit">
<editor [hidden]="!isFinalEdit" formControlName="modified_final_version" [init]="tinyMceSettings" required></editor>
<div
*ngIf="
contentForm.get('modified_final_version').invalid &&
(contentForm.get('modified_final_version').dirty || contentForm.get('modified_final_version').touched)
"
class="red-warning-text"
translate
>
This field is required.
</div>
</div>
</ng-container>
<!-- formatted statute amendment -->
<div
class="motion-text line-numbers-none"
*ngIf="!editMotion && motion.isStatuteAmendment()"
@ -878,6 +926,15 @@
>
outside
</button>
<mat-divider></mat-divider>
<button
mat-menu-item
*ngIf="!highlightedLineOpened"
(click)="highlightedLineOpened = true"
>
<mat-icon>redo</mat-icon>
<span translate>Go to line</span>
</button>
</div>
</mat-menu>
@ -918,9 +975,9 @@
<button
mat-menu-item
translate
*osPerms="'motions.can_manage'; and: isRecoMode(ChangeRecoMode.Final)"
*ngIf="motion && motion.modified_final_version"
(click)="setChangeRecoMode(ChangeRecoMode.ModifiedFinal)"
[ngClass]="{ selected: motion?.crMode === ChangeRecoMode.ModifiedFinal }"
[ngClass]="{ selected: crMode === ChangeRecoMode.ModifiedFinal }"
>
Final print template
</button>

View File

@ -23,30 +23,36 @@ span {
display: flow-root;
}
.motion-text-controls {
float: right;
margin-left: 5px;
margin-top: -15px;
height: 50px;
.motion-text-toolbar-wrapper {
display: flex;
justify-content: flex-end;
button {
font-size: 115%;
font-weight: 400;
margin-top: 7px;
}
height: 50px;
margin: -16px -16px 5px -16px;
padding: 0 16px;
> button {
// Prevent moving the buttons when the "go to line"-input is shown
margin-top: 7px;
}
.motion-goto-line {
width: 150px;
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type='number'] {
-moz-appearance: textfield;
.motion-text-controls {
.motion-goto-line {
width: 150px;
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type='number'] {
-moz-appearance: textfield;
}
}
}

View File

@ -45,7 +45,12 @@ import {
ViewMotionNotificationEditMotion,
TypeOfNotificationViewMotion
} from 'app/site/motions/models/view-motion-notify';
import { ViewMotion, ChangeRecoMode, LineNumberingMode } from 'app/site/motions/models/view-motion';
import {
ViewMotion,
ChangeRecoMode,
LineNumberingMode,
verboseChangeRecoMode
} from 'app/site/motions/models/view-motion';
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
import { ViewTag } from 'app/site/tags/models/view-tag';
import { ViewUnifiedChange } from 'app/shared/models/motions/view-unified-change';
@ -123,6 +128,30 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
return this.repo.getExtendedStateLabel(this.motion);
}
private finalEditMode = false;
/**
* check if the 'final version edit mode' is active
*
* @returns true if active
*/
public get isFinalEdit(): boolean {
return this.finalEditMode;
}
/**
* Helper to check the current state of the final version edit
*
* @returns true if the local edit of the modified_final_version differs
* from the submitted version
*/
public get finalVersionEdited(): boolean {
return (
this.crMode === ChangeRecoMode.ModifiedFinal &&
this.contentForm.get('modified_final_version').value !== this.motion.modified_final_version
);
}
/**
* Saves the target motion. Accessed via the getter and setter.
*/
@ -275,6 +304,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
*/
public ChangeRecoMode = ChangeRecoMode;
public verboseChangeRecoMode = verboseChangeRecoMode;
/**
* For using the enum constants from the template
*/
@ -704,8 +735,9 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
selected_paragraphs: [],
statute_amendment: [''], // Internal value for the checkbox, not saved to the model
statute_paragraph_id: [''],
motion_block_id: [], // TODO: Can be removed if this is not required
parent_id: []
motion_block_id: [],
parent_id: [],
modified_final_version: ['']
});
this.updateWorkflowIdForCreateForm();
@ -1062,7 +1094,10 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
'Are you sure you want to copy the final version to the print template?'
);
if (await this.promptService.open(title, null)) {
await this.updateMotion({ modified_final_version: finalVersion }, this.motion);
this.updateMotion({ modified_final_version: finalVersion }, this.motion).then(
() => this.setChangeRecoMode(ChangeRecoMode.ModifiedFinal),
this.raiseError
);
}
} else {
await this.updateMotion({ modified_final_version: finalVersion }, this.motion);
@ -1070,7 +1105,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
} catch (e) {
this.raiseError(e);
}
this.setChangeRecoMode(ChangeRecoMode.ModifiedFinal);
}
/**
@ -1079,6 +1113,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
public async deleteModifiedFinalVersion(): Promise<void> {
const title = this.translate.instant('Are you sure you want to delete the print template?');
if (await this.promptService.open(title, null)) {
this.finalEditMode = false;
this.updateMotion({ modified_final_version: '' }, this.motion).then(
() => this.setChangeRecoMode(ChangeRecoMode.Final),
this.raiseError
@ -1444,6 +1479,27 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
}
}
/**
* Submits the modified final version of the motion
*/
public onSubmitFinalVersion(): void {
const val = this.contentForm.get('modified_final_version').value;
this.updateMotion({ modified_final_version: val }, this.motion).then(() => {
this.finalEditMode = false;
this.contentForm.get('modified_final_version').markAsPristine();
}, this.raiseError);
}
/**
* Cancels the final version edit and resets the form value
*
* TODO: the tinyMCE editor content should reset, too
*/
public cancelFinalVersionEdit(): void {
this.finalEditMode = false;
this.contentForm.patchValue({ modified_final_version: this.motion.modified_final_version });
}
/**
* Toggles the favorite status
*/
@ -1548,4 +1604,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
}
}
}
/**
* Activates the 'edit final version' mode
*/
public editModifiedFinal(): void {
this.finalEditMode = true;
}
}

View File

@ -46,6 +46,10 @@
color: mat-color($warn);
}
.outline-border-bottom {
border-bottom: 1px solid $os-outline;
}
/* motion list/detail view */
.mat-chip-list.user .mat-chip {
color: mat-color($foreground, text);