From 2592862384c4aeec333fc664652a1c991ef66ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Ho=CC=88=C3=9Fl?= Date: Sun, 30 Jun 2019 09:30:11 +0200 Subject: [PATCH] Change recommendations for titles - Title changes in PDF, Diff-view and slides --- ...hange-recommendation-repository.service.ts | 86 ++++++++++++- .../motions/motion-repository.service.ts | 68 ++--------- .../src/app/core/ui-services/diff.service.ts | 30 +++++ .../models/motions/view-unified-change.ts | 5 + .../models/view-motion-amended-paragraph.ts | 4 + .../view-motion-change-recommendation.ts | 4 + ...ange-recommendation-dialog.component.html} | 0 ...ange-recommendation-dialog.component.scss} | 0 ...e-recommendation-dialog.component.spec.ts} | 16 +-- ...change-recommendation-dialog.component.ts} | 16 +-- .../motion-detail-diff.component.html | 53 ++++++-- .../motion-detail-diff.component.ts | 67 +++++++++-- ...inal-change-recommendations.component.html | 2 +- ...iginal-change-recommendations.component.ts | 7 ++ .../motion-detail.component.html | 11 +- .../motion-detail.component.scss | 42 +++++++ .../motion-detail/motion-detail.component.ts | 74 ++++++++++-- ...hange-recommendation-dialog.component.html | 19 +++ ...hange-recommendation-dialog.component.scss | 9 ++ ...ge-recommendation-dialog.component.spec.ts | 54 +++++++++ ...-change-recommendation-dialog.component.ts | 113 ++++++++++++++++++ .../motion-detail/motion-detail.module.ts | 9 +- .../motions/services/motion-pdf.service.ts | 103 +++++++++------- .../motion-slide-obj-amendment-paragraph.ts | 4 + .../motion/motion-slide-obj-change-reco.ts | 4 + .../motion/motion-slide.component.html | 8 +- .../motions/motion/motion-slide.component.ts | 28 ++++- client/src/styles.scss | 3 +- 28 files changed, 674 insertions(+), 165 deletions(-) rename client/src/app/site/motions/modules/motion-detail/components/{motion-change-recommendation/motion-change-recommendation.component.html => motion-change-recommendation-dialog/motion-change-recommendation-dialog.component.html} (100%) rename client/src/app/site/motions/modules/motion-detail/components/{motion-change-recommendation/motion-change-recommendation.component.scss => motion-change-recommendation-dialog/motion-change-recommendation-dialog.component.scss} (100%) rename client/src/app/site/motions/modules/motion-detail/components/{motion-change-recommendation/motion-change-recommendation.component.spec.ts => motion-change-recommendation-dialog/motion-change-recommendation-dialog.component.spec.ts} (76%) rename client/src/app/site/motions/modules/motion-detail/components/{motion-change-recommendation/motion-change-recommendation.component.ts => motion-change-recommendation-dialog/motion-change-recommendation-dialog.component.ts} (90%) create mode 100644 client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.html create mode 100644 client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.scss create mode 100644 client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.spec.ts create mode 100644 client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.ts diff --git a/client/src/app/core/repositories/motions/change-recommendation-repository.service.ts b/client/src/app/core/repositories/motions/change-recommendation-repository.service.ts index ee1e84bb7..58cb4826d 100644 --- a/client/src/app/core/repositories/motions/change-recommendation-repository.service.ts +++ b/client/src/app/core/repositories/motions/change-recommendation-repository.service.ts @@ -18,6 +18,9 @@ import { import { Identifiable } from 'app/shared/models/base/identifiable'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; +import { ChangeRecoMode, ViewMotion } from '../../../site/motions/models/view-motion'; +import { ViewUnifiedChange } from '../../../shared/models/motions/view-unified-change'; +import { DiffService, LineRange, ModificationType } from '../../ui-services/diff.service'; /** * Repository Services for change recommendations @@ -43,16 +46,20 @@ export class ChangeRecommendationRepositoryService extends BaseRepository< * Converts existing and incoming motions to ViewMotions * Handles CRUD using an observer to the DataStore * - * @param DS The DataStore - * @param mapperService Maps collection strings to classes - * @param dataSend sending changed objects + * @param {DataStoreService} DS The DataStore + * @param {DataSendService} dataSend sending changed objects + * @param {CollectionStringMapperService} mapperService Maps collection strings to classes + * @param {ViewModelStoreService} viewModelStoreService + * @param {TranslateService} translate + * @param {DiffService} diffService */ public constructor( DS: DataStoreService, dataSend: DataSendService, mapperService: CollectionStringMapperService, viewModelStoreService: ViewModelStoreService, - translate: TranslateService + translate: TranslateService, + private diffService: DiffService ) { super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionChangeRecommendation, [ Category, @@ -147,4 +154,75 @@ export class ChangeRecommendationRepositoryService extends BaseRepository< }); await this.dataSend.partialUpdateModel(changeReco); } + + public getTitleWithChanges = (originalTitle: string, change: ViewUnifiedChange, crMode: ChangeRecoMode): string => { + if (change) { + if (crMode === ChangeRecoMode.Changed) { + return change.getChangeNewText(); + } else if ( + (crMode === ChangeRecoMode.Final || crMode === ChangeRecoMode.ModifiedFinal) && + !change.isRejected() + ) { + return change.getChangeNewText(); + } else { + return originalTitle; + } + } else { + return originalTitle; + } + }; + + public getTitleChangesAsDiff = (originalTitle: string, change: ViewUnifiedChange): string => { + if (change) { + return this.diffService.diff(originalTitle, change.getChangeNewText()); + } else { + return ''; + } + }; + + /** + * Creates a {@link ViewMotionChangeRecommendation} object based on the motion ID and the given lange range. + * This object is not saved yet and does not yet have any changed HTML. It's meant to populate the UI form. + * + * @param {ViewMotion} motion + * @param {LineRange} lineRange + * @param {number} lineLength + */ + public createChangeRecommendationTemplate( + motion: ViewMotion, + lineRange: LineRange, + lineLength: number + ): ViewMotionChangeRecommendation { + const changeReco = new MotionChangeRecommendation(); + changeReco.line_from = lineRange.from; + changeReco.line_to = lineRange.to; + changeReco.type = ModificationType.TYPE_REPLACEMENT; + changeReco.text = this.diffService.extractMotionLineRange(motion.text, lineRange, false, lineLength, null); + changeReco.rejected = false; + changeReco.motion_id = motion.id; + + return new ViewMotionChangeRecommendation(changeReco); + } + + /** + * Creates a {@link ViewMotionChangeRecommendation} object to change the title, based on the motion ID. + * This object is not saved yet and does not yet have any changed title. It's meant to populate the UI form. + * + * @param {ViewMotion} motion + * @param {number} lineLength + */ + public createTitleChangeRecommendationTemplate( + motion: ViewMotion, + lineLength: number + ): ViewMotionChangeRecommendation { + const changeReco = new MotionChangeRecommendation(); + changeReco.line_from = 0; + changeReco.line_to = 0; + changeReco.type = ModificationType.TYPE_REPLACEMENT; + changeReco.text = motion.title; + changeReco.rejected = false; + changeReco.motion_id = motion.id; + + return new ViewMotionChangeRecommendation(changeReco); + } } diff --git a/client/src/app/core/repositories/motions/motion-repository.service.ts b/client/src/app/core/repositories/motions/motion-repository.service.ts index 978b673fb..becd50772 100644 --- a/client/src/app/core/repositories/motions/motion-repository.service.ts +++ b/client/src/app/core/repositories/motions/motion-repository.service.ts @@ -6,12 +6,12 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { Category } from 'app/shared/models/motions/category'; -import { ChangeRecoMode, ViewMotion, MotionTitleInformation } from 'app/site/motions/models/view-motion'; +import { ChangeRecoMode, MotionTitleInformation, ViewMotion } from 'app/site/motions/models/view-motion'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { ConfigService } from 'app/core/ui-services/config.service'; import { DataSendService } from '../../core-services/data-send.service'; -import { DataStoreService, CollectionIds } from '../../core-services/data-store.service'; -import { DiffLinesInParagraph, DiffService, LineRange, ModificationType } from '../../ui-services/diff.service'; +import { DataStoreService, CollectionIds } from 'app/core/core-services/data-store.service'; +import { DiffService, DiffLinesInParagraph } from 'app/core/ui-services/diff.service'; import { HttpService } from 'app/core/core-services/http.service'; import { LinenumberingService, LineNumberRange } from '../../ui-services/linenumbering.service'; import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; @@ -543,8 +543,8 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo }) .forEach((change: ViewUnifiedChange, idx: number) => { if (idx === 0) { - text += this.extractMotionLineRange( - id, + text += this.diff.extractMotionLineRange( + targetMotion.text, { from: 1, to: change.getLineFrom() @@ -554,8 +554,8 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo highlightLine ); } else if (changes[idx - 1].getLineTo() < change.getLineFrom()) { - text += this.extractMotionLineRange( - id, + text += this.diff.extractMotionLineRange( + targetMotion.text, { from: changes[idx - 1].getLineTo(), to: change.getLineFrom() @@ -612,36 +612,6 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo } } - /** - * Extracts a renderable HTML string representing the given line number range of this motion - * - * @param {number} id - * @param {LineRange} lineRange - * @param {boolean} lineNumbers - weather to add line numbers to the returned HTML string - * @param {number} lineLength - * @param {number|null} highlightedLine - */ - public extractMotionLineRange( - id: number, - lineRange: LineRange, - lineNumbers: boolean, - lineLength: number, - highlightedLine: number - ): string { - const origHtml = this.formatMotion(id, ChangeRecoMode.Original, [], lineLength); - const extracted = this.diff.extractRangeByLineNumbers(origHtml, lineRange.from, lineRange.to); - let html = - extracted.outerContextStart + - extracted.innerContextStart + - extracted.html + - extracted.innerContextEnd + - extracted.outerContextEnd; - if (lineNumbers) { - html = this.lineNumbering.insertLineNumbers(html, lineLength, highlightedLine, null, lineRange.from); - } - return html; - } - /** * Returns the last line number of a motion * @@ -655,30 +625,6 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo return range.to; } - /** - * Creates a {@link ViewMotionChangeRecommendation} object based on the motion ID and the given lange range. - * This object is not saved yet and does not yet have any changed HTML. It's meant to populate the UI form. - * - * @param {number} motionId - * @param {LineRange} lineRange - * @param {number} lineLength - */ - public createChangeRecommendationTemplate( - motionId: number, - lineRange: LineRange, - lineLength: number - ): ViewMotionChangeRecommendation { - const changeReco = new MotionChangeRecommendation(); - changeReco.line_from = lineRange.from; - changeReco.line_to = lineRange.to; - changeReco.type = ModificationType.TYPE_REPLACEMENT; - changeReco.text = this.extractMotionLineRange(motionId, lineRange, false, lineLength, null); - changeReco.rejected = false; - changeReco.motion_id = motionId; - - return new ViewMotionChangeRecommendation(changeReco); - } - /** * Given an amendment, this returns the motion affected by this amendments * diff --git a/client/src/app/core/ui-services/diff.service.ts b/client/src/app/core/ui-services/diff.service.ts index ed7c174a4..d632ca0e5 100644 --- a/client/src/app/core/ui-services/diff.service.ts +++ b/client/src/app/core/ui-services/diff.service.ts @@ -2317,4 +2317,34 @@ export class DiffService { } return html; } + + /** + * Extracts a renderable HTML string representing the given line number range of this motion text + * + * @param {string} motionText + * @param {LineRange} lineRange + * @param {boolean} lineNumbers - weather to add line numbers to the returned HTML string + * @param {number} lineLength + * @param {number|null} highlightedLine + */ + public extractMotionLineRange( + motionText: string, + lineRange: LineRange, + lineNumbers: boolean, + lineLength: number, + highlightedLine: number + ): string { + const origHtml = this.lineNumberingService.insertLineNumbers(motionText, lineLength, highlightedLine); + const extracted = this.extractRangeByLineNumbers(origHtml, lineRange.from, lineRange.to); + let html = + extracted.outerContextStart + + extracted.innerContextStart + + extracted.html + + extracted.innerContextEnd + + extracted.outerContextEnd; + if (lineNumbers) { + html = this.lineNumberingService.insertLineNumbers(html, lineLength, highlightedLine, null, lineRange.from); + } + return html; + } } diff --git a/client/src/app/shared/models/motions/view-unified-change.ts b/client/src/app/shared/models/motions/view-unified-change.ts index d7d3d4de5..46048a0b3 100644 --- a/client/src/app/shared/models/motions/view-unified-change.ts +++ b/client/src/app/shared/models/motions/view-unified-change.ts @@ -13,6 +13,11 @@ export interface ViewUnifiedChange { */ getChangeType(): ViewUnifiedChangeType; + /** + * If this is a title-related change (only implemented for change recommendations) + */ + isTitleChange(): boolean; + /** * An id that is unique considering both change recommendations and amendments, therefore needs to be * "namespaced" (e.g. "amendment.23" or "recommendation.42") diff --git a/client/src/app/site/motions/models/view-motion-amended-paragraph.ts b/client/src/app/site/motions/models/view-motion-amended-paragraph.ts index 793a81af7..cd984b9ef 100644 --- a/client/src/app/site/motions/models/view-motion-amended-paragraph.ts +++ b/client/src/app/site/motions/models/view-motion-amended-paragraph.ts @@ -111,4 +111,8 @@ export class ViewMotionAmendedParagraph implements ViewUnifiedChange { public showInFinalView(): boolean { return this.amendment.state && this.amendment.state.merge_amendment_into_final === MergeAmendment.YES; } + + public isTitleChange(): boolean { + return false; // Not implemented for amendments + } } diff --git a/client/src/app/site/motions/models/view-motion-change-recommendation.ts b/client/src/app/site/motions/models/view-motion-change-recommendation.ts index fde573338..078200c49 100644 --- a/client/src/app/site/motions/models/view-motion-change-recommendation.ts +++ b/client/src/app/site/motions/models/view-motion-change-recommendation.ts @@ -100,4 +100,8 @@ export class ViewMotionChangeRecommendation extends BaseViewModel { - let component: MotionChangeRecommendationComponent; - let fixture: ComponentFixture; + let component: MotionChangeRecommendationDialogComponent; + let fixture: ComponentFixture; const changeReco = { line_from: 1, @@ -22,7 +22,7 @@ describe('MotionChangeRecommendationComponent', () => { rejected: false, motion_id: 1 }; - const dialogData: MotionChangeRecommendationComponentData = { + const dialogData: MotionChangeRecommendationDialogComponentData = { newChangeRecommendation: true, editChangeRecommendation: false, changeRecommendation: changeReco, @@ -32,7 +32,7 @@ describe('MotionChangeRecommendationComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [E2EImportsModule], - declarations: [MotionChangeRecommendationComponent], + declarations: [MotionChangeRecommendationDialogComponent], providers: [ { provide: MatDialogRef, useValue: {} }, { @@ -44,7 +44,7 @@ describe('MotionChangeRecommendationComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(MotionChangeRecommendationComponent); + fixture = TestBed.createComponent(MotionChangeRecommendationDialogComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-change-recommendation/motion-change-recommendation.component.ts b/client/src/app/site/motions/modules/motion-detail/components/motion-change-recommendation-dialog/motion-change-recommendation-dialog.component.ts similarity index 90% rename from client/src/app/site/motions/modules/motion-detail/components/motion-change-recommendation/motion-change-recommendation.component.ts rename to client/src/app/site/motions/modules/motion-detail/components/motion-change-recommendation-dialog/motion-change-recommendation-dialog.component.ts index 901605652..b1c80cf8f 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-change-recommendation/motion-change-recommendation.component.ts +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-change-recommendation-dialog/motion-change-recommendation-dialog.component.ts @@ -14,7 +14,7 @@ import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-mot /** * Data that needs to be provided to the MotionChangeRecommendationComponent dialog */ -export interface MotionChangeRecommendationComponentData { +export interface MotionChangeRecommendationDialogComponentData { editChangeRecommendation: boolean; newChangeRecommendation: boolean; lineRange: LineRange; @@ -26,13 +26,13 @@ export interface MotionChangeRecommendationComponentData { * * @example * ```ts - * const data: MotionChangeRecommendationComponentData = { + * const data: MotionChangeRecommendationDialogComponentData = { * editChangeRecommendation: false, * newChangeRecommendation: true, * lineRange: lineRange, * changeReco: this.changeRecommendation, * }; - * this.dialogService.open(MotionChangeRecommendationComponent, { + * this.dialogService.open(MotionChangeRecommendationDialogComponent, { * height: '400px', * width: '600px', * data: data, @@ -42,10 +42,10 @@ export interface MotionChangeRecommendationComponentData { */ @Component({ selector: 'os-motion-change-recommendation', - templateUrl: './motion-change-recommendation.component.html', - styleUrls: ['./motion-change-recommendation.component.scss'] + templateUrl: './motion-change-recommendation-dialog.component.html', + styleUrls: ['./motion-change-recommendation-dialog.component.scss'] }) -export class MotionChangeRecommendationComponent extends BaseViewComponent { +export class MotionChangeRecommendationDialogComponent extends BaseViewComponent { /** * Determine if the change recommendation is edited */ @@ -91,13 +91,13 @@ export class MotionChangeRecommendationComponent extends BaseViewComponent { ]; public constructor( - @Inject(MAT_DIALOG_DATA) public data: MotionChangeRecommendationComponentData, + @Inject(MAT_DIALOG_DATA) public data: MotionChangeRecommendationDialogComponentData, title: Title, protected translate: TranslateService, matSnackBar: MatSnackBar, private formBuilder: FormBuilder, private repo: ChangeRecommendationRepositoryService, - private dialogRef: MatDialogRef + private dialogRef: MatDialogRef ) { super(title, translate, matSnackBar); diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.html b/client/src/app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.html index ddd48ccbd..2634b4d55 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.html +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.html @@ -10,12 +10,10 @@ [class.amendment]="isAmendment(change)" [class.recommendation]="isChangeRecommendation(change)" > - - {{ 'Line' | translate }} {{ change.getLineFrom() }} - - - {{ 'Line' | translate }} {{ change.getLineFrom() }} - {{ change.getLineTo() - 1 }} + + {{ 'Line' | translate }} {{ formatLineRange(change) }} + {{ 'Title' | translate }} ({{ 'Change recommendation' | translate }}) ({{ 'Amendment' | translate }} {{ change.getIdentifier() }}) @@ -44,7 +42,38 @@
-
+
+
+
+ +
+
+ {{ 'Rejected' | translate }} +
+ +
+
+ {{ 'Changed title' | translate }}: +
+
+
+
+
+
@@ -60,9 +89,9 @@
-
+
warning
@@ -144,7 +173,11 @@ delete Delete - + diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.ts b/client/src/app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.ts index 521b16957..3f3a5df05 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.ts +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail-diff/motion-detail-diff.component.ts @@ -10,10 +10,13 @@ import { ConfigService } from 'app/core/ui-services/config.service'; import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service'; import { DiffService, LineRange } from 'app/core/ui-services/diff.service'; import { - MotionChangeRecommendationComponent, - MotionChangeRecommendationComponentData -} from '../motion-change-recommendation/motion-change-recommendation.component'; -import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; + MotionChangeRecommendationDialogComponent, + MotionChangeRecommendationDialogComponentData +} from '../motion-change-recommendation-dialog/motion-change-recommendation-dialog.component'; +import { + MotionTitleChangeRecommendationDialogComponent, + MotionTitleChangeRecommendationDialogComponentData +} from '../motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component'; import { PromptService } from 'app/core/ui-services/prompt.service'; import { ViewMotion, LineNumberingMode } from 'app/site/motions/models/view-motion'; import { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change'; @@ -78,7 +81,6 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte * @param translate * @param matSnackBar * @param sanitizer - * @param motionRepo * @param diff * @param recoRepo * @param dialogService @@ -91,7 +93,6 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte protected translate: TranslateService, // protected required for ng-translate-extract matSnackBar: MatSnackBar, private sanitizer: DomSanitizer, - private motionRepo: MotionRepositoryService, private diff: DiffService, private recoRepo: ChangeRecommendationRepositoryService, private dialogService: MatDialog, @@ -121,8 +122,8 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte return ''; } - return this.motionRepo.extractMotionLineRange( - this.motion.id, + return this.diff.extractMotionLineRange( + this.motion.text, lineRange, true, this.lineLength, @@ -175,6 +176,20 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte ); } + /** + * If only one line is affected, the line number is returned; otherwise, a string like [line] "1 - 5" + * + * @param {ViewUnifiedChange} change + * @returns string + */ + public formatLineRange(change: ViewUnifiedChange): string { + if (change.getLineFrom() < change.getLineTo() - 1) { + return change.getLineFrom().toString(10) + ' - ' + (change.getLineTo() - 1).toString(10); + } else { + return change.getLineFrom().toString(10); + } + } + /** * Returns true if the change is a Change Recommendation * @@ -229,6 +244,19 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte return change.getChangeType() === ViewUnifiedChangeType.TYPE_CHANGE_RECOMMENDATION; } + public getAllTextChangingObjects(): ViewUnifiedChange[] { + return this.changes.filter((obj: ViewUnifiedChange) => !obj.isTitleChange()); + } + + public getTitleChangingObject(): ViewUnifiedChange { + return this.changes.find((obj: ViewUnifiedChange) => obj.isTitleChange()); + } + + public getFormattedTitleDiff(): SafeHtml { + const change = this.getTitleChangingObject(); + return this.sanitizer.bypassSecurityTrustHtml(this.recoRepo.getTitleChangesAsDiff(this.motion.title, change)); + } + /** * Sets a change recommendation to accepted or rejected. * The template has to make sure only to pass change recommendations to this method. @@ -286,7 +314,7 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte $event.stopPropagation(); $event.preventDefault(); - const data: MotionChangeRecommendationComponentData = { + const data: MotionChangeRecommendationDialogComponentData = { editChangeRecommendation: true, newChangeRecommendation: false, lineRange: { @@ -295,7 +323,26 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte }, changeRecommendation: reco }; - this.dialogService.open(MotionChangeRecommendationComponent, { + this.dialogService.open(MotionChangeRecommendationDialogComponent, { + height: '600px', + width: '800px', + maxHeight: '90vh', + maxWidth: '90vw', + data: data, + disableClose: true + }); + } + + public editTitleChangeRecommendation(reco: ViewMotionChangeRecommendation, $event: MouseEvent): void { + $event.stopPropagation(); + $event.preventDefault(); + + const data: MotionTitleChangeRecommendationDialogComponentData = { + editChangeRecommendation: true, + newChangeRecommendation: false, + changeRecommendation: reco + }; + this.dialogService.open(MotionTitleChangeRecommendationDialogComponent, { height: '600px', width: '800px', maxHeight: '90vh', diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component.html b/client/src/app/site/motions/modules/motion-detail/components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component.html index 04a6b2cfc..ddc5a7dd8 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component.html +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component.html @@ -1,6 +1,6 @@
    -
  • diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component.ts b/client/src/app/site/motions/modules/motion-detail/components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component.ts index ef0546646..00754d4ec 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component.ts +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component.ts @@ -105,6 +105,10 @@ export class MotionDetailOriginalChangeRecommendationsComponent implements OnIni } } + public getTextChangeRecommendations(): ViewMotionChangeRecommendation[] { + return this.changeRecommendations.filter(reco => !reco.isTitleChange()); + } + /** * Returns an array with all line numbers that are currently affected by a change recommendation * and therefor not subject to further changes @@ -112,6 +116,9 @@ export class MotionDetailOriginalChangeRecommendationsComponent implements OnIni private getAffectedLineNumbers(): number[] { const affectedLines = []; this.changeRecommendations.forEach((change: ViewMotionChangeRecommendation) => { + if (change.isTitleChange()) { + return; + } for (let j = change.line_from; j < change.line_to; j++) { affectedLines.push(j); } diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html index 48565da53..a218c07db 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.html @@ -124,7 +124,16 @@
    -

    {{ motion.title }}

    +

    + + + + + + {{ getTitleWithChanges() }} +

    diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.scss b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.scss index 650ee17bc..79edd61ec 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.scss +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.scss @@ -170,6 +170,48 @@ span { } .title-line { display: flex; + + .motion-title { + position: relative; + z-index: 1; + + // Grab the left padding of the parent element to catch hover-events for the :before element + margin-left: -20px; + padding-left: 20px; + + .change-title { + position: relative; + width: 0; + height: 0; + } + + .change-title:before { + position: absolute; + top: 18px; + left: -17px; + display: none; + cursor: pointer; + content: ''; + width: 16px; + height: 16px; + background: url('data:image/svg+xml;utf8,'); + background-size: 16px 16px; + } + + &:hover .change-title:before { + display: block; + } + + .title-change-indicator { + background-color: #0333ff; + position: absolute; + width: 4px; + height: 32px; + left: 10px; + top: 5px; + cursor: pointer; + } + } } .create-poll-button { diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.ts b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.ts index c50180890..efa4cb0ba 100644 --- a/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.ts +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-detail/motion-detail.component.ts @@ -15,7 +15,7 @@ import { CategoryRepositoryService } from 'app/core/repositories/motions/categor import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service'; import { CreateMotion } from 'app/site/motions/models/create-motion'; import { ConfigService } from 'app/core/ui-services/config.service'; -import { DiffLinesInParagraph, LineRange } from 'app/core/ui-services/diff.service'; +import { DiffLinesInParagraph, DiffService, LineRange } from 'app/core/ui-services/diff.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service'; import { LinenumberingService } from 'app/core/ui-services/linenumbering.service'; import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service'; @@ -23,9 +23,13 @@ import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service'; import { Motion } from 'app/shared/models/motions/motion'; import { - MotionChangeRecommendationComponentData, - MotionChangeRecommendationComponent -} from '../motion-change-recommendation/motion-change-recommendation.component'; + MotionChangeRecommendationDialogComponentData, + MotionChangeRecommendationDialogComponent +} from '../motion-change-recommendation-dialog/motion-change-recommendation-dialog.component'; +import { + MotionTitleChangeRecommendationDialogComponentData, + MotionTitleChangeRecommendationDialogComponent +} from '../motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component'; import { MotionPdfExportService } from 'app/site/motions/services/motion-pdf-export.service'; import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service'; import { MotionFilterListService } from 'app/site/motions/services/motion-filter-list.service'; @@ -401,6 +405,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, * @param pdfExport export the motion to pdf * @param personalNoteService: personal comments and favorite marker * @param linenumberingService The line numbering service + * @param diffService The diff service * @param categoryRepo Repository for categories * @param viewModelStore accessing view models * @param categoryRepo access the category repository @@ -435,6 +440,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, private pdfExport: MotionPdfExportService, private personalNoteService: PersonalNoteService, private linenumberingService: LinenumberingService, + private diffService: DiffService, private categoryRepo: CategoryRepositoryService, private userRepo: UserRepositoryService, private notifyService: NotifyService, @@ -569,7 +575,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, this.allChangingObjects = []; if (this.changeRecommendations) { - this.changeRecommendations.forEach((change: ViewUnifiedChange): void => { + this.changeRecommendations.forEach((change: ViewMotionChangeRecommendation): void => { this.allChangingObjects.push(change); }); } @@ -854,7 +860,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, */ public getFormattedTextPlain(): string { // Prevent this.allChangingObjects to be reordered from within formatMotion - const changes: ViewUnifiedChange[] = Object.assign([], this.allChangingObjects); + const changes: ViewUnifiedChange[] = Object.assign([], this.getAllTextChangingObjects()); return this.repo.formatMotion(this.motion.id, this.crMode, changes, this.lineLength, this.highlightedLine); } @@ -888,8 +894,9 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, * @returns safe html strings */ public getParentMotionRange(from: number, to: number): SafeHtml { - const str = this.repo.extractMotionLineRange( - this.motion.parent_id, + const parentMotion = this.repo.getViewModel(this.motion.parent_id); + const str = this.diffService.extractMotionLineRange( + parentMotion.text, { from, to }, true, this.lineLength, @@ -920,6 +927,18 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, }); } + public getAllTextChangingObjects(): ViewUnifiedChange[] { + return this.allChangingObjects.filter((obj: ViewUnifiedChange) => !obj.isTitleChange()); + } + + public getTitleChangingObject(): ViewUnifiedChange { + return this.allChangingObjects.find((obj: ViewUnifiedChange) => obj.isTitleChange()); + } + + public getTitleWithChanges(): string { + return this.changeRecoRepo.getTitleWithChanges(this.motion.title, this.getTitleChangingObject(), this.crMode); + } + /** * Trigger to delete the motion. */ @@ -1027,17 +1046,17 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, * @param lineRange */ public createChangeRecommendation(lineRange: LineRange): void { - const data: MotionChangeRecommendationComponentData = { + const data: MotionChangeRecommendationDialogComponentData = { editChangeRecommendation: false, newChangeRecommendation: true, lineRange: lineRange, - changeRecommendation: this.repo.createChangeRecommendationTemplate( - this.motion.id, + changeRecommendation: this.changeRecoRepo.createChangeRecommendationTemplate( + this.motion, lineRange, this.lineLength ) }; - this.dialogService.open(MotionChangeRecommendationComponent, { + this.dialogService.open(MotionChangeRecommendationDialogComponent, { height: '600px', width: '800px', maxHeight: '90vh', @@ -1047,6 +1066,37 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit, }); } + /** + * In the original version, the title has been clicked to create a new change recommendation + */ + public createTitleChangeRecommendation(): void { + const data: MotionTitleChangeRecommendationDialogComponentData = { + editChangeRecommendation: false, + newChangeRecommendation: true, + changeRecommendation: this.changeRecoRepo.createTitleChangeRecommendationTemplate( + this.motion, + this.lineLength + ) + }; + this.dialogService.open(MotionTitleChangeRecommendationDialogComponent, { + width: '400px', + maxHeight: '90vh', + maxWidth: '90vw', + data: data, + disableClose: true + }); + } + + public titleCanBeChanged(): boolean { + if (this.editMotion) { + return false; + } + if (this.motion.isStatuteAmendment() || this.motion.isParagraphBasedAmendment()) { + return false; + } + return this.isRecoMode(ChangeRecoMode.Original) || this.isRecoMode(ChangeRecoMode.Diff); + } + /** * In the original version, a change-recommendation-annotation has been clicked * -> Go to the diff view and scroll to the change recommendation diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.html b/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.html new file mode 100644 index 000000000..95d868b45 --- /dev/null +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.html @@ -0,0 +1,19 @@ +

    New change recommendation

    + +
    + + + + + {{ 'Public' | translate }} +
    +
    + + + + + diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.scss b/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.scss new file mode 100644 index 000000000..05eb9874a --- /dev/null +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.scss @@ -0,0 +1,9 @@ +.motion-content { + .mat-form-field { + width: 100%; + } +} + +.mat-dialog-content { + overflow: hidden; +} diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.spec.ts b/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.spec.ts new file mode 100644 index 000000000..eac2fe607 --- /dev/null +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.spec.ts @@ -0,0 +1,54 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { + MotionTitleChangeRecommendationDialogComponent, + MotionTitleChangeRecommendationDialogComponentData +} from './motion-title-change-recommendation-dialog.component'; + +import { E2EImportsModule } from 'e2e-imports.module'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { ModificationType } from 'app/core/ui-services/diff.service'; +import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation'; + +describe('MotionTitleChangeRecommendationDialogComponent', () => { + let component: MotionTitleChangeRecommendationDialogComponent; + let fixture: ComponentFixture; + + const changeReco = { + line_from: 0, + line_to: 0, + type: ModificationType.TYPE_REPLACEMENT, + text: 'Motion title', + rejected: false, + motion_id: 1 + }; + const dialogData: MotionTitleChangeRecommendationDialogComponentData = { + newChangeRecommendation: true, + editChangeRecommendation: false, + changeRecommendation: changeReco + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [E2EImportsModule], + declarations: [MotionTitleChangeRecommendationDialogComponent], + providers: [ + { provide: MatDialogRef, useValue: {} }, + { + provide: MAT_DIALOG_DATA, + useValue: dialogData + } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MotionTitleChangeRecommendationDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.ts b/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.ts new file mode 100644 index 000000000..92d102a53 --- /dev/null +++ b/client/src/app/site/motions/modules/motion-detail/components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component.ts @@ -0,0 +1,113 @@ +import { Component, Inject, ViewEncapsulation } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef, MatSnackBar } from '@angular/material'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Title } from '@angular/platform-browser'; + +import { TranslateService } from '@ngx-translate/core'; + +import { BaseViewComponent } from 'app/site/base/base-view'; +import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service'; +import { ModificationType } from 'app/core/ui-services/diff.service'; +import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation'; + +/** + * Data that needs to be provided to the MotionTitleChangeRecommendationComponent dialog + */ +export interface MotionTitleChangeRecommendationDialogComponentData { + editChangeRecommendation: boolean; + newChangeRecommendation: boolean; + changeRecommendation: ViewMotionChangeRecommendation; +} + +/** + * The dialog for creating and editing title change recommendations from within the os-motion-detail-component. + * + * @example + * ```ts + * const data: MotionTitleChangeRecommendationDialogComponentData = { + * editChangeRecommendation: false, + * newChangeRecommendation: true, + * changeReco: this.changeRecommendation, + * }; + * this.dialogService.open(MotionTitleChangeRecommendationDialogComponent, { + * height: '400px', + * width: '600px', + * data: data, + * }); + * ``` + */ +@Component({ + selector: 'os-title-motion-change-recommendation-dialog', + templateUrl: './motion-title-change-recommendation-dialog.component.html', + styleUrls: ['./motion-title-change-recommendation-dialog.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class MotionTitleChangeRecommendationDialogComponent extends BaseViewComponent { + /** + * Determine if the change recommendation is edited + */ + public editReco = false; + + /** + * Determine if the change recommendation is new + */ + public newReco = false; + + /** + * The change recommendation + */ + public changeReco: ViewMotionChangeRecommendation; + + /** + * Change recommendation content. + */ + public contentForm: FormGroup; + + public constructor( + @Inject(MAT_DIALOG_DATA) public data: MotionTitleChangeRecommendationDialogComponentData, + title: Title, + protected translate: TranslateService, + matSnackBar: MatSnackBar, + private formBuilder: FormBuilder, + private repo: ChangeRecommendationRepositoryService, + private dialogRef: MatDialogRef + ) { + super(title, translate, matSnackBar); + + this.editReco = data.editChangeRecommendation; + this.newReco = data.newChangeRecommendation; + this.changeReco = data.changeRecommendation; + + this.createForm(); + } + + /** + * Creates the forms for the Motion and the MotionVersion + */ + public createForm(): void { + this.contentForm = this.formBuilder.group({ + title: [this.changeReco.text, Validators.required], + public: [!this.changeReco.internal] + }); + } + + public async saveChangeRecommendation(): Promise { + this.changeReco.updateChangeReco( + ModificationType.TYPE_REPLACEMENT, + this.contentForm.controls.title.value, + !this.contentForm.controls.public.value + ); + + try { + if (this.newReco) { + await this.repo.createByViewModel(this.changeReco); + this.dialogRef.close(); + } else { + await this.repo.update(this.changeReco.changeRecommendation, this.changeReco); + this.dialogRef.close(); + } + } catch (e) { + this.raiseError(e); + } + } +} diff --git a/client/src/app/site/motions/modules/motion-detail/motion-detail.module.ts b/client/src/app/site/motions/modules/motion-detail/motion-detail.module.ts index c5daee26d..1a79bde83 100644 --- a/client/src/app/site/motions/modules/motion-detail/motion-detail.module.ts +++ b/client/src/app/site/motions/modules/motion-detail/motion-detail.module.ts @@ -12,7 +12,8 @@ import { MotionPollDialogComponent } from './components/motion-poll/motion-poll- import { MotionPollComponent } from './components/motion-poll/motion-poll.component'; import { MotionDetailOriginalChangeRecommendationsComponent } from './components/motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component'; import { MotionDetailDiffComponent } from './components/motion-detail-diff/motion-detail-diff.component'; -import { MotionChangeRecommendationComponent } from './components/motion-change-recommendation/motion-change-recommendation.component'; +import { MotionChangeRecommendationDialogComponent } from './components/motion-change-recommendation-dialog/motion-change-recommendation-dialog.component'; +import { MotionTitleChangeRecommendationDialogComponent } from './components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component'; @NgModule({ imports: [CommonModule, MotionDetailRoutingModule, SharedModule], @@ -26,14 +27,16 @@ import { MotionChangeRecommendationComponent } from './components/motion-change- MotionPollDialogComponent, MotionDetailDiffComponent, MotionDetailOriginalChangeRecommendationsComponent, - MotionChangeRecommendationComponent + MotionChangeRecommendationDialogComponent, + MotionTitleChangeRecommendationDialogComponent ], entryComponents: [ MotionCommentsComponent, PersonalNoteComponent, ManageSubmittersComponent, MotionPollDialogComponent, - MotionChangeRecommendationComponent + MotionChangeRecommendationDialogComponent, + MotionTitleChangeRecommendationDialogComponent ] }) export class MotionDetailModule {} diff --git a/client/src/app/site/motions/services/motion-pdf.service.ts b/client/src/app/site/motions/services/motion-pdf.service.ts index f648f1032..2b83fe40c 100644 --- a/client/src/app/site/motions/services/motion-pdf.service.ts +++ b/client/src/app/site/motions/services/motion-pdf.service.ts @@ -10,7 +10,7 @@ import { HtmlToPdfService } from 'app/core/ui-services/html-to-pdf.service'; import { MotionPollService } from './motion-poll.service'; import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; import { StatuteParagraphRepositoryService } from 'app/core/repositories/motions/statute-paragraph-repository.service'; -import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-motion'; +import { ChangeRecoMode, LineNumberingMode, ViewMotion } from '../models/view-motion'; import { LinenumberingService } from 'app/core/ui-services/linenumbering.service'; import { MotionCommentSectionRepositoryService } from 'app/core/repositories/motions/motion-comment-section-repository.service'; import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service'; @@ -124,7 +124,7 @@ export class MotionPdfService { crMode = this.configService.instant('motions_recommendation_text_mode'); } - const title = this.createTitle(motion); + const title = this.createTitle(motion, crMode, lineLength); const sequential = !infoToExport || infoToExport.includes('id'); const subtitle = this.createSubtitle(motion, sequential); @@ -167,11 +167,18 @@ export class MotionPdfService { * Create the motion title part of the doc definition * * @param motion the target motion + * @param crMode the change recommendation mode + * @param lineLength the line length * @returns doc def for the document title */ - private createTitle(motion: ViewMotion): object { + private createTitle(motion: ViewMotion, crMode: ChangeRecoMode, lineLength: number): object { + // summary of change recommendations (for motion diff version only) + const changes = this.getUnifiedChanges(motion, lineLength); + const titleChange = changes.find(change => change.isTitleChange()); + const changedTitle = this.changeRecoRepo.getTitleWithChanges(motion.title, titleChange, crMode); + const identifier = motion.identifier ? ' ' + motion.identifier : ''; - const title = `${this.translate.instant('Motion')} ${identifier}: ${motion.title}`; + const title = `${this.translate.instant('Motion')} ${identifier}: ${changedTitle}`; return { text: title, @@ -399,39 +406,44 @@ export class MotionPdfService { const columnChangeType = []; changes.forEach(change => { - // TODO: the function isTitleRecommendation() does not exist anymore. - // Not sure if required or not - // if (changeReco.isTitleRecommendation()) { - // columnLineNumbers.push(gettextCatalog.getString('Title') + ': '); - // } else { ... } - - // line numbers column - let line; - if (change.getLineFrom() >= change.getLineTo() - 1) { - line = change.getLineFrom(); - } else { - line = change.getLineFrom() + ' - ' + (change.getLineTo() - 1); - } - - // change type column - if (change.getChangeType() === ViewUnifiedChangeType.TYPE_CHANGE_RECOMMENDATION) { + if (change.isTitleChange()) { + // Is always a change recommendation const changeReco = change as ViewMotionChangeRecommendation; - columnLineNumbers.push(`${this.translate.instant('Line')} ${line}: `); + columnLineNumbers.push(`${this.translate.instant('Title')}: `); columnChangeType.push( `(${this.translate.instant('Change recommendation')}) - ${this.translate.instant( this.getRecommendationTypeName(changeReco) )}` ); - } else if (change.getChangeType() === ViewUnifiedChangeType.TYPE_AMENDMENT) { - const amendment = change as ViewMotionAmendedParagraph; - let summaryText = `(${this.translate.instant('Amendment')} ${amendment.getIdentifier()}) -`; - if (amendment.isRejected()) { - summaryText += ` ${this.translate.instant('Rejected')}`; - } else if (amendment.isAccepted()) { - summaryText += ` ${this.translate.instant(amendment.stateName)}`; - // only append line and change, if the merge of the state of the amendment is accepted. + } else { + // line numbers column + let line; + if (change.getLineFrom() >= change.getLineTo() - 1) { + line = change.getLineFrom(); + } else { + line = change.getLineFrom() + ' - ' + (change.getLineTo() - 1); + } + + // change type column + if (change.getChangeType() === ViewUnifiedChangeType.TYPE_CHANGE_RECOMMENDATION) { + const changeReco = change as ViewMotionChangeRecommendation; columnLineNumbers.push(`${this.translate.instant('Line')} ${line}: `); - columnChangeType.push(summaryText); + columnChangeType.push( + `(${this.translate.instant('Change recommendation')}) - ${this.translate.instant( + this.getRecommendationTypeName(changeReco) + )}` + ); + } else if (change.getChangeType() === ViewUnifiedChangeType.TYPE_AMENDMENT) { + const amendment = change as ViewMotionAmendedParagraph; + let summaryText = `(${this.translate.instant('Amendment')} ${amendment.getIdentifier()}) -`; + if (amendment.isRejected()) { + summaryText += ` ${this.translate.instant('Rejected')}`; + } else if (amendment.isAccepted()) { + summaryText += ` ${this.translate.instant(amendment.stateName)}`; + // only append line and change, if the merge of the state of the amendment is accepted. + columnLineNumbers.push(`${this.translate.instant('Line')} ${line}: `); + columnChangeType.push(summaryText); + } } } }); @@ -537,6 +549,7 @@ export class MotionPdfService { * Creates the motion text - uses HTML to PDF * * @param motion the motion to convert to pdf + * @param lineLength the current line length * @param lnMode determine the used line mode * @param crMode determine the used change Recommendation mode * @returns doc def for the "the assembly may decide" preamble @@ -547,10 +560,9 @@ export class MotionPdfService { lnMode: LineNumberingMode, crMode: ChangeRecoMode ): object { - let motionText: string; + let motionText = ''; if (motion.isParagraphBasedAmendment()) { - motionText = ''; // this is logically redundant with the formation of amendments in the motion-detail html. // Should be refactored in a way that a service returns the correct html for both cases for (const paragraph of this.motionRepo.getAmendmentParagraphs(motion, lineLength, false)) { @@ -577,9 +589,21 @@ export class MotionPdfService { // TODO: Consider tile change recommendation const changes = this.getUnifiedChanges(motion, lineLength); - motionText = this.motionRepo.formatMotion(motion.id, crMode, changes, lineLength); + const textChanges = changes.filter(change => !change.isTitleChange()); + const titleChange = changes.find(change => change.isTitleChange()); + + if (crMode === ChangeRecoMode.Diff && titleChange) { + const changedTitle = this.changeRecoRepo.getTitleChangesAsDiff(motion.title, titleChange); + motionText += + '' + + this.translate.instant('Changed title') + + ':
    ' + + changedTitle + + '

    '; + } + const formattedText = this.motionRepo.formatMotion(motion.id, crMode, textChanges, lineLength); // reformat motion text to split long HTML elements to easier convert into PDF - motionText = this.linenumberingService.splitInlineElementsAtLineBreaks(motionText); + motionText += this.linenumberingService.splitInlineElementsAtLineBreaks(formattedText); } return this.htmlToPdfService.convertHtml(motionText, lnMode); @@ -755,14 +779,11 @@ export class MotionPdfService { * @returns pdfMake definitions */ public textToDocDef(note: string, motion: ViewMotion, noteTitle: string): object { - const title = this.createTitle(motion); + const lineLength = this.configService.instant('motions_line_length'); + const crMode = this.configService.instant('motions_recommendation_text_mode'); + const title = this.createTitle(motion, crMode, lineLength); const subtitle = this.createSubtitle(motion); - const metaInfo = this.createMetaInfoTable( - motion, - this.configService.instant('motions_line_length'), - this.configService.instant('motions_recommendation_text_mode'), - ['submitters', 'state', 'category'] - ); + const metaInfo = this.createMetaInfoTable(motion, lineLength, crMode, ['submitters', 'state', 'category']); const noteContent = this.htmlToPdfService.convertHtml(note, LineNumberingMode.None); const subHeading = { diff --git a/client/src/app/slides/motions/motion/motion-slide-obj-amendment-paragraph.ts b/client/src/app/slides/motions/motion/motion-slide-obj-amendment-paragraph.ts index 10f280e7d..46911f77d 100644 --- a/client/src/app/slides/motions/motion/motion-slide-obj-amendment-paragraph.ts +++ b/client/src/app/slides/motions/motion/motion-slide-obj-amendment-paragraph.ts @@ -58,4 +58,8 @@ export class MotionSlideObjAmendmentParagraph implements ViewUnifiedChange { public showInFinalView(): boolean { return this.merge_amendment_into_final === 1; } + + public isTitleChange(): boolean { + return false; // Not implemented for amendments + } } diff --git a/client/src/app/slides/motions/motion/motion-slide-obj-change-reco.ts b/client/src/app/slides/motions/motion/motion-slide-obj-change-reco.ts index 765f899b3..772f267e8 100644 --- a/client/src/app/slides/motions/motion/motion-slide-obj-change-reco.ts +++ b/client/src/app/slides/motions/motion/motion-slide-obj-change-reco.ts @@ -56,4 +56,8 @@ export class MotionSlideObjChangeReco implements MotionSlideDataChangeReco, View public showInFinalView(): boolean { return !this.rejected; } + + public isTitleChange(): boolean { + return this.line_from === 0 && this.line_to === 0; + } } diff --git a/client/src/app/slides/motions/motion/motion-slide.component.html b/client/src/app/slides/motions/motion/motion-slide.component.html index 6c9c40293..87cb95d79 100644 --- a/client/src/app/slides/motions/motion/motion-slide.component.html +++ b/client/src/app/slides/motions/motion/motion-slide.component.html @@ -25,7 +25,7 @@
    -

    {{ data.data.title }}

    +

    {{ getTitleWithChanges() }}

    Motion {{ data.data.identifier }}

    @@ -43,6 +43,12 @@ [class.line-numbers-inline]="isLineNumberingInline()" [class.line-numbers-outside]="isLineNumberingOutside()" > +
    +
    + {{ 'Changed title' | translate }}: +
    +
    +
    diff --git a/client/src/app/slides/motions/motion/motion-slide.component.ts b/client/src/app/slides/motions/motion/motion-slide.component.ts index c12a30c5f..18883fdab 100644 --- a/client/src/app/slides/motions/motion/motion-slide.component.ts +++ b/client/src/app/slides/motions/motion/motion-slide.component.ts @@ -13,6 +13,7 @@ import { SlideData } from 'app/core/core-services/projector-data.service'; import { MotionSlideObjAmendmentParagraph } from './motion-slide-obj-amendment-paragraph'; import { BaseMotionSlideComponent } from '../base/base-motion-slide'; import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; +import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service'; import { IBaseScaleScrollSlideComponent } from 'app/slides/base-scale-scroll-slide-component'; @Component({ @@ -111,6 +112,7 @@ export class MotionSlideComponent extends BaseMotionSlideComponent !obj.isTitleChange()); + } + + public getTitleChangingObject(): ViewUnifiedChange { + return this.allChangingObjects.find((obj: ViewUnifiedChange) => obj.isTitleChange()); + } + + public getTitleWithChanges(): string { + return this.changeRepo.getTitleWithChanges(this.data.data.title, this.getTitleChangingObject(), this.crMode); + } + + public getFormattedTitleDiff(): SafeHtml { + const change = this.getTitleChangingObject(); + const diff = this.changeRepo.getTitleChangesAsDiff(this.data.data.title, change); + return this.sanitizer.bypassSecurityTrustHtml(diff); + } + /** * get the formated motion text from the repository. * @@ -309,13 +329,13 @@ export class MotionSlideComponent extends BaseMotionSlideComponent { + const changes = this.getAllTextChangingObjects().filter(change => { return change.showInDiffView(); }); changes.forEach((change: ViewUnifiedChange, idx: number) => { @@ -339,7 +359,7 @@ export class MotionSlideComponent extends BaseMotionSlideComponent + const appliedChanges: ViewUnifiedChange[] = this.getAllTextChangingObjects().filter(change => change.showInFinalView() ); return this.diff.getTextWithChanges(motion.text, appliedChanges, this.lineLength, this.highlightedLine); @@ -354,7 +374,7 @@ export class MotionSlideComponent extends BaseMotionSlideComponent + const appliedChangeObjects: ViewUnifiedChange[] = this.getAllTextChangingObjects().filter(change => change.showInFinalView() ); return this.diff.getTextWithChanges( diff --git a/client/src/styles.scss b/client/src/styles.scss index 929776dcd..86e055bcb 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -262,7 +262,8 @@ a { } strong, -b { +b, +.bold { font-weight: 500; }