Creating multi-paragraph amendments

- new config option to enable/disable multiple paragraphs
This commit is contained in:
Tobias Hößl 2019-04-06 13:24:21 +02:00 committed by Emanuel Schütze
parent 028c358a7f
commit 5978868c37
11 changed files with 264 additions and 99 deletions

View File

@ -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');

View File

@ -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
* *

View File

@ -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.
*/ */

View File

@ -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>&nbsp;<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 *ngIf="paragraph.lineFrom >= paragraph.lineTo - 1" class="line-number">
> {{ 'Line' | translate }} {{ paragraph.lineFrom }}</span>&nbsp;<span>*</span>
<span translate>Amendment text</span>&nbsp;<span>*</span> <span *ngIf="paragraph.lineFrom < paragraph.lineTo - 1" class="line-number">
</h3> {{ 'Line' | translate }} {{ paragraph.lineFrom }}
<editor formControlName="text" [init]="tinyMceSettings" required></editor> - {{ paragraph.lineTo - 1 }}</span>&nbsp;<span>*</span>
<div </h4>
*ngIf=" <editor [formControlName]="'text_' + paragraph.paragraphNo" [init]="tinyMceSettings" required></editor>
contentForm.get('text').invalid && <div
(contentForm.get('text').dirty || contentForm.get('text').touched) *ngIf="contentForm.get('text_' + paragraph.paragraphNo).invalid && (
" contentForm.get('text_' + paragraph.paragraphNo).dirty ||
class="red-warning-text" contentForm.get('text_' + paragraph.paragraphNo).touched
translate )"
> class="red-warning-text"
This field is required. translate
</div> >
This field is required.
</div>
</section>
<!-- Reason --> <!-- Reason -->
<h3 <h3

View File

@ -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,

View File

@ -623,17 +623,43 @@
[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()">
<div <editor formControlName="text" [init]="tinyMceSettings" required></editor>
*ngIf=" <div
*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)
" "
class="red-warning-text" class="red-warning-text"
translate translate
> >
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>&nbsp;
<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()">

View File

@ -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';
@ -643,11 +643,28 @@ 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';
@ -679,6 +696,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
@ -731,11 +749,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

View File

@ -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"

View File

@ -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"
@ -2360,4 +2363,4 @@ msgid "undocumented"
msgstr "" msgstr ""
msgid "withdrawed" msgid "withdrawed"
msgstr "" msgstr ""

View File

@ -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(