Merge pull request #4818 from CatoTH/OS3-title-change-recommendation
Change recommendations for titles
This commit is contained in:
commit
e416231ef4
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -100,4 +100,8 @@ export class ViewMotionChangeRecommendation extends BaseViewModel<MotionChangeRe
|
||||
public showInFinalView(): boolean {
|
||||
return !this.rejected;
|
||||
}
|
||||
|
||||
public isTitleChange(): boolean {
|
||||
return this.line_from === 0 && this.line_to === 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import {
|
||||
MotionChangeRecommendationComponent,
|
||||
MotionChangeRecommendationComponentData
|
||||
} from './motion-change-recommendation.component';
|
||||
MotionChangeRecommendationDialogComponent,
|
||||
MotionChangeRecommendationDialogComponentData
|
||||
} from './motion-change-recommendation-dialog.component';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
@ -11,8 +11,8 @@ import { ModificationType } from 'app/core/ui-services/diff.service';
|
||||
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation';
|
||||
|
||||
describe('MotionChangeRecommendationComponent', () => {
|
||||
let component: MotionChangeRecommendationComponent;
|
||||
let fixture: ComponentFixture<MotionChangeRecommendationComponent>;
|
||||
let component: MotionChangeRecommendationDialogComponent;
|
||||
let fixture: ComponentFixture<MotionChangeRecommendationDialogComponent>;
|
||||
|
||||
const changeReco = <ViewMotionChangeRecommendation>{
|
||||
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();
|
||||
});
|
@ -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<MotionChangeRecommendationComponent>
|
||||
private dialogRef: MatDialogRef<MotionChangeRecommendationDialogComponent>
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
|
@ -10,12 +10,10 @@
|
||||
[class.amendment]="isAmendment(change)"
|
||||
[class.recommendation]="isChangeRecommendation(change)"
|
||||
>
|
||||
<span *ngIf="change.getLineFrom() >= change.getLineTo() - 1" class="line-number">
|
||||
{{ 'Line' | translate }} {{ change.getLineFrom() }}
|
||||
</span>
|
||||
<span *ngIf="change.getLineFrom() < change.getLineTo() - 1" class="line-number">
|
||||
{{ 'Line' | translate }} {{ change.getLineFrom() }} - {{ change.getLineTo() - 1 }}
|
||||
<span *ngIf="!change.isTitleChange()" class="line-number">
|
||||
{{ 'Line' | translate }} {{ formatLineRange(change) }}
|
||||
</span>
|
||||
<span *ngIf="change.isTitleChange()">{{ 'Title' | translate }}</span>
|
||||
<span *ngIf="isChangeRecommendation(change)"> ({{ 'Change recommendation' | translate }})</span>
|
||||
<span *ngIf="isAmendment(change)"> ({{ 'Amendment' | translate }} {{ change.getIdentifier() }})</span>
|
||||
<span class="operation" *ngIf="isChangeRecommendation(change)">
|
||||
@ -44,7 +42,38 @@
|
||||
|
||||
<!-- The actual diff view -->
|
||||
<div class="motion-text-with-diffs">
|
||||
<div *ngFor="let change of changes; let i = index">
|
||||
<div *ngIf="getTitleChangingObject() as changedTitle">
|
||||
<div
|
||||
class="diff-box diff-box-{{ changedTitle.getChangeId() }} clearfix">
|
||||
<div class="action-row" *osPerms="'motions.can_manage'">
|
||||
<button
|
||||
mat-icon-button
|
||||
*ngIf="isRecommendation(changedTitle)"
|
||||
type="button"
|
||||
[matMenuTriggerFor]="changeRecommendationMenu"
|
||||
[matMenuTriggerData]="{ change: changedTitle }"
|
||||
>
|
||||
<mat-icon>more_horiz</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="status-row" *ngIf="changedTitle.isRejected()">
|
||||
<i class="grey">{{ 'Rejected' | translate }}</i>
|
||||
</div>
|
||||
|
||||
<div class="motion-text motion-text-diff"
|
||||
[class.line-numbers-none]="isLineNumberingNone()"
|
||||
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||
[attr.data-change-id]="changedTitle.getChangeId()"
|
||||
>
|
||||
<div class="bold">
|
||||
{{ 'Changed title' | translate }}:
|
||||
</div>
|
||||
<div [innerHTML]="getFormattedTitleDiff()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngFor="let change of getAllTextChangingObjects(); let i = index">
|
||||
<div
|
||||
class="motion-text"
|
||||
[class.line-numbers-none]="isLineNumberingNone()"
|
||||
@ -52,7 +81,7 @@
|
||||
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||
>
|
||||
<os-motion-detail-original-change-recommendations
|
||||
[html]="getTextBetweenChanges(changes[i - 1], change)"
|
||||
[html]="getTextBetweenChanges(getAllTextChangingObjects()[i - 1], change)"
|
||||
[changeRecommendations]="[]"
|
||||
(createChangeRecommendation)="onCreateChangeRecommendation($event)"
|
||||
></os-motion-detail-original-change-recommendations>
|
||||
@ -60,9 +89,9 @@
|
||||
|
||||
<div
|
||||
class="diff-box diff-box-{{ change.getChangeId() }} clearfix"
|
||||
[class.collides]="hasCollissions(change, changes)"
|
||||
[class.collides]="hasCollissions(change, getAllTextChangingObjects())"
|
||||
>
|
||||
<div class="collission-hint" *ngIf="hasCollissions(change, changes)">
|
||||
<div class="collission-hint" *ngIf="hasCollissions(change, getAllTextChangingObjects())">
|
||||
<mat-icon matTooltip="{{ 'This change collides with another one.' | translate }}">warning</mat-icon>
|
||||
</div>
|
||||
<div class="action-row" *osPerms="'motions.can_manage'">
|
||||
@ -144,7 +173,11 @@
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete</span>
|
||||
</button>
|
||||
<button type="button" mat-menu-item (click)="editChangeRecommendation(change, $event)">
|
||||
<button type="button" mat-menu-item (click)="editChangeRecommendation(change, $event)" *ngIf="!change.isTitleChange()">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span translate>Edit</span>
|
||||
</button>
|
||||
<button type="button" mat-menu-item (click)="editTitleChangeRecommendation(change, $event)" *ngIf="change.isTitleChange()">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span translate>Edit</span>
|
||||
</button>
|
||||
|
@ -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',
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="text"></div>
|
||||
<ul class="change-recommendation-list" *ngIf="showChangeRecommendations">
|
||||
<li *ngFor="let reco of changeRecommendations" [title]="reco.getTitle()"
|
||||
<li *ngFor="let reco of getTextChangeRecommendations()" [title]="reco.getTitle()"
|
||||
[style.top]="calcRecoTop(reco)" [style.height]="calcRecoHeight(reco)"
|
||||
[class.delete]="recoIsDeletion(reco)" [class.insert]="recoIsInsertion(reco)"
|
||||
[class.replace]="recoIsReplacement(reco)" (click)="gotoReco(reco)"></li>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -124,7 +124,16 @@
|
||||
<!-- Title -->
|
||||
<div class="title" *ngIf="motion && !editMotion">
|
||||
<div class="title-line">
|
||||
<h1>{{ motion.title }}</h1>
|
||||
<h1 class="motion-title">
|
||||
<span *ngIf="titleCanBeChanged()">
|
||||
<span class="title-change-indicator" *ngIf="getTitleChangingObject()"
|
||||
(click)="gotoChangeRecommendation(getTitleChangingObject())"></span>
|
||||
<span class="change-title" *osPerms="'motions.can_manage'; and: !getTitleChangingObject()"
|
||||
(click)="createTitleChangeRecommendation()"></span>
|
||||
</span>
|
||||
|
||||
{{ getTitleWithChanges() }}
|
||||
</h1>
|
||||
<button mat-icon-button color="primary" (click)="toggleFavorite()">
|
||||
<mat-icon>{{ motion.star ? 'star' : 'star_border' }}</mat-icon>
|
||||
</button>
|
||||
|
@ -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,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z" fill="%23337ab7"/><path d="M0 0h24v24H0z" fill="none"/></svg>');
|
||||
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 {
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,19 @@
|
||||
<h1 mat-dialog-title translate>New change recommendation</h1>
|
||||
<mat-dialog-content>
|
||||
<form class="motion-content" [formGroup]="contentForm" (ngSubmit)="saveChangeRecommendation()">
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="{{ 'New title' | translate }}" formControlName="title" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox formControlName="public">{{ 'Public' | translate }}</mat-checkbox>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
|
||||
<button mat-button (click)="saveChangeRecommendation()">
|
||||
<span translate>Save</span>
|
||||
</button>
|
||||
<button mat-button mat-dialog-close>
|
||||
<span translate>Cancel</span>
|
||||
</button>
|
||||
</mat-dialog-actions>
|
@ -0,0 +1,9 @@
|
||||
.motion-content {
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-dialog-content {
|
||||
overflow: hidden;
|
||||
}
|
@ -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<MotionTitleChangeRecommendationDialogComponent>;
|
||||
|
||||
const changeReco = <ViewMotionChangeRecommendation>{
|
||||
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();
|
||||
});
|
||||
});
|
@ -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<MotionTitleChangeRecommendationDialogComponent>
|
||||
) {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {}
|
||||
|
@ -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 +=
|
||||
'<span><strong>' +
|
||||
this.translate.instant('Changed title') +
|
||||
':</strong><br>' +
|
||||
changedTitle +
|
||||
'</span><br>';
|
||||
}
|
||||
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<number>('motions_line_length');
|
||||
const crMode = this.configService.instant<ChangeRecoMode>('motions_recommendation_text_mode');
|
||||
const title = this.createTitle(motion, crMode, lineLength);
|
||||
const subtitle = this.createSubtitle(motion);
|
||||
const metaInfo = this.createMetaInfoTable(
|
||||
motion,
|
||||
this.configService.instant<number>('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 = {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
<!-- Title -->
|
||||
<div class="spacer" [ngStyle]="{height: projector.show_header_footer ? '50px' : '0'}"></div>
|
||||
<div class="slidetitle">
|
||||
<h1>{{ data.data.title }}</h1>
|
||||
<h1>{{ getTitleWithChanges() }}</h1>
|
||||
<h2><span translate>Motion</span> {{ data.data.identifier }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
@ -43,6 +43,12 @@
|
||||
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||
>
|
||||
<div *ngIf="getTitleChangingObject() && crMode === 'diff'">
|
||||
<div class="bold">
|
||||
{{ 'Changed title' | translate }}:
|
||||
</div>
|
||||
<div [innerHTML]="getFormattedTitleDiff()"></div>
|
||||
</div>
|
||||
<div [innerHTML]="sanitizedText(getFormattedText())"></div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@ -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<MotionSlideDa
|
||||
public constructor(
|
||||
translate: TranslateService,
|
||||
motionRepo: MotionRepositoryService,
|
||||
private changeRepo: ChangeRecommendationRepositoryService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private lineNumbering: LinenumberingService,
|
||||
private diff: DiffService
|
||||
@ -293,6 +295,24 @@ export class MotionSlideComponent extends BaseMotionSlideComponent<MotionSlideDa
|
||||
return html;
|
||||
}
|
||||
|
||||
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.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<MotionSlideDa
|
||||
case ChangeRecoMode.Changed:
|
||||
return this.diff.getTextWithChanges(
|
||||
motion.text,
|
||||
this.allChangingObjects,
|
||||
this.getAllTextChangingObjects(),
|
||||
this.lineLength,
|
||||
this.highlightedLine
|
||||
);
|
||||
case ChangeRecoMode.Diff:
|
||||
let text = '';
|
||||
const changes = this.allChangingObjects.filter(change => {
|
||||
const changes = this.getAllTextChangingObjects().filter(change => {
|
||||
return change.showInDiffView();
|
||||
});
|
||||
changes.forEach((change: ViewUnifiedChange, idx: number) => {
|
||||
@ -339,7 +359,7 @@ export class MotionSlideComponent extends BaseMotionSlideComponent<MotionSlideDa
|
||||
);
|
||||
return text;
|
||||
case ChangeRecoMode.Final:
|
||||
const appliedChanges: ViewUnifiedChange[] = this.allChangingObjects.filter(change =>
|
||||
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<MotionSlideDa
|
||||
);
|
||||
} else {
|
||||
// Use the final version as fallback, if the modified does not exist.
|
||||
const appliedChangeObjects: ViewUnifiedChange[] = this.allChangingObjects.filter(change =>
|
||||
const appliedChangeObjects: ViewUnifiedChange[] = this.getAllTextChangingObjects().filter(change =>
|
||||
change.showInFinalView()
|
||||
);
|
||||
return this.diff.getTextWithChanges(
|
||||
|
@ -262,7 +262,8 @@ a {
|
||||
}
|
||||
|
||||
strong,
|
||||
b {
|
||||
b,
|
||||
.bold {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user