Merge pull request #4573 from CatoTH/OS3-multi-paragraph-amendments
Creating multi-paragraph amendments
This commit is contained in:
commit
58b5ec3f5e
@ -120,6 +120,7 @@ _('Amendments');
|
|||||||
_('Activate statute amendments');
|
_('Activate statute amendments');
|
||||||
_('Activate amendments');
|
_('Activate amendments');
|
||||||
_('Show amendments together with motions');
|
_('Show amendments together with motions');
|
||||||
|
_('Amendments can change multiple paragraphs');
|
||||||
_('Prefix for the identifier for amendments');
|
_('Prefix for the identifier for amendments');
|
||||||
_('The title of the motion is always applied.');
|
_('The title of the motion is always applied.');
|
||||||
_('How to create new amendments');
|
_('How to create new amendments');
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@ -14,7 +15,7 @@ import { DataStoreService } from '../../core-services/data-store.service';
|
|||||||
import { DiffLinesInParagraph, DiffService, LineRange, ModificationType } from '../../ui-services/diff.service';
|
import { DiffLinesInParagraph, DiffService, LineRange, ModificationType } from '../../ui-services/diff.service';
|
||||||
import { HttpService } from 'app/core/core-services/http.service';
|
import { HttpService } from 'app/core/core-services/http.service';
|
||||||
import { Item } from 'app/shared/models/agenda/item';
|
import { Item } from 'app/shared/models/agenda/item';
|
||||||
import { LinenumberingService } from '../../ui-services/linenumbering.service';
|
import { LinenumberingService, LineNumberRange } from '../../ui-services/linenumbering.service';
|
||||||
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
||||||
import { Motion } from 'app/shared/models/motions/motion';
|
import { Motion } from 'app/shared/models/motions/motion';
|
||||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||||
@ -45,6 +46,36 @@ import { OperatorService } from 'app/core/core-services/operator.service';
|
|||||||
|
|
||||||
type SortProperty = 'callListWeight' | 'identifier';
|
type SortProperty = 'callListWeight' | 'identifier';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the single paragraphs from the base motion.
|
||||||
|
*/
|
||||||
|
export interface ParagraphToChoose {
|
||||||
|
/**
|
||||||
|
* The paragraph number.
|
||||||
|
*/
|
||||||
|
paragraphNo: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The raw HTML of this paragraph.
|
||||||
|
*/
|
||||||
|
rawHtml: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTML of this paragraph, wrapped in a `SafeHtml`-object.
|
||||||
|
*/
|
||||||
|
safeHtml: SafeHtml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first line number
|
||||||
|
*/
|
||||||
|
lineFrom: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last line number
|
||||||
|
*/
|
||||||
|
lineTo: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository Services for motions (and potentially categories)
|
* Repository Services for motions (and potentially categories)
|
||||||
*
|
*
|
||||||
@ -74,6 +105,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
|
|||||||
* @param mapperService Maps collection strings to classes
|
* @param mapperService Maps collection strings to classes
|
||||||
* @param dataSend sending changed objects
|
* @param dataSend sending changed objects
|
||||||
* @param httpService OpenSlides own Http service
|
* @param httpService OpenSlides own Http service
|
||||||
|
* @param sanitizer DOM Sanitizer
|
||||||
* @param lineNumbering Line numbering for motion text
|
* @param lineNumbering Line numbering for motion text
|
||||||
* @param diff Display changes in motion text as diff.
|
* @param diff Display changes in motion text as diff.
|
||||||
* @param personalNoteService service fo personal notes
|
* @param personalNoteService service fo personal notes
|
||||||
@ -87,6 +119,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
|
|||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
config: ConfigService,
|
config: ConfigService,
|
||||||
private httpService: HttpService,
|
private httpService: HttpService,
|
||||||
|
private readonly sanitizer: DomSanitizer,
|
||||||
private readonly lineNumbering: LinenumberingService,
|
private readonly lineNumbering: LinenumberingService,
|
||||||
private readonly diff: DiffService,
|
private readonly diff: DiffService,
|
||||||
private treeService: TreeService,
|
private treeService: TreeService,
|
||||||
@ -637,6 +670,25 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
|
|||||||
return this.lineNumbering.splitToParagraphs(html);
|
return this.lineNumbering.splitToParagraphs(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data structure used for creating and editing amendments
|
||||||
|
*
|
||||||
|
* @param {ViewMotion} motion
|
||||||
|
* @param {number} lineLength
|
||||||
|
*/
|
||||||
|
public getParagraphsToChoose(motion: ViewMotion, lineLength: number): ParagraphToChoose[] {
|
||||||
|
return this.getTextParagraphs(motion, true, lineLength).map((paragraph: string, index: number) => {
|
||||||
|
const affected: LineNumberRange = this.lineNumbering.getLineNumberRange(paragraph);
|
||||||
|
return {
|
||||||
|
paragraphNo: index,
|
||||||
|
safeHtml: this.sanitizer.bypassSecurityTrustHtml(paragraph),
|
||||||
|
rawHtml: this.lineNumbering.stripLineNumbers(paragraph),
|
||||||
|
lineFrom: affected.from,
|
||||||
|
lineTo: affected.to
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all paragraphs that are affected by the given amendment in diff-format
|
* Returns all paragraphs that are affected by the given amendment in diff-format
|
||||||
*
|
*
|
||||||
|
@ -21,7 +21,7 @@ interface BreakablePoint {
|
|||||||
/**
|
/**
|
||||||
* An object specifying a range of line numbers.
|
* An object specifying a range of line numbers.
|
||||||
*/
|
*/
|
||||||
interface LineNumberRange {
|
export interface LineNumberRange {
|
||||||
/**
|
/**
|
||||||
* The first line number to be included.
|
* The first line number to be included.
|
||||||
*/
|
*/
|
||||||
|
@ -17,18 +17,24 @@
|
|||||||
|
|
||||||
<form [formGroup]="contentForm" (ngSubmit)="saveAmendment()" class="on-transition-fade">
|
<form [formGroup]="contentForm" (ngSubmit)="saveAmendment()" class="on-transition-fade">
|
||||||
<mat-horizontal-stepper #matStepper linear>
|
<mat-horizontal-stepper #matStepper linear>
|
||||||
<mat-step [completed]="contentForm.value.selectedParagraph">
|
<mat-step [completed]="contentForm.value.selectedParagraphs.length > 0">
|
||||||
<ng-template matStepLabel>{{ 'Select paragraph' | translate }}</ng-template>
|
<ng-template matStepLabel>{{ 'Select paragraphs' | translate }}</ng-template>
|
||||||
<div>
|
<div>
|
||||||
<section
|
<section
|
||||||
*ngFor="let paragraph of paragraphs"
|
*ngFor="let paragraph of paragraphs"
|
||||||
class="paragraph-row"
|
class="paragraph-row"
|
||||||
[class.active]="contentForm.value.selectedParagraph === paragraph.paragraphNo"
|
[class.active]="isParagraphSelected(paragraph)"
|
||||||
(click)="selectParagraph(paragraph)"
|
(click)="onParagraphClicked(paragraph)"
|
||||||
>
|
>
|
||||||
<mat-radio-button
|
<mat-checkbox
|
||||||
|
*ngIf="multipleParagraphsAllowed"
|
||||||
class="paragraph-select"
|
class="paragraph-select"
|
||||||
[checked]="contentForm.value.selectedParagraph === paragraph.paragraphNo"
|
[checked]="isParagraphSelected(paragraph)"
|
||||||
|
></mat-checkbox>
|
||||||
|
<mat-radio-button
|
||||||
|
*ngIf="!multipleParagraphsAllowed"
|
||||||
|
class="paragraph-select"
|
||||||
|
[checked]="isParagraphSelected(paragraph)"
|
||||||
></mat-radio-button>
|
></mat-radio-button>
|
||||||
<div class="paragraph-text motion-text" [innerHTML]="paragraph.safeHtml"></div>
|
<div class="paragraph-text motion-text" [innerHTML]="paragraph.safeHtml"></div>
|
||||||
</section>
|
</section>
|
||||||
@ -37,28 +43,33 @@
|
|||||||
<mat-step>
|
<mat-step>
|
||||||
<ng-template matStepLabel>{{ 'Change paragraph' | translate }}</ng-template>
|
<ng-template matStepLabel>{{ 'Change paragraph' | translate }}</ng-template>
|
||||||
|
|
||||||
|
<h3><span translate>Amendment text</span> <span>*</span></h3>
|
||||||
|
|
||||||
<!-- Text -->
|
<!-- Text -->
|
||||||
<h3
|
<section *ngFor="let paragraph of contentForm.value.selectedParagraphs">
|
||||||
[ngClass]="
|
<h4 [class.red-warning-text]="contentForm.get('text_' + paragraph.paragraphNo).invalid && (
|
||||||
contentForm.get('text').invalid &&
|
contentForm.get('text_' + paragraph.paragraphNo).dirty ||
|
||||||
(contentForm.get('text').dirty || contentForm.get('text').touched)
|
contentForm.get('text_' + paragraph.paragraphNo).touched
|
||||||
? 'red-warning-text'
|
)"
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span translate>Amendment text</span> <span>*</span>
|
<span *ngIf="paragraph.lineFrom >= paragraph.lineTo - 1" class="line-number">
|
||||||
</h3>
|
{{ 'Line' | translate }} {{ paragraph.lineFrom }}</span> <span>*</span>
|
||||||
<editor formControlName="text" [init]="tinyMceSettings" required></editor>
|
<span *ngIf="paragraph.lineFrom < paragraph.lineTo - 1" class="line-number">
|
||||||
|
{{ 'Line' | translate }} {{ paragraph.lineFrom }}
|
||||||
|
- {{ paragraph.lineTo - 1 }}</span> <span>*</span>
|
||||||
|
</h4>
|
||||||
|
<editor [formControlName]="'text_' + paragraph.paragraphNo" [init]="tinyMceSettings" required></editor>
|
||||||
<div
|
<div
|
||||||
*ngIf="
|
*ngIf="contentForm.get('text_' + paragraph.paragraphNo).invalid && (
|
||||||
contentForm.get('text').invalid &&
|
contentForm.get('text_' + paragraph.paragraphNo).dirty ||
|
||||||
(contentForm.get('text').dirty || contentForm.get('text').touched)
|
contentForm.get('text_' + paragraph.paragraphNo).touched
|
||||||
"
|
)"
|
||||||
class="red-warning-text"
|
class="red-warning-text"
|
||||||
translate
|
translate
|
||||||
>
|
>
|
||||||
This field is required.
|
This field is required.
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Reason -->
|
<!-- Reason -->
|
||||||
<h3
|
<h3
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
@ -9,30 +9,9 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||||
import { CreateMotion } from 'app/site/motions/models/create-motion';
|
import { CreateMotion } from 'app/site/motions/models/create-motion';
|
||||||
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
import { MotionRepositoryService, ParagraphToChoose } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
|
||||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||||
|
|
||||||
/**
|
|
||||||
* Describes the single paragraphs from the base motion.
|
|
||||||
*/
|
|
||||||
interface ParagraphToChoose {
|
|
||||||
/**
|
|
||||||
* The paragraph number.
|
|
||||||
*/
|
|
||||||
paragraphNo: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The raw HTML of this paragraph.
|
|
||||||
*/
|
|
||||||
rawHtml: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The HTML of this paragraph, wrapped in a `SafeHtml`-object.
|
|
||||||
*/
|
|
||||||
safeHtml: SafeHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The wizard used to create a new amendment based on a motion.
|
* The wizard used to create a new amendment based on a motion.
|
||||||
*/
|
*/
|
||||||
@ -67,6 +46,11 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
|
|||||||
*/
|
*/
|
||||||
public reasonRequired: boolean;
|
public reasonRequired: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if an amendment can change multiple paragraphs or only one
|
||||||
|
*/
|
||||||
|
public multipleParagraphsAllowed: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs this component.
|
* Constructs this component.
|
||||||
*
|
*
|
||||||
@ -77,8 +61,6 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
|
|||||||
* @param {MotionRepositoryService} repo Motion Repository
|
* @param {MotionRepositoryService} repo Motion Repository
|
||||||
* @param {ActivatedRoute} route The activated route
|
* @param {ActivatedRoute} route The activated route
|
||||||
* @param {Router} router The router
|
* @param {Router} router The router
|
||||||
* @param {DomSanitizer} sanitizer The DOM Sanitizing library
|
|
||||||
* @param {LinenumberingService} lineNumbering The line numbering service
|
|
||||||
* @param {MatSnackBar} matSnackBar Material Design SnackBar
|
* @param {MatSnackBar} matSnackBar Material Design SnackBar
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
@ -89,8 +71,6 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
|
|||||||
private repo: MotionRepositoryService,
|
private repo: MotionRepositoryService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private sanitizer: DomSanitizer,
|
|
||||||
private lineNumbering: LinenumberingService,
|
|
||||||
matSnackBar: MatSnackBar
|
matSnackBar: MatSnackBar
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
@ -104,6 +84,10 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
|
|||||||
this.configService.get<boolean>('motions_reason_required').subscribe(required => {
|
this.configService.get<boolean>('motions_reason_required').subscribe(required => {
|
||||||
this.reasonRequired = required;
|
this.reasonRequired = required;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.configService.get<boolean>('motions_amendments_multiple_paragraphs').subscribe(allowed => {
|
||||||
|
this.multipleParagraphsAllowed = allowed;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -114,16 +98,7 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
|
|||||||
this.route.params.subscribe(params => {
|
this.route.params.subscribe(params => {
|
||||||
this.repo.getViewModelObservable(params.id).subscribe(newViewMotion => {
|
this.repo.getViewModelObservable(params.id).subscribe(newViewMotion => {
|
||||||
this.motion = newViewMotion;
|
this.motion = newViewMotion;
|
||||||
|
this.paragraphs = this.repo.getParagraphsToChoose(newViewMotion, this.lineLength);
|
||||||
this.paragraphs = this.repo
|
|
||||||
.getTextParagraphs(this.motion, true, this.lineLength)
|
|
||||||
.map((paragraph: string, index: number) => {
|
|
||||||
return {
|
|
||||||
paragraphNo: index,
|
|
||||||
safeHtml: this.sanitizer.bypassSecurityTrustHtml(paragraph),
|
|
||||||
rawHtml: this.lineNumbering.stripLineNumbers(paragraph)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -133,22 +108,85 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
|
|||||||
*/
|
*/
|
||||||
public createForm(): void {
|
public createForm(): void {
|
||||||
this.contentForm = this.formBuilder.group({
|
this.contentForm = this.formBuilder.group({
|
||||||
selectedParagraph: [null, Validators.required],
|
selectedParagraphs: [[], Validators.required],
|
||||||
text: ['', Validators.required],
|
|
||||||
reason: ['', Validators.required]
|
reason: ['', Validators.required]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isParagraphSelected(paragraph: ParagraphToChoose): boolean {
|
||||||
|
return !!this.contentForm.value.selectedParagraphs.find(para => para.paragraphNo === paragraph.paragraphNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the template when a paragraph is clicked in single paragraph mode.
|
||||||
|
* Behaves like a radio-button
|
||||||
|
*
|
||||||
|
* @param {ParagraphToChoose} paragraph
|
||||||
|
*/
|
||||||
|
public setParagraph(paragraph: ParagraphToChoose): void {
|
||||||
|
this.contentForm.value.selectedParagraphs.forEach(para => {
|
||||||
|
this.contentForm.removeControl('text_' + para.paragraphNo);
|
||||||
|
});
|
||||||
|
this.contentForm.addControl(
|
||||||
|
'text_' + paragraph.paragraphNo,
|
||||||
|
new FormControl(paragraph.rawHtml, Validators.required)
|
||||||
|
);
|
||||||
|
this.contentForm.patchValue({
|
||||||
|
selectedParagraphs: [paragraph]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by the template when a paragraph is clicked in multiple paragraph mode.
|
||||||
|
* Behaves like a checkbox
|
||||||
|
*
|
||||||
|
* @param {ParagraphToChoose} paragraph
|
||||||
|
*/
|
||||||
|
public toggleParagraph(paragraph: ParagraphToChoose): void {
|
||||||
|
let newParagraphs: ParagraphToChoose[];
|
||||||
|
const oldSelected: ParagraphToChoose[] = this.contentForm.value.selectedParagraphs;
|
||||||
|
if (this.isParagraphSelected(paragraph)) {
|
||||||
|
newParagraphs = oldSelected.filter(para => para.paragraphNo !== paragraph.paragraphNo);
|
||||||
|
this.contentForm.patchValue({
|
||||||
|
selectedParagraphs: newParagraphs
|
||||||
|
});
|
||||||
|
this.contentForm.removeControl('text_' + paragraph.paragraphNo);
|
||||||
|
} else {
|
||||||
|
newParagraphs = Object.assign([], oldSelected);
|
||||||
|
newParagraphs.push(paragraph);
|
||||||
|
newParagraphs.sort(
|
||||||
|
(para1: ParagraphToChoose, para2: ParagraphToChoose): number => {
|
||||||
|
if (para1.paragraphNo < para2.paragraphNo) {
|
||||||
|
return -1;
|
||||||
|
} else if (para1.paragraphNo > para2.paragraphNo) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.contentForm.addControl(
|
||||||
|
'text_' + paragraph.paragraphNo,
|
||||||
|
new FormControl(paragraph.rawHtml, Validators.required)
|
||||||
|
);
|
||||||
|
this.contentForm.patchValue({
|
||||||
|
selectedParagraphs: newParagraphs
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the template when a paragraph is clicked.
|
* Called by the template when a paragraph is clicked.
|
||||||
*
|
*
|
||||||
* @param {ParagraphToChoose} paragraph
|
* @param {ParagraphToChoose} paragraph
|
||||||
*/
|
*/
|
||||||
public selectParagraph(paragraph: ParagraphToChoose): void {
|
public onParagraphClicked(paragraph: ParagraphToChoose): void {
|
||||||
this.contentForm.patchValue({
|
if (this.multipleParagraphsAllowed) {
|
||||||
selectedParagraph: paragraph.paragraphNo,
|
this.toggleParagraph(paragraph);
|
||||||
text: paragraph.rawHtml
|
} else {
|
||||||
});
|
this.setParagraph(paragraph);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -157,10 +195,12 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async saveAmendment(): Promise<void> {
|
public async saveAmendment(): Promise<void> {
|
||||||
|
let text = '';
|
||||||
const amendedParagraphs = this.paragraphs.map(
|
const amendedParagraphs = this.paragraphs.map(
|
||||||
(paragraph: ParagraphToChoose, index: number): string => {
|
(paragraph: ParagraphToChoose, index: number): string => {
|
||||||
if (index === this.contentForm.value.selectedParagraph) {
|
if (this.contentForm.value.selectedParagraphs.find(para => para.paragraphNo === index)) {
|
||||||
return this.contentForm.value.text;
|
text = this.contentForm.value['text_' + index];
|
||||||
|
return this.contentForm.value['text_' + index];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -169,6 +209,7 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
|
|||||||
const newMotionValues = {
|
const newMotionValues = {
|
||||||
...this.contentForm.value,
|
...this.contentForm.value,
|
||||||
title: this.translate.instant('Amendment to') + ' ' + this.motion.identifier,
|
title: this.translate.instant('Amendment to') + ' ' + this.motion.identifier,
|
||||||
|
text: text, // Workaround as 'text' is required from the backend
|
||||||
parent_id: this.motion.id,
|
parent_id: this.motion.id,
|
||||||
category_id: this.motion.category_id,
|
category_id: this.motion.category_id,
|
||||||
motion_block_id: this.motion.motion_block_id,
|
motion_block_id: this.motion.motion_block_id,
|
||||||
|
@ -623,8 +623,9 @@
|
|||||||
[innerHTML]="getFormattedStatuteAmendment()"
|
[innerHTML]="getFormattedStatuteAmendment()"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<!-- The HTML Editor -->
|
<!-- The HTML Editor for motions and traditional amendments -->
|
||||||
<editor formControlName="text" [init]="tinyMceSettings" *ngIf="motion && editMotion" required></editor>
|
<ng-container *ngIf="motion && editMotion && !motion.isParagraphBasedAmendment()">
|
||||||
|
<editor formControlName="text" [init]="tinyMceSettings" required></editor>
|
||||||
<div
|
<div
|
||||||
*ngIf="
|
*ngIf="
|
||||||
contentForm.get('text').invalid && (contentForm.get('text').dirty || contentForm.get('text').touched)
|
contentForm.get('text').invalid && (contentForm.get('text').dirty || contentForm.get('text').touched)
|
||||||
@ -634,6 +635,31 @@
|
|||||||
>
|
>
|
||||||
This field is required.
|
This field is required.
|
||||||
</div>
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- The HTML Editor for paragraph-based amendments -->
|
||||||
|
<ng-container *ngIf="motion && editMotion && motion.isParagraphBasedAmendment()">
|
||||||
|
<section *ngFor="let paragraph of contentForm.value.selected_paragraphs">
|
||||||
|
<h4>
|
||||||
|
<span *ngIf="paragraph.lineFrom >= paragraph.lineTo - 1" class="line-number">
|
||||||
|
{{ 'Line' | translate }} {{ paragraph.lineFrom }}</span>
|
||||||
|
<span *ngIf="paragraph.lineFrom < paragraph.lineTo - 1" class="line-number">
|
||||||
|
{{ 'Line' | translate }} {{ paragraph.lineFrom }} - {{ paragraph.lineTo - 1 }}</span>
|
||||||
|
<span>*</span>
|
||||||
|
</h4>
|
||||||
|
<editor [formControlName]="'text_' + paragraph.paragraphNo" [init]="tinyMceSettings" required></editor>
|
||||||
|
<div
|
||||||
|
*ngIf="contentForm.get('text_' + paragraph.paragraphNo).invalid && (
|
||||||
|
contentForm.get('text_' + paragraph.paragraphNo).dirty ||
|
||||||
|
contentForm.get('text_' + paragraph.paragraphNo).touched
|
||||||
|
)"
|
||||||
|
class="red-warning-text"
|
||||||
|
translate
|
||||||
|
>
|
||||||
|
This field is required.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<!-- Paragraph-based amendments -->
|
<!-- Paragraph-based amendments -->
|
||||||
<ng-container *ngIf="!editMotion && motion.isParagraphBasedAmendment()">
|
<ng-container *ngIf="!editMotion && motion.isParagraphBasedAmendment()">
|
||||||
|
@ -25,7 +25,7 @@ import {
|
|||||||
MotionChangeRecommendationComponent
|
MotionChangeRecommendationComponent
|
||||||
} from '../motion-change-recommendation/motion-change-recommendation.component';
|
} from '../motion-change-recommendation/motion-change-recommendation.component';
|
||||||
import { MotionPdfExportService } from 'app/site/motions/services/motion-pdf-export.service';
|
import { MotionPdfExportService } from 'app/site/motions/services/motion-pdf-export.service';
|
||||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
import { MotionRepositoryService, ParagraphToChoose } from 'app/core/repositories/motions/motion-repository.service';
|
||||||
import { NotifyService } from 'app/core/core-services/notify.service';
|
import { NotifyService } from 'app/core/core-services/notify.service';
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { PersonalNoteService } from 'app/core/ui-services/personal-note.service';
|
import { PersonalNoteService } from 'app/core/ui-services/personal-note.service';
|
||||||
@ -645,12 +645,29 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (formMotion.isParagraphBasedAmendment()) {
|
if (formMotion.isParagraphBasedAmendment()) {
|
||||||
contentPatch.text = formMotion.amendment_paragraphs.find(
|
contentPatch.selected_paragraphs = [];
|
||||||
(para: string): boolean => {
|
const parentMotion = this.repo.getViewModel(formMotion.parent_id);
|
||||||
return para !== null;
|
// Hint: lineLength is sometimes not loaded yet when this form is initialized;
|
||||||
|
// This doesn't hurt as long as patchForm is called when editing mode is started, i.e., later.
|
||||||
|
if (parentMotion && this.lineLength) {
|
||||||
|
const paragraphsToChoose = this.repo.getParagraphsToChoose(parentMotion, this.lineLength);
|
||||||
|
|
||||||
|
paragraphsToChoose.forEach(
|
||||||
|
(paragraph: ParagraphToChoose, paragraphNo: number): void => {
|
||||||
|
if (formMotion.amendment_paragraphs[paragraphNo]) {
|
||||||
|
this.contentForm.addControl(
|
||||||
|
'text_' + paragraphNo,
|
||||||
|
new FormControl('', Validators.required)
|
||||||
|
);
|
||||||
|
|
||||||
|
contentPatch.selected_paragraphs.push(paragraph);
|
||||||
|
contentPatch.text = formMotion.amendment_paragraphs[paragraphNo]; // Workaround as 'text' is required from the backend
|
||||||
|
contentPatch['text_' + paragraphNo] = formMotion.amendment_paragraphs[paragraphNo];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const statuteAmendmentFieldName = 'statute_amendment';
|
const statuteAmendmentFieldName = 'statute_amendment';
|
||||||
contentPatch[statuteAmendmentFieldName] = formMotion.isStatuteAmendment();
|
contentPatch[statuteAmendmentFieldName] = formMotion.isStatuteAmendment();
|
||||||
@ -681,6 +698,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
supporters_id: [[]],
|
supporters_id: [[]],
|
||||||
workflow_id: [],
|
workflow_id: [],
|
||||||
origin: [''],
|
origin: [''],
|
||||||
|
selected_paragraphs: [],
|
||||||
statute_amendment: [''], // Internal value for the checkbox, not saved to the model
|
statute_amendment: [''], // Internal value for the checkbox, not saved to the model
|
||||||
statute_paragraph_id: [''],
|
statute_paragraph_id: [''],
|
||||||
motion_block_id: [], // TODO: Can be removed if this is not required
|
motion_block_id: [], // TODO: Can be removed if this is not required
|
||||||
@ -733,11 +751,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
|||||||
const motion = new ctor();
|
const motion = new ctor();
|
||||||
if (this.motion.isParagraphBasedAmendment()) {
|
if (this.motion.isParagraphBasedAmendment()) {
|
||||||
motion.amendment_paragraphs = this.motion.amendment_paragraphs.map(
|
motion.amendment_paragraphs = this.motion.amendment_paragraphs.map(
|
||||||
(paragraph: string): string => {
|
(paragraph: string, paragraphNo: number): string => {
|
||||||
if (paragraph === null) {
|
if (paragraph === null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return motionValues.text;
|
return motionValues['text_' + paragraphNo];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
File diff suppressed because one or more lines are too long
@ -1708,8 +1708,8 @@ msgstr "Redner/in auswählen oder suchen ..."
|
|||||||
msgid "Select or search new submitter ..."
|
msgid "Select or search new submitter ..."
|
||||||
msgstr "Antragsteller/in auswählen oder suchen ..."
|
msgstr "Antragsteller/in auswählen oder suchen ..."
|
||||||
|
|
||||||
msgid "Select paragraph"
|
msgid "Select paragraphs"
|
||||||
msgstr "Absatz auswählen"
|
msgstr "Absätze auswählen"
|
||||||
|
|
||||||
msgid "Selected values"
|
msgid "Selected values"
|
||||||
msgstr "Ausgewählte Werte"
|
msgstr "Ausgewählte Werte"
|
||||||
@ -1786,6 +1786,9 @@ msgstr "Änderungsantrag im Hauptantrag anzeigen"
|
|||||||
msgid "Show amendments together with motions"
|
msgid "Show amendments together with motions"
|
||||||
msgstr "Änderungsanträge zusätzlich in der Hauptantragsübersicht anzeigen"
|
msgstr "Änderungsanträge zusätzlich in der Hauptantragsübersicht anzeigen"
|
||||||
|
|
||||||
|
msgid "Amendments can change multiple paragraphs"
|
||||||
|
msgstr "Änderungsanträge können mehrere Absätze ändern"
|
||||||
|
|
||||||
msgid "Show clock"
|
msgid "Show clock"
|
||||||
msgstr "Uhr anzeigen"
|
msgstr "Uhr anzeigen"
|
||||||
|
|
||||||
|
@ -1651,7 +1651,7 @@ msgstr ""
|
|||||||
msgid "Select or search new submitter ..."
|
msgid "Select or search new submitter ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Select paragraph"
|
msgid "Select paragraphs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Selected values"
|
msgid "Selected values"
|
||||||
@ -1726,7 +1726,10 @@ msgstr ""
|
|||||||
msgid "Show amendment in parent motoin"
|
msgid "Show amendment in parent motoin"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Show amendments together with motions"
|
msgid "Amendments can change multiple paragraphs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Restrict amendments to only one paragraph"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Show clock"
|
msgid "Show clock"
|
||||||
|
@ -261,6 +261,16 @@ def get_config_variables():
|
|||||||
subgroup="Amendments",
|
subgroup="Amendments",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
|
name="motions_amendments_multiple_paragraphs",
|
||||||
|
default_value=False,
|
||||||
|
input_type="boolean",
|
||||||
|
label="Amendments can change multiple paragraphs",
|
||||||
|
weight=343,
|
||||||
|
group="Motions",
|
||||||
|
subgroup="Amendments",
|
||||||
|
)
|
||||||
|
|
||||||
# Supporters
|
# Supporters
|
||||||
|
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
|
Loading…
Reference in New Issue
Block a user